From 56a9f85068c1932b689d8ee06d389718707f2f5f Mon Sep 17 00:00:00 2001 From: Bernie White Date: Sat, 22 Apr 2023 23:26:12 +1000 Subject: [PATCH 001/177] Update ./modules.json (#1510) * Update ./modules.json * Bump change log --------- Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> --- docs/CHANGELOG-v2.md | 2 ++ modules.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/CHANGELOG-v2.md b/docs/CHANGELOG-v2.md index ecaebbdb74..4d7390b665 100644 --- a/docs/CHANGELOG-v2.md +++ b/docs/CHANGELOG-v2.md @@ -36,6 +36,8 @@ What's changed since release v2.8.1: - Improved schema display names by @BernieWhite. [#1488](https://github.com/microsoft/PSRule/issues/1488) - Engineering: + - Bump Pester to v5.4.1. + [#1510](https://github.com/microsoft/PSRule/pull/1510) - Bump Microsoft.CodeAnalysis.Common to v4.5.0. [#1455](https://github.com/microsoft/PSRule/pull/1455) - Bug fixes: diff --git a/modules.json b/modules.json index ae1faa7485..bd9a5c25d8 100644 --- a/modules.json +++ b/modules.json @@ -2,7 +2,7 @@ "dependencies": {}, "devDependencies": { "Pester": { - "version": "5.4.0" + "version": "5.4.1" }, "platyPS": { "version": "0.14.2" From 1c553b00aec70ffdb84bb44151fb4f21e9f9bcba Mon Sep 17 00:00:00 2001 From: Bernie White Date: Sun, 23 Apr 2023 10:59:01 +1000 Subject: [PATCH 002/177] Analysis report successes (#1511) --- ps-rule.yaml | 41 +++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/ps-rule.yaml b/ps-rule.yaml index 9829454240..c699afa657 100644 --- a/ps-rule.yaml +++ b/ps-rule.yaml @@ -10,31 +10,32 @@ repository: baseRef: main output: + sarifProblemsOnly: false culture: - - 'en-US' + - 'en-US' input: pathIgnore: - - '.vscode/' - - '.github/workflows/' - - '*.md' - - '*.Designer.cs' - - '*.resx' - - '*.sln' - - '*.txt' - - '*.html' - - '*.ico' - - '*.png' - - 'ps-docs.yaml' - - 'ps-project.yaml' - - 'ps-rule.yaml' - - 'mkdocs.yml' - - '**/.editorconfig' - - '.markdownlint.json' - - '.github/dependabot.yml' + - '.vscode/' + - '.github/workflows/' + - '*.md' + - '*.Designer.cs' + - '*.resx' + - '*.sln' + - '*.txt' + - '*.html' + - '*.ico' + - '*.png' + - 'ps-docs.yaml' + - 'ps-project.yaml' + - 'ps-rule.yaml' + - 'mkdocs.yml' + - '**/.editorconfig' + - '.markdownlint.json' + - '.github/dependabot.yml' - # Bug #1269: There is an issue preventing #1259 from being merged. Ignore this until bug is fixed. - - 'docs/scenarios/containers/dockerfile' + # Bug #1269: There is an issue preventing #1259 from being merged. Ignore this until bug is fixed. + - 'docs/scenarios/containers/dockerfile' include: path: [] From 00baa9269a78c2936f73c66f72075f815d60b15c Mon Sep 17 00:00:00 2001 From: Bernie White Date: Sun, 23 Apr 2023 15:40:29 +1000 Subject: [PATCH 003/177] Update sarif result include (#1512) --- ps-rule.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/ps-rule.yaml b/ps-rule.yaml index c699afa657..9092fcf74c 100644 --- a/ps-rule.yaml +++ b/ps-rule.yaml @@ -10,7 +10,6 @@ repository: baseRef: main output: - sarifProblemsOnly: false culture: - 'en-US' From 657c0543fe9310348d0d68fbfa143e00983a7da7 Mon Sep 17 00:00:00 2001 From: Bernie White Date: Sun, 23 Apr 2023 21:41:32 +1000 Subject: [PATCH 004/177] Rename and improvement of logging options #1456 (#1513) --- README.md | 6 +- docs/CHANGELOG-v2.md | 11 + .../PSRule/en-US/about_PSRule_Options.md | 257 +++++++++++++++++- docs/deprecations.md | 12 + schemas/PSRule-options.schema.json | 74 ++++- .../RunspaceContextDiagnosticExtensions.cs | 18 +- src/PSRule/Configuration/ExecutionOption.cs | 166 +++++++++-- src/PSRule/PSRule.psm1 | 68 +++++ src/PSRule/Pipeline/InvokeRulePipeline.cs | 12 +- .../Pipeline/PipelineWriterExtensions.cs | 8 - src/PSRule/Runtime/RunspaceContext.cs | 64 +++-- tests/PSRule.Tests/PSRule.Common.Tests.ps1 | 56 ++-- tests/PSRule.Tests/PSRule.Options.Tests.ps1 | 196 ++++++++++++- tests/PSRule.Tests/PSRule.Reason.Tests.ps1 | 2 +- tests/PSRule.Tests/PSRule.Recommend.Tests.ps1 | 6 +- tests/PSRule.Tests/PSRule.Tests.yml | 46 ++-- tests/PSRule.Tests/PSRule.Tests10.yml | 2 +- tests/PSRule.Tests/PSRule.Tests11.yml | 2 +- tests/PSRule.Tests/PSRule.Tests12.yml | 2 +- tests/PSRule.Tests/PSRule.Tests13.yml | 2 +- tests/PSRule.Tests/PSRule.Tests5.yml | 5 +- tests/PSRule.Tests/PSRule.Tests9.yml | 2 +- tests/PSRule.Tests/PipelineTests.cs | 2 +- 23 files changed, 865 insertions(+), 154 deletions(-) diff --git a/README.md b/README.md index 38cd5b6387..e83277091b 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ PSRule works great and integrates with popular continuous integration (CI) syste - [Maintainers](#maintainers) - [License](#license) -### Features of PSRule include: +### Features of PSRule include - [DevOps][2] - Built to support DevOps culture and tools. - [Extensible][3] - Define tests using YAML, JSON, or PowerShell format. @@ -290,15 +290,19 @@ The following conceptual topics exist in the `PSRule` module: - [Binding.UseQualifiedName](https://aka.ms/ps-rule/options#bindingusequalifiedname) - [Configuration](https://aka.ms/ps-rule/options#configuration) - [Convention.Include](https://aka.ms/ps-rule/options#conventioninclude) + - [Execution.AliasReference](https://aka.ms/ps-rule/options#executionaliasreference) - [Execution.AliasReferenceWarning](https://aka.ms/ps-rule/options#executionaliasreferencewarning) - [Execution.DuplicateResourceId](https://aka.ms/ps-rule/options#executionduplicateresourceid) - [Execution.LanguageMode](https://aka.ms/ps-rule/options#executionlanguagemode) - [Execution.InconclusiveWarning](https://aka.ms/ps-rule/options#executioninconclusivewarning) + - [Execution.InvariantCulture](https://aka.ms/ps-rule/options#executioninvariantculture) - [Execution.InvariantCultureWarning](https://aka.ms/ps-rule/options#executioninvariantculturewarning) - [Execution.InitialSessionState](https://aka.ms/ps-rule/options#executioninitialsessionstate) - [Execution.NotProcessedWarning](https://aka.ms/ps-rule/options#executionnotprocessedwarning) + - [Execution.RuleInconclusive](https://aka.ms/ps-rule/options#executionruleinconclusive) - [Execution.SuppressedRuleWarning](https://aka.ms/ps-rule/options#executionsuppressedrulewarning) - [Execution.SuppressionGroupExpired](https://aka.ms/ps-rule/options#executionsuppressiongroupexpired) + - [Execution.UnprocessedObject](https://aka.ms/ps-rule/options#executionunprocessedobject) - [Include.Module](https://aka.ms/ps-rule/options#includemodule) - [Include.Path](https://aka.ms/ps-rule/options#includepath) - [Input.Format](https://aka.ms/ps-rule/options#inputformat) diff --git a/docs/CHANGELOG-v2.md b/docs/CHANGELOG-v2.md index 4d7390b665..5a01e016df 100644 --- a/docs/CHANGELOG-v2.md +++ b/docs/CHANGELOG-v2.md @@ -33,6 +33,17 @@ See [upgrade notes][1] for helpful information when upgrading from previous vers What's changed since release v2.8.1: - General improvements: + - **Important change**: Rename of execution options by @BernieWhite. + [#1456](https://github.com/microsoft/PSRule/issues/1456) + - Renamed options allow configuration of output level as `Ignore`, `Warn`, `Error`, or `Debug`. + - `Execution.AliasReferenceWarning` is replaced with `Execution.AliasReference`. + - `Execution.InconclusiveWarning` is replaced with `Execution.RuleInconclusive`. + - `Execution.InvariantCultureWarning` is replaced with `Execution.InvariantCulture`. + - `Execution.NotProcessedWarning` is replaced with `Execution.UnprocessedObject`. + - Deprecated `AliasReferenceWarning` option, which will be removed in v3. + - Deprecated `InconclusiveWarning` option, which will be removed in v3. + - Deprecated `InvariantCultureWarning` option, which will be removed in v3. + - Deprecated `NotProcessedWarning` option, which will be removed in v3. - Improved schema display names by @BernieWhite. [#1488](https://github.com/microsoft/PSRule/issues/1488) - Engineering: diff --git a/docs/concepts/PSRule/en-US/about_PSRule_Options.md b/docs/concepts/PSRule/en-US/about_PSRule_Options.md index dbf45e98a8..5418af226c 100644 --- a/docs/concepts/PSRule/en-US/about_PSRule_Options.md +++ b/docs/concepts/PSRule/en-US/about_PSRule_Options.md @@ -14,15 +14,19 @@ This topic describes what options are available, when to and how to use them. The following workspace options are available for use: - [Convention.Include](#conventioninclude) +- [Execution.AliasReference](#executionaliasreference) - [Execution.AliasReferenceWarning](#executionaliasreferencewarning) - [Execution.DuplicateResourceId](#executionduplicateresourceid) - [Execution.LanguageMode](#executionlanguagemode) - [Execution.InconclusiveWarning](#executioninconclusivewarning) +- [Execution.InvariantCulture](#executioninvariantculture) - [Execution.InvariantCultureWarning](#executioninvariantculturewarning) - [Execution.InitialSessionState](#executioninitialsessionstate) - [Execution.NotProcessedWarning](#executionnotprocessedwarning) +- [Execution.RuleInconclusive](#executionruleinconclusive) - [Execution.SuppressedRuleWarning](#executionsuppressedrulewarning) - [Execution.SuppressionGroupExpired](#executionsuppressiongroupexpired) +- [Execution.UnprocessedObject](#executionunprocessedobject) - [Include.Module](#includemodule) - [Include.Path](#includepath) - [Input.Format](#inputformat) @@ -673,8 +677,69 @@ variables: value: 'Convention1;Convention2' ``` +### Execution.AliasReference + +Determines how to handle when an alias to a resource is used. +By defaut, a warning is generated, however this behaviour can be modified by this option. + +This option replaces `AliasReferenceWarning`. +You do not need to configure both options. +If `AliasReferenceWarning` is configured, it will override `AliasReference` with `Warn` or `Ignore` until removal in PSRule v3. + +The following preferences are available: + +- `None` (0) - No preference. + Inherits the default of `Warn`. +- `Ignore` (1) - Continue to execute silently. +- `Warn` (2) - Continue to execute but log a warning. + This is the default. +- `Error` (3) - Abort and throw an error. +- `Debug` (4) - Continue to execute but log a debug message. + +```powershell +# PowerShell: Using the ExecutionAliasReference parameter +$option = New-PSRuleOption -ExecutionAliasReference 'Error'; +``` + +```powershell +# PowerShell: Using the Execution.AliasReference hashtable key +$option = New-PSRuleOption -Option @{ 'Execution.AliasReference' = 'Error' }; +``` + +```powershell +# PowerShell: Using the ExecutionAliasReference parameter to set YAML +Set-PSRuleOption -ExecutionAliasReference 'Error'; +``` + +```yaml +# YAML: Using the execution/aliasReference property +execution: + aliasReference: Error +``` + +```bash +# Bash: Using environment variable +export PSRULE_EXECUTION_ALIASREFERENCE=Error +``` + +```yaml +# GitHub Actions: Using environment variable +env: + PSRULE_EXECUTION_ALIASREFERENCE: Error +``` + +```yaml +# Azure Pipelines: Using environment variable +variables: +- name: PSRULE_EXECUTION_ALIASREFERENCE + value: Error +``` + ### Execution.AliasReferenceWarning +This option has been deprecated and will be removed from v3 in favor of `aliasReference`. +Use `aliasReference` instead. + Rules may define one or more aliases. These aliases are alternative names to identify the rule. An alias may be used to reference the rule anywhere a rule name is used. @@ -828,6 +893,9 @@ variables: ### Execution.InconclusiveWarning +This option has been deprecated and will be removed from v3 in favor of `ruleInconclusive`. +Use `ruleInconclusive` instead. + When defining rules, it is possible not return a valid `$True` or `$False` result within the definition script block. Rule authors should not intentionally avoid returning a result, however a possible cause for not returning a result may be a rule logic error. @@ -882,8 +950,69 @@ variables: value: false ``` +### Execution.InvariantCulture + +Determines how to report when an invariant culture is used. +By defaut, a warning is generated, however this behaviour can be modified by this option. + +This option replaces `InvariantCultureWarning`. +You do not need to configure both options. +If `InvariantCultureWarning` is configured, it will override `InvariantCulture` with `Warn` or `Ignore` until removal in PSRule v3. + +The following preferences are available: + +- `None` (0) - No preference. + Inherits the default of `Warn`. +- `Ignore` (1) - Continue to execute silently. +- `Warn` (2) - Continue to execute but log a warning. + This is the default. +- `Error` (3) - Abort and throw an error. +- `Debug` (4) - Continue to execute but log a debug message. + +```powershell +# PowerShell: Using the ExecutionInvariantCulture parameter +$option = New-PSRuleOption -ExecutionInvariantCulture 'Error'; +``` + +```powershell +# PowerShell: Using the Execution.InvariantCulture hashtable key +$option = New-PSRuleOption -Option @{ 'Execution.InvariantCulture' = 'Error' }; +``` + +```powershell +# PowerShell: Using the ExecutionInvariantCulture parameter to set YAML +Set-PSRuleOption -ExecutionInvariantCulture 'Error'; +``` + +```yaml +# YAML: Using the execution/invariantCulture property +execution: + invariantCulture: Error +``` + +```bash +# Bash: Using environment variable +export PSRULE_EXECUTION_INVARIANTCULTURE=Error +``` + +```yaml +# GitHub Actions: Using environment variable +env: + PSRULE_EXECUTION_INVARIANTCULTURE: Error +``` + +```yaml +# Azure Pipelines: Using environment variable +variables: +- name: PSRULE_EXECUTION_INVARIANTCULTURE + value: Error +``` + ### Execution.InvariantCultureWarning +This option has been deprecated and will be removed from v3 in favor of `invariantCulture`. +Use `invariantCulture` instead. + When evaluating rules inside a CI host, if invariant culture is used, a warning is shown by default. You can suppress this warning if you set the culture with `-Culture` or the `Output.Culture` option. @@ -979,6 +1108,9 @@ variables: ### Execution.NotProcessedWarning +This option has been deprecated and will be removed from v3 in favor of `unprocessedObject`. +Use `unprocessedObject` instead. + When evaluating rules, it is possible to incorrectly select a path with rules that use pre-conditions that do not accept the pipeline object. In this case the object has not been processed by any rule. @@ -1028,6 +1160,64 @@ variables: value: false ``` +### Execution.RuleInconclusive + +Determines how to handle rules that generate inconclusive results. +By defaut, a warning is generated, however this behaviour can be modified by this option. + +This option replaces `InconclusiveWarning`. +You do not need to configure both options. +If `InconclusiveWarning` is configured, it will override `RuleInconclusive` with `Warn` or `Ignore` until removal in PSRule v3. + +The following preferences are available: + +- `None` (0) - No preference. + Inherits the default of `Warn`. +- `Ignore` (1) - Continue to execute silently. +- `Warn` (2) - Continue to execute but log a warning. + This is the default. +- `Error` (3) - Abort and throw an error. +- `Debug` (4) - Continue to execute but log a debug message. + +```powershell +# PowerShell: Using the ExecutionRuleInconclusive parameter +$option = New-PSRuleOption -ExecutionRuleInconclusive 'Error'; +``` + +```powershell +# PowerShell: Using the Execution.RuleInconclusive hashtable key +$option = New-PSRuleOption -Option @{ 'Execution.RuleInconclusive' = 'Error' }; +``` + +```powershell +# PowerShell: Using the ExecutionRuleInconclusive parameter to set YAML +Set-PSRuleOption -ExecutionRuleInconclusive 'Error'; +``` + +```yaml +# YAML: Using the execution/ruleInconclusive property +execution: + ruleInconclusive: Error +``` + +```bash +# Bash: Using environment variable +export PSRULE_EXECUTION_RULEINCONCLUSIVE=Error +``` + +```yaml +# GitHub Actions: Using environment variable +env: + PSRULE_EXECUTION_RULEINCONCLUSIVE: Error +``` + +```yaml +# Azure Pipelines: Using environment variable +variables: +- name: PSRULE_EXECUTION_RULEINCONCLUSIVE + value: Error +``` + ### Execution.SuppressedRuleWarning This option has been deprecated and will be removed from v3 in favor of `ruleSuppressed`. @@ -1251,6 +1441,64 @@ variables: value: Error ``` +### Execution.UnprocessedObject + +Determines how to report objects that are not processed by any rule. +By defaut, a warning is generated, however this behaviour can be modified by this option. + +This option replaces `NotProcessedWarning`. +You do not need to configure both options. +If `NotProcessedWarning` is configured, it will override `UnprocessedObject` with `Warn` or `Ignore` until removal in PSRule v3. + +The following preferences are available: + +- `None` (0) - No preference. + Inherits the default of `Warn`. +- `Ignore` (1) - Continue to execute silently. +- `Warn` (2) - Continue to execute but log a warning. + This is the default. +- `Error` (3) - Abort and throw an error. +- `Debug` (4) - Continue to execute but log a debug message. + +```powershell +# PowerShell: Using the ExecutionUnprocessedObject parameter +$option = New-PSRuleOption -ExecutionUnprocessedObject 'Error'; +``` + +```powershell +# PowerShell: Using the Execution.UnprocessedObject hashtable key +$option = New-PSRuleOption -Option @{ 'Execution.UnprocessedObject' = 'Error' }; +``` + +```powershell +# PowerShell: Using the ExecutionUnprocessedObject parameter to set YAML +Set-PSRuleOption -ExecutionUnprocessedObject 'Error'; +``` + +```yaml +# YAML: Using the execution/unprocessedObject property +execution: + unprocessedObject: Error +``` + +```bash +# Bash: Using environment variable +export PSRULE_EXECUTION_UNPROCESSEDOBJECT=Error +``` + +```yaml +# GitHub Actions: Using environment variable +env: + PSRULE_EXECUTION_UNPROCESSEDOBJECT: Error +``` + +```yaml +# Azure Pipelines: Using environment variable +variables: +- name: PSRULE_EXECUTION_UNPROCESSEDOBJECT + value: Error +``` + ### Include.Module Automatically include rules and resources from the specified module. @@ -3256,13 +3504,14 @@ convention: # Configure execution options execution: - aliasReferenceWarning: true + aliasReference: Warn duplicateResourceId: Error + invariantCulture: Warn languageMode: FullLanguage - inconclusiveWarning: true - notProcessedWarning: true - suppressedRuleWarning: true + ruleInconclusive: Warn + ruleSuppressed: Warn suppressionGroupExpired: Warn + unprocessedObject: Warn # Configure include options include: diff --git a/docs/deprecations.md b/docs/deprecations.md index 9f177686bb..93bbfaac99 100644 --- a/docs/deprecations.md +++ b/docs/deprecations.md @@ -26,6 +26,18 @@ The following execution options have been deprecated and will be removed from _v - `Execution.SuppressedRuleWarning` is replaced with `Execution.RuleSuppressed`. Set `Execution.RuleSuppressed` to `Warn` to log a warning from _v2.8.0_. If both options are set, `Execution.SuppressedRuleWarning` takes precedence until _v3_. +- `Execution.AliasReferenceWarning` is replaced with `Execution.AliasReference`. + Set `Execution.AliasReference` to `Warn` to log a warning from _v2.9.0_. + If both options are set, `Execution.AliasReferenceWarning` takes precedence until _v3_. +- `Execution.InconclusiveWarning` is replaced with `Execution.RuleInconclusive`. + Set `Execution.RuleInconclusive` to `Warn` to log a warning from _v2.9.0_. + If both options are set, `Execution.InconclusiveWarning` takes precedence until _v3_. +- `Execution.InvariantCultureWarning` is replaced with `Execution.InvariantCulture`. + Set `Execution.InvariantCulture` to `Warn` to log a warning from _v2.9.0_. + If both options are set, `Execution.InvariantCultureWarning` takes precedence until _v3_. +- `Execution.NotProcessedWarning` is replaced with `Execution.UnprocessedObject`. + Set `Execution.UnprocessedObject` to `Warn` to log a warning from _v2.9.0_. + If both options are set, `Execution.NotProcessedWarning` takes precedence until _v3_. !!! Tip You do not need to configure both options. diff --git a/schemas/PSRule-options.schema.json b/schemas/PSRule-options.schema.json index a42a38c166..e0024e2cbf 100644 --- a/schemas/PSRule-options.schema.json +++ b/schemas/PSRule-options.schema.json @@ -283,10 +283,11 @@ "properties": { "aliasReferenceWarning": { "type": "boolean", - "title": "Warn on resource aliases", + "title": "Warn on resource aliases | DEPRECATED", "description": "Enable or disable warnings when an alias to a resource is used. The default is true.", "markdownDescription": "Enable or disable warnings when an alias to a resource is used. The default is `true`. [See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Options/#executionaliasreferencewarning)", - "default": true + "default": true, + "deprecated": true }, "duplicateResourceId": { "type": "string", @@ -314,17 +315,19 @@ }, "inconclusiveWarning": { "type": "boolean", - "title": "Warn on inconclusive rules", + "title": "Warn on inconclusive rules | DEPRECATED", "description": "Enable or disable warnings for inconclusive rules. The default is true.", "markdownDescription": "Enable or disable warnings for inconclusive rules. The default is `true`. [See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Options/#executioninconclusivewarning)", - "default": true + "default": true, + "deprecated": true }, "invariantCultureWarning": { "type": "boolean", - "title": "Warn on invariant culture", + "title": "Warn on invariant culture | DEPRECATED", "description": "Enable or disable warning when invariant culture is used. The default is true.", "markdownDescription": "Enable or disable warning when invariant culture is used. The default is `true`. [See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Options/#executioninvariantculturewarning)", - "default": true + "default": true, + "deprecated": true }, "initialSessionState": { "type": "string", @@ -339,10 +342,11 @@ }, "notProcessedWarning": { "type": "boolean", - "title": "Warn on unprocessed objects", + "title": "Warn on unprocessed objects | DEPRECATED", "description": "Enable or disable warnings for objects that are not processed by any rule. The default is true.", "markdownDescription": "Enable or disable warnings for objects that are not processed by any rule. The default is `true`. [See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Options/#executionnotprocessedwarning)", - "default": true + "default": true, + "deprecated": true }, "suppressedRuleWarning": { "type": "boolean", @@ -369,7 +373,7 @@ "type": "string", "title": "Rule excluded", "description": "Determines how to handle excluded rules. Regardless of the value, excluded rules are ignored. By default, rules are excluded silently. When set to Error, an error is thrown. When set to Warn, a warning is generated. When set to Debug, a message is written to the debug log.", - "markdownDescription": "Determines how to handle excluded rules.\n\nRegardless of the value, excluded rules are ignored. By default, rules are excluded silently.\n\n- When set to `Error`, an error is thrown.\n- When set to `Warn`, a warning is generated.\n- When set to `Debug`, a message is written to the debug log.\n\n[See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Options/#executionruleexecluded)", + "markdownDescription": "Determines how to handle excluded rules.\n\nRegardless of the value, excluded rules are ignored. By default, rules are excluded silently.\n\n- When set to `Error`, an error is thrown.\n- When set to `Warn`, a warning is generated.\n- When set to `Debug`, a message is written to the debug log.\n\n[See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Options/#executionruleexcluded)", "enum": [ "Ignore", "Warn", @@ -390,6 +394,58 @@ "Debug" ], "default": "Warn" + }, + "aliasReference": { + "type": "string", + "title": "Alias references", + "description": "Determines how to handle when an alias to a resource is used. By default, a warning is generated. When set to Error, an error is thrown. When set to Debug, a message is written to the debug log. When set to Ignore, no output will be displayed. This option replaces aliasReferenceWarning. You do not need to configure both options. If aliasReferenceWarning is configured, it will override aliasReference with Warn or Ignore until removal in PSRule v3.", + "markdownDescription": "Determines how to handle when an alias to a resource is used. By default, a warning is generated.\n\n- When set to `Error`, an error is thrown.\n- When set to `Debug`, a message is written to the debug log.\n- When set to `Ignore`, no output will be displayed.\n\nThis option replaces `aliasReferenceWarning`. You do not need to configure both options. If `aliasReferenceWarning` is configured, it will override `aliasReference` with `Warn` or `Ignore` until removal in PSRule v3.\n\n[See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Options/#executionaliasreference)", + "enum": [ + "Ignore", + "Warn", + "Error", + "Debug" + ], + "default": "Warn" + }, + "ruleInconclusive": { + "type": "string", + "title": "Inconclusive rules", + "description": "Determines how to handle rules that generate inconclusive results. By default, a warning is generated. When set to Error, an error is thrown. When set to Debug, a message is written to the debug log. When set to Ignore, no output will be displayed. This option replaces inconclusiveWarning. You do not need to configure both options. If inconclusiveWarning is configured, it will override ruleInconclusive with Warn or Ignore until removal in PSRule v3.", + "markdownDescription": "Determines how to handle rules that generate inconclusive results. By default, a warning is generated.\n\n- When set to `Error`, an error is thrown.\n- When set to `Debug`, a message is written to the debug log.\n- When set to `Ignore`, no output will be displayed.\n\nThis option replaces `inconclusiveWarning`. You do not need to configure both options. If `inconclusiveWarning` is configured, it will override `ruleInconclusive` with `Warn` or `Ignore` until removal in PSRule v3.\n\n[See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Options/#executionruleinconclusive)", + "enum": [ + "Ignore", + "Warn", + "Error", + "Debug" + ], + "default": "Warn" + }, + "invariantCulture": { + "type": "string", + "title": "Invariant culture", + "description": "Determines how to report when an invariant culture is used. By default, a warning is generated. When set to Error, an error is thrown. When set to Debug, a message is written to the debug log. When set to Ignore, no output will be displayed. This option replaces invariantCultureWarning. You do not need to configure both options. If invariantCultureWarning is configured, it will override invariantCulture with Warn or Ignore until removal in PSRule v3.", + "markdownDescription": "Determines how to report when an invariant culture is used. By default, a warning is generated.\n\n- When set to `Error`, an error is thrown.\n- When set to `Debug`, a message is written to the debug log.\n- When set to `Ignore`, no output will be displayed.\n\nThis option replaces `invariantCultureWarning`. You do not need to configure both options. If `invariantCultureWarning` is configured, it will override `invariantCulture` with `Warn` or `Ignore` until removal in PSRule v3.\n\n[See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Options/#executioninvariantculture)", + "enum": [ + "Ignore", + "Warn", + "Error", + "Debug" + ], + "default": "Warn" + }, + "unprocessedObject": { + "type": "string", + "title": "Unprocessed object", + "description": "Determines how to report objects that are not processed by any rule. By default, a warning is generated. When set to Error, an error is thrown. When set to Debug, a message is written to the debug log. When set to Ignore, no output will be displayed. This option replaces notProcessedWarning. You do not need to configure both options. If notProcessedWarning is configured, it will override unprocessedObject with Warn or Ignore until removal in PSRule v3.", + "markdownDescription": "Determines how to report objects that are not processed by any rule. By default, a warning is generated.\n\n- When set to `Error`, an error is thrown.\n- When set to `Debug`, a message is written to the debug log.\n- When set to `Ignore`, no output will be displayed.\n\nThis option replaces `notProcessedWarning`. You do not need to configure both options. If `notProcessedWarning` is configured, it will override `unprocessedObject` with `Warn` or `Ignore` until removal in PSRule v3.\n\n[See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Options/#executionunprocessedobject)", + "enum": [ + "Ignore", + "Warn", + "Error", + "Debug" + ], + "default": "Warn" } }, "additionalProperties": false diff --git a/src/PSRule/Common/RunspaceContextDiagnosticExtensions.cs b/src/PSRule/Common/RunspaceContextDiagnosticExtensions.cs index c8976a7230..65ea4cfe30 100644 --- a/src/PSRule/Common/RunspaceContextDiagnosticExtensions.cs +++ b/src/PSRule/Common/RunspaceContextDiagnosticExtensions.cs @@ -102,18 +102,24 @@ internal static void DebugPropertyObsolete(this RunspaceContext context, string internal static void WarnAliasReference(this RunspaceContext context, ResourceKind kind, string resourceId, string targetId, string alias) { - if (context.Writer == null || !context.Writer.ShouldWriteWarning() || !context.Pipeline.Option.Execution.AliasReferenceWarning.GetValueOrDefault(ExecutionOption.Default.AliasReferenceWarning.Value)) - return; +#pragma warning disable CS0618 // Type or member is obsolete + var action = context.Pipeline.Option.Execution.AliasReference.GetValueOrDefault(ExecutionOption.Default.AliasReference.Value); + if (context.Pipeline.Option.Execution.AliasReferenceWarning.HasValue) + action = context.Pipeline.Option.Execution.AliasReferenceWarning.Value ? ExecutionActionPreference.Warn : ExecutionActionPreference.Ignore; +#pragma warning restore CS0618 // Type or member is obsolete - context.Writer.WriteWarning(PSRuleResources.AliasReference, kind.ToString(), resourceId, targetId, alias); + Throw(context, action, PSRuleResources.AliasReference, kind.ToString(), resourceId, targetId, alias); } internal static void WarnAliasSuppression(this RunspaceContext context, string targetId, string alias) { - if (context.Writer == null || !context.Writer.ShouldWriteWarning() || !context.Pipeline.Option.Execution.AliasReferenceWarning.GetValueOrDefault(ExecutionOption.Default.AliasReferenceWarning.Value)) - return; +#pragma warning disable CS0618 // Type or member is obsolete + var action = context.Pipeline.Option.Execution.AliasReference.GetValueOrDefault(ExecutionOption.Default.AliasReference.Value); + if (context.Pipeline.Option.Execution.AliasReferenceWarning.HasValue) + action = context.Pipeline.Option.Execution.AliasReferenceWarning.Value ? ExecutionActionPreference.Warn : ExecutionActionPreference.Ignore; +#pragma warning restore CS0618 // Type or member is obsolete - context.Writer.WriteWarning(PSRuleResources.AliasSuppression, targetId, alias); + Throw(context, action, PSRuleResources.AliasSuppression, targetId, alias); } } } diff --git a/src/PSRule/Configuration/ExecutionOption.cs b/src/PSRule/Configuration/ExecutionOption.cs index b1c87d9590..268016db95 100644 --- a/src/PSRule/Configuration/ExecutionOption.cs +++ b/src/PSRule/Configuration/ExecutionOption.cs @@ -16,28 +16,28 @@ namespace PSRule.Configuration public sealed class ExecutionOption : IEquatable { private const LanguageMode DEFAULT_LANGUAGEMODE = Configuration.LanguageMode.FullLanguage; - private const bool DEFAULT_INCONCLUSIVEWARNING = true; - private const bool DEFAULT_NOTPROCESSEDWARNING = true; - private const bool DEFAULT_ALIASREFERENCEWARNING = true; - private const bool DEFAULT_INVARIANTCULTUREWARNING = true; private const ExecutionActionPreference DEFAULT_DUPLICATERESOURCEID = ExecutionActionPreference.Error; private const SessionState DEFAULT_INITIALSESSIONSTATE = SessionState.BuiltIn; private const ExecutionActionPreference DEFAULT_SUPPRESSIONGROUPEXPIRED = ExecutionActionPreference.Warn; private const ExecutionActionPreference DEFAULT_RULEEXCLUDED = ExecutionActionPreference.Ignore; private const ExecutionActionPreference DEFAULT_RULESUPPRESSED = ExecutionActionPreference.Warn; + private const ExecutionActionPreference DEFAULT_ALIASREFERENCE = ExecutionActionPreference.Warn; + private const ExecutionActionPreference DEFAULT_RULEINCONCLUSIVE = ExecutionActionPreference.Warn; + private const ExecutionActionPreference DEFAULT_INVARIANTCULTURE = ExecutionActionPreference.Warn; + private const ExecutionActionPreference DEFAULT_UNPROCESSEDOBJECT = ExecutionActionPreference.Warn; internal static readonly ExecutionOption Default = new() { - AliasReferenceWarning = DEFAULT_ALIASREFERENCEWARNING, DuplicateResourceId = DEFAULT_DUPLICATERESOURCEID, LanguageMode = DEFAULT_LANGUAGEMODE, - InconclusiveWarning = DEFAULT_INCONCLUSIVEWARNING, - InvariantCultureWarning = DEFAULT_INVARIANTCULTUREWARNING, InitialSessionState = DEFAULT_INITIALSESSIONSTATE, - NotProcessedWarning = DEFAULT_NOTPROCESSEDWARNING, SuppressionGroupExpired = DEFAULT_SUPPRESSIONGROUPEXPIRED, RuleExcluded = DEFAULT_RULEEXCLUDED, RuleSuppressed = DEFAULT_RULESUPPRESSED, + AliasReference = DEFAULT_ALIASREFERENCE, + RuleInconclusive = DEFAULT_RULEINCONCLUSIVE, + InvariantCulture = DEFAULT_INVARIANTCULTURE, + UnprocessedObject = DEFAULT_UNPROCESSEDOBJECT, }; /// @@ -45,6 +45,7 @@ public sealed class ExecutionOption : IEquatable /// public ExecutionOption() { +#pragma warning disable CS0618 // Type or member is obsolete AliasReferenceWarning = null; DuplicateResourceId = null; LanguageMode = null; @@ -52,12 +53,15 @@ public ExecutionOption() InvariantCultureWarning = null; InitialSessionState = null; NotProcessedWarning = null; -#pragma warning disable CS0612 // Type or member is obsolete SuppressedRuleWarning = null; -#pragma warning restore CS0612 // Type or member is obsolete +#pragma warning restore CS0618 // Type or member is obsolete SuppressionGroupExpired = null; RuleExcluded = null; RuleSuppressed = null; + AliasReference = null; + RuleInconclusive = null; + InvariantCulture = null; + UnprocessedObject = null; } /// @@ -69,6 +73,7 @@ public ExecutionOption(ExecutionOption option) if (option == null) return; +#pragma warning disable CS0618 // Type or member is obsolete AliasReferenceWarning = option.AliasReferenceWarning; DuplicateResourceId = option.DuplicateResourceId; LanguageMode = option.LanguageMode; @@ -76,12 +81,15 @@ public ExecutionOption(ExecutionOption option) InvariantCultureWarning = option.InvariantCultureWarning; InitialSessionState = option.InitialSessionState; NotProcessedWarning = option.NotProcessedWarning; -#pragma warning disable CS0612 // Type or member is obsolete SuppressedRuleWarning = option.SuppressedRuleWarning; -#pragma warning restore CS0612 // Type or member is obsolete +#pragma warning restore CS0618 // Type or member is obsolete SuppressionGroupExpired = option.SuppressionGroupExpired; RuleExcluded = option.RuleExcluded; RuleSuppressed = option.RuleSuppressed; + AliasReference = option.AliasReference; + RuleInconclusive = option.RuleInconclusive; + InvariantCulture = option.InvariantCulture; + UnprocessedObject = option.UnprocessedObject; } /// @@ -93,7 +101,7 @@ public override bool Equals(object obj) /// public bool Equals(ExecutionOption other) { -#pragma warning disable CS0612 // Type or member is obsolete +#pragma warning disable CS0618 // Type or member is obsolete return other != null && AliasReferenceWarning == other.AliasReferenceWarning && DuplicateResourceId == other.DuplicateResourceId && @@ -105,8 +113,12 @@ public bool Equals(ExecutionOption other) SuppressedRuleWarning == other.SuppressedRuleWarning && SuppressionGroupExpired == other.SuppressionGroupExpired && RuleExcluded == other.RuleExcluded && - RuleSuppressed == other.RuleSuppressed; -#pragma warning restore CS0612 // Type or member is obsolete + RuleSuppressed == other.RuleSuppressed && + AliasReference == other.AliasReference && + RuleInconclusive == other.RuleInconclusive && + InvariantCulture == other.InvariantCulture && + UnprocessedObject == other.UnprocessedObject; +#pragma warning restore CS0618 // Type or member is obsolete } /// @@ -115,6 +127,7 @@ public override int GetHashCode() unchecked // Overflow is fine { var hash = 17; +#pragma warning disable CS0618 // Type or member is obsolete hash = hash * 23 + (AliasReferenceWarning.HasValue ? AliasReferenceWarning.Value.GetHashCode() : 0); hash = hash * 23 + (DuplicateResourceId.HasValue ? DuplicateResourceId.Value.GetHashCode() : 0); hash = hash * 23 + (LanguageMode.HasValue ? LanguageMode.Value.GetHashCode() : 0); @@ -122,12 +135,15 @@ public override int GetHashCode() hash = hash * 23 + (InvariantCultureWarning.HasValue ? InvariantCultureWarning.Value.GetHashCode() : 0); hash = hash * 23 + (InitialSessionState.HasValue ? InitialSessionState.Value.GetHashCode() : 0); hash = hash * 23 + (NotProcessedWarning.HasValue ? NotProcessedWarning.Value.GetHashCode() : 0); -#pragma warning disable CS0612 // Type or member is obsolete hash = hash * 23 + (SuppressedRuleWarning.HasValue ? SuppressedRuleWarning.Value.GetHashCode() : 0); -#pragma warning restore CS0612 // Type or member is obsolete +#pragma warning restore CS0618 // Type or member is obsolete hash = hash * 23 + (SuppressionGroupExpired.HasValue ? SuppressionGroupExpired.Value.GetHashCode() : 0); hash = hash * 23 + (RuleExcluded.HasValue ? RuleExcluded.Value.GetHashCode() : 0); hash = hash * 23 + (RuleSuppressed.HasValue ? RuleSuppressed.Value.GetHashCode() : 0); + hash = hash * 23 + (AliasReference.HasValue ? AliasReference.Value.GetHashCode() : 0); + hash = hash * 23 + (RuleInconclusive.HasValue ? RuleInconclusive.Value.GetHashCode() : 0); + hash = hash * 23 + (InvariantCulture.HasValue ? InvariantCulture.Value.GetHashCode() : 0); + hash = hash * 23 + (UnprocessedObject.HasValue ? UnprocessedObject.Value.GetHashCode() : 0); return hash; } } @@ -138,7 +154,7 @@ public override int GetHashCode() /// internal static ExecutionOption Combine(ExecutionOption o1, ExecutionOption o2) { -#pragma warning disable CS0612 // Type or member is obsolete +#pragma warning disable CS0618 // Type or member is obsolete var result = new ExecutionOption(o1) { AliasReferenceWarning = o1.AliasReferenceWarning ?? o2.AliasReferenceWarning, @@ -152,15 +168,19 @@ internal static ExecutionOption Combine(ExecutionOption o1, ExecutionOption o2) 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, }; -#pragma warning restore CS0612 // Type or member is obsolete +#pragma warning restore CS0618 // Type or member is obsolete return result; } /// /// Determines if a warning is raised when an alias to a resource is used. /// - [DefaultValue(null)] + [DefaultValue(null), Obsolete("Use AliasReference instead. See https://aka.ms/ps-rule/deprecations for more detail.")] public bool? AliasReferenceWarning { get; set; } /// @@ -183,13 +203,13 @@ internal static ExecutionOption Combine(ExecutionOption o1, ExecutionOption o2) /// /// Determines if a warning is raised when a rule does not return pass or fail. /// - [DefaultValue(null)] + [DefaultValue(null), Obsolete("Use RuleInconclusive instead. See https://aka.ms/ps-rule/deprecations for more detail.")] public bool? InconclusiveWarning { get; set; } /// /// Determines if warning is raised when invariant culture is used. /// - [DefaultValue(null)] + [DefaultValue(null), Obsolete("Use InvariantCulture instead. See https://aka.ms/ps-rule/deprecations for more detail.")] public bool? InvariantCultureWarning { get; set; } /// @@ -202,7 +222,7 @@ internal static ExecutionOption Combine(ExecutionOption o1, ExecutionOption o2) /// /// Determines if a warning is raised when an object is not processed by any rule. /// - [DefaultValue(null)] + [DefaultValue(null), Obsolete("Use UnprocessedObject instead. See https://aka.ms/ps-rule/deprecations for more detail.")] public bool? NotProcessedWarning { get; set; } /// @@ -224,7 +244,7 @@ internal static ExecutionOption Combine(ExecutionOption o1, ExecutionOption o2) /// /// Determines how to handle rules that are excluded. - /// By default, a excluded rules do not generated any output. + /// By default, excluded rules do not generated any output. /// When set to Error, an error is thrown. /// When set to Warn, a warning is generated. /// When set to Debug, a message is written to the debug log. @@ -247,8 +267,72 @@ internal static ExecutionOption Combine(ExecutionOption o1, ExecutionOption o2) [DefaultValue(null)] public ExecutionActionPreference? RuleSuppressed { get; set; } + /// + /// Determines how to handle when an alias to a resource is used. + /// This option replaces . + /// By default, a warning is generated. + /// When set to Error, an error is thrown. + /// When set to Debug, a message is written to the debug log. + /// When set to Ignore, no output will be displayed. + /// + /// + /// If is true this option will be overridden to Warn. + /// If is false this option will be overridden to Ignore. + /// + [DefaultValue(null)] + public ExecutionActionPreference? AliasReference { get; set; } + + /// + /// Determines how to handle rules that generate inconclusive results. + /// This option replaces . + /// By default, a warning is generated. + /// When set to Error, an error is thrown. + /// When set to Debug, a message is written to the debug log. + /// When set to Ignore, no output will be displayed. + /// + /// + /// If is true this option will be overridden to Warn. + /// If is false this option will be overridden to Ignore. + /// + [DefaultValue(null)] + public ExecutionActionPreference? RuleInconclusive { get; set; } + + /// + /// Determines how to report when an invariant culture is used. + /// This option replaces . + /// By default, a warning is generated. + /// When set to Error, an error is thrown. + /// When set to Debug, a message is written to the debug log. + /// When set to Ignore, no output will be displayed. + /// + /// + /// If is true this option will be overridden to Warn. + /// If is false this option will be overridden to Ignore. + /// + [DefaultValue(null)] + public ExecutionActionPreference? InvariantCulture { get; set; } + + /// + /// Determines how to report objects that are not processed by any rule. + /// This option replaces . + /// By default, a warning is generated. + /// When set to Error, an error is thrown. + /// When set to Debug, a message is written to the debug log. + /// When set to Ignore, no output will be displayed. + /// + /// + /// If is true this option will be overridden to Warn. + /// If is false this option will be overridden to Ignore. + /// + [DefaultValue(null)] + public ExecutionActionPreference? UnprocessedObject { get; set; } + + /// + /// Load from environment variables. + /// internal void Load(EnvironmentHelper env) { +#pragma warning disable CS0618 // Type or member is obsolete if (env.TryBool("PSRULE_EXECUTION_ALIASREFERENCEWARNING", out var bvalue)) AliasReferenceWarning = bvalue; @@ -271,9 +355,8 @@ internal void Load(EnvironmentHelper env) NotProcessedWarning = bvalue; if (env.TryBool("PSRULE_EXECUTION_SUPPRESSEDRULEWARNING", out bvalue)) -#pragma warning disable CS0612 // Type or member is obsolete SuppressedRuleWarning = bvalue; -#pragma warning restore CS0612 // Type or member is obsolete +#pragma warning restore CS0618 // Type or member is obsolete if (env.TryEnum("PSRULE_EXECUTION_SUPPRESSIONGROUPEXPIRED", out ExecutionActionPreference suppressionGroupExpired)) SuppressionGroupExpired = suppressionGroupExpired; @@ -283,10 +366,26 @@ internal void Load(EnvironmentHelper env) if (env.TryEnum("PSRULE_EXECUTION_RULESUPPRESSED", out ExecutionActionPreference ruleSuppressed)) RuleSuppressed = ruleSuppressed; + + if (env.TryEnum("PSRULE_EXECUTION_ALIASREFERENCE", out ExecutionActionPreference aliasReference)) + AliasReference = aliasReference; + + if (env.TryEnum("PSRULE_EXECUTION_RULEINCONCLUSIVE", out ExecutionActionPreference ruleInconclusive)) + RuleInconclusive = ruleInconclusive; + + if (env.TryEnum("PSRULE_EXECUTION_INVARIANTCULTURE", out ExecutionActionPreference invariantCulture)) + InvariantCulture = invariantCulture; + + if (env.TryEnum("PSRULE_EXECUTION_UNPROCESSEDOBJECT", out ExecutionActionPreference unprocessedObject)) + UnprocessedObject = unprocessedObject; } + /// + /// Load from dictionary. + /// internal void Load(Dictionary index) { +#pragma warning disable CS0618 // Type or member is obsolete if (index.TryPopBool("Execution.AliasReferenceWarning", out var bvalue)) AliasReferenceWarning = bvalue; @@ -309,9 +408,8 @@ internal void Load(Dictionary index) NotProcessedWarning = bvalue; if (index.TryPopBool("Execution.SuppressedRuleWarning", out bvalue)) -#pragma warning disable CS0612 // Type or member is obsolete SuppressedRuleWarning = bvalue; -#pragma warning restore CS0612 // Type or member is obsolete +#pragma warning restore CS0618 // Type or member is obsolete if (index.TryPopEnum("Execution.SuppressionGroupExpired", out ExecutionActionPreference suppressionGroupExpired)) SuppressionGroupExpired = suppressionGroupExpired; @@ -321,6 +419,18 @@ internal void Load(Dictionary index) if (index.TryPopEnum("Execution.RuleSuppressed", out ExecutionActionPreference ruleSuppressed)) RuleSuppressed = ruleSuppressed; + + if (index.TryPopEnum("Execution.AliasReference", out ExecutionActionPreference aliasReference)) + AliasReference = aliasReference; + + if (index.TryPopEnum("Execution.RuleInconclusive", out ExecutionActionPreference ruleInconclusive)) + RuleInconclusive = ruleInconclusive; + + if (index.TryPopEnum("Execution.InvariantCulture", out ExecutionActionPreference invariantCulture)) + InvariantCulture = invariantCulture; + + if (index.TryPopEnum("Execution.UnprocessedObject", out ExecutionActionPreference unprocessedObject)) + UnprocessedObject = unprocessedObject; } } } diff --git a/src/PSRule/PSRule.psm1 b/src/PSRule/PSRule.psm1 index 8750151d2e..55f844e86c 100644 --- a/src/PSRule/PSRule.psm1 +++ b/src/PSRule/PSRule.psm1 @@ -1213,6 +1213,22 @@ function New-PSRuleOption { [Parameter(Mandatory = $False)] [PSRule.Configuration.ExecutionActionPreference]$ExecutionRuleSuppressed = [PSRule.Configuration.ExecutionActionPreference]::Warn, + # Sets the Execution.AliasReference option + [Parameter(Mandatory = $False)] + [PSRule.Configuration.ExecutionActionPreference]$ExecutionAliasReference = [PSRule.Configuration.ExecutionActionPreference]::Warn, + + # Sets the Execution.RuleInconclusive option + [Parameter(Mandatory = $False)] + [PSRule.Configuration.ExecutionActionPreference]$ExecutionRuleInconclusive = [PSRule.Configuration.ExecutionActionPreference]::Warn, + + # Sets the Execution.InvariantCulture option + [Parameter(Mandatory = $False)] + [PSRule.Configuration.ExecutionActionPreference]$ExecutionInvariantCulture = [PSRule.Configuration.ExecutionActionPreference]::Warn, + + # Sets the Execution.UnprocessedObject option + [Parameter(Mandatory = $False)] + [PSRule.Configuration.ExecutionActionPreference]$ExecutionUnprocessedObject = [PSRule.Configuration.ExecutionActionPreference]::Warn, + # Sets the Include.Module option [Parameter(Mandatory = $False)] [String[]]$IncludeModule, @@ -1523,6 +1539,22 @@ function Set-PSRuleOption { [Parameter(Mandatory = $False)] [PSRule.Configuration.ExecutionActionPreference]$ExecutionRuleSuppressed = [PSRule.Configuration.ExecutionActionPreference]::Warn, + # Sets the Execution.AliasReference option + [Parameter(Mandatory = $False)] + [PSRule.Configuration.ExecutionActionPreference]$ExecutionAliasReference = [PSRule.Configuration.ExecutionActionPreference]::Warn, + + # Sets the Execution.RuleInconclusive option + [Parameter(Mandatory = $False)] + [PSRule.Configuration.ExecutionActionPreference]$ExecutionRuleInconclusive = [PSRule.Configuration.ExecutionActionPreference]::Warn, + + # Sets the Execution.InvariantCulture option + [Parameter(Mandatory = $False)] + [PSRule.Configuration.ExecutionActionPreference]$ExecutionInvariantCulture = [PSRule.Configuration.ExecutionActionPreference]::Warn, + + # Sets the Execution.UnprocessedObject option + [Parameter(Mandatory = $False)] + [PSRule.Configuration.ExecutionActionPreference]$ExecutionUnprocessedObject = [PSRule.Configuration.ExecutionActionPreference]::Warn, + # Sets the Include.Module option [Parameter(Mandatory = $False)] [String[]]$IncludeModule, @@ -2279,6 +2311,22 @@ function SetOptions { [Parameter(Mandatory = $False)] [PSRule.Configuration.ExecutionActionPreference]$ExecutionRuleSuppressed = [PSRule.Configuration.ExecutionActionPreference]::Warn, + # Sets the Execution.AliasReference option + [Parameter(Mandatory = $False)] + [PSRule.Configuration.ExecutionActionPreference]$ExecutionAliasReference = [PSRule.Configuration.ExecutionActionPreference]::Warn, + + # Sets the Execution.RuleInconclusive option + [Parameter(Mandatory = $False)] + [PSRule.Configuration.ExecutionActionPreference]$ExecutionRuleInconclusive = [PSRule.Configuration.ExecutionActionPreference]::Warn, + + # Sets the Execution.InvariantCulture option + [Parameter(Mandatory = $False)] + [PSRule.Configuration.ExecutionActionPreference]$ExecutionInvariantCulture = [PSRule.Configuration.ExecutionActionPreference]::Warn, + + # Sets the Execution.UnprocessedObject option + [Parameter(Mandatory = $False)] + [PSRule.Configuration.ExecutionActionPreference]$ExecutionUnprocessedObject = [PSRule.Configuration.ExecutionActionPreference]::Warn, + # Sets the Include.Module option [Parameter(Mandatory = $False)] [String[]]$IncludeModule, @@ -2501,6 +2549,26 @@ function SetOptions { $Option.Execution.RuleSuppressed = $ExecutionRuleSuppressed; } + # Sets option Execution.AliasReference + if ($PSBoundParameters.ContainsKey('ExecutionAliasReference')) { + $Option.Execution.AliasReference = $ExecutionAliasReference; + } + + # Sets option Execution.RuleInconclusive + if ($PSBoundParameters.ContainsKey('ExecutionRuleInconclusive')) { + $Option.Execution.RuleInconclusive = $ExecutionRuleInconclusive; + } + + # Sets option Execution.InvariantCulture + if ($PSBoundParameters.ContainsKey('ExecutionInvariantCulture')) { + $Option.Execution.InvariantCulture = $ExecutionInvariantCulture; + } + + # Sets option Execution.UnprocessedObject + if ($PSBoundParameters.ContainsKey('ExecutionUnprocessedObject')) { + $Option.Execution.UnprocessedObject = $ExecutionUnprocessedObject; + } + # Sets option Include.Module if ($PSBoundParameters.ContainsKey('IncludeModule')) { $Option.Include.Module = $IncludeModule; diff --git a/src/PSRule/Pipeline/InvokeRulePipeline.cs b/src/PSRule/Pipeline/InvokeRulePipeline.cs index 7146cc360a..b06fb14888 100644 --- a/src/PSRule/Pipeline/InvokeRulePipeline.cs +++ b/src/PSRule/Pipeline/InvokeRulePipeline.cs @@ -36,10 +36,18 @@ internal InvokeRulePipeline(PipelineContext context, Source[] source, IPipelineW if (RuleCount == 0) Context.WarnRuleNotFound(); -#pragma warning disable CS0612 // Type or member is obsolete +#pragma warning disable CS0618 // Type or member is obsolete if (context.Option.Execution.SuppressedRuleWarning.HasValue) Context.WarnDeprecatedOption("Execution.SuppressedRuleWarning"); -#pragma warning restore CS0612 // Type or member is obsolete + if (context.Option.Execution.AliasReferenceWarning.HasValue) + Context.WarnDeprecatedOption("Execution.AliasReferenceWarning"); + if (context.Option.Execution.InconclusiveWarning.HasValue) + Context.WarnDeprecatedOption("Execution.InconclusiveWarning"); + if (context.Option.Execution.InvariantCultureWarning.HasValue) + Context.WarnDeprecatedOption("Execution.InvariantCultureWarning"); + if (context.Option.Execution.NotProcessedWarning.HasValue) + Context.WarnDeprecatedOption("Execution.NotProcessedWarning"); +#pragma warning restore CS0618 // Type or member is obsolete _Outcome = outcome; _IsSummary = context.Option.Output.As.Value == ResultFormat.Summary; diff --git a/src/PSRule/Pipeline/PipelineWriterExtensions.cs b/src/PSRule/Pipeline/PipelineWriterExtensions.cs index bf0c71fb0d..4b3dcdc327 100644 --- a/src/PSRule/Pipeline/PipelineWriterExtensions.cs +++ b/src/PSRule/Pipeline/PipelineWriterExtensions.cs @@ -32,14 +32,6 @@ internal static void DebugMessage(this IPipelineWriter logger, string message) logger.WriteDebug(new DebugRecord(message)); } - internal static void WarnUsingInvariantCulture(this IPipelineWriter writer) - { - if (writer == null || !writer.ShouldWriteWarning()) - return; - - writer.WriteWarning(PSRuleResources.UsingInvariantCulture); - } - internal static void WarnRulePathNotFound(this IPipelineWriter writer) { if (writer == null || !writer.ShouldWriteWarning()) diff --git a/src/PSRule/Runtime/RunspaceContext.cs b/src/PSRule/Runtime/RunspaceContext.cs index c322d545d9..8a3302013e 100644 --- a/src/PSRule/Runtime/RunspaceContext.cs +++ b/src/PSRule/Runtime/RunspaceContext.cs @@ -75,10 +75,10 @@ internal sealed class RunspaceContext : IDisposable, ILogger internal RuleBlock RuleBlock; internal ITargetBindingResult Binding; - private readonly bool _InconclusiveWarning; - private readonly bool _NotProcessedWarning; - private readonly ExecutionActionPreference _SuppressedRuleWarning; - private readonly bool _InvariantCultureWarning; + private readonly ExecutionActionPreference _RuleInconclusive; + private readonly ExecutionActionPreference _UnprocessedObject; + private readonly ExecutionActionPreference _RuleSuppressed; + private readonly ExecutionActionPreference _InvariantCulture; private readonly OutcomeLogStream _FailStream; private readonly OutcomeLogStream _PassStream; @@ -117,17 +117,30 @@ internal RunspaceContext(PipelineContext pipeline, IPipelineWriter writer) CurrentThread = this; Pipeline = pipeline; - _InconclusiveWarning = Pipeline.Option.Execution.InconclusiveWarning ?? ExecutionOption.Default.InconclusiveWarning.Value; - _NotProcessedWarning = Pipeline.Option.Execution.NotProcessedWarning ?? ExecutionOption.Default.NotProcessedWarning.Value; +#pragma warning disable CS0618 // Type or member is obsolete + + if (Pipeline.Option.Execution.InconclusiveWarning.HasValue) + _RuleInconclusive = Pipeline.Option.Execution.InconclusiveWarning.Value ? ExecutionActionPreference.Warn : ExecutionActionPreference.Ignore; + else + _RuleInconclusive = Pipeline.Option.Execution.RuleInconclusive.GetValueOrDefault(ExecutionOption.Default.RuleInconclusive.Value); + + if (Pipeline.Option.Execution.NotProcessedWarning.HasValue) + _UnprocessedObject = Pipeline.Option.Execution.NotProcessedWarning.Value ? ExecutionActionPreference.Warn : ExecutionActionPreference.Ignore; + else + _UnprocessedObject = Pipeline.Option.Execution.UnprocessedObject.GetValueOrDefault(ExecutionOption.Default.UnprocessedObject.Value); -#pragma warning disable CS0612 // Type or member is obsolete if (Pipeline.Option.Execution.SuppressedRuleWarning.HasValue) - _SuppressedRuleWarning = Pipeline.Option.Execution.SuppressedRuleWarning.Value ? ExecutionActionPreference.Warn : ExecutionActionPreference.Ignore; + _RuleSuppressed = Pipeline.Option.Execution.SuppressedRuleWarning.Value ? ExecutionActionPreference.Warn : ExecutionActionPreference.Ignore; + else + _RuleSuppressed = Pipeline.Option.Execution.RuleSuppressed.GetValueOrDefault(ExecutionOption.Default.RuleSuppressed.Value); + + if (Pipeline.Option.Execution.InvariantCultureWarning.HasValue) + _InvariantCulture = Pipeline.Option.Execution.InvariantCultureWarning.Value ? ExecutionActionPreference.Warn : ExecutionActionPreference.Ignore; else - _SuppressedRuleWarning = Pipeline.Option.Execution.RuleSuppressed.GetValueOrDefault(ExecutionOption.Default.RuleSuppressed.Value); -#pragma warning restore CS0612 // Type or member is obsolete + _InvariantCulture = Pipeline.Option.Execution.InvariantCulture.GetValueOrDefault(ExecutionOption.Default.InvariantCulture.Value); + +#pragma warning restore CS0618 // Type or member is obsolete - _InvariantCultureWarning = Pipeline.Option.Execution.InvariantCultureWarning ?? ExecutionOption.Default.InvariantCultureWarning.Value; _FailStream = Pipeline.Option.Logging.RuleFail ?? LoggingOption.Default.RuleFail.Value; _PassStream = Pipeline.Option.Logging.RulePass ?? LoggingOption.Default.RulePass.Value; _WarnOnce = new HashSet(); @@ -245,28 +258,22 @@ public void Fail() public void WarnRuleInconclusive(string ruleId) { - if (Writer == null || !Writer.ShouldWriteWarning() || !_InconclusiveWarning) - return; - - Writer.WriteWarning(PSRuleResources.RuleInconclusive, ruleId, Binding.TargetName); + this.Throw(_RuleInconclusive, PSRuleResources.RuleInconclusive, ruleId, Binding.TargetName); } public void WarnObjectNotProcessed() { - if (Writer == null || !Writer.ShouldWriteWarning() || !_NotProcessedWarning) - return; - - Writer.WriteWarning(PSRuleResources.ObjectNotProcessed, Binding.TargetName); + this.Throw(_UnprocessedObject, PSRuleResources.ObjectNotProcessed, Binding.TargetName); } public void RuleSuppressed(string ruleId) { - this.Throw(_SuppressedRuleWarning, PSRuleResources.RuleSuppressed, ruleId, Binding.TargetName); + this.Throw(_RuleSuppressed, PSRuleResources.RuleSuppressed, ruleId, Binding.TargetName); } public void WarnRuleCountSuppressed(int ruleCount) { - this.Throw(_SuppressedRuleWarning, PSRuleResources.RuleCountSuppressed, ruleCount, Binding.TargetName); + this.Throw(_RuleSuppressed, PSRuleResources.RuleCountSuppressed, ruleCount, Binding.TargetName); } public void RuleSuppressionGroup(string ruleId, ISuppressionInfo suppression) @@ -275,9 +282,9 @@ public void RuleSuppressionGroup(string ruleId, ISuppressionInfo suppression) return; if (suppression.Synopsis != null && suppression.Synopsis.HasValue) - this.Throw(_SuppressedRuleWarning, PSRuleResources.RuleSuppressionGroupExtended, ruleId, suppression.Id, Binding.TargetName, suppression.Synopsis.Text); + this.Throw(_RuleSuppressed, PSRuleResources.RuleSuppressionGroupExtended, ruleId, suppression.Id, Binding.TargetName, suppression.Synopsis.Text); else - this.Throw(_SuppressedRuleWarning, PSRuleResources.RuleSuppressionGroup, ruleId, suppression.Id, Binding.TargetName); + this.Throw(_RuleSuppressed, PSRuleResources.RuleSuppressionGroup, ruleId, suppression.Id, Binding.TargetName); } public void RuleSuppressionGroupCount(ISuppressionInfo suppression, int count) @@ -286,9 +293,9 @@ public void RuleSuppressionGroupCount(ISuppressionInfo suppression, int count) return; if (suppression.Synopsis != null && suppression.Synopsis.HasValue) - this.Throw(_SuppressedRuleWarning, PSRuleResources.RuleSuppressionGroupExtendedCount, count, suppression.Id, Binding.TargetName, suppression.Synopsis.Text); + this.Throw(_RuleSuppressed, PSRuleResources.RuleSuppressionGroupExtendedCount, count, suppression.Id, Binding.TargetName, suppression.Synopsis.Text); else - this.Throw(_SuppressedRuleWarning, PSRuleResources.RuleSuppressionGroupCount, count, suppression.Id, Binding.TargetName); + this.Throw(_RuleSuppressed, PSRuleResources.RuleSuppressionGroupCount, count, suppression.Id, Binding.TargetName); } public void ErrorInvaildRuleResult() @@ -845,13 +852,10 @@ public string GetLocalizedPath(string file, out string culture) if (string.IsNullOrEmpty(Source.File.HelpPath)) return null; - //var cultures = Pipeline.Baseline.GetCulture(); var cultures = LanguageScope.Culture; - if (!_RaisedUsingInvariantCulture && - (cultures == null || cultures.Length == 0) && - _InvariantCultureWarning) + if (!_RaisedUsingInvariantCulture && (cultures == null || cultures.Length == 0)) { - Writer.WarnUsingInvariantCulture(); + this.Throw(_InvariantCulture, PSRuleResources.UsingInvariantCulture); _RaisedUsingInvariantCulture = true; return null; } diff --git a/tests/PSRule.Tests/PSRule.Common.Tests.ps1 b/tests/PSRule.Tests/PSRule.Common.Tests.ps1 index ae7bdefb93..70303cb089 100644 --- a/tests/PSRule.Tests/PSRule.Common.Tests.ps1 +++ b/tests/PSRule.Tests/PSRule.Common.Tests.ps1 @@ -100,7 +100,7 @@ Describe 'Invoke-PSRule' -Tag 'Invoke-PSRule','Common' { } It 'Returns inconclusive' { - $option = @{ 'Execution.InconclusiveWarning' = $False }; + $option = @{ 'Execution.RuleInconclusive' = 'Ignore' }; $result = $testObject | Invoke-PSRule -Path $ruleFilePath -Name 'FromFile3' -Outcome All -Option $option; $result | Should -Not -BeNullOrEmpty; $result.IsSuccess() | Should -Be $False; @@ -1213,7 +1213,7 @@ Describe 'Invoke-PSRule' -Tag 'Invoke-PSRule','Common' { Context 'Detail' { It 'Show Warnings' { - $option = New-PSRuleOption -SuppressTargetName @{ FromFile1 = 'TestObject1'; FromFile2 = 'TestObject1'; } -ExecutionRuleSuppressed Warn -OutputAs Detail -InvariantCultureWarning $False; + $option = New-PSRuleOption -SuppressTargetName @{ FromFile1 = 'TestObject1'; FromFile2 = 'TestObject1'; } -ExecutionRuleSuppressed Warn -OutputAs Detail -ExecutionInvariantCulture 'Ignore'; $Null = $testObject | Invoke-PSRule -Path $ruleFilePath -Option $option -Name 'FromFile1', 'FromFile2' -WarningVariable outWarnings -WarningAction SilentlyContinue; @@ -1227,7 +1227,7 @@ Describe 'Invoke-PSRule' -Tag 'Invoke-PSRule','Common' { } It 'No warnings' { - $option = New-PSRuleOption -SuppressTargetName @{ FromFile1 = 'TestObject1'; FromFile2 = 'TestObject1'; } -ExecutionRuleSuppressed Ignore -OutputAs Detail -InvariantCultureWarning $False; + $option = New-PSRuleOption -SuppressTargetName @{ FromFile1 = 'TestObject1'; FromFile2 = 'TestObject1'; } -ExecutionRuleSuppressed Ignore -OutputAs Detail -ExecutionInvariantCulture 'Ignore'; $Null = $testObject | Invoke-PSRule -Path $ruleFilePath -Option $option -Name 'FromFile1', 'FromFile2' -WarningVariable outWarnings -WarningAction SilentlyContinue; @@ -1238,7 +1238,7 @@ Describe 'Invoke-PSRule' -Tag 'Invoke-PSRule','Common' { Context 'Summary' { It 'Show warnings' { - $option = New-PSRuleOption -SuppressTargetName @{ FromFile1 = 'TestObject1'; FromFile2 = 'TestObject1'; } -ExecutionRuleSuppressed Warn -OutputAs Summary -InvariantCultureWarning $False; + $option = New-PSRuleOption -SuppressTargetName @{ FromFile1 = 'TestObject1'; FromFile2 = 'TestObject1'; } -ExecutionRuleSuppressed Warn -OutputAs Summary -ExecutionInvariantCulture 'Ignore'; $Null = $testObject | Invoke-PSRule -Path $ruleFilePath -Option $option -Name 'FromFile1', 'FromFile2' -WarningVariable outWarnings -WarningAction SilentlyContinue; @@ -1250,7 +1250,7 @@ Describe 'Invoke-PSRule' -Tag 'Invoke-PSRule','Common' { } It 'No warnings' { - $option = New-PSRuleOption -SuppressTargetName @{ FromFile1 = 'TestObject1'; FromFile2 = 'TestObject1'; } -ExecutionRuleSuppressed Ignore -OutputAs Summary -InvariantCultureWarning $False; + $option = New-PSRuleOption -SuppressTargetName @{ FromFile1 = 'TestObject1'; FromFile2 = 'TestObject1'; } -ExecutionRuleSuppressed Ignore -OutputAs Summary -ExecutionInvariantCulture 'Ignore'; $Null = $testObject | Invoke-PSRule -Path $ruleFilePath -Option $option -Name 'FromFile1', 'FromFile2' -WarningVariable outWarnings -WarningAction SilentlyContinue; @@ -1294,7 +1294,7 @@ Describe 'Invoke-PSRule' -Tag 'Invoke-PSRule','Common' { Context 'Detail' { It 'Show warnings' { - $option = New-PSRuleOption -ExecutionRuleSuppressed Warn -OutputAs Detail -InvariantCultureWarning $False -OutputCulture 'en-US'; + $option = New-PSRuleOption -ExecutionRuleSuppressed Warn -OutputAs Detail -ExecutionInvariantCulture Ignore -OutputCulture 'en-US'; $Null = $testObject | Invoke-PSRule @invokeParams -Option $option -Name 'FromFile1', 'FromFile2', 'WithTag2' -WarningVariable outWarnings -WarningAction SilentlyContinue; @@ -1318,7 +1318,7 @@ Describe 'Invoke-PSRule' -Tag 'Invoke-PSRule','Common' { } It 'Show warnings for all rules when rule property is null or empty' { - $option = New-PSRuleOption -ExecutionRuleSuppressed Warn -OutputAs Detail -InvariantCultureWarning $False -SuppressionGroupExpired Ignore; + $option = New-PSRuleOption -ExecutionRuleSuppressed Warn -OutputAs Detail -ExecutionInvariantCulture Ignore -SuppressionGroupExpired Ignore; $Null = $testObject | Invoke-PSRule @invokeParams2 -Option $option -WarningVariable outWarnings -WarningAction SilentlyContinue; @@ -1330,7 +1330,7 @@ Describe 'Invoke-PSRule' -Tag 'Invoke-PSRule','Common' { } It 'No warnings' { - $option = New-PSRuleOption -ExecutionRuleSuppressed Ignore -OutputAs Detail -InvariantCultureWarning $False -SuppressionGroupExpired Ignore; + $option = New-PSRuleOption -ExecutionRuleSuppressed Ignore -OutputAs Detail -ExecutionInvariantCulture Ignore -SuppressionGroupExpired Ignore; $Null = $testObject | Invoke-PSRule @invokeParams -Option $option -Name 'FromFile1', 'FromFile2', 'WithTag2' -WarningVariable outWarnings -WarningAction SilentlyContinue; @@ -1341,7 +1341,7 @@ Describe 'Invoke-PSRule' -Tag 'Invoke-PSRule','Common' { Context 'Summary' { It 'Show warnings' { - $option = New-PSRuleOption -ExecutionRuleSuppressed Warn -OutputAs Summary -InvariantCultureWarning $False -SuppressionGroupExpired Ignore -OutputCulture 'en-US'; + $option = New-PSRuleOption -ExecutionRuleSuppressed Warn -OutputAs Summary -ExecutionInvariantCulture Ignore -SuppressionGroupExpired Ignore -OutputCulture 'en-US'; $Null = $testObject | Invoke-PSRule @invokeParams -Option $option -Name 'FromFile3', 'FromFile5', 'WithTag3' -WarningVariable outWarnings -WarningAction SilentlyContinue; @@ -1359,7 +1359,7 @@ Describe 'Invoke-PSRule' -Tag 'Invoke-PSRule','Common' { } It 'Show warnings for all rules when rule property is null or empty' { - $option = New-PSRuleOption -ExecutionRuleSuppressed Warn -OutputAs Summary -InvariantCultureWarning $False; + $option = New-PSRuleOption -ExecutionRuleSuppressed Warn -OutputAs Summary -ExecutionInvariantCulture Ignore; $Null = $testObject | Invoke-PSRule @invokeParams2 -Option $option -WarningVariable outWarnings -WarningAction SilentlyContinue; @@ -1373,7 +1373,7 @@ Describe 'Invoke-PSRule' -Tag 'Invoke-PSRule','Common' { } It 'No warnings' { - $option = New-PSRuleOption -ExecutionRuleSuppressed Ignore -OutputAs Summary -InvariantCultureWarning $False -SuppressionGroupExpired Ignore; + $option = New-PSRuleOption -ExecutionRuleSuppressed Ignore -OutputAs Summary -ExecutionInvariantCulture Ignore -SuppressionGroupExpired Ignore; $Null = $testObject | Invoke-PSRule @invokeParams -Option $option -Name 'FromFile3', 'FromFile5', 'WithTag3' -WarningVariable outWarnings -WarningAction SilentlyContinue; @@ -1408,7 +1408,7 @@ Describe 'Test-PSRuleTarget' -Tag 'Test-PSRuleTarget','Common' { $result | Should -Be $True; # Check result with one failing rule - $option = @{ 'Execution.InconclusiveWarning' = $False }; + $option = @{ 'Execution.RuleInconclusive' = 'Ignore' }; $result = $testObject | Test-PSRuleTarget -Path $ruleFilePath -Name 'FromFile1', 'FromFile2', 'FromFile3' -Option $option; $result | Should -Not -BeNullOrEmpty; $result | Should -BeOfType System.Boolean; @@ -1632,7 +1632,7 @@ Describe 'Assert-PSRule' -Tag 'Assert-PSRule','Common' { # Check multiple $assertParams = @{ Path = $ruleFilePath - Option = @{ 'Execution.InconclusiveWarning' = $False; 'Output.Style' = 'Plain' } + Option = @{ 'Execution.RuleInconclusive' = 'Ignore'; 'Output.Style' = 'Plain' } Name = 'FromFile1', 'FromFile2', 'FromFile3' ErrorVariable = 'errorOut' } @@ -1657,7 +1657,7 @@ Describe 'Assert-PSRule' -Tag 'Assert-PSRule','Common' { $testOutputPath = (Join-Path -Path $outputPath -ChildPath 'newPath/assert.results.json'); $assertParams = @{ Path = $ruleFilePath - Option = @{ 'Execution.InconclusiveWarning' = $False; 'Output.Style' = 'Plain'; 'Binding.Field' = @{ extra = 'Name'} } + Option = @{ 'Execution.RuleInconclusive' = 'Ignore'; 'Output.Style' = 'Plain'; 'Binding.Field' = @{ extra = 'Name'} } Name = 'FromFile1', 'FromFile2', 'FromFile3' ErrorVariable = 'errorOut' OutputFormat = 'Json' @@ -1676,7 +1676,7 @@ Describe 'Assert-PSRule' -Tag 'Assert-PSRule','Common' { It 'With -WarningAction' { $assertParams = @{ Path = $ruleFilePath - Option = @{ 'Execution.InconclusiveWarning' = $False; 'Output.Style' = 'Plain' } + Option = @{ 'Execution.RuleInconclusive' = 'Ignore'; 'Output.Style' = 'Plain' } Name = 'WithWarning' } $result = $testObject | Assert-PSRule @assertParams 6>&1 | Out-String; @@ -1694,7 +1694,7 @@ Describe 'Assert-PSRule' -Tag 'Assert-PSRule','Common' { $testOutputPath = (Join-Path -Path $outputPath -ChildPath 'newPath/assert.results2.json'); $assertParams = @{ Path = $ruleFilePath - Option = @{ 'Execution.InconclusiveWarning' = $False; 'Output.Style' = 'Plain'; 'Binding.Field' = @{ extra = 'Name'} } + Option = @{ 'Execution.RuleInconclusive' = 'Ignore'; 'Output.Style' = 'Plain'; 'Binding.Field' = @{ extra = 'Name'} } Name = 'FromFile2', 'FromFile3', 'WithError', 'WithException' ErrorVariable = 'errorOut' OutputFormat = 'Json' @@ -1831,7 +1831,7 @@ Describe 'Get-PSRule' -Tag 'Get-PSRule','Common' { It 'Returns rules in current path' { try { Push-Location -Path $searchPath; - $result = @(Get-PSRule -Path $PWD -Option @{ 'Execution.InvariantCultureWarning' = $False }) + $result = @(Get-PSRule -Path $PWD -Option @{ 'Execution.InvariantCulture' = 'Ignore' }) $result | Should -Not -BeNullOrEmpty; $result.Length | Should -Be 3; $result.RuleName | Should -BeIn 'M1.Rule1', 'M1.Rule2', 'M1.YamlTestName'; @@ -1947,31 +1947,31 @@ Describe 'Get-PSRule' -Tag 'Get-PSRule','Common' { It 'Uses rules with include option' { Push-Location -Path (Join-Path -Path $here -ChildPath 'rules/') try { - $result = @(Get-PSRule -Path $PWD -Option @{ 'Execution.InvariantCultureWarning' = $False }) + $result = @(Get-PSRule -Path $PWD -Option @{ 'Execution.InvariantCulture' = 'Ignore' }) $result.Length | Should -Be 4; - $result = @(Get-PSRule -Option @{ 'Include.Path' = 'main/'; 'Execution.InvariantCultureWarning' = $False }) + $result = @(Get-PSRule -Option @{ 'Include.Path' = 'main/'; 'Execution.InvariantCulture' = 'Ignore' }) $result.Length | Should -Be 1; - $result = @(Get-PSRule -Option @{ 'Include.Path' = 'main/', 'extra/'; 'Execution.InvariantCultureWarning' = $False }) + $result = @(Get-PSRule -Option @{ 'Include.Path' = 'main/', 'extra/'; 'Execution.InvariantCulture' = 'Ignore' }) $result.Length | Should -Be 2; - $result = @(Get-PSRule -Option @{ 'Include.Path' = '.'; 'Execution.InvariantCultureWarning' = $False }) + $result = @(Get-PSRule -Option @{ 'Include.Path' = '.'; 'Execution.InvariantCulture' = 'Ignore' }) $result.Length | Should -Be 4; - $result = @(Get-PSRule -Path 'main/' -Option @{ 'Execution.InvariantCultureWarning' = $False }) + $result = @(Get-PSRule -Path 'main/' -Option @{ 'Execution.InvariantCulture' = 'Ignore' }) $result.Length | Should -Be 2; - $result = @(Get-PSRule -Path 'main/' -Option @{ 'Include.Path' = @(); 'Execution.InvariantCultureWarning' = $False }) + $result = @(Get-PSRule -Path 'main/' -Option @{ 'Include.Path' = @(); 'Execution.InvariantCulture' = 'Ignore' }) $result.Length | Should -Be 1; - $result = @(Get-PSRule -Path 'main/' -Option @{ 'Include.Path' = 'extra/'; 'Execution.InvariantCultureWarning' = $False }) + $result = @(Get-PSRule -Path 'main/' -Option @{ 'Include.Path' = 'extra/'; 'Execution.InvariantCulture' = 'Ignore' }) $result.Length | Should -Be 2; - $result = @(Get-PSRule -Path 'main/' -Option @{ 'Include.Path' = 'extra/', '.ps-rule/'; 'Execution.InvariantCultureWarning' = $False }) + $result = @(Get-PSRule -Path 'main/' -Option @{ 'Include.Path' = 'extra/', '.ps-rule/'; 'Execution.InvariantCulture' = 'Ignore' }) $result.Length | Should -Be 3; - $result = @(Get-PSRule -Path 'main/' -Option @{ 'Include.Path' = 'main/'; 'Execution.InvariantCultureWarning' = $False }) + $result = @(Get-PSRule -Path 'main/' -Option @{ 'Include.Path' = 'main/'; 'Execution.InvariantCulture' = 'Ignore' }) $result.Length | Should -Be 1; } finally { @@ -2368,7 +2368,7 @@ Describe 'Get-PSRuleHelp' -Tag 'Get-PSRuleHelp', 'Common' { BeforeAll { # Get a list of rules $searchPath = Join-Path -Path $here -ChildPath 'TestModule'; - $options = @{ 'Execution.InvariantCultureWarning' = $False } + $options = @{ 'Execution.InvariantCulture' = 'Ignore' } } It 'Docs from imported module' { @@ -2429,7 +2429,7 @@ Describe 'Get-PSRuleHelp' -Tag 'Get-PSRuleHelp', 'Common' { try { Push-Location $searchPath; { Get-PSRuleHelp -Path $PWD } | Should -Throw "The resource '.\M1.Rule2' is using a duplicate resource identifier. A resource with the identifier '.\M1.Rule2' already exists. Each resource must have a unique name, ref, and aliases. See https://aka.ms/ps-rule/naming for guidance on naming within PSRule."; - Get-PSRuleHelp -Path $PWD -Option @{ 'Execution.DuplicateResourceId' = 'Warn'; 'Execution.InvariantCultureWarning' = $False } -WarningVariable outWarn -WarningAction SilentlyContinue; + Get-PSRuleHelp -Path $PWD -Option @{ 'Execution.DuplicateResourceId' = 'Warn'; 'Execution.InvariantCulture' = 'Ignore' } -WarningVariable outWarn -WarningAction SilentlyContinue; $warnings = @($outWarn); $warnings.Count | Should -Be 1; $warnings | Should -Be "The resource '.\M1.Rule2' is using a duplicate resource identifier. A resource with the identifier '.\M1.Rule2' already exists. Each resource must have a unique name, ref, and aliases. See https://aka.ms/ps-rule/naming for guidance on naming within PSRule."; diff --git a/tests/PSRule.Tests/PSRule.Options.Tests.ps1 b/tests/PSRule.Tests/PSRule.Options.Tests.ps1 index ef4f37387b..17452206e1 100644 --- a/tests/PSRule.Tests/PSRule.Options.Tests.ps1 +++ b/tests/PSRule.Tests/PSRule.Options.Tests.ps1 @@ -625,7 +625,7 @@ Describe 'New-PSRuleOption' -Tag 'Option','New-PSRuleOption' { Context 'Read Execution.InconclusiveWarning' { It 'from default' { $option = New-PSRuleOption -Default; - $option.Execution.InconclusiveWarning | Should -Be $True; + $option.Execution.InconclusiveWarning | Should -Be $Null; } It 'from Hashtable' { @@ -664,7 +664,7 @@ Describe 'New-PSRuleOption' -Tag 'Option','New-PSRuleOption' { Context 'Read Execution.NotProcessedWarning' { It 'from default' { $option = New-PSRuleOption -Default; - $option.Execution.NotProcessedWarning | Should -Be $True; + $option.Execution.NotProcessedWarning | Should -Be $Null; } It 'from Hashtable' { @@ -875,10 +875,198 @@ Describe 'New-PSRuleOption' -Tag 'Option','New-PSRuleOption' { } } + Context 'Read Execution.AliasReference' { + It 'from default' { + $option = New-PSRuleOption -Default; + $option.Execution.AliasReference | Should -Be 'Warn'; + } + + It 'from Hashtable' { + $option = New-PSRuleOption -Option @{ 'Execution.AliasReference' = 'error' }; + $option.Execution.AliasReference | Should -Be 'Error'; + + $option = New-PSRuleOption -Option @{ 'Execution.AliasReference' = 'Error' }; + $option.Execution.AliasReference | Should -Be 'Error'; + } + + It 'from YAML' { + $option = New-PSRuleOption -Option (Join-Path -Path $here -ChildPath 'PSRule.Tests.yml'); + $option.Execution.AliasReference | Should -Be 'Ignore'; + } + + It 'from Environment' { + try { + # With enum + $Env:PSRULE_EXECUTION_ALIASREFERENCE = 'error'; + $option = New-PSRuleOption; + $option.Execution.AliasReference | Should -Be 'Error'; + + # With enum + $Env:PSRULE_EXECUTION_ALIASREFERENCE = 'Error'; + $option = New-PSRuleOption; + $option.Execution.AliasReference | Should -Be 'Error'; + + # With int + $Env:PSRULE_EXECUTION_ALIASREFERENCE = '3'; + $option = New-PSRuleOption; + $option.Execution.AliasReference | Should -Be 'Error'; + } + finally { + Remove-Item 'Env:PSRULE_EXECUTION_ALIASREFERENCE' -Force; + } + } + + It 'from parameter' { + $option = New-PSRuleOption -ExecutionAliasReference 'Error' -Path $emptyOptionsFilePath; + $option.Execution.AliasReference | Should -Be 'Error'; + } + } + + Context 'Read Execution.RuleInconclusive' { + It 'from default' { + $option = New-PSRuleOption -Default; + $option.Execution.RuleInconclusive | Should -Be 'Warn'; + } + + It 'from Hashtable' { + $option = New-PSRuleOption -Option @{ 'Execution.RuleInconclusive' = 'error' }; + $option.Execution.RuleInconclusive | Should -Be 'Error'; + + $option = New-PSRuleOption -Option @{ 'Execution.RuleInconclusive' = 'Error' }; + $option.Execution.RuleInconclusive | Should -Be 'Error'; + } + + It 'from YAML' { + $option = New-PSRuleOption -Option (Join-Path -Path $here -ChildPath 'PSRule.Tests.yml'); + $option.Execution.RuleInconclusive | Should -Be 'Ignore'; + } + + It 'from Environment' { + try { + # With enum + $Env:PSRULE_EXECUTION_RULEINCONCLUSIVE = 'error'; + $option = New-PSRuleOption; + $option.Execution.RuleInconclusive | Should -Be 'Error'; + + # With enum + $Env:PSRULE_EXECUTION_RULEINCONCLUSIVE = 'Error'; + $option = New-PSRuleOption; + $option.Execution.RuleInconclusive | Should -Be 'Error'; + + # With int + $Env:PSRULE_EXECUTION_RULEINCONCLUSIVE = '3'; + $option = New-PSRuleOption; + $option.Execution.RuleInconclusive | Should -Be 'Error'; + } + finally { + Remove-Item 'Env:PSRULE_EXECUTION_RULEINCONCLUSIVE' -Force; + } + } + + It 'from parameter' { + $option = New-PSRuleOption -ExecutionRuleInconclusive 'Error' -Path $emptyOptionsFilePath; + $option.Execution.RuleInconclusive | Should -Be 'Error'; + } + } + + Context 'Read Execution.InvariantCulture' { + It 'from default' { + $option = New-PSRuleOption -Default; + $option.Execution.InvariantCulture | Should -Be 'Warn'; + } + + It 'from Hashtable' { + $option = New-PSRuleOption -Option @{ 'Execution.InvariantCulture' = 'error' }; + $option.Execution.InvariantCulture | Should -Be 'Error'; + + $option = New-PSRuleOption -Option @{ 'Execution.InvariantCulture' = 'Error' }; + $option.Execution.InvariantCulture | Should -Be 'Error'; + } + + It 'from YAML' { + $option = New-PSRuleOption -Option (Join-Path -Path $here -ChildPath 'PSRule.Tests.yml'); + $option.Execution.InvariantCulture | Should -Be 'Ignore'; + } + + It 'from Environment' { + try { + # With enum + $Env:PSRULE_EXECUTION_INVARIANTCULTURE = 'error'; + $option = New-PSRuleOption; + $option.Execution.InvariantCulture | Should -Be 'Error'; + + # With enum + $Env:PSRULE_EXECUTION_INVARIANTCULTURE = 'Error'; + $option = New-PSRuleOption; + $option.Execution.InvariantCulture | Should -Be 'Error'; + + # With int + $Env:PSRULE_EXECUTION_INVARIANTCULTURE = '3'; + $option = New-PSRuleOption; + $option.Execution.InvariantCulture | Should -Be 'Error'; + } + finally { + Remove-Item 'Env:PSRULE_EXECUTION_INVARIANTCULTURE' -Force; + } + } + + It 'from parameter' { + $option = New-PSRuleOption -ExecutionInvariantCulture 'Error' -Path $emptyOptionsFilePath; + $option.Execution.InvariantCulture | Should -Be 'Error'; + } + } + + Context 'Read Execution.UnprocessedObject' { + It 'from default' { + $option = New-PSRuleOption -Default; + $option.Execution.UnprocessedObject | Should -Be 'Warn'; + } + + It 'from Hashtable' { + $option = New-PSRuleOption -Option @{ 'Execution.UnprocessedObject' = 'error' }; + $option.Execution.UnprocessedObject | Should -Be 'Error'; + + $option = New-PSRuleOption -Option @{ 'Execution.UnprocessedObject' = 'Error' }; + $option.Execution.UnprocessedObject | Should -Be 'Error'; + } + + It 'from YAML' { + $option = New-PSRuleOption -Option (Join-Path -Path $here -ChildPath 'PSRule.Tests.yml'); + $option.Execution.UnprocessedObject | Should -Be 'Ignore'; + } + + It 'from Environment' { + try { + # With enum + $Env:PSRULE_EXECUTION_UNPROCESSEDOBJECT = 'error'; + $option = New-PSRuleOption; + $option.Execution.UnprocessedObject | Should -Be 'Error'; + + # With enum + $Env:PSRULE_EXECUTION_UNPROCESSEDOBJECT = 'Error'; + $option = New-PSRuleOption; + $option.Execution.UnprocessedObject | Should -Be 'Error'; + + # With int + $Env:PSRULE_EXECUTION_UNPROCESSEDOBJECT = '3'; + $option = New-PSRuleOption; + $option.Execution.UnprocessedObject | Should -Be 'Error'; + } + finally { + Remove-Item 'Env:PSRULE_EXECUTION_UNPROCESSEDOBJECT' -Force; + } + } + + It 'from parameter' { + $option = New-PSRuleOption -ExecutionUnprocessedObject 'Error' -Path $emptyOptionsFilePath; + $option.Execution.UnprocessedObject | Should -Be 'Error'; + } + } + Context 'Read Execution.AliasReferenceWarning' { It 'from default' { $option = New-PSRuleOption -Default; - $option.Execution.AliasReferenceWarning | Should -Be $True; + $option.Execution.AliasReferenceWarning | Should -Be $Null; } It 'from Hashtable' { @@ -964,7 +1152,7 @@ Describe 'New-PSRuleOption' -Tag 'Option','New-PSRuleOption' { Context 'Read Execution.InvariantCultureWarning' { It 'from default' { $option = New-PSRuleOption -Default; - $option.Execution.InvariantCultureWarning | Should -Be $True; + $option.Execution.InvariantCultureWarning | Should -Be $Null; } It 'from Hashtable' { diff --git a/tests/PSRule.Tests/PSRule.Reason.Tests.ps1 b/tests/PSRule.Tests/PSRule.Reason.Tests.ps1 index d4937c4f94..577f7a2180 100644 --- a/tests/PSRule.Tests/PSRule.Reason.Tests.ps1 +++ b/tests/PSRule.Tests/PSRule.Reason.Tests.ps1 @@ -36,7 +36,7 @@ Describe 'PSRule -- Reason keyword' -Tag 'Reason' { } It 'Sets reason' { - $option = @{ 'Execution.InconclusiveWarning' = $False }; + $option = @{ 'Execution.RuleInconclusive' = 'Ignore' }; $result = @($testObject | Invoke-PSRule -Path $ruleFilePath -Option $option -Name 'ReasonTest','ReasonTest2','ReasonTest3' -Outcome All); $result | Should -Not -BeNullOrEmpty; $result.Length | Should -Be 3; diff --git a/tests/PSRule.Tests/PSRule.Recommend.Tests.ps1 b/tests/PSRule.Tests/PSRule.Recommend.Tests.ps1 index 0dd5ecdd94..8dcde77ec3 100644 --- a/tests/PSRule.Tests/PSRule.Recommend.Tests.ps1 +++ b/tests/PSRule.Tests/PSRule.Recommend.Tests.ps1 @@ -34,7 +34,7 @@ Describe 'PSRule -- Recommend keyword' -Tag 'Recommend' { } It 'Sets result properties' { - $option = @{ 'Execution.InconclusiveWarning' = $False }; + $option = @{ 'Execution.RuleInconclusive' = 'Ignore' }; $result = @($testObject | Invoke-PSRule -Path (Join-Path -Path $here -ChildPath 'FromFile.Rule.ps1') -Option $option -Name 'RecommendTest' -Outcome All -WarningVariable outWarning); $warningMessages = @($outWarning); $result | Should -Not -BeNullOrEmpty; @@ -45,7 +45,7 @@ Describe 'PSRule -- Recommend keyword' -Tag 'Recommend' { } It 'Uses comment metadata' { - $option = @{ 'Execution.InconclusiveWarning' = $False }; + $option = @{ 'Execution.RuleInconclusive' = 'Ignore' }; $result = @($testObject | Invoke-PSRule -Path (Join-Path -Path $here -ChildPath 'FromFile.Rule.ps1') -Option $option -Name 'TestWithDescription', 'TestWithSynopsis' -Outcome All); $result | Should -Not -BeNullOrEmpty; $result.Length | Should -Be 2; @@ -54,7 +54,7 @@ Describe 'PSRule -- Recommend keyword' -Tag 'Recommend' { } It 'Uses documentation' { - $option = @{ 'Execution.InconclusiveWarning' = $False }; + $option = @{ 'Execution.RuleInconclusive' = 'Ignore' }; $result = @($testObject | Invoke-PSRule -Path (Join-Path -Path $here -ChildPath 'FromFile.Rule.ps1') -Option $option -Name 'RecommendTest2' -Culture en-ZZ -Outcome All); $result | Should -Not -BeNullOrEmpty; $result.Length | Should -Be 1; diff --git a/tests/PSRule.Tests/PSRule.Tests.yml b/tests/PSRule.Tests/PSRule.Tests.yml index c71f8706b4..16a83ec8e3 100644 --- a/tests/PSRule.Tests/PSRule.Tests.yml +++ b/tests/PSRule.Tests/PSRule.Tests.yml @@ -7,10 +7,10 @@ repository: # Configure baseline rule: include: - - rule1 + - rule1 includeLocal: true exclude: - - rule3 + - rule3 tag: key1: value1 @@ -18,48 +18,52 @@ rule: configuration: option1: option option2: 2 - option3: [ 'option3a', 'option3b' ] + option3: ['option3a', 'option3b'] option4: - - location: 'East US' - zones: [ "1", "2", "3" ] - - location: 'Australia South East' - zones: [ ] + - location: 'East US' + zones: ['1', '2', '3'] + - location: 'Australia South East' + zones: [] option5: - - option5a - - option5b + - option5a + - option5b # Configure conventions convention: include: - - 'Convention1' + - 'Convention1' # Configure binding binding: field: id: - - resourceId + - resourceId ignoreCase: false nameSeparator: '::' preferTargetInfo: true targetName: - - ResourceName + - ResourceName targetType: - - ResourceType + - ResourceType useQualifiedName: true # Configure execution options execution: + aliasReference: Ignore aliasReferenceWarning: false duplicateResourceId: Warn languageMode: ConstrainedLanguage inconclusiveWarning: false + invariantCulture: Ignore invariantCultureWarning: false initialSessionState: Minimal notProcessedWarning: false suppressedRuleWarning: false suppressionGroupExpired: Debug ruleExcluded: Warn + ruleInconclusive: Ignore ruleSuppressed: Error + unprocessedObject: Ignore # Configure input options input: @@ -69,17 +73,17 @@ input: ignoreRepositoryCommon: false objectPath: items pathIgnore: - - '*.Designer.cs' + - '*.Designer.cs' targetType: - - virtualMachine + - virtualMachine ignoreUnchangedPath: true # Configure logging options logging: limitDebug: - - TestRule2 + - TestRule2 limitVerbose: - - TestRule2 + - TestRule2 ruleFail: Warning rulePass: Warning @@ -87,7 +91,7 @@ logging: output: as: Summary banner: Minimal - culture: [ 'en-CC' ] + culture: ['en-CC'] encoding: UTF7 footer: RuleCount format: Json @@ -101,6 +105,6 @@ output: suppression: SuppressionTest1: targetName: - - TestObject1 - - TestObject3 - SuppressionTest2: [ 'TestObject1', 'TestObject3' ] + - TestObject1 + - TestObject3 + SuppressionTest2: ['TestObject1', 'TestObject3'] diff --git a/tests/PSRule.Tests/PSRule.Tests10.yml b/tests/PSRule.Tests/PSRule.Tests10.yml index 6e7805844a..4543a2a797 100644 --- a/tests/PSRule.Tests/PSRule.Tests10.yml +++ b/tests/PSRule.Tests/PSRule.Tests10.yml @@ -5,4 +5,4 @@ output: jsonIndent: 1 execution: - invariantCultureWarning: false \ No newline at end of file + invariantCulture: Ignore diff --git a/tests/PSRule.Tests/PSRule.Tests11.yml b/tests/PSRule.Tests/PSRule.Tests11.yml index 319c2eec21..b5b8f84e47 100644 --- a/tests/PSRule.Tests/PSRule.Tests11.yml +++ b/tests/PSRule.Tests/PSRule.Tests11.yml @@ -5,4 +5,4 @@ output: jsonIndent: 2 execution: - invariantCultureWarning: false \ No newline at end of file + invariantCulture: Ignore diff --git a/tests/PSRule.Tests/PSRule.Tests12.yml b/tests/PSRule.Tests/PSRule.Tests12.yml index 64826fa8ef..1330b81fa7 100644 --- a/tests/PSRule.Tests/PSRule.Tests12.yml +++ b/tests/PSRule.Tests/PSRule.Tests12.yml @@ -5,4 +5,4 @@ output: jsonIndent: 3 execution: - invariantCultureWarning: false \ No newline at end of file + invariantCulture: Ignore diff --git a/tests/PSRule.Tests/PSRule.Tests13.yml b/tests/PSRule.Tests/PSRule.Tests13.yml index b4d1ebfb86..ebdfdab23b 100644 --- a/tests/PSRule.Tests/PSRule.Tests13.yml +++ b/tests/PSRule.Tests/PSRule.Tests13.yml @@ -5,4 +5,4 @@ output: jsonIndent: 4 execution: - invariantCultureWarning: false \ No newline at end of file + invariantCulture: Ignore diff --git a/tests/PSRule.Tests/PSRule.Tests5.yml b/tests/PSRule.Tests/PSRule.Tests5.yml index 29e7d22ecd..d72e4dd60a 100644 --- a/tests/PSRule.Tests/PSRule.Tests5.yml +++ b/tests/PSRule.Tests/PSRule.Tests5.yml @@ -1,5 +1,4 @@ - # Options for processing with some rules execution: - inconclusiveWarning: false - invariantCultureWarning: false + ruleInconclusive: Ignore + invariantCulture: Ignore diff --git a/tests/PSRule.Tests/PSRule.Tests9.yml b/tests/PSRule.Tests/PSRule.Tests9.yml index cc802ffb1c..7e7c2b92c7 100644 --- a/tests/PSRule.Tests/PSRule.Tests9.yml +++ b/tests/PSRule.Tests/PSRule.Tests9.yml @@ -5,4 +5,4 @@ output: jsonIndent: 0 execution: - invariantCultureWarning: false \ No newline at end of file + invariantCulture: Ignore diff --git a/tests/PSRule.Tests/PipelineTests.cs b/tests/PSRule.Tests/PipelineTests.cs index 93748789ca..5f9b385a74 100644 --- a/tests/PSRule.Tests/PipelineTests.cs +++ b/tests/PSRule.Tests/PipelineTests.cs @@ -175,7 +175,7 @@ public void PipelineWithInvariantCultureDisabled() { PSRuleOption.UseCurrentCulture(CultureInfo.InvariantCulture); var option = new PSRuleOption(); - option.Execution.InvariantCultureWarning = false; + option.Execution.InvariantCulture = ExecutionActionPreference.Ignore; var context = PipelineContext.New(option, null, null, null, null, null, new OptionContext(), null); var writer = new TestWriter(option); var pipeline = new GetRulePipeline(context, GetSource(), new PipelineReader(null, null, null), writer, false); From 3206b39dd5856c9d19c3e5a0f480d101e22fbf62 Mon Sep 17 00:00:00 2001 From: Bernie White Date: Sun, 23 Apr 2023 22:38:02 +1000 Subject: [PATCH 005/177] Pre-release v2.9.0-B0013 (#1514) * Pre-release v2.9.0-B0013 * Bump deprecations note * Update docs --- docs/CHANGELOG-v2.md | 4 + .../commands/PSRule/en-US/New-PSRuleOption.md | 144 ++++++++++++++---- .../commands/PSRule/en-US/Set-PSRuleOption.md | 78 +++++++++- 3 files changed, 195 insertions(+), 31 deletions(-) diff --git a/docs/CHANGELOG-v2.md b/docs/CHANGELOG-v2.md index 5a01e016df..a1d0245438 100644 --- a/docs/CHANGELOG-v2.md +++ b/docs/CHANGELOG-v2.md @@ -10,6 +10,8 @@ See [upgrade notes][1] for helpful information when upgrading from previous vers **Important notes**: +- Several options have been renamed and the old names will be removed in v3. + See [deprecations][2] for details. - Several properties of rule and language block elements will be removed from v3. See [deprecations][2] for details. @@ -30,6 +32,8 @@ See [upgrade notes][1] for helpful information when upgrading from previous vers ## Unreleased +## v2.9.0-B0013 (pre-release) + What's changed since release v2.8.1: - General improvements: diff --git a/docs/commands/PSRule/en-US/New-PSRuleOption.md b/docs/commands/PSRule/en-US/New-PSRuleOption.md index 98534106da..37225b880f 100644 --- a/docs/commands/PSRule/en-US/New-PSRuleOption.md +++ b/docs/commands/PSRule/en-US/New-PSRuleOption.md @@ -25,16 +25,20 @@ New-PSRuleOption [[-Path] ] [-Configuration ] [-InconclusiveWarning ] [-InvariantCultureWarning ] [-InitialSessionState ] [-NotProcessedWarning ] [-SuppressedRuleWarning ] [-SuppressionGroupExpired ] [-ExecutionRuleExcluded ] - [-ExecutionRuleSuppressed ] [-IncludeModule ] [-IncludePath ] - [-Format ] [-InputIgnoreGitPath ] [-InputIgnoreRepositoryCommon ] - [-InputIgnoreObjectSource ] [-InputIgnoreUnchangedPath ] [-ObjectPath ] - [-InputTargetType ] [-InputPathIgnore ] [-LoggingLimitDebug ] - [-LoggingLimitVerbose ] [-LoggingRuleFail ] [-LoggingRulePass ] - [-OutputAs ] [-OutputBanner ] [-OutputCulture ] - [-OutputEncoding ] [-OutputFooter ] [-OutputFormat ] - [-OutputJobSummaryPath ] [-OutputJsonIndent ] [-OutputOutcome ] - [-OutputPath ] [-OutputSarifProblemsOnly ] [-OutputStyle ] - [-RepositoryBaseRef ] [-RepositoryUrl ] [-RuleIncludeLocal ] [] + [-ExecutionRuleSuppressed ] [-ExecutionAliasReference ] + [-ExecutionRuleInconclusive ] + [-ExecutionInvariantCulture ] + [-ExecutionUnprocessedObject ] [-IncludeModule ] + [-IncludePath ] [-Format ] [-InputIgnoreGitPath ] + [-InputIgnoreRepositoryCommon ] [-InputIgnoreObjectSource ] + [-InputIgnoreUnchangedPath ] [-ObjectPath ] [-InputTargetType ] + [-InputPathIgnore ] [-LoggingLimitDebug ] [-LoggingLimitVerbose ] + [-LoggingRuleFail ] [-LoggingRulePass ] [-OutputAs ] + [-OutputBanner ] [-OutputCulture ] [-OutputEncoding ] + [-OutputFooter ] [-OutputFormat ] [-OutputJobSummaryPath ] + [-OutputJsonIndent ] [-OutputOutcome ] [-OutputPath ] + [-OutputSarifProblemsOnly ] [-OutputStyle ] [-RepositoryBaseRef ] + [-RepositoryUrl ] [-RuleIncludeLocal ] [] ``` ### FromOption @@ -49,16 +53,20 @@ New-PSRuleOption [-Option] [-Configuration ] [-InconclusiveWarning ] [-InvariantCultureWarning ] [-InitialSessionState ] [-NotProcessedWarning ] [-SuppressedRuleWarning ] [-SuppressionGroupExpired ] [-ExecutionRuleExcluded ] - [-ExecutionRuleSuppressed ] [-IncludeModule ] [-IncludePath ] - [-Format ] [-InputIgnoreGitPath ] [-InputIgnoreRepositoryCommon ] - [-InputIgnoreObjectSource ] [-InputIgnoreUnchangedPath ] [-ObjectPath ] - [-InputTargetType ] [-InputPathIgnore ] [-LoggingLimitDebug ] - [-LoggingLimitVerbose ] [-LoggingRuleFail ] [-LoggingRulePass ] - [-OutputAs ] [-OutputBanner ] [-OutputCulture ] - [-OutputEncoding ] [-OutputFooter ] [-OutputFormat ] - [-OutputJobSummaryPath ] [-OutputJsonIndent ] [-OutputOutcome ] - [-OutputPath ] [-OutputSarifProblemsOnly ] [-OutputStyle ] - [-RepositoryBaseRef ] [-RepositoryUrl ] [-RuleIncludeLocal ] [] + [-ExecutionRuleSuppressed ] [-ExecutionAliasReference ] + [-ExecutionRuleInconclusive ] + [-ExecutionInvariantCulture ] + [-ExecutionUnprocessedObject ] [-IncludeModule ] + [-IncludePath ] [-Format ] [-InputIgnoreGitPath ] + [-InputIgnoreRepositoryCommon ] [-InputIgnoreObjectSource ] + [-InputIgnoreUnchangedPath ] [-ObjectPath ] [-InputTargetType ] + [-InputPathIgnore ] [-LoggingLimitDebug ] [-LoggingLimitVerbose ] + [-LoggingRuleFail ] [-LoggingRulePass ] [-OutputAs ] + [-OutputBanner ] [-OutputCulture ] [-OutputEncoding ] + [-OutputFooter ] [-OutputFormat ] [-OutputJobSummaryPath ] + [-OutputJsonIndent ] [-OutputOutcome ] [-OutputPath ] + [-OutputSarifProblemsOnly ] [-OutputStyle ] [-RepositoryBaseRef ] + [-RepositoryUrl ] [-RuleIncludeLocal ] [] ``` ### FromDefault @@ -72,16 +80,20 @@ New-PSRuleOption [-Default] [-Configuration ] [-SuppressTar [-InconclusiveWarning ] [-InvariantCultureWarning ] [-InitialSessionState ] [-NotProcessedWarning ] [-SuppressedRuleWarning ] [-SuppressionGroupExpired ] [-ExecutionRuleExcluded ] - [-ExecutionRuleSuppressed ] [-IncludeModule ] [-IncludePath ] - [-Format ] [-InputIgnoreGitPath ] [-InputIgnoreRepositoryCommon ] - [-InputIgnoreObjectSource ] [-InputIgnoreUnchangedPath ] [-ObjectPath ] - [-InputTargetType ] [-InputPathIgnore ] [-LoggingLimitDebug ] - [-LoggingLimitVerbose ] [-LoggingRuleFail ] [-LoggingRulePass ] - [-OutputAs ] [-OutputBanner ] [-OutputCulture ] - [-OutputEncoding ] [-OutputFooter ] [-OutputFormat ] - [-OutputJobSummaryPath ] [-OutputJsonIndent ] [-OutputOutcome ] - [-OutputPath ] [-OutputSarifProblemsOnly ] [-OutputStyle ] - [-RepositoryBaseRef ] [-RepositoryUrl ] [-RuleIncludeLocal ] [] + [-ExecutionRuleSuppressed ] [-ExecutionAliasReference ] + [-ExecutionRuleInconclusive ] + [-ExecutionInvariantCulture ] + [-ExecutionUnprocessedObject ] [-IncludeModule ] + [-IncludePath ] [-Format ] [-InputIgnoreGitPath ] + [-InputIgnoreRepositoryCommon ] [-InputIgnoreObjectSource ] + [-InputIgnoreUnchangedPath ] [-ObjectPath ] [-InputTargetType ] + [-InputPathIgnore ] [-LoggingLimitDebug ] [-LoggingLimitVerbose ] + [-LoggingRuleFail ] [-LoggingRulePass ] [-OutputAs ] + [-OutputBanner ] [-OutputCulture ] [-OutputEncoding ] + [-OutputFooter ] [-OutputFormat ] [-OutputJobSummaryPath ] + [-OutputJsonIndent ] [-OutputOutcome ] [-OutputPath ] + [-OutputSarifProblemsOnly ] [-OutputStyle ] [-RepositoryBaseRef ] + [-RepositoryUrl ] [-RuleIncludeLocal ] [] ``` ## DESCRIPTION @@ -462,6 +474,78 @@ Accept pipeline input: False Accept wildcard characters: False ``` +### -ExecutionAliasReference + +Sets the `Execution.AliasReference` option. +Determines how to handle when an alias to a resource is used. +See about_PSRule_Options for more information. + +```yaml +Type: ExecutionActionPreference +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -ExecutionInvariantCulture + +Sets the `Execution.InvariantCulture` option. +Determines how to report when an invariant culture is used. +See about_PSRule_Options for more information. + +```yaml +Type: ExecutionActionPreference +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -ExecutionRuleInconclusive + +Sets the `Execution.RuleInconclusive` option. +Determines how to handle rules that generate inconclusive results. +See about_PSRule_Options for more information. + +```yaml +Type: ExecutionActionPreference +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -ExecutionUnprocessedObject + +Sets the `Execution.UnprocessedObject` option. +Determines how to report objects that are not processed by any rule. +See about_PSRule_Options for more information. + +```yaml +Type: ExecutionActionPreference +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + ### -IncludeModule Sets the `Include.Module` option to include additional module sources. diff --git a/docs/commands/PSRule/en-US/Set-PSRuleOption.md b/docs/commands/PSRule/en-US/Set-PSRuleOption.md index 9e010f724d..18caaa4214 100644 --- a/docs/commands/PSRule/en-US/Set-PSRuleOption.md +++ b/docs/commands/PSRule/en-US/Set-PSRuleOption.md @@ -22,7 +22,11 @@ Set-PSRuleOption [[-Path] ] [-Option ] [-PassThru] [-Force [-InvariantCultureWarning ] [-InitialSessionState ] [-NotProcessedWarning ] [-SuppressedRuleWarning ] [-SuppressionGroupExpired ] [-ExecutionRuleExcluded ] [-ExecutionRuleSuppressed ] - [-IncludeModule ] [-IncludePath ] [-Format ] [-InputIgnoreGitPath ] + [-ExecutionAliasReference ] + [-ExecutionRuleInconclusive ] + [-ExecutionInvariantCulture ] + [-ExecutionUnprocessedObject ] [-IncludeModule ] + [-IncludePath ] [-Format ] [-InputIgnoreGitPath ] [-InputIgnoreObjectSource ] [-InputIgnoreRepositoryCommon ] [-InputIgnoreUnchangedPath ] [-ObjectPath ] [-InputPathIgnore ] [-InputTargetType ] [-LoggingLimitDebug ] [-LoggingLimitVerbose ] @@ -349,6 +353,78 @@ Accept pipeline input: False Accept wildcard characters: False ``` +### -ExecutionAliasReference + +Sets the `Execution.AliasReference` option. +Determines how to handle when an alias to a resource is used. +See about_PSRule_Options for more information. + +```yaml +Type: ExecutionActionPreference +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -ExecutionInvariantCulture + +Sets the `Execution.InvariantCulture` option. +Determines how to report when an invariant culture is used. +See about_PSRule_Options for more information. + +```yaml +Type: ExecutionActionPreference +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -ExecutionRuleInconclusive + +Sets the `Execution.RuleInconclusive` option. +Determines how to handle rules that generate inconclusive results. +See about_PSRule_Options for more information. + +```yaml +Type: ExecutionActionPreference +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -ExecutionUnprocessedObject + +Sets the `Execution.UnprocessedObject` option. +Determines how to report objects that are not processed by any rule. +See about_PSRule_Options for more information. + +```yaml +Type: ExecutionActionPreference +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + ### -IncludeModule Sets the `Include.Module` option to include additional module sources. From 776cb5510ebea7b26e9e1d2a414bb043d9e4e09a Mon Sep 17 00:00:00 2001 From: Bernie White Date: Mon, 24 Apr 2023 00:04:19 +1000 Subject: [PATCH 006/177] Updates to docs (#1515) --- docs/concepts/PSRule/en-US/about_PSRule_Conventions.md | 8 +++----- docs/creating-your-pipeline.md | 7 ++----- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/docs/concepts/PSRule/en-US/about_PSRule_Conventions.md b/docs/concepts/PSRule/en-US/about_PSRule_Conventions.md index 6eddcc6ef1..9198f34828 100644 --- a/docs/concepts/PSRule/en-US/about_PSRule_Conventions.md +++ b/docs/concepts/PSRule/en-US/about_PSRule_Conventions.md @@ -105,8 +105,7 @@ Export-PSRuleConvention 'ExampleConvention' -Process { Conventions can be included by name within options in addition to using the `-Convention` parameter. To specify a convention within YAML options use the following: -```yaml -# Example ps-docs.yaml +```yaml title="ps-rule.yaml" convention: include: - 'ExampleConvention1' @@ -123,8 +122,7 @@ To use a convention included in a module use the `-Convention` parameter or opti A module can automatically include a convention by specifying the convention by name in module configuration. For example: -```yaml -# Example Config.Rule.yaml +```yaml title="Config.Rule.yaml" --- apiVersion: github.com/microsoft/PSRule/v1 kind: ModuleConfig @@ -155,7 +153,7 @@ When conventions are specified from multiple locations PSRule orders conventions ## NOTE -An online version of this document is available at https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Conventions/. +An online version of this document is available at . ## SEE ALSO diff --git a/docs/creating-your-pipeline.md b/docs/creating-your-pipeline.md index 9404c29573..99ed17359c 100644 --- a/docs/creating-your-pipeline.md +++ b/docs/creating-your-pipeline.md @@ -28,7 +28,7 @@ Within the root directory of your IaC repository: # Analyze Azure resources using PSRule for Azure - name: Analyze Azure template files - uses: microsoft/ps-rule@v2.7.0 + uses: microsoft/ps-rule@v2.8.1 with: modules: 'PSRule.Rules.Azure' ``` @@ -138,9 +138,6 @@ To prevent a rule executing you can either: Meaningful comments help during peer review within a Pull Request (PR). Also consider including a date if the exclusions or suppressions are temporary. - [3]: concepts/PSRule/en-US/about_PSRule_Options.md#ruleexclude - [4]: concepts/PSRule/en-US/about_PSRule_Options.md#suppression - [5]: concepts/PSRule/en-US/about_PSRule_SuppressionGroups.md [6]: addon-modules.md [7]: authoring/packaging-rules.md @@ -169,7 +166,7 @@ To only process files that have changed within a pull request, set the `Input.Ig # Analyze Azure resources using PSRule for Azure - name: Analyze Azure template files - uses: microsoft/ps-rule@v2.7.0 + uses: microsoft/ps-rule@v2.8.1 with: modules: 'PSRule.Rules.Azure' env: From 27fa06be4110cc85184b88645a4faa635a7fce12 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 Apr 2023 22:25:50 +1000 Subject: [PATCH 007/177] Bump mkdocs-material from 9.1.6 to 9.1.7 (#1516) Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.1.6 to 9.1.7. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.1.6...9.1.7) --- updated-dependencies: - dependency-name: mkdocs-material dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements-docs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-docs.txt b/requirements-docs.txt index a470847e5e..59527c7253 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -1,5 +1,5 @@ mkdocs==1.4.2 -mkdocs-material==9.1.6 +mkdocs-material==9.1.7 pymdown-extensions==9.11 mike==1.1.2 mkdocs-simple-hooks==0.1.5 From e46a54aee42e4c2b57918392f353fc1a9a369e5b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 27 Apr 2023 12:34:05 +1000 Subject: [PATCH 008/177] Bump mkdocs-material from 9.1.7 to 9.1.8 (#1517) Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.1.7 to 9.1.8. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.1.7...9.1.8) --- updated-dependencies: - dependency-name: mkdocs-material dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements-docs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-docs.txt b/requirements-docs.txt index 59527c7253..8eaec814b4 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -1,5 +1,5 @@ mkdocs==1.4.2 -mkdocs-material==9.1.7 +mkdocs-material==9.1.8 pymdown-extensions==9.11 mike==1.1.2 mkdocs-simple-hooks==0.1.5 From 75b5898b2b690c7c81501e07ad7fd93ba4bb1872 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 3 May 2023 10:08:58 +1000 Subject: [PATCH 009/177] Bump mkdocs-material from 9.1.8 to 9.1.9 (#1522) Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.1.8 to 9.1.9. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.1.8...9.1.9) --- updated-dependencies: - dependency-name: mkdocs-material dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements-docs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-docs.txt b/requirements-docs.txt index 8eaec814b4..a3c8ba628f 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -1,5 +1,5 @@ mkdocs==1.4.2 -mkdocs-material==9.1.8 +mkdocs-material==9.1.9 pymdown-extensions==9.11 mike==1.1.2 mkdocs-simple-hooks==0.1.5 From 4b38a9c0a8a28a7479c767946299fd1115c384f6 Mon Sep 17 00:00:00 2001 From: Bernie White Date: Sun, 7 May 2023 12:25:51 +1000 Subject: [PATCH 010/177] Added sub-selector quantifiers #1423 (#1525) --- .vscode/settings.json | 10 +- docs/CHANGELOG-v2.md | 8 + .../PSRule/en-US/about_PSRule_Expressions.md | 8 + docs/expressions/functions.md | 6 + docs/expressions/sub-selectors.md | 85 ++- schemas/PSRule-language.schema.json | 520 +++++++++++++----- .../Expressions/LanguageExpressions.cs | 55 +- .../FromFileSubSelector.Rule.jsonc | 142 +++-- .../FromFileSubSelector.Rule.yaml | 24 +- tests/PSRule.Tests/RulesTests.cs | 45 ++ tests/PSRule.Tests/SelectorTests.cs | 19 +- tests/PSRule.Tests/Selectors.Rule.jsonc | 20 + tests/PSRule.Tests/Selectors.Rule.yaml | 14 + 13 files changed, 734 insertions(+), 222 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index a20257902f..fc6bbbc17a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -25,18 +25,14 @@ "/ps-rule.yaml" ], "./schemas/PSRule-language.schema.json": [ - "/tests/PSRule.Tests/**.Rule.yaml", - "/tests/PSRule.Tests/**/**.Rule.yaml", - "/docs/scenarios/*/*.Rule.yaml", - "/docs/expressions/**/*.Rule.yaml" + "/**/**.Rule.yaml", + "/**/docs/scenarios/baselines/Baseline.rule.yaml" ] }, "json.schemas": [ { "fileMatch": [ - "/tests/PSRule.Tests/**.Rule.jsonc", - "/tests/PSRule.Tests/**/**.Rule.jsonc", - "/docs/expressions/**/*.Rule.jsonc" + "/**/**.Rule.jsonc" ], "url": "./schemas/PSRule-resources.schema.json" } diff --git a/docs/CHANGELOG-v2.md b/docs/CHANGELOG-v2.md index a1d0245438..360e6a11c7 100644 --- a/docs/CHANGELOG-v2.md +++ b/docs/CHANGELOG-v2.md @@ -32,6 +32,14 @@ See [upgrade notes][1] for helpful information when upgrading from previous vers ## Unreleased +What's changed since pre-release v2.9.0-B0013: + +- New features: + - Added sub-selector quantifiers for `allOf` or `anyOf` operators by @BernieWhite. + [#1423](https://github.com/microsoft/PSRule/issues/1423) + - Quantifiers allow you to specify the number of matches with `count`, `less`, `lessOrEqual`, `greater`, or `greaterOrEqual`. + - See [Sub-selectors][4] for more information. + ## v2.9.0-B0013 (pre-release) What's changed since release v2.8.1: diff --git a/docs/concepts/PSRule/en-US/about_PSRule_Expressions.md b/docs/concepts/PSRule/en-US/about_PSRule_Expressions.md index 41ccf147f2..da86b349d2 100644 --- a/docs/concepts/PSRule/en-US/about_PSRule_Expressions.md +++ b/docs/concepts/PSRule/en-US/about_PSRule_Expressions.md @@ -77,6 +77,10 @@ The `allOf` operator is used to require all nested expressions to match. When any nested expression does not match, `allOf` does not match. This is similar to a logical _and_ operation. +Additionally sub-selectors can be used to modify the `allOf` operator. +Sub-selectors allow filtering and looping through arrays of objects before the `allOf` operator is applied. +See sub-selectors for more information. + Syntax: ```yaml @@ -121,6 +125,10 @@ The `anyOf` operator is used to require one or more nested expressions to match. When any nested expression matches, `allOf` matches. This is similar to a logical _or_ operation. +Additionally sub-selectors can be used to modify the `anyOf` operator. +Sub-selectors allow filtering and looping through arrays of objects before the `anyOf` operator is applied. +See sub-selectors for more information. + Syntax: ```yaml diff --git a/docs/expressions/functions.md b/docs/expressions/functions.md index 7269c74910..b3461b8ce7 100644 --- a/docs/expressions/functions.md +++ b/docs/expressions/functions.md @@ -136,3 +136,9 @@ spec: - string: '-' - path: name ``` + +## Recommended content + +- [Create a standalone rule](../quickstart/standalone-rule.md) +- [Expressions](../concepts/PSRule/en-US/about_PSRule_Expressions.md) +- [Sub-selectors](sub-selectors.md) diff --git a/docs/expressions/sub-selectors.md b/docs/expressions/sub-selectors.md index 995a21a538..57cea7450b 100644 --- a/docs/expressions/sub-selectors.md +++ b/docs/expressions/sub-selectors.md @@ -184,7 +184,7 @@ In the example: ### When there are no results -Given the example, is important to understand what happens if: +Given the example, is important to understand what happens by default if: - The `resources` property doesn't exist. **OR** - The `resources` property doesn't contain any items that match the sub-selector condition. @@ -196,6 +196,7 @@ If this was not the desired behavior, you could: - Use a pre-condition to avoid running the rule. - Group the sub-selector into a `anyOf`, and provide a secondary condition. +- Use a quantifier to determine how many items must match sub-selector and match the `allOf` / `anyOf` operator. For example: @@ -270,3 +271,85 @@ In the example: - If the `resources` property exists but has 0 items of type `Microsoft.Web/sites/config`, the rule fails. - If the `resources` property exists and has any items of type `Microsoft.Web/sites/config` but any fail, the rule fails. - If the `resources` property exists and has any items of type `Microsoft.Web/sites/config` and all pass, the rule passes. + +### Using a quantifier with sub-selectors + +When iterating over a list of items, you may want to determine how many items must match. +A quantifier determines how many items in the list match. +Matching items must be: + +- Selected by the sub-selector. +- Match the condition of the operator. + +Supported quantifiers are: + +- `count` — The number of items must equal then the specified value. +- `less` — The number of items must less then the specified value. +- `lessOrEqual` — The number of items must less or equal to the specified value. +- `greater` — The number of items must greater then the specified value. +- `greaterOrEqual` — The number of items must greater or equal to the specified value. + +For example: + +=== "YAML" + + ```yaml hl_lines="13" + --- + # Synopsis: A rule with a sub-selector quantifier. + apiVersion: github.com/microsoft/PSRule/v1 + kind: Rule + metadata: + name: Yaml.Subselector.Quantifier + spec: + condition: + field: resources + where: + type: '.' + equals: 'Microsoft.Web/sites/config' + greaterOrEqual: 1 + allOf: + - field: properties.detailedErrorLoggingEnabled + equals: true + ``` + +=== "JSON" + + ```json hl_lines="15" + { + // Synopsis: A rule with a sub-selector quantifier. + "apiVersion": "github.com/microsoft/PSRule/v1", + "kind": "Rule", + "metadata": { + "name": "Json.Subselector.Quantifier" + }, + "spec": { + "condition": { + "field": "resources", + "where": { + "type": ".", + "equals": "Microsoft.Web/sites/config" + }, + "greaterOrEqual": 1, + "allOf": [ + { + "field": "properties.detailedErrorLoggingEnabled", + "equals": true + } + ] + } + } + } + ``` + +In the example: + +- If the array property `resources` exists, any items with a type of `Microsoft.Web/sites/config` are evaluated. + - Each item must have the `properties.detailedErrorLoggingEnabled` property set to `true` to pass. + - The number of items that pass must be greater or equal to `1`. +- If the `resources` property does not exist or is empty, the number of items is `0` which fails greater or equal to `1`. + +## Recommended content + +- [Create a standalone rule](../quickstart/standalone-rule.md) +- [Functions](functions.md) +- [Expressions](../concepts/PSRule/en-US/about_PSRule_Expressions.md) diff --git a/schemas/PSRule-language.schema.json b/schemas/PSRule-language.schema.json index bd1a6feb4d..e64eafeac6 100644 --- a/schemas/PSRule-language.schema.json +++ b/schemas/PSRule-language.schema.json @@ -845,14 +845,12 @@ }, { "type": "object", - "ztitle": "Value for object", - "zdescription": "A value to compare.", + "title": "Value for object", + "description": "A value to compare.", "not": { - "propertyNames": { - "enum": [ - "$" - ] - } + "required": [ + "$" + ] } }, { @@ -1365,15 +1363,7 @@ "markdownDescription": "Must have the specified value.\n\n[See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Expressions/#equals)", "properties": { "equals": { - "title": "Equals", - "description": "Must have the specified value.", - "markdownDescription": "Must have the specified value.\n\n[See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Expressions/#equals)", - "default": "", - "oneOf": [ - { - "$ref": "#/definitions/selectorExpressionValue" - } - ] + "$ref": "#/definitions/selectorExpressionValue" }, "convert": { "type": "boolean", @@ -1415,7 +1405,8 @@ { "$ref": "#/definitions/expressions/definitions/operands" } - ] + ], + "additionalProperties": false }, "count": { "type": "object", @@ -1454,34 +1445,27 @@ "required": [ "exists", "field" - ] + ], + "additionalProperties": false }, "notEquals": { "type": "object", "properties": { "notEquals": { - "title": "Not Equals", - "description": "Must not have the specified value.", - "markdownDescription": "Must not have the specified value. [See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Expressions/#notequals)", - "default": "", - "oneOf": [ - { - "$ref": "#/definitions/selectorExpressionValue" - } - ] + "$ref": "#/definitions/selectorExpressionValue" }, "convert": { "type": "boolean", "title": "Type conversion", "description": "Convert type of compared operand.", - "markdownDescription": "Convert type of compared operand. [See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Expressions/#notequals)", + "markdownDescription": "Convert type of compared operand.\n\n[See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Expressions/#notequals)", "default": false }, "caseSensitive": { "type": "boolean", "title": "Case sensitive", "description": "Determines if comparing values is case-sensitive. Only applies to string values.", - "markdownDescription": "Determines if comparing values is case-sensitive. Only applies to string values. [See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Expressions/#notequals)", + "markdownDescription": "Determines if comparing values is case-sensitive. Only applies to string values.\n\n[See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Expressions/#notequals)", "default": false }, "field": { @@ -1510,7 +1494,8 @@ { "$ref": "#/definitions/expressions/definitions/operands" } - ] + ], + "additionalProperties": false }, "hasValue": { "type": "object", @@ -1536,7 +1521,8 @@ { "$ref": "#/definitions/expressions/definitions/operands" } - ] + ], + "additionalProperties": false }, "match": { "type": "object", @@ -1545,14 +1531,14 @@ "type": "string", "title": "Match", "description": "Must match the regular expression.", - "markdownDescription": "Must match the regular expression. [See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Expressions/#match)", + "markdownDescription": "Must match the regular expression.\n\n[See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Expressions/#match)", "default": "" }, "caseSensitive": { "type": "boolean", "title": "Case sensitive", "description": "Determines if the regular expression uses case-sensitive matching.", - "markdownDescription": "Determines if the regular expression uses case-sensitive matching. [See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Expressions/#match)", + "markdownDescription": "Determines if the regular expression uses case-sensitive matching.\n\n[See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Expressions/#match)", "default": false }, "field": { @@ -1590,14 +1576,14 @@ "type": "string", "title": "Not Match", "description": "Must not match the regular expression.", - "markdownDescription": "Must not match the regular expression. [See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Expressions/#notmatch)", + "markdownDescription": "Must not match the regular expression.\n\n[See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Expressions/#notmatch)", "default": "" }, "caseSensitive": { "type": "boolean", "title": "Case sensitive", "description": "Determines if the regular expression uses case-sensitive matching.", - "markdownDescription": "Determines if the regular expression uses case-sensitive matching. [See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Expressions/#notmatch)", + "markdownDescription": "Determines if the regular expression uses case-sensitive matching.\n\n[See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Expressions/#notmatch)", "default": false }, "field": { @@ -1635,7 +1621,7 @@ "type": "array", "title": "In", "description": "Must equal one of the specified values.", - "markdownDescription": "Must equal one of the specified values. [See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Expressions/#in)", + "markdownDescription": "Must equal one of the specified values.\n\n[See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Expressions/#in)", "default": [ "" ], @@ -1676,7 +1662,7 @@ "type": "array", "title": "Not In", "description": "Must not equal any of the specified values.", - "markdownDescription": "Must not equal any of the specified values. [See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Expressions/#notin)", + "markdownDescription": "Must not equal any of the specified values.\n\n[See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Expressions/#notin)", "default": [ "" ], @@ -1717,7 +1703,7 @@ "type": "array", "title": "SetOf", "description": "Must include all of but only specified values.", - "markdownDescription": "Must include all of but only values. [See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Expressions/#setof)", + "markdownDescription": "Must include all of but only values.\n\n[See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Expressions/#setof)", "default": [ "" ], @@ -1727,7 +1713,7 @@ "type": "boolean", "title": "Case sensitive", "description": "Determines if comparing values is case-sensitive.", - "markdownDescription": "Determines if comparing values is case-sensitive. [See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Expressions/#setof)", + "markdownDescription": "Determines if comparing values is case-sensitive.\n\n[See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Expressions/#setof)", "default": false }, "field": { @@ -1762,7 +1748,7 @@ "type": "array", "title": "Subset", "description": "Must include all of the specified values.", - "markdownDescription": "Must include all of the specified values. [See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Expressions/#subset)", + "markdownDescription": "Must include all of the specified values.\n\n[See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Expressions/#subset)", "default": [ "" ], @@ -1772,7 +1758,7 @@ "type": "boolean", "title": "Case sensitive", "description": "Determines if comparing values is case-sensitive.", - "markdownDescription": "Determines if comparing values is case-sensitive. [See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Expressions/#subset)", + "markdownDescription": "Determines if comparing values is case-sensitive.\n\n[See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Expressions/#subset)", "default": false }, "unique": { @@ -1814,7 +1800,7 @@ "type": "integer", "title": "NotCount", "description": "Determines if operand does not have number of items.", - "markdownDescription": "Determines if operand does not have number of items. [See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Expressions/#notcount)", + "markdownDescription": "Determines if operand does not have number of items.\n\n[See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Expressions/#notcount)", "minimum": 0, "default": 0 }, @@ -1841,7 +1827,7 @@ "less": { "title": "Less", "description": "Must be less then the specified value.", - "markdownDescription": "Must be less then the specified value. [See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Expressions/#less)", + "markdownDescription": "Must be less then the specified value.\n\n[See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Expressions/#less)", "default": 0, "oneOf": [ { @@ -1857,7 +1843,7 @@ "type": "boolean", "title": "Type conversion", "description": "Convert type of compared operand.", - "markdownDescription": "Convert type of compared operand. [See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Expressions/#less)", + "markdownDescription": "Convert type of compared operand.\n\n[See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Expressions/#less)", "default": false }, "field": { @@ -1895,7 +1881,7 @@ "lessOrEquals": { "title": "Less or Equal to", "description": "Must be less or equal to the specified value.", - "markdownDescription": "Must be less or equal to the specified value. [See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Expressions/#lessorequals)", + "markdownDescription": "Must be less or equal to the specified value.\n\n[See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Expressions/#lessorequals)", "default": 0, "oneOf": [ { @@ -1911,7 +1897,7 @@ "type": "boolean", "title": "Type conversion", "description": "Convert type of compared operand.", - "markdownDescription": "Convert type of compared operand. [See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Expressions/#less)", + "markdownDescription": "Convert type of compared operand.\n\n[See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Expressions/#less)", "default": false }, "field": { @@ -1949,7 +1935,7 @@ "greater": { "title": "Greater", "description": "Must be greater then the specified value.", - "markdownDescription": "Must be greater then the specified value. [See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Expressions/#greater)", + "markdownDescription": "Must be greater then the specified value.\n\n[See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Expressions/#greater)", "default": 0, "oneOf": [ { @@ -1965,7 +1951,7 @@ "type": "boolean", "title": "Type conversion", "description": "Convert type of compared operand.", - "markdownDescription": "Convert type of compared operand. [See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Expressions/#less)", + "markdownDescription": "Convert type of compared operand.\n\n[See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Expressions/#less)", "default": false }, "field": { @@ -1999,57 +1985,232 @@ }, "greaterOrEquals": { "type": "object", - "properties": { - "greaterOrEquals": { - "title": "Greater or Equal to", - "description": "Must be greater or equal to the specified value.", - "markdownDescription": "Must be greater or equal to the specified value. [See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Expressions/#greaterorequals)", - "default": 0, - "oneOf": [ - { - "type": "integer" + "oneOf": [ + { + "type": "object", + "properties": { + "greaterOrEquals": { + "title": "Greater or Equal to", + "description": "Must be greater or equal to the specified value.", + "markdownDescription": "Must be greater or equal to the specified value.\n\n[See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Expressions/#greaterorequals)", + "default": 0, + "oneOf": [ + { + "type": "integer" + }, + { + "type": "object", + "$ref": "#/definitions/fn" + } + ] }, - { - "type": "object", - "$ref": "#/definitions/fn" + "convert": { + "type": "boolean", + "title": "Type conversion", + "description": "Convert type of compared operand.", + "markdownDescription": "Convert type of compared operand.\n\n[See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Expressions/#less)", + "default": false + }, + "field": { + "$ref": "#/definitions/expressions/definitions/properties/definitions/field" } - ] - }, - "convert": { - "type": "boolean", - "title": "Type conversion", - "description": "Convert type of compared operand.", - "markdownDescription": "Convert type of compared operand. [See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Expressions/#less)", - "default": false - }, - "field": { - "$ref": "#/definitions/expressions/definitions/properties/definitions/field" + }, + "required": [ + "greaterOrEquals", + "field" + ], + "additionalProperties": false }, - "value": { - "$ref": "#/definitions/expressions/definitions/properties/definitions/value" + { + "type": "object", + "properties": { + "greaterOrEquals": { + "title": "Greater or Equal to", + "description": "Must be greater or equal to the specified value.", + "markdownDescription": "Must be greater or equal to the specified value.\n\n[See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Expressions/#greaterorequals)", + "default": 0, + "oneOf": [ + { + "type": "integer" + }, + { + "type": "object", + "$ref": "#/definitions/fn" + } + ] + }, + "convert": { + "type": "boolean", + "title": "Type conversion", + "description": "Convert type of compared operand.", + "markdownDescription": "Convert type of compared operand.\n\n[See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Expressions/#less)", + "default": false + }, + "value": { + "$ref": "#/definitions/expressions/definitions/properties/definitions/value" + } + }, + "required": [ + "greaterOrEquals", + "value" + ], + "additionalProperties": false }, - "type": { - "$ref": "#/definitions/expressions/definitions/properties/definitions/type" + { + "type": "object", + "properties": { + "greaterOrEquals": { + "title": "Greater or Equal to", + "description": "Must be greater or equal to the specified value.", + "markdownDescription": "Must be greater or equal to the specified value.\n\n[See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Expressions/#greaterorequals)", + "default": 0, + "oneOf": [ + { + "type": "integer" + }, + { + "type": "object", + "$ref": "#/definitions/fn" + } + ] + }, + "convert": { + "type": "boolean", + "title": "Type conversion", + "description": "Convert type of compared operand.", + "markdownDescription": "Convert type of compared operand.\n\n[See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Expressions/#less)", + "default": false + }, + "type": { + "$ref": "#/definitions/expressions/definitions/properties/definitions/type" + } + }, + "required": [ + "greaterOrEquals", + "type" + ], + "additionalProperties": false }, - "name": { - "$ref": "#/definitions/expressions/definitions/properties/definitions/name" + { + "type": "object", + "properties": { + "greaterOrEquals": { + "title": "Greater or Equal to", + "description": "Must be greater or equal to the specified value.", + "markdownDescription": "Must be greater or equal to the specified value.\n\n[See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Expressions/#greaterorequals)", + "default": 0, + "oneOf": [ + { + "type": "integer" + }, + { + "type": "object", + "$ref": "#/definitions/fn" + } + ] + }, + "convert": { + "type": "boolean", + "title": "Type conversion", + "description": "Convert type of compared operand.", + "markdownDescription": "Convert type of compared operand.\n\n[See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Expressions/#less)", + "default": false + }, + "name": { + "$ref": "#/definitions/expressions/definitions/properties/definitions/name" + } + }, + "required": [ + "greaterOrEquals", + "name" + ], + "additionalProperties": false }, - "source": { - "$ref": "#/definitions/expressions/definitions/properties/definitions/source" + { + "type": "object", + "properties": { + "greaterOrEquals": { + "title": "Greater or Equal to", + "description": "Must be greater or equal to the specified value.", + "markdownDescription": "Must be greater or equal to the specified value.\n\n[See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Expressions/#greaterorequals)", + "default": 0, + "oneOf": [ + { + "type": "integer" + }, + { + "type": "object", + "$ref": "#/definitions/fn" + } + ] + }, + "convert": { + "type": "boolean", + "title": "Type conversion", + "description": "Convert type of compared operand.", + "markdownDescription": "Convert type of compared operand.\n\n[See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Expressions/#less)", + "default": false + }, + "source": { + "$ref": "#/definitions/expressions/definitions/properties/definitions/source" + } + }, + "required": [ + "greaterOrEquals", + "source" + ], + "additionalProperties": false }, - "scope": { - "$ref": "#/definitions/expressions/definitions/properties/definitions/scope" - } - }, - "required": [ - "greaterOrEquals" - ], - "oneOf": [ { - "$ref": "#/definitions/expressions/definitions/operands" + "type": "object", + "properties": { + "greaterOrEquals": { + "title": "Greater or Equal to", + "description": "Must be greater or equal to the specified value.", + "markdownDescription": "Must be greater or equal to the specified value.\n\n[See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Expressions/#greaterorequals)", + "default": 0, + "oneOf": [ + { + "type": "integer" + }, + { + "type": "object", + "$ref": "#/definitions/fn" + } + ] + }, + "convert": { + "type": "boolean", + "title": "Type conversion", + "description": "Convert type of compared operand.", + "markdownDescription": "Convert type of compared operand.\n\n[See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Expressions/#less)", + "default": false + }, + "scope": { + "$ref": "#/definitions/expressions/definitions/properties/definitions/scope" + } + }, + "required": [ + "greaterOrEquals", + "scope" + ], + "additionalProperties": false } ], - "additionalProperties": false + "not": { + "anyOf": [ + { + "required": [ + "allOf" + ] + }, + { + "required": [ + "anyOf" + ] + } + ] + } }, "startsWith": { "type": "object", @@ -2977,75 +3138,149 @@ "title": "allOf", "description": "All of the expressions must be true.", "markdownDescription": "All of the expressions must be true.\n\n[See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Expressions/#allof)", - "properties": { - "allOf": { - "type": "array", - "title": "AllOf", - "description": "All of the expressions must be true.", - "markdownDescription": "All of the expressions must be true.\n\n[See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Expressions/#allof)", - "items": { - "$ref": "#/definitions/expressions" - } - }, - "field": { - "$ref": "#/definitions/expressions/definitions/properties/definitions/field" - }, - "where": { - "$ref": "#/definitions/expressions/definitions/properties/definitions/where" - } - }, - "required": [ - "allOf" - ], "oneOf": [ { + "type": "object", + "properties": { + "allOf": { + "type": "array", + "title": "AllOf", + "description": "All of the expressions must be true.", + "markdownDescription": "All of the expressions must be true.\n\n[See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Expressions/#allof)", + "items": { + "$ref": "#/definitions/expressions" + } + } + }, + "required": [ + "allOf" + ], + "additionalProperties": false + }, + { + "type": "object", "properties": { + "allOf": { + "type": "array", + "title": "AllOf", + "description": "All of the expressions must be true.", + "markdownDescription": "All of the expressions must be true.\n\n[See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Expressions/#allof)", + "items": { + "$ref": "#/definitions/expressions" + } + }, "field": { "$ref": "#/definitions/expressions/definitions/properties/definitions/field" }, "where": { "$ref": "#/definitions/expressions/definitions/properties/definitions/where" + }, + "less": { + "type": "integer", + "title": "Less than", + "minimum": 0 + }, + "lessOrEqual": { + "type": "integer", + "title": "Less or equal to", + "minimum": 0 + }, + "greater": { + "type": "integer", + "title": "Greater than", + "minimum": 0 + }, + "greaterOrEqual": { + "type": "integer", + "title": "Greater or equal to", + "minimum": 0 + }, + "count": { + "type": "integer", + "title": "Count", + "minimum": 0 } - } + }, + "required": [ + "allOf", + "field" + ], + "additionalProperties": false } - ], - "additionalProperties": false + ] }, "anyOf": { "type": "object", - "properties": { - "anyOf": { - "type": "array", - "title": "AnyOf", - "description": "One of the expressions must be true.", - "markdownDescription": "All of the expressions must be true.\n\n[See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Expressions/#anyof)", - "items": { - "$ref": "#/definitions/expressions" - } - }, - "field": { - "$ref": "#/definitions/expressions/definitions/properties/definitions/field" - }, - "where": { - "$ref": "#/definitions/expressions/definitions/properties/definitions/where" - } - }, - "required": [ - "anyOf" - ], "oneOf": [ { + "type": "object", + "properties": { + "anyOf": { + "type": "array", + "title": "AnyOf", + "description": "One of the expressions must be true.", + "markdownDescription": "All of the expressions must be true.\n\n[See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Expressions/#anyof)", + "items": { + "$ref": "#/definitions/expressions" + } + } + }, + "required": [ + "anyOf" + ], + "additionalProperties": false + }, + { + "type": "object", "properties": { + "anyOf": { + "type": "array", + "title": "AnyOf", + "description": "One of the expressions must be true.", + "markdownDescription": "All of the expressions must be true.\n\n[See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Expressions/#anyof)", + "items": { + "$ref": "#/definitions/expressions" + } + }, "field": { "$ref": "#/definitions/expressions/definitions/properties/definitions/field" }, "where": { "$ref": "#/definitions/expressions/definitions/properties/definitions/where" + }, + "less": { + "type": "integer", + "title": "Less than", + "minimum": 0 + }, + "lessOrEqual": { + "type": "integer", + "title": "Less or equal to", + "minimum": 0 + }, + "greater": { + "type": "integer", + "title": "Greater than", + "minimum": 0 + }, + "greaterOrEqual": { + "type": "integer", + "title": "Greater or equal to", + "minimum": 0 + }, + "count": { + "type": "integer", + "title": "Count", + "minimum": 0 } - } + }, + "required": [ + "anyOf", + "field" + ], + "additionalProperties": false } - ], - "additionalProperties": false + ] }, "not": { "type": "object", @@ -3068,9 +3303,10 @@ } }, "fn": { - "ztitle": "Value from function", - "zdescription": "A function expression that once evaluated specifies the value.", - "zmarkdownDescription": "A function expression that once evaluated specifies the value.", + "type": "object", + "title": "Value from function", + "description": "A function expression that once evaluated specifies the value.", + "markdownDescription": "A function expression that once evaluated specifies the value.", "properties": { "$": { "type": "object", @@ -3507,13 +3743,13 @@ "oneOf": [ { "type": "string", - "description": "The split function returns converts a string into an array by spliting based on a delimiter.", - "markdownDescription": "The `split` function returns converts a string into an array by spliting based on a delimiter." + "description": "The split function converts a string into an array by spliting based on a delimiter.", + "markdownDescription": "The `split` function converts a string into an array by spliting based on a delimiter." }, { "type": "object", - "description": "The split function returns converts a string into an array by spliting based on a delimiter.", - "markdownDescription": "The `split` function returns converts a string into an array by spliting based on a delimiter.", + "description": "The split function converts a string into an array by spliting based on a delimiter.", + "markdownDescription": "The `split` function converts a string into an array by spliting based on a delimiter.", "$ref": "#/definitions/fn/definitions/function" } ], diff --git a/src/PSRule/Definitions/Expressions/LanguageExpressions.cs b/src/PSRule/Definitions/Expressions/LanguageExpressions.cs index c15f9d1e8a..7dce3d47d8 100644 --- a/src/PSRule/Definitions/Expressions/LanguageExpressions.cs +++ b/src/PSRule/Definitions/Expressions/LanguageExpressions.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Threading; using PSRule.Configuration; @@ -86,7 +87,13 @@ internal sealed class LanguageExpressionBuilder private const char Dot = '.'; private const char OpenBracket = '['; private const char CloseBracket = ']'; - private const string Where = ".where"; + + private const string DOTWHERE = ".where"; + private const string LESS = "less"; + private const string LESSOREQUAL = "lessOrEqual"; + private const string GREATER = "greater"; + private const string GREATEROREQUAL = "greaterOrEqual"; + private const string COUNT = "count"; private readonly bool _Debugger; @@ -279,27 +286,59 @@ private LanguageExpressionOuterFn Operator(string path, LanguageOperator express } else { - var subselector = expression.Subselector != null ? Expression(string.Concat(path, Where), expression.Subselector) : null; + var subselector = expression.Subselector != null ? Expression(string.Concat(path, DOTWHERE), expression.Subselector) : null; return (context, o) => { - if (!ObjectHelper.GetPath(context, o, Value(context, expression.Property["field"]), caseSensitive: false, out object[] items) || - items == null || items.Length == 0) - return false; + ObjectHelper.GetPath(context, o, Value(context, expression.Property["field"]), caseSensitive: false, out object[] items); + + var quantifier = GetQuantifier(expression); + var pass = 0; // If any fail, all fail - for (var i = 0; i < items.Length; i++) + for (var i = 0; items != null && i < items.Length; i++) { if (subselector == null || subselector(context, items[i]).GetValueOrDefault(true)) { if (!expression.Descriptor.Fn(context, info, innerA, items[i])) - return false; + { + if (quantifier == null) + return false; + } + else + { + pass++; + } } } - return true; + return quantifier == null || quantifier(pass); }; } } + /// + /// Returns a quantifier function if set for the expression. + /// + private Func GetQuantifier(LanguageOperator expression) + { + if (expression.Property.TryGetLong(GREATEROREQUAL, out var q)) + return (number) => number >= q.Value; + + if (expression.Property.TryGetLong(GREATER, out q)) + return (number) => number > q.Value; + + if (expression.Property.TryGetLong(LESSOREQUAL, out q)) + return (number) => number <= q.Value; + + if (expression.Property.TryGetLong(LESS, out q)) + return (number) => number < q.Value; + + if (expression.Property.TryGetLong(COUNT, out q)) + return (number) => number == q.Value; + + return null; + } + + [DebuggerStepThrough] private string Value(ExpressionContext context, object v) { return v as string; diff --git a/tests/PSRule.Tests/FromFileSubSelector.Rule.jsonc b/tests/PSRule.Tests/FromFileSubSelector.Rule.jsonc index f831aa0394..93a3eaba2f 100644 --- a/tests/PSRule.Tests/FromFileSubSelector.Rule.jsonc +++ b/tests/PSRule.Tests/FromFileSubSelector.Rule.jsonc @@ -1,66 +1,90 @@ [ - { - // Synopsis: A rule with sub-selector pre-condition. - "apiVersion": "github.com/microsoft/PSRule/v1", - "kind": "Rule", - "metadata": { - "name": "JsonRuleWithPrecondition" - }, - "spec": { - "where": { - "field": "kind", - "equals": "test" - }, - "condition": { - "field": "resources", - "count": 2 - } - } + { + // Synopsis: A rule with sub-selector pre-condition. + "apiVersion": "github.com/microsoft/PSRule/v1", + "kind": "Rule", + "metadata": { + "name": "JsonRuleWithPrecondition" }, - { - // Synopsis: A rule with sub-selector filter. - "apiVersion": "github.com/microsoft/PSRule/v1", - "kind": "Rule", - "metadata": { - "name": "JsonRuleWithSubselector" - }, - "spec": { - "condition": { - "field": "resources", - "where": { - "field": ".", - "isString": true - }, - "allOf": [ - { - "field": ".", - "equals": "abc" - } - ] - } - } + "spec": { + "where": { + "field": "kind", + "equals": "test" + }, + "condition": { + "field": "resources", + "count": 2 + } + } + }, + { + // Synopsis: A rule with sub-selector filter. + "apiVersion": "github.com/microsoft/PSRule/v1", + "kind": "Rule", + "metadata": { + "name": "JsonRuleWithSubselector" }, - { - // Synopsis: A rule with sub-selector filter. - "apiVersion": "github.com/microsoft/PSRule/v1", - "kind": "Rule", - "metadata": { - "name": "JsonRuleWithSubselectorReordered" + "spec": { + "condition": { + "field": "resources", + "where": { + "field": ".", + "isString": true }, - "spec": { - "condition": { - "allOf": [ - { - "field": ".", - "equals": "abc" - } - ], - "field": "resources", - "where": { - "field": ".", - "equals": "abc" - } - } + "allOf": [ + { + "field": ".", + "equals": "abc" + } + ] + } + } + }, + { + // Synopsis: A rule with sub-selector filter. + "apiVersion": "github.com/microsoft/PSRule/v1", + "kind": "Rule", + "metadata": { + "name": "JsonRuleWithSubselectorReordered" + }, + "spec": { + "condition": { + "allOf": [ + { + "field": ".", + "equals": "abc" + } + ], + "field": "resources", + "where": { + "field": ".", + "equals": "abc" } + } + } + }, + { + // Synopsis: A rule with a sub-selector quantifier. + "apiVersion": "github.com/microsoft/PSRule/v1", + "kind": "Rule", + "metadata": { + "name": "JsonRuleWithQuantifier" + }, + "spec": { + "condition": { + "field": "resources[*].properties.logs[*]", + "greaterOrEqual": 1, + "allOf": [ + { + "field": "category", + "equals": "firewall" + }, + { + "field": "enabled", + "equals": true + } + ] + } } + } ] diff --git a/tests/PSRule.Tests/FromFileSubSelector.Rule.yaml b/tests/PSRule.Tests/FromFileSubSelector.Rule.yaml index 9ff8d2b065..c65dcf83a7 100644 --- a/tests/PSRule.Tests/FromFileSubSelector.Rule.yaml +++ b/tests/PSRule.Tests/FromFileSubSelector.Rule.yaml @@ -32,8 +32,8 @@ spec: field: '.' isString: true allOf: - - field: '.' - equals: abc + - field: '.' + equals: abc --- # Synopsis: A rule with sub-selector filter. @@ -44,9 +44,25 @@ metadata: spec: condition: allOf: - - field: '.' - equals: abc + - field: '.' + equals: abc field: resources where: field: '.' equals: 'abc' + +--- +# Synopsis: A rule with a sub-selector quantifier. +apiVersion: github.com/microsoft/PSRule/v1 +kind: Rule +metadata: + name: YamlRuleWithQuantifier +spec: + condition: + field: resources[*].properties.logs[*] + greaterOrEqual: 1 + allOf: + - field: category + equals: firewall + - field: enabled + equals: true diff --git a/tests/PSRule.Tests/RulesTests.cs b/tests/PSRule.Tests/RulesTests.cs index c47e72ce3d..5102999f01 100644 --- a/tests/PSRule.Tests/RulesTests.cs +++ b/tests/PSRule.Tests/RulesTests.cs @@ -72,12 +72,14 @@ public void ReadYamlSubSelectorRule() Assert.Equal("YamlRuleWithPrecondition", rule[0].Name); Assert.Equal("YamlRuleWithSubselector", rule[1].Name); Assert.Equal("YamlRuleWithSubselectorReordered", rule[2].Name); + Assert.Equal("YamlRuleWithQuantifier", rule[3].Name); context.Init(GetSource("FromFileSubSelector.Rule.yaml")); context.Begin(); var subselector1 = GetRuleVisitor(context, "YamlRuleWithPrecondition", GetSource("FromFileSubSelector.Rule.yaml")); var subselector2 = GetRuleVisitor(context, "YamlRuleWithSubselector", GetSource("FromFileSubSelector.Rule.yaml")); var subselector3 = GetRuleVisitor(context, "YamlRuleWithSubselectorReordered", GetSource("FromFileSubSelector.Rule.yaml")); + var subselector4 = GetRuleVisitor(context, "YamlRuleWithQuantifier", GetSource("FromFileSubSelector.Rule.yaml")); context.EnterLanguageScope(subselector1.Source); var actual1 = GetObject((name: "kind", value: "test"), (name: "resources", value: new string[] { "abc", "abc" })); @@ -109,6 +111,24 @@ public void ReadYamlSubSelectorRule() context.EnterTargetObject(actual2); context.EnterRuleBlock(subselector3); Assert.True(subselector3.Condition.If().AllOf()); + + // YamlRuleWithQuantifier + var fromFile = GetObjectAsTarget("ObjectFromFile3.json"); + actual1 = fromFile[0]; + actual2 = fromFile[1]; + var actual3 = fromFile[2]; + + context.EnterTargetObject(actual1); + context.EnterRuleBlock(subselector4); + Assert.True(subselector4.Condition.If().AllOf()); + + context.EnterTargetObject(actual2); + context.EnterRuleBlock(subselector4); + Assert.False(subselector4.Condition.If().AllOf()); + + context.EnterTargetObject(actual3); + context.EnterRuleBlock(subselector4); + Assert.True(subselector4.Condition.If().AllOf()); } [Fact] @@ -255,12 +275,14 @@ public void ReadJsonSubSelectorRule() Assert.Equal("JsonRuleWithPrecondition", rule[0].Name); Assert.Equal("JsonRuleWithSubselector", rule[1].Name); Assert.Equal("JsonRuleWithSubselectorReordered", rule[2].Name); + Assert.Equal("JsonRuleWithQuantifier", rule[3].Name); context.Init(GetSource("FromFileSubSelector.Rule.yaml")); context.Begin(); var subselector1 = GetRuleVisitor(context, "JsonRuleWithPrecondition", GetSource("FromFileSubSelector.Rule.jsonc")); var subselector2 = GetRuleVisitor(context, "JsonRuleWithSubselector", GetSource("FromFileSubSelector.Rule.jsonc")); var subselector3 = GetRuleVisitor(context, "JsonRuleWithSubselectorReordered", GetSource("FromFileSubSelector.Rule.jsonc")); + var subselector4 = GetRuleVisitor(context, "JsonRuleWithQuantifier", GetSource("FromFileSubSelector.Rule.jsonc")); context.EnterLanguageScope(subselector1.Source); var actual1 = GetObject((name: "kind", value: "test"), (name: "resources", value: new string[] { "abc", "abc" })); @@ -292,6 +314,24 @@ public void ReadJsonSubSelectorRule() context.EnterTargetObject(actual2); context.EnterRuleBlock(subselector3); Assert.True(subselector3.Condition.If().AllOf()); + + // JsonRuleWithQuantifier + var fromFile = GetObjectAsTarget("ObjectFromFile3.json"); + actual1 = fromFile[0]; + actual2 = fromFile[1]; + var actual3 = fromFile[2]; + + context.EnterTargetObject(actual1); + context.EnterRuleBlock(subselector4); + Assert.True(subselector4.Condition.If().AllOf()); + + context.EnterTargetObject(actual2); + context.EnterRuleBlock(subselector4); + Assert.False(subselector4.Condition.If().AllOf()); + + context.EnterTargetObject(actual3); + context.EnterRuleBlock(subselector4); + Assert.True(subselector4.Condition.If().AllOf()); } #endregion Json rules @@ -324,6 +364,11 @@ private static object[] GetObject(string path) return JsonConvert.DeserializeObject(File.ReadAllText(path)); } + private static TargetObject[] GetObjectAsTarget(string path) + { + return JsonConvert.DeserializeObject(File.ReadAllText(path)).Select(o => new TargetObject(new PSObject(o))).ToArray(); + } + private static RuleBlock GetRuleVisitor(RunspaceContext context, string name, Source[] source = null) { var block = HostHelper.GetRuleBlockGraph(source ?? GetSource(), context).GetAll(); diff --git a/tests/PSRule.Tests/SelectorTests.cs b/tests/PSRule.Tests/SelectorTests.cs index e5ca17ec58..c9be56f7a8 100644 --- a/tests/PSRule.Tests/SelectorTests.cs +++ b/tests/PSRule.Tests/SelectorTests.cs @@ -41,7 +41,7 @@ public void ReadSelector(string type, string path) context.Begin(); var selector = HostHelper.GetSelectorForTests(GetSource(path), context).ToArray(); Assert.NotNull(selector); - Assert.Equal(101, selector.Length); + Assert.Equal(102, selector.Length); var actual = selector[0]; var visitor = new SelectorVisitor(context, actual.Id, actual.Source, actual.Spec.If); @@ -1661,6 +1661,23 @@ public void AllOf(string type, string path) Assert.False(allOf.Match(actual2)); Assert.True(allOf.Match(actual3)); Assert.False(allOf.Match(actual4)); + + // With quantifier + allOf = GetSelectorVisitor($"{type}AllOfWithQuantifier", GetSource(path), out _); + actual1 = GetObject((name: "Name", value: "TargetObject1"), (name: "properties", value: GetObject((name: "logs", value: new object[] + { + GetObject((name: "name", value: "log1")) + })))); + actual2 = GetObject((name: "Name", value: "TargetObject1"), (name: "properties", value: GetObject((name: "logs", value: new object[] + { + GetObject((name: "name", value: "log1")), + GetObject((name: "name", value: "log2")) + })))); + actual3 = GetObject((name: "Name", value: "TargetObject1"), (name: "properties", value: GetObject((name: "logs", value: new object[] {})))); + + Assert.True(allOf.Match(actual1)); + Assert.True(allOf.Match(actual2)); + Assert.False(allOf.Match(actual3)); } [Theory] diff --git a/tests/PSRule.Tests/Selectors.Rule.jsonc b/tests/PSRule.Tests/Selectors.Rule.jsonc index 70e121a8e6..342d69d78d 100644 --- a/tests/PSRule.Tests/Selectors.Rule.jsonc +++ b/tests/PSRule.Tests/Selectors.Rule.jsonc @@ -1904,5 +1904,25 @@ "apiVersion": "" } } + }, + { + // Synopsis: Test all of with quantifier. + "apiVersion": "github.com/microsoft/PSRule/v1", + "kind": "Selector", + "metadata": { + "name": "JsonAllOfWithQuantifier" + }, + "spec": { + "if": { + "field": "properties.logs[*]", + "greaterOrEqual": 1, + "allOf": [ + { + "field": "name", + "equals": "log1" + } + ] + } + } } ] diff --git a/tests/PSRule.Tests/Selectors.Rule.yaml b/tests/PSRule.Tests/Selectors.Rule.yaml index da9613ec34..4bbf6a30b1 100644 --- a/tests/PSRule.Tests/Selectors.Rule.yaml +++ b/tests/PSRule.Tests/Selectors.Rule.yaml @@ -1363,3 +1363,17 @@ spec: if: field: dateVersion apiVersion: '' + +--- +# Synopsis: Test all of with quantifier. +apiVersion: github.com/microsoft/PSRule/v1 +kind: Selector +metadata: + name: YamlAllOfWithQuantifier +spec: + if: + field: properties.logs[*] + greaterOrEqual: 1 + allOf: + - field: name + equals: log1 From 431e13a44e02bf34efead6f5830922872f096920 Mon Sep 17 00:00:00 2001 From: Bernie White Date: Sun, 7 May 2023 13:58:14 +1000 Subject: [PATCH 011/177] Added padLeft and padRight functions #1422 (#1526) --- docs/CHANGELOG-v2.md | 3 + docs/expressions/functions.md | 2 + schemas/PSRule-language.schema.json | 90 ++++++++++ src/PSRule/Common/DictionaryExtensions.cs | 20 +++ .../Definitions/Expressions/Functions.cs | 47 +++++ tests/PSRule.Tests/FunctionTests.cs | 168 ++++++++++++++++++ tests/PSRule.Tests/Functions.Rule.jsonc | 44 +++++ tests/PSRule.Tests/Functions.Rule.yaml | 58 ++++-- tests/PSRule.Tests/SelectorTests.cs | 6 +- 9 files changed, 424 insertions(+), 14 deletions(-) diff --git a/docs/CHANGELOG-v2.md b/docs/CHANGELOG-v2.md index 360e6a11c7..af46ec23fb 100644 --- a/docs/CHANGELOG-v2.md +++ b/docs/CHANGELOG-v2.md @@ -39,6 +39,9 @@ What's changed since pre-release v2.9.0-B0013: [#1423](https://github.com/microsoft/PSRule/issues/1423) - Quantifiers allow you to specify the number of matches with `count`, `less`, `lessOrEqual`, `greater`, or `greaterOrEqual`. - See [Sub-selectors][4] for more information. + - Added support for new functions by @BernieWhite. + [#1422](https://github.com/microsoft/PSRule/issues/1422) + - Added support for `padLeft`, and `padRight`. ## v2.9.0-B0013 (pre-release) diff --git a/docs/expressions/functions.md b/docs/expressions/functions.md index b3461b8ce7..0ece90cbb8 100644 --- a/docs/expressions/functions.md +++ b/docs/expressions/functions.md @@ -27,6 +27,8 @@ It may be necessary to perform minor transformation before evaluating a conditio - `first` - Return the first element in an array or the first character of a string. - `integer` - Convert a value to an integer. - `last` - Return the last element in an array or the last character of a string. +- `padLeft` - Pad a value with a character on the left to meet the specified length. +- `padRight` - Pad a value with a character on the right to meet the specified length. - `path` - Get a value from an object path. - `replace` - Replace an old string with a new string. - `split` - Split a string into an array by a delimiter. diff --git a/schemas/PSRule-language.schema.json b/schemas/PSRule-language.schema.json index e64eafeac6..314457d697 100644 --- a/schemas/PSRule-language.schema.json +++ b/schemas/PSRule-language.schema.json @@ -3357,6 +3357,12 @@ }, { "$ref": "#/definitions/fn/definitions/function/definitions/split" + }, + { + "$ref": "#/definitions/fn/definitions/function/definitions/padLeft" + }, + { + "$ref": "#/definitions/fn/definitions/function/definitions/padRight" } ], "definitions": { @@ -3781,6 +3787,90 @@ "split", "delimiter" ] + }, + "padLeft": { + "type": "object", + "properties": { + "padLeft": { + "oneOf": [ + { + "type": "string", + "title": "Pad Left", + "description": "The padLeft function returns a string with a minimum of the specified length.", + "markdownDescription": "The `padLeft` function returns a string with a minimum of the specified length." + }, + { + "type": "object", + "title": "Pad Left", + "description": "The padLeft function returns a string with a minimum of the specified length.", + "markdownDescription": "The `padLeft` function returns a string with a minimum of the specified length.", + "$ref": "#/definitions/fn/definitions/function" + } + ], + "default": {} + }, + "totalLength": { + "type": "integer", + "title": "Total length", + "description": "Sets the number of characters to pad the string to. If the string is less then the specified number of characters one or more padding characters will be added until the length is reached.", + "minimum": 1 + }, + "paddingCharacter": { + "type": "string", + "title": "Padding characters", + "description": "Sets the character to use for padding the string to reach the configured total length.", + "minLength": 1, + "maxLength": 1, + "default": " " + } + }, + "additionalProperties": false, + "required": [ + "padLeft", + "totalLength" + ] + }, + "padRight": { + "type": "object", + "properties": { + "padRight": { + "oneOf": [ + { + "type": "string", + "title": "Pad Right", + "description": "The padRight function returns a string with a minimum of the specified length.", + "markdownDescription": "The `padRight` function returns a string with a minimum of the specified length." + }, + { + "type": "object", + "title": "Pad Right", + "description": "The padRight function returns a string with a minimum of the specified length.", + "markdownDescription": "The `padRight` function returns a string with a minimum of the specified length.", + "$ref": "#/definitions/fn/definitions/function" + } + ], + "default": {} + }, + "totalLength": { + "type": "integer", + "title": "Total length", + "description": "Sets the number of characters to pad the string to. If the string is less then the specified number of characters one or more padding characters will be added until the length is reached.", + "minimum": 1 + }, + "paddingCharacter": { + "type": "string", + "title": "Padding characters", + "description": "Sets the character to use for padding the string to reach the configured total length.", + "minLength": 1, + "maxLength": 1, + "default": " " + } + }, + "additionalProperties": false, + "required": [ + "padRight", + "totalLength" + ] } } } diff --git a/src/PSRule/Common/DictionaryExtensions.cs b/src/PSRule/Common/DictionaryExtensions.cs index db7e31fefd..b0f14c7fa8 100644 --- a/src/PSRule/Common/DictionaryExtensions.cs +++ b/src/PSRule/Common/DictionaryExtensions.cs @@ -106,6 +106,26 @@ public static bool TryGetInt(this IDictionary dictionary, string return false; } + [DebuggerStepThrough] + public static bool TryGetChar(this IDictionary dictionary, string key, out char? value) + { + value = null; + if (!dictionary.TryGetValue(key, out var o)) + return false; + + if (o is string svalue && svalue.Length == 1) + { + value = svalue[0]; + return true; + } + if (o is char cvalue) + { + value = cvalue; + return true; + } + return false; + } + [DebuggerStepThrough] public static bool TryGetString(this IDictionary dictionary, string key, out string value) { diff --git a/src/PSRule/Definitions/Expressions/Functions.cs b/src/PSRule/Definitions/Expressions/Functions.cs index d260714f61..7c8a8bdc10 100644 --- a/src/PSRule/Definitions/Expressions/Functions.cs +++ b/src/PSRule/Definitions/Expressions/Functions.cs @@ -30,10 +30,16 @@ internal static class Functions private const string FIRST = "first"; private const string LAST = "last"; private const string SPLIT = "split"; + private const string PADLEFT = "padLeft"; + private const string PADRIGHT = "padRight"; private const string DELIMITER = "delimiter"; private const string OLDSTRING = "oldstring"; private const string NEWSTRING = "newstring"; private const string CASESENSITIVE = "casesensitive"; + private const string TOTALLENGTH = "totalLength"; + private const string PADDINGCHARACTER = "paddingCharacter"; + + private const char SPACE = ' '; /// /// The available built-in functions. @@ -52,6 +58,8 @@ internal static class Functions new FunctionDescriptor(FIRST, First), new FunctionDescriptor(LAST, Last), new FunctionDescriptor(SPLIT, Split), + new FunctionDescriptor(PADLEFT, PadLeft), + new FunctionDescriptor(PADRIGHT, PadRight), }; private static ExpressionFnOuter Boolean(IExpressionContext context, PropertyBag properties) @@ -244,6 +252,45 @@ private static ExpressionFnOuter Split(IExpressionContext context, PropertyBag p }; } + private static ExpressionFnOuter PadLeft(IExpressionContext context, PropertyBag properties) + { + if (properties == null || + properties.Count == 0 || + + !TryProperty(properties, PADLEFT, out ExpressionFnOuter next)) + return null; + + var paddingChar = properties.TryGetChar(PADDINGCHARACTER, out var c) ? c : SPACE; + var totalWidth = properties.TryGetInt(TOTALLENGTH, out var i) ? i : 0; + return (context) => + { + var value = next(context); + if (ExpressionHelpers.TryString(value, convert: true, value: out var s)) + return totalWidth > s.Length ? s.PadLeft(totalWidth.Value, paddingChar.Value) : s; + + return null; + }; + } + + private static ExpressionFnOuter PadRight(IExpressionContext context, PropertyBag properties) + { + if (properties == null || + properties.Count == 0 || + !TryProperty(properties, PADRIGHT, out ExpressionFnOuter next)) + return null; + + var paddingChar = properties.TryGetChar(PADDINGCHARACTER, out var c) ? c : SPACE; + var totalWidth = properties.TryGetInt(TOTALLENGTH, out var i) ? i : 0; + return (context) => + { + var value = next(context); + if (ExpressionHelpers.TryString(value, convert: true, value: out var s)) + return totalWidth > s.Length ? s.PadRight(totalWidth.Value, paddingChar.Value) : s; + + return null; + }; + } + #region Helper functions private static bool TryProperty(PropertyBag properties, string name, out int? value) diff --git a/tests/PSRule.Tests/FunctionTests.cs b/tests/PSRule.Tests/FunctionTests.cs index 036fe41e61..44d0dd87f8 100644 --- a/tests/PSRule.Tests/FunctionTests.cs +++ b/tests/PSRule.Tests/FunctionTests.cs @@ -427,6 +427,174 @@ public void Split() Assert.Null(fn(context, properties)(context)); } + [Fact] + public void PadLeft() + { + var context = GetContext(); + var fn = GetFunction("padLeft"); + + var properties = new LanguageExpression.PropertyBag + { + { "padLeft", "One" }, + { "totalLength", 5 } + }; + Assert.Equal(" One", fn(context, properties)(context)); + + + properties = new LanguageExpression.PropertyBag + { + { "padLeft", "One" }, + { "totalLength", 3 } + }; + Assert.Equal("One", fn(context, properties)(context)); + + properties = new LanguageExpression.PropertyBag + { + { "padLeft", "One" }, + { "totalLength", 5 }, + { "paddingCharacter", '_' } + }; + Assert.Equal("__One", fn(context, properties)(context)); + + properties = new LanguageExpression.PropertyBag + { + { "padLeft", "One" }, + { "totalLength", 5 }, + { "paddingCharacter", "_" } + }; + Assert.Equal("__One", fn(context, properties)(context)); + + properties = new LanguageExpression.PropertyBag + { + { "padLeft", "One" }, + { "totalLength", 5 }, + { "paddingCharacter", "__" } + }; + Assert.Equal(" One", fn(context, properties)(context)); + + properties = new LanguageExpression.PropertyBag + { + { "padLeft", "One" }, + { "totalLength", 3 }, + { "paddingCharacter", "_" } + }; + Assert.Equal("One", fn(context, properties)(context)); + + properties = new LanguageExpression.PropertyBag + { + { "padLeft", "One" }, + { "totalLength", 1 }, + { "paddingCharacter", "_" } + }; + Assert.Equal("One", fn(context, properties)(context)); + + properties = new LanguageExpression.PropertyBag + { + { "padLeft", "One" }, + { "totalLength", -1 }, + { "paddingCharacter", "_" } + }; + Assert.Equal("One", fn(context, properties)(context)); + + properties = new LanguageExpression.PropertyBag + { + { "padLeft", null }, + { "totalLength", 5 } + }; + Assert.Null(fn(context, properties)(context)); + + properties = new LanguageExpression.PropertyBag + { + { "padLeft", "One" }, + { "totalLength", null } + }; + Assert.Equal("One", fn(context, properties)(context)); + } + + [Fact] + public void PadRight() + { + var context = GetContext(); + var fn = GetFunction("padRight"); + + var properties = new LanguageExpression.PropertyBag + { + { "padRight", "One" }, + { "totalLength", 5 } + }; + Assert.Equal("One ", fn(context, properties)(context)); + + + properties = new LanguageExpression.PropertyBag + { + { "padRight", "One" }, + { "totalLength", 3 } + }; + Assert.Equal("One", fn(context, properties)(context)); + + properties = new LanguageExpression.PropertyBag + { + { "padRight", "One" }, + { "totalLength", 5 }, + { "paddingCharacter", '_' } + }; + Assert.Equal("One__", fn(context, properties)(context)); + + properties = new LanguageExpression.PropertyBag + { + { "padRight", "One" }, + { "totalLength", 5 }, + { "paddingCharacter", "_" } + }; + Assert.Equal("One__", fn(context, properties)(context)); + + properties = new LanguageExpression.PropertyBag + { + { "padRight", "One" }, + { "totalLength", 5 }, + { "paddingCharacter", "__" } + }; + Assert.Equal("One ", fn(context, properties)(context)); + + properties = new LanguageExpression.PropertyBag + { + { "padRight", "One" }, + { "totalLength", 3 }, + { "paddingCharacter", "_" } + }; + Assert.Equal("One", fn(context, properties)(context)); + + properties = new LanguageExpression.PropertyBag + { + { "padRight", "One" }, + { "totalLength", 1 }, + { "paddingCharacter", "_" } + }; + Assert.Equal("One", fn(context, properties)(context)); + + properties = new LanguageExpression.PropertyBag + { + { "padRight", "One" }, + { "totalLength", -1 }, + { "paddingCharacter", "_" } + }; + Assert.Equal("One", fn(context, properties)(context)); + + properties = new LanguageExpression.PropertyBag + { + { "padRight", null }, + { "totalLength", 5 } + }; + Assert.Null(fn(context, properties)(context)); + + properties = new LanguageExpression.PropertyBag + { + { "padRight", "One" }, + { "totalLength", null } + }; + Assert.Equal("One", fn(context, properties)(context)); + } + #region Helper methods private static ExpressionBuilderFn GetFunction(string name) diff --git a/tests/PSRule.Tests/Functions.Rule.jsonc b/tests/PSRule.Tests/Functions.Rule.jsonc index 452c3ca9f6..a198d2de30 100644 --- a/tests/PSRule.Tests/Functions.Rule.jsonc +++ b/tests/PSRule.Tests/Functions.Rule.jsonc @@ -240,5 +240,49 @@ ] } } + }, + { + // Synopsis: A test for the padLeft function. + "apiVersion": "github.com/microsoft/PSRule/v1", + "kind": "Selector", + "metadata": { + "name": "Json.Fn.PadLeft" + }, + "spec": { + "if": { + "value": { + "$": { + "padLeft": { + "string": "One" + }, + "totalLength": 5, + "paddingCharacter": "_" + } + }, + "equals": "__One" + } + } + }, + { + // Synopsis: A test for the padRight function. + "apiVersion": "github.com/microsoft/PSRule/v1", + "kind": "Selector", + "metadata": { + "name": "Json.Fn.PadRight" + }, + "spec": { + "if": { + "value": { + "$": { + "padRight": { + "string": "One" + }, + "totalLength": 5, + "paddingCharacter": "_" + } + }, + "equals": "One__" + } + } } ] diff --git a/tests/PSRule.Tests/Functions.Rule.yaml b/tests/PSRule.Tests/Functions.Rule.yaml index 5d3b64752e..86d8c14722 100644 --- a/tests/PSRule.Tests/Functions.Rule.yaml +++ b/tests/PSRule.Tests/Functions.Rule.yaml @@ -53,9 +53,9 @@ spec: value: $: concat: - - path: name - - string: '-' - - path: name + - path: name + - string: '-' + - path: name equals: TestObject1-TestObject1 --- @@ -83,9 +83,9 @@ spec: equals: $: concat: - - path: name - - string: '-' - - path: name + - path: name + - string: '-' + - path: name --- # Synopsis: A test for the replace function. @@ -128,8 +128,8 @@ spec: value: $: first: - - string: abc - - string: def + - string: abc + - string: def equals: abc --- @@ -143,8 +143,8 @@ spec: value: $: last: - - string: abc - - string: def + - string: abc + - string: def equals: def --- @@ -162,6 +162,38 @@ spec: delimiter: - ' ' equals: - - One - - Two - - Three + - One + - Two + - Three + +--- +# Synopsis: A test for the padLeft function. +apiVersion: github.com/microsoft/PSRule/v1 +kind: Selector +metadata: + name: Yaml.Fn.PadLeft +spec: + if: + value: + $: + padLeft: + string: One + paddingCharacter: _ + totalLength: 5 + equals: __One + +--- +# Synopsis: A test for the padRight function. +apiVersion: github.com/microsoft/PSRule/v1 +kind: Selector +metadata: + name: Yaml.Fn.PadRight +spec: + if: + value: + $: + padRight: + string: One + paddingCharacter: _ + totalLength: 5 + equals: One__ diff --git a/tests/PSRule.Tests/SelectorTests.cs b/tests/PSRule.Tests/SelectorTests.cs index c9be56f7a8..2282da2529 100644 --- a/tests/PSRule.Tests/SelectorTests.cs +++ b/tests/PSRule.Tests/SelectorTests.cs @@ -1673,7 +1673,7 @@ public void AllOf(string type, string path) GetObject((name: "name", value: "log1")), GetObject((name: "name", value: "log2")) })))); - actual3 = GetObject((name: "Name", value: "TargetObject1"), (name: "properties", value: GetObject((name: "logs", value: new object[] {})))); + actual3 = GetObject((name: "Name", value: "TargetObject1"), (name: "properties", value: GetObject((name: "logs", value: new object[] { })))); Assert.True(allOf.Match(actual1)); Assert.True(allOf.Match(actual2)); @@ -1831,6 +1831,8 @@ public void WithFunctionSpecific(string type, string path) var example3 = GetSelectorVisitor($"{type}.Fn.First", GetSource(path), out _); var example4 = GetSelectorVisitor($"{type}.Fn.Last", GetSource(path), out _); var example5 = GetSelectorVisitor($"{type}.Fn.Split", GetSource(path), out _); + var example6 = GetSelectorVisitor($"{type}.Fn.PadLeft", GetSource(path), out _); + var example7 = GetSelectorVisitor($"{type}.Fn.PadRight", GetSource(path), out _); var actual1 = GetObject( (name: "Name", value: "TestObject1") ); @@ -1840,6 +1842,8 @@ public void WithFunctionSpecific(string type, string path) Assert.True(example3.Match(actual1)); Assert.True(example4.Match(actual1)); Assert.True(example5.Match(actual1)); + Assert.True(example6.Match(actual1)); + Assert.True(example7.Match(actual1)); } #endregion Functions From ad260dd8da1ac1caf6130b7e2c4e1ca1a0f63c92 Mon Sep 17 00:00:00 2001 From: Bernie White Date: Sun, 7 May 2023 14:19:13 +1000 Subject: [PATCH 012/177] Pre-release v2.9.0-B0033 (#1527) --- docs/CHANGELOG-v2.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/CHANGELOG-v2.md b/docs/CHANGELOG-v2.md index af46ec23fb..37261e024c 100644 --- a/docs/CHANGELOG-v2.md +++ b/docs/CHANGELOG-v2.md @@ -32,6 +32,8 @@ See [upgrade notes][1] for helpful information when upgrading from previous vers ## Unreleased +## v2.9.0-B0033 (pre-release) + What's changed since pre-release v2.9.0-B0013: - New features: From 1ebde2e00f44e0dbab42c62efc09b16bf2c07430 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 10 May 2023 08:39:52 +1000 Subject: [PATCH 013/177] Bump mkdocs-material from 9.1.9 to 9.1.11 (#1528) Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.1.9 to 9.1.11. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.1.9...9.1.11) --- updated-dependencies: - dependency-name: mkdocs-material dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements-docs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-docs.txt b/requirements-docs.txt index a3c8ba628f..32d6c84ee2 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -1,5 +1,5 @@ mkdocs==1.4.2 -mkdocs-material==9.1.9 +mkdocs-material==9.1.11 pymdown-extensions==9.11 mike==1.1.2 mkdocs-simple-hooks==0.1.5 From aba4b6713ba472ecc47620594b2de93d018557b7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 13 May 2023 08:08:31 +1000 Subject: [PATCH 014/177] Bump mkdocs-material from 9.1.11 to 9.1.12 (#1530) Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.1.11 to 9.1.12. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.1.11...9.1.12) --- updated-dependencies: - dependency-name: mkdocs-material dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements-docs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-docs.txt b/requirements-docs.txt index 32d6c84ee2..a99d22a40e 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -1,5 +1,5 @@ mkdocs==1.4.2 -mkdocs-material==9.1.11 +mkdocs-material==9.1.12 pymdown-extensions==9.11 mike==1.1.2 mkdocs-simple-hooks==0.1.5 From bda91df3f1468c53cbac93338d3ac2d1ea209d6d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 16 May 2023 09:24:28 +1000 Subject: [PATCH 015/177] Bump pymdown-extensions from 9.11 to 10.0 (#1531) Bumps [pymdown-extensions](https://github.com/facelessuser/pymdown-extensions) from 9.11 to 10.0. - [Release notes](https://github.com/facelessuser/pymdown-extensions/releases) - [Commits](https://github.com/facelessuser/pymdown-extensions/compare/9.11...10.0) --- updated-dependencies: - dependency-name: pymdown-extensions dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements-docs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-docs.txt b/requirements-docs.txt index a99d22a40e..72c3b1b11f 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -1,6 +1,6 @@ mkdocs==1.4.2 mkdocs-material==9.1.12 -pymdown-extensions==9.11 +pymdown-extensions==10.0 mike==1.1.2 mkdocs-simple-hooks==0.1.5 mkdocs-git-revision-date-plugin==0.3.2 From ead001c99d5dfa6c93742498f3a5f9dbbb1a187a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 16 May 2023 23:16:45 +1000 Subject: [PATCH 016/177] Bump mkdocs from 1.4.2 to 1.4.3 (#1523) Bumps [mkdocs](https://github.com/mkdocs/mkdocs) from 1.4.2 to 1.4.3. - [Release notes](https://github.com/mkdocs/mkdocs/releases) - [Commits](https://github.com/mkdocs/mkdocs/compare/1.4.2...1.4.3) --- updated-dependencies: - dependency-name: mkdocs dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements-docs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-docs.txt b/requirements-docs.txt index 72c3b1b11f..337f2c6543 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -1,4 +1,4 @@ -mkdocs==1.4.2 +mkdocs==1.4.3 mkdocs-material==9.1.12 pymdown-extensions==10.0 mike==1.1.2 From 15bef64cddd9676cdbed746a3d83946703289d6b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 17 May 2023 16:27:02 +1000 Subject: [PATCH 017/177] Bump pymdown-extensions from 10.0 to 10.0.1 (#1533) Bumps [pymdown-extensions](https://github.com/facelessuser/pymdown-extensions) from 10.0 to 10.0.1. - [Release notes](https://github.com/facelessuser/pymdown-extensions/releases) - [Commits](https://github.com/facelessuser/pymdown-extensions/compare/10.0...10.0.1) --- updated-dependencies: - dependency-name: pymdown-extensions dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements-docs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-docs.txt b/requirements-docs.txt index 337f2c6543..9c008b6749 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -1,6 +1,6 @@ mkdocs==1.4.3 mkdocs-material==9.1.12 -pymdown-extensions==10.0 +pymdown-extensions==10.0.1 mike==1.1.2 mkdocs-simple-hooks==0.1.5 mkdocs-git-revision-date-plugin==0.3.2 From 43900b8ed14b581e3afd4f60e44db0346d43d10d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 18 May 2023 23:49:16 +1000 Subject: [PATCH 018/177] Bump mkdocs-material from 9.1.12 to 9.1.13 (#1535) Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.1.12 to 9.1.13. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.1.12...9.1.13) --- updated-dependencies: - dependency-name: mkdocs-material dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements-docs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-docs.txt b/requirements-docs.txt index 9c008b6749..f7da9faff0 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -1,5 +1,5 @@ mkdocs==1.4.3 -mkdocs-material==9.1.12 +mkdocs-material==9.1.13 pymdown-extensions==10.0.1 mike==1.1.2 mkdocs-simple-hooks==0.1.5 From e5c3633e7a5578a44ebe30758526f06ef2b5099a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 May 2023 19:10:40 +1000 Subject: [PATCH 019/177] Bump mkdocs-material from 9.1.13 to 9.1.14 (#1536) Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.1.13 to 9.1.14. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.1.13...9.1.14) --- updated-dependencies: - dependency-name: mkdocs-material dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements-docs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-docs.txt b/requirements-docs.txt index f7da9faff0..6ed2874c5a 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -1,5 +1,5 @@ mkdocs==1.4.3 -mkdocs-material==9.1.13 +mkdocs-material==9.1.14 pymdown-extensions==10.0.1 mike==1.1.2 mkdocs-simple-hooks==0.1.5 From 404a864689ccfca0701233bb28c39f7498a0fe59 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 30 May 2023 21:12:35 +1000 Subject: [PATCH 020/177] Bump mkdocs-material from 9.1.14 to 9.1.15 (#1538) Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.1.14 to 9.1.15. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.1.14...9.1.15) --- updated-dependencies: - dependency-name: mkdocs-material dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements-docs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-docs.txt b/requirements-docs.txt index 6ed2874c5a..0e8b590205 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -1,5 +1,5 @@ mkdocs==1.4.3 -mkdocs-material==9.1.14 +mkdocs-material==9.1.15 pymdown-extensions==10.0.1 mike==1.1.2 mkdocs-simple-hooks==0.1.5 From 8a46a88b0e701ef505b056574784ec88150e8536 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 3 Jun 2023 15:54:12 +1000 Subject: [PATCH 021/177] Bump Microsoft.NET.Test.Sdk from 17.5.0 to 17.6.1 (#1540) * Bump Microsoft.NET.Test.Sdk from 17.5.0 to 17.6.1 Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 17.5.0 to 17.6.1. - [Release notes](https://github.com/microsoft/vstest/releases) - [Changelog](https://github.com/microsoft/vstest/blob/main/docs/releases.md) - [Commits](https://github.com/microsoft/vstest/compare/v17.5.0...v17.6.1) --- updated-dependencies: - dependency-name: Microsoft.NET.Test.Sdk dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * Bump change log --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Bernie White --- docs/CHANGELOG-v2.md | 6 ++++++ tests/PSRule.Tests/PSRule.Tests.csproj | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/CHANGELOG-v2.md b/docs/CHANGELOG-v2.md index 37261e024c..2416b26523 100644 --- a/docs/CHANGELOG-v2.md +++ b/docs/CHANGELOG-v2.md @@ -32,6 +32,12 @@ See [upgrade notes][1] for helpful information when upgrading from previous vers ## Unreleased +What's changed since pre-release v2.9.0-B0033: + +- Engineering: + - Bump Microsoft.NET.Test.Sdk to v17.6.1. + [#1540](https://github.com/microsoft/PSRule/pull/1540) + ## v2.9.0-B0033 (pre-release) What's changed since pre-release v2.9.0-B0013: diff --git a/tests/PSRule.Tests/PSRule.Tests.csproj b/tests/PSRule.Tests/PSRule.Tests.csproj index 3fa976873e..327ed15505 100644 --- a/tests/PSRule.Tests/PSRule.Tests.csproj +++ b/tests/PSRule.Tests/PSRule.Tests.csproj @@ -12,7 +12,7 @@ - + From ad5b12bb7aa20b0a3f081364aaa77176c1597855 Mon Sep 17 00:00:00 2001 From: Bernie White Date: Sun, 4 Jun 2023 05:27:42 +1000 Subject: [PATCH 022/177] Added support for baseline groups #1541 (#1542) --- .vscode/settings.json | 3 + README.md | 1 + docs/CHANGELOG-v2.md | 10 + .../commands/PSRule/en-US/New-PSRuleOption.md | 51 ++-- .../commands/PSRule/en-US/Set-PSRuleOption.md | 34 ++- .../PSRule/en-US/about_PSRule_Options.md | 49 ++++ docs/concepts/baselines.md | 80 ++++++ docs/troubleshooting.md | 19 ++ mkdocs.yml | 1 + schemas/PSRule-options.schema.json | 32 ++- src/PSRule.Badges/PSRule.Badges.csproj | 1 + src/PSRule.SDK/PSRule.SDK.csproj | 1 + src/PSRule.Tool/PSRule.Tool.csproj | 2 +- src/PSRule.Types/Converters/TypeConverter.cs | 264 ++++++++++++++++++ .../Converters/Yaml/StringArrayConverter.cs | 48 ++++ .../Yaml/StringArrayMapConverter.cs | 81 ++++++ src/PSRule.Types/Data/StringArrayMap.cs | 134 +++++++++ .../DictionaryExtensions.cs | 80 +++++- src/PSRule.Types/Environment.cs | 211 ++++++++++++++ src/PSRule.Types/HashtableExtensions.cs | 30 ++ src/PSRule.Types/Options/BaselineOption.cs | 105 +++++++ src/PSRule.Types/PSRule.Types.csproj | 1 + src/PSRule.Types/Properties/AssemblyInfo.cs | 3 +- .../Common/BaselineYamlSerializationMapper.cs | 2 +- src/PSRule/Common/EnvironmentHelper.cs | 143 ---------- src/PSRule/Common/ExpressionHelpers.cs | 158 +---------- src/PSRule/Common/ExternalToolHelper.cs | 4 +- src/PSRule/Common/GitHelper.cs | 30 +- src/PSRule/Common/JsonCommentWriter.cs | 5 +- src/PSRule/Common/KeyMapDictionary.cs | 7 +- src/PSRule/Common/LoggerExtensions.cs | 15 + src/PSRule/Common/YamlConverters.cs | 34 --- src/PSRule/Configuration/BaselineOption.cs | 24 +- src/PSRule/Configuration/BindingOption.cs | 21 ++ .../Configuration/ConfigurationOption.cs | 4 +- src/PSRule/Configuration/ConventionOption.cs | 4 +- src/PSRule/Configuration/ExecutionOption.cs | 32 +-- src/PSRule/Configuration/IncludeOption.cs | 6 +- src/PSRule/Configuration/InputOption.cs | 18 +- src/PSRule/Configuration/LoggingOption.cs | 10 +- src/PSRule/Configuration/OutputOption.cs | 26 +- src/PSRule/Configuration/PSRuleOption.cs | 37 ++- src/PSRule/Configuration/RepositoryOption.cs | 6 +- src/PSRule/Configuration/RequiresOption.cs | 4 +- .../Expressions/LanguageExpressions.cs | 4 +- src/PSRule/Definitions/Resource.cs | 6 +- src/PSRule/Help/HelpLexer.cs | 4 +- src/PSRule/Help/MarkdownStream.cs | 2 +- src/PSRule/Host/Host.cs | 4 +- src/PSRule/Host/HostHelper.cs | 4 +- src/PSRule/PSRule.psm1 | 27 +- .../Pipeline/ExportBaselinePipelineBuilder.cs | 5 +- .../Pipeline/Formatters/AssertFormatter.cs | 4 +- .../Pipeline/GetBaselinePipelineBuilder.cs | 7 +- ...ineBuiler.cs => GetRulePipelineBuilder.cs} | 2 +- src/PSRule/Pipeline/InvokePipelineBuilder.cs | 4 +- src/PSRule/Pipeline/Output/CsvOutputWriter.cs | 7 +- .../Pipeline/Output/NUnit3OutputWriter.cs | 12 +- src/PSRule/Pipeline/PipelineBuilder.cs | 43 ++- src/PSRule/Pipeline/PipelineContext.cs | 24 +- .../Pipeline/PipelineWriterExtensions.cs | 8 - src/PSRule/Pipeline/Source.cs | 1 - src/PSRule/Pipeline/SourcePipeline.cs | 2 +- .../Resources/PSRuleResources.Designer.cs | 27 +- src/PSRule/Resources/PSRuleResources.resx | 9 +- src/PSRule/Rules/RuleHelpInfo.cs | 2 +- src/PSRule/Rules/RuleRecord.cs | 3 +- src/PSRule/Runtime/ILogger.cs | 7 + src/PSRule/Runtime/RunspaceContext.cs | 13 +- tests/PSRule.Tests/OutputWriterTests.cs | 5 +- tests/PSRule.Tests/PSRule.Baseline.Tests.ps1 | 24 ++ tests/PSRule.Tests/PSRule.Options.Tests.ps1 | 41 +++ tests/PSRule.Tests/PSRule.Tests.csproj | 3 + tests/PSRule.Tests/PSRule.Tests.yml | 4 + tests/PSRule.Tests/PSRuleOptionTests.cs | 18 +- tests/PSRule.Tests/PipelineTests.cs | 30 +- tests/PSRule.Tests/SelectorTests.cs | 2 +- 77 files changed, 1635 insertions(+), 554 deletions(-) create mode 100644 docs/concepts/baselines.md create mode 100644 src/PSRule.Types/Converters/TypeConverter.cs create mode 100644 src/PSRule.Types/Converters/Yaml/StringArrayConverter.cs create mode 100644 src/PSRule.Types/Converters/Yaml/StringArrayMapConverter.cs create mode 100644 src/PSRule.Types/Data/StringArrayMap.cs rename src/{PSRule/Common => PSRule.Types}/DictionaryExtensions.cs (66%) create mode 100644 src/PSRule.Types/Environment.cs create mode 100644 src/PSRule.Types/HashtableExtensions.cs create mode 100644 src/PSRule.Types/Options/BaselineOption.cs delete mode 100644 src/PSRule/Common/EnvironmentHelper.cs rename src/PSRule/Pipeline/{GetRulePipelineBuiler.cs => GetRulePipelineBuilder.cs} (97%) diff --git a/.vscode/settings.json b/.vscode/settings.json index fc6bbbc17a..cac8e90ff0 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -57,6 +57,9 @@ "document" ] }, + "[markdown]": { + "editor.formatOnSave": false + }, "[powershell]": { "editor.formatOnSave": false, "editor.tabSize": 4 diff --git a/README.md b/README.md index e83277091b..b4b8569298 100644 --- a/README.md +++ b/README.md @@ -281,6 +281,7 @@ The following conceptual topics exist in the `PSRule` module: - [WithinPath](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Expressions/#withinpath) - [Version](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Expressions/#version) - [Options](https://aka.ms/ps-rule/options) + - [Baseline.Group](https://aka.ms/ps-rule/options#baselinegroup) - [Binding.Field](https://aka.ms/ps-rule/options#bindingfield) - [Binding.IgnoreCase](https://aka.ms/ps-rule/options#bindingignorecase) - [Binding.NameSeparator](https://aka.ms/ps-rule/options#bindingnameseparator) diff --git a/docs/CHANGELOG-v2.md b/docs/CHANGELOG-v2.md index 2416b26523..8fd6d44888 100644 --- a/docs/CHANGELOG-v2.md +++ b/docs/CHANGELOG-v2.md @@ -19,6 +19,8 @@ See [upgrade notes][1] for helpful information when upgrading from previous vers **Experimental features**: +- Baseline groups allow you to use a friendly name to reference baselines. + See [baselines][6] for more information. - Functions within YAML and JSON expressions can be used to perform manipulation prior to testing a condition. See [functions][3] for more information. - Sub-selectors within YAML and JSON expressions can be used to filter rules and list properties. @@ -29,11 +31,19 @@ See [upgrade notes][1] for helpful information when upgrading from previous vers [3]: expressions/functions.md [4]: expressions/sub-selectors.md [5]: creating-your-pipeline.md#processing-changed-files-only + [6]: concepts/baselines.md ## Unreleased What's changed since pre-release v2.9.0-B0033: +- New features: + - **Experimental**: Added support for baseline groups by @BernieWhite. + [#1541](https://github.com/microsoft/PSRule/issues/1541) + - Baseline groups allow you to reference a baseline by a friendly name. + - Update the baseline group to point to a new baseline. + - Currently only a single baseline can be referenced by a baseline group. + - See [baselines][6] for more information. - Engineering: - Bump Microsoft.NET.Test.Sdk to v17.6.1. [#1540](https://github.com/microsoft/PSRule/pull/1540) diff --git a/docs/commands/PSRule/en-US/New-PSRuleOption.md b/docs/commands/PSRule/en-US/New-PSRuleOption.md index 37225b880f..51440be336 100644 --- a/docs/commands/PSRule/en-US/New-PSRuleOption.md +++ b/docs/commands/PSRule/en-US/New-PSRuleOption.md @@ -18,10 +18,10 @@ Create options to configure PSRule execution. ```text New-PSRuleOption [[-Path] ] [-Configuration ] [-SuppressTargetName ] [-BindTargetName ] - [-BindTargetType ] [-BindingIgnoreCase ] [-BindingField ] - [-BindingNameSeparator ] [-BindingPreferTargetInfo ] [-TargetName ] - [-TargetType ] [-BindingUseQualifiedName ] [-Convention ] - [-AliasReferenceWarning ] [-DuplicateResourceId ] + [-BindTargetType ] [-BaselineGroup ] [-BindingIgnoreCase ] + [-BindingField ] [-BindingNameSeparator ] [-BindingPreferTargetInfo ] + [-TargetName ] [-TargetType ] [-BindingUseQualifiedName ] + [-Convention ] [-AliasReferenceWarning ] [-DuplicateResourceId ] [-InconclusiveWarning ] [-InvariantCultureWarning ] [-InitialSessionState ] [-NotProcessedWarning ] [-SuppressedRuleWarning ] [-SuppressionGroupExpired ] [-ExecutionRuleExcluded ] @@ -46,10 +46,10 @@ New-PSRuleOption [[-Path] ] [-Configuration ] ```text New-PSRuleOption [-Option] [-Configuration ] [-SuppressTargetName ] [-BindTargetName ] - [-BindTargetType ] [-BindingIgnoreCase ] [-BindingField ] - [-BindingNameSeparator ] [-BindingPreferTargetInfo ] [-TargetName ] - [-TargetType ] [-BindingUseQualifiedName ] [-Convention ] - [-AliasReferenceWarning ] [-DuplicateResourceId ] + [-BindTargetType ] [-BaselineGroup ] [-BindingIgnoreCase ] + [-BindingField ] [-BindingNameSeparator ] [-BindingPreferTargetInfo ] + [-TargetName ] [-TargetType ] [-BindingUseQualifiedName ] + [-Convention ] [-AliasReferenceWarning ] [-DuplicateResourceId ] [-InconclusiveWarning ] [-InvariantCultureWarning ] [-InitialSessionState ] [-NotProcessedWarning ] [-SuppressedRuleWarning ] [-SuppressionGroupExpired ] [-ExecutionRuleExcluded ] @@ -73,14 +73,15 @@ New-PSRuleOption [-Option] [-Configuration ] ```text New-PSRuleOption [-Default] [-Configuration ] [-SuppressTargetName ] - [-BindTargetName ] [-BindTargetType ] [-BindingIgnoreCase ] - [-BindingField ] [-BindingNameSeparator ] [-BindingPreferTargetInfo ] - [-TargetName ] [-TargetType ] [-BindingUseQualifiedName ] - [-Convention ] [-AliasReferenceWarning ] [-DuplicateResourceId ] - [-InconclusiveWarning ] [-InvariantCultureWarning ] [-InitialSessionState ] - [-NotProcessedWarning ] [-SuppressedRuleWarning ] - [-SuppressionGroupExpired ] [-ExecutionRuleExcluded ] - [-ExecutionRuleSuppressed ] [-ExecutionAliasReference ] + [-BindTargetName ] [-BindTargetType ] [-BaselineGroup ] + [-BindingIgnoreCase ] [-BindingField ] [-BindingNameSeparator ] + [-BindingPreferTargetInfo ] [-TargetName ] [-TargetType ] + [-BindingUseQualifiedName ] [-Convention ] [-AliasReferenceWarning ] + [-DuplicateResourceId ] [-InconclusiveWarning ] + [-InvariantCultureWarning ] [-InitialSessionState ] [-NotProcessedWarning ] + [-SuppressedRuleWarning ] [-SuppressionGroupExpired ] + [-ExecutionRuleExcluded ] [-ExecutionRuleSuppressed ] + [-ExecutionAliasReference ] [-ExecutionRuleInconclusive ] [-ExecutionInvariantCulture ] [-ExecutionUnprocessedObject ] [-IncludeModule ] @@ -259,6 +260,24 @@ Accept pipeline input: False Accept wildcard characters: False ``` +### -BaselineGroup + +Sets the option `Baseline.Group`. +The option `Baseline.Group` allows a named group of baselines to be defined and later referenced. +See about_PSRule_Options for more information. + +```yaml +Type: Hashtable +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + ### -BindTargetType Configures a custom function to use to bind TargetType of an object. diff --git a/docs/commands/PSRule/en-US/Set-PSRuleOption.md b/docs/commands/PSRule/en-US/Set-PSRuleOption.md index 18caaa4214..e4a509cc19 100644 --- a/docs/commands/PSRule/en-US/Set-PSRuleOption.md +++ b/docs/commands/PSRule/en-US/Set-PSRuleOption.md @@ -15,14 +15,14 @@ Sets options that configure PSRule execution. ```text Set-PSRuleOption [[-Path] ] [-Option ] [-PassThru] [-Force] [-AllowClobber] - [-BindingIgnoreCase ] [-BindingField ] [-BindingNameSeparator ] - [-BindingPreferTargetInfo ] [-TargetName ] [-TargetType ] - [-BindingUseQualifiedName ] [-Convention ] [-AliasReferenceWarning ] - [-DuplicateResourceId ] [-InconclusiveWarning ] - [-InvariantCultureWarning ] [-InitialSessionState ] [-NotProcessedWarning ] - [-SuppressedRuleWarning ] [-SuppressionGroupExpired ] - [-ExecutionRuleExcluded ] [-ExecutionRuleSuppressed ] - [-ExecutionAliasReference ] + [-BaselineGroup ] [-BindingIgnoreCase ] [-BindingField ] + [-BindingNameSeparator ] [-BindingPreferTargetInfo ] [-TargetName ] + [-TargetType ] [-BindingUseQualifiedName ] [-Convention ] + [-AliasReferenceWarning ] [-DuplicateResourceId ] + [-InconclusiveWarning ] [-InvariantCultureWarning ] [-InitialSessionState ] + [-NotProcessedWarning ] [-SuppressedRuleWarning ] + [-SuppressionGroupExpired ] [-ExecutionRuleExcluded ] + [-ExecutionRuleSuppressed ] [-ExecutionAliasReference ] [-ExecutionRuleInconclusive ] [-ExecutionInvariantCulture ] [-ExecutionUnprocessedObject ] [-IncludeModule ] @@ -155,6 +155,24 @@ Accept pipeline input: False Accept wildcard characters: False ``` +### -BaselineGroup + +Sets the option `Baseline.Group`. +The option `Baseline.Group` allows a named group of baselines to be defined and later referenced. +See about_PSRule_Options for more information. + +```yaml +Type: Hashtable +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + ### -BindingIgnoreCase Sets the option `Binding.IgnoreCase`. diff --git a/docs/concepts/PSRule/en-US/about_PSRule_Options.md b/docs/concepts/PSRule/en-US/about_PSRule_Options.md index 5418af226c..8fc560c861 100644 --- a/docs/concepts/PSRule/en-US/about_PSRule_Options.md +++ b/docs/concepts/PSRule/en-US/about_PSRule_Options.md @@ -13,6 +13,7 @@ This topic describes what options are available, when to and how to use them. The following workspace options are available for use: +- [Baseline.Group](#baselinegroup) - [Convention.Include](#conventioninclude) - [Execution.AliasReference](#executionaliasreference) - [Execution.AliasReferenceWarning](#executionaliasreferencewarning) @@ -169,6 +170,54 @@ Boolean values are case-insensitive. - String array values can specify multiple items by using a semi-colon separator. For example `PSRULE_INPUT_TARGETTYPE` could be set to `virtualMachine;virtualNetwork`. +### Baseline.Group + +You can use a baseline group to provide a friendly name to an existing baseline. +When you run PSRule you can opt to use the baseline group name as an alternative name for the baseline. +To indicate a baseline group, prefix the group name with `@` where you would use the name of a baseline. + +Baseline groups can be specified using: + +```powershell +# PowerShell: Using the BaselineGroup parameter +$option = New-PSRuleOption -BaselineGroup @{ latest = 'YourBaseline' }; +``` + +```powershell +# PowerShell: Using the Baseline.Group hashtable key +$option = New-PSRuleOption -Option @{ 'Baseline.Group' = @{ latest = 'YourBaseline' } }; +``` + +```powershell +# PowerShell: Using the BaselineGroup parameter to set YAML +Set-PSRuleOption -BaselineGroup @{ latest = 'YourBaseline' }; +``` + +```yaml +# YAML: Using the baseline/group property +baseline: + group: + latest: YourBaseline +``` + +```bash +# Bash: Using environment variable +export PSRULE_BASELINE_GROUP='latest=YourBaseline' +``` + +```yaml +# GitHub Actions: Using environment variable +env: + PSRULE_BASELINE_GROUP: 'latest=YourBaseline' +``` + +```yaml +# Azure Pipelines: Using environment variable +variables: +- name: PSRULE_BASELINE_GROUP + value: 'latest=YourBaseline' +``` + ### Binding.Field When an object is passed from the pipeline, PSRule automatically extracts fields from object properties. diff --git a/docs/concepts/baselines.md b/docs/concepts/baselines.md new file mode 100644 index 0000000000..49f740f707 --- /dev/null +++ b/docs/concepts/baselines.md @@ -0,0 +1,80 @@ +# Baselines + +!!! Abstract + A _baseline_ is a set of rules and configuration options. + You can define a named baseline to run a set of rules for a specific use case. + +Baselines cover two (2) main scenarios: + +- **Rules** — PSRule supports running rules by name or tag. + However, when working with a large number of rules it is often easier to group and run rules based on a name. +- **Configuration** — A baseline allows you to run any included rules with a predefined configuration by name. + +## Defining baselines + +A baseline is defined as a resource within YAML or JSON. +Baselines can be defined side-by-side with rules you create or included separately as a custom baseline. + +Continue reading [baseline][1] reference. + + [1]: ./PSRule/en-US/about_PSRule_Baseline.md + +## Baseline groups + +:octicons-milestone-24: v2.9.0 + +In addition to regular baselines, you can use a baseline group to provide a friendly name to an existing baseline. +A baseline groups are set by configuring the [Baseline.Group][2] option. + +!!! Experimental + _Baseline groups_ are a work in progress and subject to change. + Currently, _baseline groups_ allow only a single baseline to be referenced. + [Join or start a disucssion][3] to let us know how we can improve this feature going forward. + +!!! Tip + You can use baseline groups to reference a baseline. + If a new baseline is made available in the future, update your baseline group in one place to start using the new baseline. + +In the following example, two baseline groups `latest` and `preview` are defined: + +```yaml title="ps-rule.yaml" +baseline: + group: + latest: PSRule.Rules.Azure\Azure.GA_2023_03 + preview: PSRule.Rules.Azure\Azure.Preview_2023_03 +``` + +- The `latest` baseline group is set to `Azure.GA_2023_03` within the `PSRule.Rules.Azure` module. +- The `preview` baseline group is set to `Azure.Preview_2023_03` within the `PSRule.Rules.Azure` module. + +To use the baseline group, prefix the group name with `@` when running PSRule. +For example: + +=== "GitHub Actions" + + ```yaml + - name: Run PSRule + uses: microsoft/ps-rule@v2.8.1 + with: + modules: 'PSRule.Rules.Azure' + baseline: '@latest' + ``` + +=== "Azure Pipelines" + + ```yaml + - task: ps-rule-assert@2 + displayName: Run PSRule + inputs: + modules: 'PSRule.Rules.Azure' + baseline: '@latest' + ``` + +=== "Generic with PowerShell" + + ```powershell + Assert-PSRule -InputPath '.' -Baseline '@latest' -Module PSRule.Rules.Azure -Format File; + ``` + + [2]: ./PSRule/en-US/about_PSRule_Options.md#baselinegroup + [3]: https://github.com/microsoft/PSRule/discussions diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 0c5498a8a0..3d1c8975ee 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -88,3 +88,22 @@ Choose to use one or the other. If you have a specific use case your would like to enable, please start a [discussion][3]. [3]: https://github.com/microsoft/PSRule/discussions + +## PSR0003 - The specified baseline group is not known + +!!! Error + + PSR0003: The specified baseline group 'latest' is not known. + +This error is caused by attempting to reference a baseline group which has not been defined. +To define a baseline group, see [Baseline.Group][4] option. + + [4]: https://aka.ms/ps-rule/options#baselinegroup + +## PSR0004 - The specified resource is not known + +!!! Error + + PSR0004: The specified Baseline resource 'TestModule4\Module4' is not known. + +This error is caused when you attempt to reference a resource such as a baseline, rule, or selector which has not been defined. diff --git a/mkdocs.yml b/mkdocs.yml index c76ad0ccf8..fc7af9826d 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -61,6 +61,7 @@ nav: - Kubernetes resource validation example: scenarios/kubernetes-resources/kubernetes-resources.md - Using PSRule from a Container: scenarios/containers/container-execution.md - Concepts: + - Baselines: concepts/baselines.md - Functions: expressions/functions.md - Grouping rules: concepts/grouping-rules.md - Sub-selectors: expressions/sub-selectors.md diff --git a/schemas/PSRule-options.schema.json b/schemas/PSRule-options.schema.json index e0024e2cbf..0d0efa7d94 100644 --- a/schemas/PSRule-options.schema.json +++ b/schemas/PSRule-options.schema.json @@ -204,6 +204,32 @@ }, "additionalProperties": false }, + "baseline-option": { + "type": "object", + "title": "Baseline options", + "description": "Options that configure baselines.", + "properties": { + "group": { + "type": "object", + "title": "Baseline group", + "description": "Configure baseline group names.", + "additionalProperties": { + "title": "Group mapping", + "description": "Maps a group name to a baseline.", + "type": "string" + }, + "defaultSnippets": [ + { + "label": "Baseline group", + "body": { + "${1:Group}": "${2:Baseline}" + } + } + ] + } + }, + "additionalProperties": false + }, "binding-option": { "type": "object", "title": "Object binding", @@ -851,6 +877,10 @@ }, "options": { "properties": { + "baseline": { + "type": "object", + "$ref": "#/definitions/baseline-option" + }, "binding": { "type": "object", "oneOf": [ @@ -966,7 +996,7 @@ }, "rule-option": { "type": "object", - "title": "Baseline options", + "title": "Rule options", "description": "Options that include/ exclude and configure rules.", "properties": { "include": { diff --git a/src/PSRule.Badges/PSRule.Badges.csproj b/src/PSRule.Badges/PSRule.Badges.csproj index 5339c5174a..4dc36456d2 100644 --- a/src/PSRule.Badges/PSRule.Badges.csproj +++ b/src/PSRule.Badges/PSRule.Badges.csproj @@ -7,6 +7,7 @@ Library {309bed8b-4e60-4c42-a2b4-37a2e7ebef3f} README.md + True diff --git a/src/PSRule.SDK/PSRule.SDK.csproj b/src/PSRule.SDK/PSRule.SDK.csproj index 1be7cf1c8b..66adb69620 100644 --- a/src/PSRule.SDK/PSRule.SDK.csproj +++ b/src/PSRule.SDK/PSRule.SDK.csproj @@ -6,6 +6,7 @@ README.md false {07a84e67-1ca3-4766-b9ea-1fdd9df6516f} + True diff --git a/src/PSRule.Tool/PSRule.Tool.csproj b/src/PSRule.Tool/PSRule.Tool.csproj index 6769a911ca..34f0358e23 100644 --- a/src/PSRule.Tool/PSRule.Tool.csproj +++ b/src/PSRule.Tool/PSRule.Tool.csproj @@ -9,7 +9,7 @@ PSRule.Tool.Program true true - false + True true ps-rule README.md diff --git a/src/PSRule.Types/Converters/TypeConverter.cs b/src/PSRule.Types/Converters/TypeConverter.cs new file mode 100644 index 0000000000..4722c27efe --- /dev/null +++ b/src/PSRule.Types/Converters/TypeConverter.cs @@ -0,0 +1,264 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using Newtonsoft.Json.Linq; + +namespace PSRule.Converters +{ + internal static class TypeConverter + { + public static bool TryString(object o, out string value) + { + if (o is string s) + { + value = s; + return true; + } + else if (o is JToken token && token.Type == JTokenType.String) + { + value = token.Value(); + return true; + } + value = null; + return false; + } + + public static bool TryString(object o, bool convert, out string value) + { + if (TryString(o, out value)) + return true; + + if (convert && o is Enum evalue) + { + value = evalue.ToString(); + return true; + } + else if (convert && TryLong(o, false, out var l_value)) + { + value = l_value.ToString(Thread.CurrentThread.CurrentCulture); + return true; + } + else if (convert && TryBool(o, false, out var b_value)) + { + value = b_value.ToString(Thread.CurrentThread.CurrentCulture); + return true; + } + else if (convert && TryInt(o, false, out var i_value)) + { + value = i_value.ToString(Thread.CurrentThread.CurrentCulture); + return true; + } + return false; + } + + public static bool TryArray(object o, out Array value) + { + value = null; + if (o is string) return false; + if (o is Array a) + value = a; + + else if (o is JArray jArray) + value = jArray.Values().ToArray(); + + else if (o is IEnumerable e) + value = e.OfType().ToArray(); + + return value != null; + } + + public static bool TryStringOrArray(object o, bool convert, out string[] value) + { + // Handle single string + if (TryString(o, convert, value: out var s)) + { + value = new string[] { s }; + return true; + } + + // Handle multiple strings + return TryStringArray(o, convert, out value); + } + + public static bool TryStringArray(object o, bool convert, out string[] value) + { + value = null; + if (o is Array array) + { + value = new string[array.Length]; + for (var i = 0; i < array.Length; i++) + { + if (TryString(array.GetValue(i), convert, value: out var s)) + value[i] = s; + } + } + else if (o is JArray jArray) + { + value = new string[jArray.Count]; + for (var i = 0; i < jArray.Count; i++) + { + if (TryString(jArray[i], convert, out var s)) + value[i] = s; + } + } + else if (o is IEnumerable enumerable) + { + value = enumerable.ToArray(); + } + else if (o is IEnumerable e) + { + value = e.OfType().ToArray(); + } + return value != null; + } + + /// + /// Try to get an int from the existing object. + /// + public static bool TryInt(object o, bool convert, out int value) + { + if (o is int ivalue) + { + value = ivalue; + return true; + } + if (o is long lvalue && lvalue <= int.MaxValue && lvalue >= int.MinValue) + { + value = (int)lvalue; + return true; + } + else if (o is JToken token && token.Type == JTokenType.Integer) + { + value = token.Value(); + return true; + } + else if (convert && TryString(o, out var s) && int.TryParse(s, out ivalue)) + { + value = ivalue; + return true; + } + value = default; + return false; + } + + public static bool TryBool(object o, bool convert, out bool value) + { + if (o is bool bvalue) + { + value = bvalue; + return true; + } + else if (o is JToken token && token.Type == JTokenType.Boolean) + { + value = token.Value(); + return true; + } + else if (convert && TryString(o, out var s) && bool.TryParse(s, out bvalue)) + { + value = bvalue; + return true; + } + else if (convert && TryLong(o, convert: false, out var lvalue)) + { + value = lvalue > 0; + return true; + } + value = default; + return false; + } + + public static bool TryByte(object o, bool convert, out byte value) + { + if (o is byte bvalue) + { + value = bvalue; + return true; + } + else if (o is JToken token && token.Type == JTokenType.Integer) + { + value = token.Value(); + return true; + } + else if (convert && TryString(o, out var s) && byte.TryParse(s, out bvalue)) + { + value = bvalue; + return true; + } + value = default; + return false; + } + + public static bool TryLong(object o, bool convert, out long value) + { + if (o is byte b) + { + value = b; + return true; + } + else if (o is int i) + { + value = i; + return true; + } + else if (o is uint ui) + { + value = (long)ui; + return true; + } + else if (o is long l) + { + value = l; + return true; + } + else if (o is ulong ul && ul <= long.MaxValue) + { + value = (long)ul; + return true; + } + else if (o is JToken token && token.Type == JTokenType.Integer) + { + value = token.Value(); + return true; + } + else if (convert && TryString(o, out var s) && long.TryParse(s, out l)) + { + value = l; + return true; + } + value = default; + return false; + } + + public static bool TryFloat(object o, bool convert, out float value) + { + if (o is float fvalue || (convert && o is string s && float.TryParse(s, out fvalue))) + { + value = fvalue; + return true; + } + else if (convert && o is int ivalue) + { + value = ivalue; + return true; + } + value = default; + return false; + } + + public static bool TryDouble(object o, bool convert, out double value) + { + if (o is double dvalue || (convert && o is string s && double.TryParse(s, out dvalue))) + { + value = dvalue; + return true; + } + value = default; + return false; + } + } +} diff --git a/src/PSRule.Types/Converters/Yaml/StringArrayConverter.cs b/src/PSRule.Types/Converters/Yaml/StringArrayConverter.cs new file mode 100644 index 0000000000..e22d544195 --- /dev/null +++ b/src/PSRule.Types/Converters/Yaml/StringArrayConverter.cs @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using YamlDotNet.Core; +using YamlDotNet.Core.Events; +using YamlDotNet.Serialization; + +namespace PSRule.Converters.Yaml +{ + /// + /// A YAML converter for deserializing a string array. + /// + public sealed class StringArrayConverter : IYamlTypeConverter + { + /// + bool IYamlTypeConverter.Accepts(Type type) + { + return type == typeof(string[]); + } + + /// + object IYamlTypeConverter.ReadYaml(IParser parser, Type type) + { + if (parser.TryConsume(out _)) + { + var result = new List(); + while (parser.TryConsume(out var scalar)) + result.Add(scalar.Value); + + parser.Consume(); + return result.ToArray(); + } + else if (parser.TryConsume(out var scalar)) + { + return new string[] { scalar.Value }; + } + return null; + } + + /// + void IYamlTypeConverter.WriteYaml(IEmitter emitter, object value, Type type) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/PSRule.Types/Converters/Yaml/StringArrayMapConverter.cs b/src/PSRule.Types/Converters/Yaml/StringArrayMapConverter.cs new file mode 100644 index 0000000000..aa0b814fc0 --- /dev/null +++ b/src/PSRule.Types/Converters/Yaml/StringArrayMapConverter.cs @@ -0,0 +1,81 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using PSRule.Data; +using YamlDotNet.Core; +using YamlDotNet.Core.Events; +using YamlDotNet.Serialization; + +namespace PSRule.Converters.Yaml +{ + /// + /// A YAML converter for de/serializing . + /// + public sealed class StringArrayMapConverter : IYamlTypeConverter + { + /// + bool IYamlTypeConverter.Accepts(Type type) + { + return type == typeof(StringArrayMap); + } + + /// + object IYamlTypeConverter.ReadYaml(IParser parser, Type type) + { + var result = new StringArrayMap(); + if (parser.TryConsume(out _)) + { + while (parser.TryConsume(out Scalar scalar)) + { + var key = scalar.Value; + if (parser.TryConsume(out _)) + { + var values = new List(); + while (!parser.Accept(out _)) + { + if (parser.TryConsume(out scalar)) + values.Add(scalar.Value); + } + result[key] = values.ToArray(); + parser.Require(); + parser.MoveNext(); + } + else if (parser.TryConsume(out scalar)) + { + result[key] = new string[] { scalar.Value }; + } + } + parser.Require(); + parser.MoveNext(); + } + return result; + } + + /// + void IYamlTypeConverter.WriteYaml(IEmitter emitter, object value, Type type) + { + if (type == typeof(StringArrayMap) && value == null) + { + emitter.Emit(new MappingStart()); + emitter.Emit(new MappingEnd()); + } + if (value is not StringArrayMap map) + return; + + emitter.Emit(new MappingStart()); + foreach (var field in map) + { + emitter.Emit(new Scalar(field.Key)); + emitter.Emit(new SequenceStart(null, null, false, SequenceStyle.Block)); + for (var i = 0; i < field.Value.Length; i++) + { + emitter.Emit(new Scalar(field.Value[i])); + } + emitter.Emit(new SequenceEnd()); + } + emitter.Emit(new MappingEnd()); + } + } +} diff --git a/src/PSRule.Types/Data/StringArrayMap.cs b/src/PSRule.Types/Data/StringArrayMap.cs new file mode 100644 index 0000000000..b37d178bc8 --- /dev/null +++ b/src/PSRule.Types/Data/StringArrayMap.cs @@ -0,0 +1,134 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections; +using System.Collections.Generic; +using PSRule.Converters; + +namespace PSRule.Data +{ + /// + /// A mapping of string to string arrays. + /// + public sealed class StringArrayMap : IEnumerable> + { + private readonly Dictionary _Map; + + /// + /// Create an empty instance. + /// + public StringArrayMap() + { + _Map = new Dictionary(StringComparer.OrdinalIgnoreCase); + } + + /// + /// Create an instance by copying an existing . + /// + internal StringArrayMap(StringArrayMap map) + { + _Map = new Dictionary(map._Map, StringComparer.OrdinalIgnoreCase); + } + + /// + /// Create an instance by copying mapped keys from a string dictionary. + /// + internal StringArrayMap(Dictionary map) + { + _Map = new Dictionary(map, StringComparer.OrdinalIgnoreCase); + } + + /// + /// Create an instance by copying mapped keys from a . + /// + /// + internal StringArrayMap(Hashtable map) + : this() + { + if (map != null) Load(this, map.IndexByString()); + } + + /// + /// The number of mapped keys. + /// + public int Count => _Map.Count; + + /// + /// Get or set mapping for a specified key. + /// + public string[] this[string key] + { + get + { + return !string.IsNullOrEmpty(key) && _Map.TryGetValue(key, out var value) ? value : Array.Empty(); + } + set + { + if (!string.IsNullOrEmpty(key)) + _Map[key] = value; + } + } + + /// + /// + /// + /// + public static implicit operator StringArrayMap(Hashtable hashtable) + { + return new StringArrayMap(hashtable); + } + + /// + /// Try to get a mapping by key. + /// + /// The key. + /// Returns an array of mapped keys. + /// Returns true if the key was found. Otherwise false is returned. + public bool TryGetValue(string key, out string[] value) + { + return _Map.TryGetValue(key, out value); + } + + /// + /// Load a key map from an existing dictionary. + /// + internal static void Load(StringArrayMap map, IDictionary properties) + { + foreach (var property in properties) + { + if (TypeConverter.TryStringOrArray(property.Value, convert: true, out var values)) + map[property.Key] = values; + } + } + + /// + public IEnumerator> GetEnumerator() + { + return ((IEnumerable>)_Map).GetEnumerator(); + } + + /// + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + /// + /// Convert the instance into a dictionary. + /// + /// + public IDictionary ToDictionary() + { + return _Map; + } + + /// + /// Convert a hashtable into a instance. + /// + public static StringArrayMap FromHashtable(Hashtable hashtable) + { + return new StringArrayMap(hashtable); + } + } +} diff --git a/src/PSRule/Common/DictionaryExtensions.cs b/src/PSRule.Types/DictionaryExtensions.cs similarity index 66% rename from src/PSRule/Common/DictionaryExtensions.cs rename to src/PSRule.Types/DictionaryExtensions.cs index b0f14c7fa8..0471647c0b 100644 --- a/src/PSRule/Common/DictionaryExtensions.cs +++ b/src/PSRule.Types/DictionaryExtensions.cs @@ -5,17 +5,28 @@ using System.Collections; using System.Collections.Generic; using System.Diagnostics; +using PSRule.Converters; +using PSRule.Data; namespace PSRule { - internal static class DictionaryExtensions + /// + /// Extension methods for . + /// + public static class DictionaryExtensions { + /// + /// Try to get a value and remove it from the dictionary. + /// [DebuggerStepThrough] public static bool TryPopValue(this IDictionary dictionary, string key, out object value) { return dictionary.TryGetValue(key, out value) && dictionary.Remove(key); } + /// + /// Try to get a value and remove it from the dictionary. + /// [DebuggerStepThrough] public static bool TryPopValue(this IDictionary dictionary, string key, out T value) { @@ -28,6 +39,9 @@ public static bool TryPopValue(this IDictionary dictionary, s return false; } + /// + /// Try to get a and remove it from the dictionary. + /// [DebuggerStepThrough] public static bool TryPopBool(this IDictionary dictionary, string key, out bool value) { @@ -35,6 +49,9 @@ public static bool TryPopBool(this IDictionary dictionary, strin return TryPopValue(dictionary, key, out var v) && bool.TryParse(v.ToString(), out value); } + /// + /// Try to get a and remove it from the dictionary. + /// [DebuggerStepThrough] public static bool TryPopEnum(this IDictionary dictionary, string key, out TEnum value) where TEnum : struct { @@ -42,6 +59,9 @@ public static bool TryPopEnum(this IDictionary dictionary return TryPopValue(dictionary, key, out var v) && Enum.TryParse(v.ToString(), ignoreCase: true, result: out value); } + /// + /// Try to get a and remove it from the dictionary. + /// [DebuggerStepThrough] public static bool TryPopString(this IDictionary dictionary, string key, out string value) { @@ -54,13 +74,39 @@ public static bool TryPopString(this IDictionary dictionary, str return false; } + /// + /// Try to get an array of strings and remove it from the dictionary. + /// [DebuggerStepThrough] public static bool TryPopStringArray(this IDictionary dictionary, string key, out string[] value) { value = default; - return TryPopValue(dictionary, key, out var v) && ExpressionHelpers.TryStringOrArray(v, convert: true, value: out value); + return TryPopValue(dictionary, key, out var v) && TypeConverter.TryStringOrArray(v, convert: true, value: out value); } + /// + /// Try to get a and remove it from the dictionary. + /// + [DebuggerStepThrough] + public static bool TryPopStringArrayMap(this IDictionary dictionary, string key, out StringArrayMap value) + { + value = default; + if (TryPopValue(dictionary, key, out var v) && v is StringArrayMap svalue) + { + value = svalue; + return true; + } + if (v is Hashtable hashtable) + { + value = StringArrayMap.FromHashtable(hashtable); + return true; + } + return false; + } + + /// + /// Try to get the value as a . + /// [DebuggerStepThrough] public static bool TryGetBool(this IDictionary dictionary, string key, out bool? value) { @@ -76,6 +122,9 @@ public static bool TryGetBool(this IDictionary dictionary, strin return false; } + /// + /// Try to get the value as a . + /// [DebuggerStepThrough] public static bool TryGetLong(this IDictionary dictionary, string key, out long? value) { @@ -83,7 +132,7 @@ public static bool TryGetLong(this IDictionary dictionary, strin if (!dictionary.TryGetValue(key, out var o)) return false; - if (ExpressionHelpers.TryLong(o, true, out var i_value)) + if (TypeConverter.TryLong(o, convert: true, value: out var i_value)) { value = i_value; return true; @@ -91,6 +140,9 @@ public static bool TryGetLong(this IDictionary dictionary, strin return false; } + /// + /// Try to get the value as a . + /// [DebuggerStepThrough] public static bool TryGetInt(this IDictionary dictionary, string key, out int? value) { @@ -98,7 +150,7 @@ public static bool TryGetInt(this IDictionary dictionary, string if (!dictionary.TryGetValue(key, out var o)) return false; - if (ExpressionHelpers.TryInt(o, true, out var i_value)) + if (TypeConverter.TryInt(o, convert: true, value: out var i_value)) { value = i_value; return true; @@ -106,6 +158,9 @@ public static bool TryGetInt(this IDictionary dictionary, string return false; } + /// + /// Try to get the value as a . + /// [DebuggerStepThrough] public static bool TryGetChar(this IDictionary dictionary, string key, out char? value) { @@ -126,6 +181,9 @@ public static bool TryGetChar(this IDictionary dictionary, strin return false; } + /// + /// Try to get the value as a . + /// [DebuggerStepThrough] public static bool TryGetString(this IDictionary dictionary, string key, out string value) { @@ -141,6 +199,9 @@ public static bool TryGetString(this IDictionary dictionary, str return false; } + /// + /// Try to get the value as an . + /// [DebuggerStepThrough] public static bool TryGetEnumerable(this IDictionary dictionary, string key, out IEnumerable value) { @@ -156,19 +217,28 @@ public static bool TryGetEnumerable(this IDictionary dictionary, return false; } + /// + /// Try to get the value as an array of strings. + /// [DebuggerStepThrough] public static bool TryGetStringArray(this IDictionary dictionary, string key, out string[] value) { value = null; - return dictionary.TryGetValue(key, out var o) && ExpressionHelpers.TryStringOrArray(o, convert: true, value: out value); + return dictionary.TryGetValue(key, out var o) && TypeConverter.TryStringOrArray(o, convert: true, value: out value); } + /// + /// Add unique keys to the dictionary. + /// Duplicate keys are ignored. + /// [DebuggerStepThrough] public static void AddUnique(this IDictionary dictionary, IEnumerable> values) { foreach (var kv in values) + { if (!dictionary.ContainsKey(kv.Key)) dictionary.Add(kv.Key, kv.Value); + } } internal static SortedDictionary ToSortedDictionary(this IDictionary dictionary) diff --git a/src/PSRule.Types/Environment.cs b/src/PSRule.Types/Environment.cs new file mode 100644 index 0000000000..ac1256dbcc --- /dev/null +++ b/src/PSRule.Types/Environment.cs @@ -0,0 +1,211 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Net; +using System.Security; +using PSRule.Data; + +namespace PSRule +{ + /// + /// A helper for accessing environment variables. + /// + public static class Environment + { + private static readonly char[] STRINGARRAYMAP_ITEMSEPARATOR = new char[] { ',' }; + private static readonly char[] STRINGARRAY_SEPARATOR = new char[] { ';' }; + private static readonly char[] LINUX_PATH_ENV_SEPARATOR = new char[] { ':' }; + private static readonly char[] WINDOWS_PATH_ENV_SEPARATOR = new char[] { ';' }; + + private const char STRINGARRYAMAP_PAIRSEPARATOR = '='; + private const string PATH_ENV = "PATH"; + private const string DEFAULT_CREDENTIAL_USERNAME = "na"; + private const string TF_BUILD = "TF_BUILD"; + private const string GITHUB_ACTIONS = "GITHUB_ACTIONS"; + + /// + /// Determine if the environment is running within Azure Pipelines. + /// + public static bool IsAzurePipelines() + { + return TryBool(TF_BUILD, out var azp) && azp; + } + + /// + /// Determines if the environment is running within GitHub Actions. + /// + public static bool IsGitHubActions() + { + return TryBool(GITHUB_ACTIONS, out var gh) && gh; + } + + /// + /// Determine if the environment is running within Visual Studio Code. + /// + public static bool IsVisualStudioCode() + { + return TryString("TERM_PROGRAM", out var term) && term == "vscode"; + } + + /// + /// Get the run identifier for the current environment. + /// + public static string GetRunId() + { + if (TryString("PSRULE_RUN_ID", out var runId)) + return runId; + + return TryString("BUILD_REPOSITORY_NAME", out var prefix) && TryString("BUILD_BUILDID", out var suffix) || + TryString("GITHUB_REPOSITORY", out prefix) && TryString("GITHUB_RUN_ID", out suffix) + ? string.Concat(prefix, "/", suffix) + : null; + } + + /// + /// Try to get the environment variable as a . + /// + public static bool TryString(string key, out string value) + { + return TryVariable(key, out value) && !string.IsNullOrEmpty(value); + } + + /// + /// Try to get the environment variable as a . + /// + public static bool TrySecureString(string key, out SecureString value) + { + value = null; + if (!TryString(key, out var variable)) + return false; + + value = new NetworkCredential(DEFAULT_CREDENTIAL_USERNAME, variable).SecurePassword; + return true; + } + + /// + /// Try to get the environment variable as an . + /// + public static bool TryInt(string key, out int value) + { + value = default; + return TryVariable(key, out var variable) && int.TryParse(variable, out value); + } + + /// + /// Try to get the environment variable as a . + /// + public static bool TryBool(string key, out bool value) + { + value = default; + return TryVariable(key, out var variable) && TryParseBool(variable, out value); + } + + /// + /// Try to get the environment variable as a enum of type . + /// + public static bool TryEnum(string key, out TEnum value) where TEnum : struct + { + value = default; + return TryVariable(key, out var variable) && Enum.TryParse(variable, ignoreCase: true, out value); + } + + /// + /// Try to get the environment variable as an array of strings. + /// + public static bool TryStringArray(string key, out string[] value) + { + value = default; + if (!TryVariable(key, out var variable)) + return false; + + value = variable.Split(STRINGARRAY_SEPARATOR, options: StringSplitOptions.RemoveEmptyEntries); + return value != null; + } + + /// + /// Try to get the environment variable as a . + /// + public static bool TryStringArrayMap(string key, out StringArrayMap value) + { + value = default; + if (!TryVariable(key, out var variable)) + return false; + + var pairs = variable.Split(STRINGARRAY_SEPARATOR, options: StringSplitOptions.RemoveEmptyEntries); + if (pairs == null) + return false; + + var map = new StringArrayMap(); + for (var i = 0; i < pairs.Length; i++) + { + var index = pairs[i].IndexOf(STRINGARRYAMAP_PAIRSEPARATOR); + if (index < 1 || index + 1 >= pairs[i].Length) continue; + + var left = pairs[i].Substring(0, index); + var right = pairs[i].Substring(index + 1); + var pair = right.Split(STRINGARRAYMAP_ITEMSEPARATOR, StringSplitOptions.RemoveEmptyEntries); + map[left] = pair; + } + value = map; + return true; + } + + /// + /// Try to get the PATH environment variable. + /// + public static bool TryPathEnvironmentVariable(out string[] value) + { + return TryPathEnvironmentVariable(PATH_ENV, out value); + } + + /// + /// Try to get a PATH environment variable with a specific name. + /// + public static bool TryPathEnvironmentVariable(string key, out string[] value) + { + value = default; + if (!TryVariable(key, out var variable)) + return false; + + var separator = System.Environment.OSVersion.Platform == PlatformID.Win32NT ? WINDOWS_PATH_ENV_SEPARATOR : LINUX_PATH_ENV_SEPARATOR; + value = variable.Split(separator, options: StringSplitOptions.RemoveEmptyEntries); + return value != null; + } + + /// + /// Try to get any environment variable with a specific prefix. + /// + public static IEnumerable> GetByPrefix(string prefix) + { + var env = System.Environment.GetEnvironmentVariables(); + var enumerator = env.GetEnumerator(); + while (enumerator.MoveNext()) + { + var key = enumerator.Key.ToString(); + if (key.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) + yield return new KeyValuePair(key, enumerator.Value); + } + } + + private static bool TryVariable(string key, out string variable) + { + variable = System.Environment.GetEnvironmentVariable(key); + return variable != null; + } + + private static bool TryParseBool(string variable, out bool value) + { + if (bool.TryParse(variable, out value)) + return true; + + if (int.TryParse(variable, out var ivalue)) + { + value = ivalue > 0; + return true; + } + return false; + } + } +} diff --git a/src/PSRule.Types/HashtableExtensions.cs b/src/PSRule.Types/HashtableExtensions.cs new file mode 100644 index 0000000000..4cb8ea9667 --- /dev/null +++ b/src/PSRule.Types/HashtableExtensions.cs @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; + +namespace PSRule +{ + /// + /// Extension methods for . + /// + public static class HashtableExtensions + { + /// + /// Map the hashtable into a dictionary string a string key. + /// + [DebuggerStepThrough] + public static IDictionary IndexByString(this Hashtable hashtable, bool ignoreCase = true) + { + var comparer = ignoreCase ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal; + var index = new Dictionary(comparer); + foreach (DictionaryEntry entry in hashtable) + index.Add(entry.Key.ToString(), entry.Value); + + return index; + } + } +} diff --git a/src/PSRule.Types/Options/BaselineOption.cs b/src/PSRule.Types/Options/BaselineOption.cs new file mode 100644 index 0000000000..77047fbecc --- /dev/null +++ b/src/PSRule.Types/Options/BaselineOption.cs @@ -0,0 +1,105 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using PSRule.Data; + +namespace PSRule.Options +{ + /// + /// Options that configure baselines. + /// + public sealed class BaselineOption : IEquatable + { + internal static readonly BaselineOption Default = new() + { + }; + + /// + /// Create an option instance. + /// + public BaselineOption() + { + Group = null; + } + + /// + /// Create an option instance based on an existing object. + /// + /// The existing object to copy. + public BaselineOption(BaselineOption option) + { + if (option == null) + return; + + Group = option.Group; + } + + /// + public override bool Equals(object obj) + { + return obj is BaselineOption option && Equals(option); + } + + /// + public bool Equals(BaselineOption other) + { + return other != null && + Group == other.Group; + } + + /// + public override int GetHashCode() + { + unchecked // Overflow is fine + { + var hash = 17; + hash = hash * 23 + (Group != null ? Group.GetHashCode() : 0); + return hash; + } + } + + /// + /// Combines two option instances into a new merged instance. + /// The new instance uses any non-null values from . + /// Any null values from are replaced with . + /// + public static BaselineOption Combine(BaselineOption o1, BaselineOption o2) + { + var result = new BaselineOption(o1) + { + Group = o1.Group ?? o2.Group, + }; + return result; + } + + /// + /// A mapping of baseline group names to baselines. + /// + /// + /// See . + /// + [DefaultValue(null)] + public StringArrayMap Group { get; set; } + + /// + /// Load from environment variables. + /// + internal void Load() + { + if (Environment.TryStringArrayMap("PSRULE_BASELINE_GROUP", out var group)) + Group = group; + } + + /// + /// Load from a dictionary. + /// + internal void Load(Dictionary index) + { + if (index.TryPopStringArrayMap("Baseline.Group", out var group)) + Group = group; + } + } +} diff --git a/src/PSRule.Types/PSRule.Types.csproj b/src/PSRule.Types/PSRule.Types.csproj index 984f938318..8b99a1db53 100644 --- a/src/PSRule.Types/PSRule.Types.csproj +++ b/src/PSRule.Types/PSRule.Types.csproj @@ -7,6 +7,7 @@ Library {5fe4db0b-63d1-4ddb-9762-9c0d29168bc9} README.md + True diff --git a/src/PSRule.Types/Properties/AssemblyInfo.cs b/src/PSRule.Types/Properties/AssemblyInfo.cs index d20fb5e7fc..efee02b75d 100644 --- a/src/PSRule.Types/Properties/AssemblyInfo.cs +++ b/src/PSRule.Types/Properties/AssemblyInfo.cs @@ -1,6 +1,7 @@ -// Copyright (c) Microsoft Corporation. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using System.Runtime.CompilerServices; +[assembly: InternalsVisibleTo("Microsoft.PSRule.Core")] [assembly: InternalsVisibleTo("PSRule.Tests")] diff --git a/src/PSRule/Common/BaselineYamlSerializationMapper.cs b/src/PSRule/Common/BaselineYamlSerializationMapper.cs index ddd85109ae..0b57d496aa 100644 --- a/src/PSRule/Common/BaselineYamlSerializationMapper.cs +++ b/src/PSRule/Common/BaselineYamlSerializationMapper.cs @@ -226,4 +226,4 @@ private static void MapPSObjectArraySequence(IEmitter emitter, PSObject[] sequen emitter.Emit(new SequenceEnd()); } } -} \ No newline at end of file +} diff --git a/src/PSRule/Common/EnvironmentHelper.cs b/src/PSRule/Common/EnvironmentHelper.cs deleted file mode 100644 index 080e03ef0d..0000000000 --- a/src/PSRule/Common/EnvironmentHelper.cs +++ /dev/null @@ -1,143 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.Collections.Generic; -using System.Net; -using System.Security; - -namespace PSRule -{ - internal static class EnvironmentHelperExtensions - { - public static bool IsAzurePipelines(this EnvironmentHelper helper) - { - return helper.TryBool("TF_BUILD", out var azp) && azp; - } - - public static bool IsGitHubActions(this EnvironmentHelper helper) - { - return helper.TryBool("GITHUB_ACTIONS", out var gh) && gh; - } - - public static bool IsVisualStudioCode(this EnvironmentHelper helper) - { - return helper.TryString("TERM_PROGRAM", out var term) && term == "vscode"; - } - - public static string GetRunId(this EnvironmentHelper helper) - { - if (helper.TryString("PSRULE_RUN_ID", out var runId)) - return runId; - - return helper.TryString("BUILD_REPOSITORY_NAME", out var prefix) && helper.TryString("BUILD_BUILDID", out var suffix) || - helper.TryString("GITHUB_REPOSITORY", out prefix) && helper.TryString("GITHUB_RUN_ID", out suffix) - ? string.Concat(prefix, "/", suffix) - : null; - } - } - - internal sealed class EnvironmentHelper - { - private static readonly char[] STRINGARRAY_SEPARATOR = new char[] { ';' }; - private static readonly char[] LINUX_PATH_ENV_SEPARATOR = new char[] { ':' }; - private static readonly char[] WINDOWS_PATH_ENV_SEPARATOR = new char[] { ';' }; - - private const string PATH_ENV = "PATH"; - private const string DEFAULT_CREDENTIAL_USERNAME = "na"; - - public static readonly EnvironmentHelper Default = new(); - - internal bool TryString(string key, out string value) - { - return TryVariable(key, out value) && !string.IsNullOrEmpty(value); - } - - internal bool TrySecureString(string key, out SecureString value) - { - value = null; - if (!TryString(key, out var variable)) - return false; - - value = new NetworkCredential(DEFAULT_CREDENTIAL_USERNAME, variable).SecurePassword; - return true; - } - - internal bool TryInt(string key, out int value) - { - value = default; - return TryVariable(key, out var variable) && int.TryParse(variable, out value); - } - - internal bool TryBool(string key, out bool value) - { - value = default; - return TryVariable(key, out var variable) && TryParseBool(variable, out value); - } - - internal bool TryEnum(string key, out TEnum value) where TEnum : struct - { - value = default; - return TryVariable(key, out var variable) && Enum.TryParse(variable, ignoreCase: true, out value); - } - - internal bool TryStringArray(string key, out string[] value) - { - value = default; - if (!TryVariable(key, out var variable)) - return false; - - value = variable.Split(STRINGARRAY_SEPARATOR, options: StringSplitOptions.RemoveEmptyEntries); - return value != null; - } - - internal bool TryPathEnvironmentVariable(out string[] value) - { - return TryPathEnvironmentVariable(PATH_ENV, out value); - } - - internal bool TryPathEnvironmentVariable(string key, out string[] value) - { - value = default; - if (!TryVariable(key, out var variable)) - return false; - - var separator = Environment.OSVersion.Platform == PlatformID.Win32NT ? WINDOWS_PATH_ENV_SEPARATOR : LINUX_PATH_ENV_SEPARATOR; - value = variable.Split(separator, options: StringSplitOptions.RemoveEmptyEntries); - return value != null; - } - - private static bool TryVariable(string key, out string variable) - { - variable = Environment.GetEnvironmentVariable(key); - return variable != null; - } - - private static bool TryParseBool(string variable, out bool value) - { - if (bool.TryParse(variable, out value)) - return true; - - if (int.TryParse(variable, out var ivalue)) - { - value = ivalue > 0; - return true; - } - return false; - } - - internal IEnumerable> WithPrefix(string prefix) - { - var env = Environment.GetEnvironmentVariables(); - var enumerator = env.GetEnumerator(); - while (enumerator.MoveNext()) - { - var key = enumerator.Key.ToString(); - if (key.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) - { - yield return new KeyValuePair(key, enumerator.Value); - } - } - } - } -} diff --git a/src/PSRule/Common/ExpressionHelpers.cs b/src/PSRule/Common/ExpressionHelpers.cs index 41314f4cf8..07d348130c 100644 --- a/src/PSRule/Common/ExpressionHelpers.cs +++ b/src/PSRule/Common/ExpressionHelpers.cs @@ -12,6 +12,7 @@ using System.Threading; using Newtonsoft.Json.Linq; using PSRule.Configuration; +using PSRule.Converters; using PSRule.Data; using PSRule.Pipeline; using PSRule.Runtime; @@ -186,19 +187,7 @@ internal static bool CompareNumeric(object actual, object expected, bool convert internal static bool TryString(object o, out string value) { - o = GetBaseObject(o); - if (o is string s) - { - value = s; - return true; - } - else if (o is JToken token && token.Type == JTokenType.String) - { - value = token.Value(); - return true; - } - value = null; - return false; + return TypeConverter.TryString(GetBaseObject(o), out value); } internal static bool TryString(object o, bool convert, out string value) @@ -231,19 +220,7 @@ internal static bool TryString(object o, bool convert, out string value) internal static bool TryArray(object o, out Array value) { - o = GetBaseObject(o); - value = null; - if (o is string) return false; - if (o is Array a) - value = a; - - else if (o is JArray jArray) - value = jArray.Values().ToArray(); - - else if (o is IEnumerable e) - value = e.OfType().ToArray(); - - return value != null; + return TypeConverter.TryArray(GetBaseObject(o), out value); } internal static bool TryStringOrArray(object o, bool convert, out string[] value) @@ -296,149 +273,32 @@ internal static bool TryStringArray(object o, bool convert, out string[] value) /// internal static bool TryInt(object o, bool convert, out int value) { - o = GetBaseObject(o); - if (o is int ivalue) - { - value = ivalue; - return true; - } - if (o is long lvalue && lvalue <= int.MaxValue && lvalue >= int.MinValue) - { - value = (int)lvalue; - return true; - } - else if (o is JToken token && token.Type == JTokenType.Integer) - { - value = token.Value(); - return true; - } - else if (convert && TryString(o, out var s) && int.TryParse(s, out ivalue)) - { - value = ivalue; - return true; - } - value = default; - return false; + return TypeConverter.TryInt(GetBaseObject(o), convert, out value); } internal static bool TryBool(object o, bool convert, out bool value) { - o = GetBaseObject(o); - if (o is bool bvalue) - { - value = bvalue; - return true; - } - else if (o is JToken token && token.Type == JTokenType.Boolean) - { - value = token.Value(); - return true; - } - else if (convert && TryString(o, out var s) && bool.TryParse(s, out bvalue)) - { - value = bvalue; - return true; - } - else if (convert && TryLong(o, convert: false, out var lvalue)) - { - value = lvalue > 0; - return true; - } - value = default; - return false; + return TypeConverter.TryBool(GetBaseObject(o), convert, out value); } internal static bool TryByte(object o, bool convert, out byte value) { - o = GetBaseObject(o); - if (o is byte bvalue) - { - value = bvalue; - return true; - } - else if (o is JToken token && token.Type == JTokenType.Integer) - { - value = token.Value(); - return true; - } - else if (convert && TryString(o, out var s) && byte.TryParse(s, out bvalue)) - { - value = bvalue; - return true; - } - value = default; - return false; + return TypeConverter.TryByte(GetBaseObject(o), convert, out value); } internal static bool TryLong(object o, bool convert, out long value) { - o = GetBaseObject(o); - if (o is byte b) - { - value = b; - return true; - } - else if (o is int i) - { - value = i; - return true; - } - else if (o is uint ui) - { - value = (long)ui; - return true; - } - else if (o is long l) - { - value = l; - return true; - } - else if (o is ulong ul && ul <= long.MaxValue) - { - value = (long)ul; - return true; - } - else if (o is JToken token && token.Type == JTokenType.Integer) - { - value = token.Value(); - return true; - } - else if (convert && TryString(o, out var s) && long.TryParse(s, out l)) - { - value = l; - return true; - } - value = default; - return false; + return TypeConverter.TryLong(GetBaseObject(o), convert, out value); } internal static bool TryFloat(object o, bool convert, out float value) { - o = GetBaseObject(o); - if (o is float fvalue || (convert && o is string s && float.TryParse(s, out fvalue))) - { - value = fvalue; - return true; - } - else if (convert && o is int ivalue) - { - value = ivalue; - return true; - } - value = default; - return false; + return TypeConverter.TryFloat(GetBaseObject(o), convert, out value); } internal static bool TryDouble(object o, bool convert, out double value) { - o = GetBaseObject(o); - if (o is double dvalue || (convert && o is string s && double.TryParse(s, out dvalue))) - { - value = dvalue; - return true; - } - value = default; - return false; + return TypeConverter.TryDouble(GetBaseObject(o), convert, out value); } internal static bool TryStringLength(object o, out int value) diff --git a/src/PSRule/Common/ExternalToolHelper.cs b/src/PSRule/Common/ExternalToolHelper.cs index fa0bebdaaf..00880027ea 100644 --- a/src/PSRule/Common/ExternalToolHelper.cs +++ b/src/PSRule/Common/ExternalToolHelper.cs @@ -119,7 +119,7 @@ private void Bicep_ErrorDataReceived(object sender, DataReceivedEventArgs e) private static string[] GetErrorLine(string input) { - var lines = input.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries); + var lines = input.Split(new string[] { System.Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries); var result = new List(); for (var i = 0; i < lines.Length; i++) if (!lines[i].Contains(": Warning ") && !lines[i].Contains(": Info ")) @@ -131,7 +131,7 @@ private static string[] GetErrorLine(string input) private static bool TryPathFromEnvironment(string binary, out string binaryPath) { binaryPath = null; - if (!EnvironmentHelper.Default.TryPathEnvironmentVariable(out var path)) + if (!Environment.TryPathEnvironmentVariable(out var path)) return false; for (var i = 0; path != null && i < path.Length; i++) diff --git a/src/PSRule/Common/GitHelper.cs b/src/PSRule/Common/GitHelper.cs index 58c0febdac..f74f2afbd5 100644 --- a/src/PSRule/Common/GitHelper.cs +++ b/src/PSRule/Common/GitHelper.cs @@ -50,17 +50,17 @@ internal static class GitHelper public static bool TryHeadRef(out string value, string path = null) { // Try PSRule - if (EnvironmentHelper.Default.TryString(ENV_PSRULE_REPO_REF, out value) || - EnvironmentHelper.Default.TryString(ENV_PSRULE_REPO_HEADREF, out value)) + if (Environment.TryString(ENV_PSRULE_REPO_REF, out value) || + Environment.TryString(ENV_PSRULE_REPO_HEADREF, out value)) return true; // Try Azure Pipelines - if (EnvironmentHelper.Default.TryString(ENV_ADO_REPO_REF, out value)) + if (Environment.TryString(ENV_ADO_REPO_REF, out value)) return true; // Try GitHub Actions - if (EnvironmentHelper.Default.TryString(ENV_GITHUB_REPO_REF, out value) || - EnvironmentHelper.Default.TryString(ENV_GITHUB_REPO_HEADREF, out value)) + if (Environment.TryString(ENV_GITHUB_REPO_REF, out value) || + Environment.TryString(ENV_GITHUB_REPO_HEADREF, out value)) return true; // Try .git/ @@ -82,15 +82,15 @@ public static bool TryHeadBranch(out string value, string path = null) public static bool TryBaseRef(out string value, string path = null) { // Try PSRule - if (EnvironmentHelper.Default.TryString(ENV_PSRULE_REPO_BASEREF, out value)) + if (Environment.TryString(ENV_PSRULE_REPO_BASEREF, out value)) return true; // Try Azure Pipelines - if (EnvironmentHelper.Default.TryString(ENV_ADO_REPO_BASEREF, out value)) + if (Environment.TryString(ENV_ADO_REPO_BASEREF, out value)) return true; // Try GitHub Actions - if (EnvironmentHelper.Default.TryString(ENV_GITHUB_REPO_BASEREF, out value)) + if (Environment.TryString(ENV_GITHUB_REPO_BASEREF, out value)) return true; // Try .git/ @@ -100,15 +100,15 @@ public static bool TryBaseRef(out string value, string path = null) public static bool TryRevision(out string value, string path = null) { // Try PSRule - if (EnvironmentHelper.Default.TryString(ENV_PSRULE_REPO_REVISION, out value)) + if (Environment.TryString(ENV_PSRULE_REPO_REVISION, out value)) return true; // Try Azure Pipelines - if (EnvironmentHelper.Default.TryString(ENV_ADO_REPO_REVISION, out value)) + if (Environment.TryString(ENV_ADO_REPO_REVISION, out value)) return true; // Try GitHub Actions - if (EnvironmentHelper.Default.TryString(ENV_GITHUB_REPO_REVISION, out value)) + if (Environment.TryString(ENV_GITHUB_REPO_REVISION, out value)) return true; // Try .git/ @@ -118,15 +118,15 @@ public static bool TryRevision(out string value, string path = null) public static bool TryRepository(out string value, string path = null) { // Try PSRule - if (EnvironmentHelper.Default.TryString(ENV_PSRULE_REPO_URL, out value)) + if (Environment.TryString(ENV_PSRULE_REPO_URL, out value)) return true; // Try Azure Pipelines - if (EnvironmentHelper.Default.TryString(ENV_ADO_REPO_URL, out value)) + if (Environment.TryString(ENV_ADO_REPO_URL, out value)) return true; // Try GitHub Actions - if (EnvironmentHelper.Default.TryString(ENV_GITHUB_REPO_URL, out value)) + if (Environment.TryString(ENV_GITHUB_REPO_URL, out value)) { value = string.Concat(GITHUB_BASE_URL, value); return true; @@ -150,7 +150,7 @@ public static bool TryGetChangedFiles(string baseRef, string filter, string opti if (!tool.WaitForExit(args, out var exitCode) || exitCode != 0) return false; - files = tool.GetOutput().Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries); + files = tool.GetOutput().Split(new string[] { System.Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries); return true; } diff --git a/src/PSRule/Common/JsonCommentWriter.cs b/src/PSRule/Common/JsonCommentWriter.cs index 61c562fe25..fe6d6648e1 100644 --- a/src/PSRule/Common/JsonCommentWriter.cs +++ b/src/PSRule/Common/JsonCommentWriter.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.IO; using Newtonsoft.Json; @@ -18,12 +17,12 @@ public override void WriteComment(string text) if (Indentation > 0 && Formatting == Formatting.Indented) WriteIndent(); else - WriteRaw(Environment.NewLine); + WriteRaw(System.Environment.NewLine); WriteRaw("// "); WriteRaw(text); if (Indentation == 0 || Formatting == Formatting.None) - WriteRaw(Environment.NewLine); + WriteRaw(System.Environment.NewLine); } } } diff --git a/src/PSRule/Common/KeyMapDictionary.cs b/src/PSRule/Common/KeyMapDictionary.cs index c4c6ed58d4..cbea199dd9 100644 --- a/src/PSRule/Common/KeyMapDictionary.cs +++ b/src/PSRule/Common/KeyMapDictionary.cs @@ -177,12 +177,9 @@ protected void Load(Hashtable hashtable) /// Keys that appear in both will replaced by environment variable values. /// /// Is raised if the environment helper is null. - internal void Load(string prefix, EnvironmentHelper env, Func format = null) + internal void Load(string prefix, Func format = null) { - if (env == null) - throw new ArgumentNullException(nameof(env)); - - foreach (var variable in env.WithPrefix(prefix)) + foreach (var variable in Environment.GetByPrefix(prefix)) { if (TryKeyPrefix(variable.Key, prefix, out var suffix)) { diff --git a/src/PSRule/Common/LoggerExtensions.cs b/src/PSRule/Common/LoggerExtensions.cs index ba696bb70c..901882c529 100644 --- a/src/PSRule/Common/LoggerExtensions.cs +++ b/src/PSRule/Common/LoggerExtensions.cs @@ -2,7 +2,9 @@ // Licensed under the MIT License. using System; +using System.Threading; using PSRule.Definitions; +using PSRule.Pipeline; using PSRule.Resources; using PSRule.Runtime; @@ -17,5 +19,18 @@ internal static void WarnResourceObsolete(this ILogger logger, ResourceKind kind logger.Warning(PSRuleResources.ResourceObsolete, Enum.GetName(typeof(ResourceKind), kind), id); } + + internal static void ErrorResourceUnresolved(this ILogger logger, ResourceKind kind, string id) + { + if (logger == null || !logger.ShouldLog(LogLevel.Error)) + return; + + logger.Error(new PipelineBuilderException(string.Format( + Thread.CurrentThread.CurrentCulture, + PSRuleResources.PSR0004, + Enum.GetName(typeof(ResourceKind), + kind), id + )), "PSR0004"); + } } } diff --git a/src/PSRule/Common/YamlConverters.cs b/src/PSRule/Common/YamlConverters.cs index 1a8879b4c2..5b1452d71a 100644 --- a/src/PSRule/Common/YamlConverters.cs +++ b/src/PSRule/Common/YamlConverters.cs @@ -133,40 +133,6 @@ public void WriteYaml(IEmitter emitter, object value, Type type) } } - /// - /// A YAML converter that handles string to string array. - /// - internal sealed class StringArrayYamlTypeConverter : IYamlTypeConverter - { - public bool Accepts(Type type) - { - return type == typeof(string[]); - } - - public object ReadYaml(IParser parser, Type type) - { - if (parser.TryConsume(out _)) - { - var result = new List(); - while (parser.TryConsume(out var scalar)) - result.Add(scalar.Value); - - parser.Consume(); - return result.ToArray(); - } - else if (parser.TryConsume(out var scalar)) - { - return new string[] { scalar.Value }; - } - return null; - } - - public void WriteYaml(IEmitter emitter, object value, Type type) - { - throw new NotImplementedException(); - } - } - /// /// A YAML converter to deserialize a map/ object as a PSObject. /// diff --git a/src/PSRule/Configuration/BaselineOption.cs b/src/PSRule/Configuration/BaselineOption.cs index e55512957c..a48f3b0bfd 100644 --- a/src/PSRule/Configuration/BaselineOption.cs +++ b/src/PSRule/Configuration/BaselineOption.cs @@ -88,44 +88,44 @@ public static BaselineOption FromString(string value) return string.IsNullOrEmpty(value) ? null : new BaselineRef(value); } - internal static void Load(IBaselineV1Spec option, EnvironmentHelper env) + internal static void Load(IBaselineV1Spec option) { // Binding.Field - currently not supported - if (env.TryBool("PSRULE_BINDING_IGNORECASE", out var ignoreCase)) + if (Environment.TryBool("PSRULE_BINDING_IGNORECASE", out var ignoreCase)) option.Binding.IgnoreCase = ignoreCase; - if (env.TryString("PSRULE_BINDING_NAMESEPARATOR", out var nameSeparator)) + if (Environment.TryString("PSRULE_BINDING_NAMESEPARATOR", out var nameSeparator)) option.Binding.NameSeparator = nameSeparator; - if (env.TryBool("PSRULE_BINDING_PREFERTARGETINFO", out var preferTargetInfo)) + if (Environment.TryBool("PSRULE_BINDING_PREFERTARGETINFO", out var preferTargetInfo)) option.Binding.PreferTargetInfo = preferTargetInfo; - if (env.TryStringArray("PSRULE_BINDING_TARGETNAME", out var targetName)) + if (Environment.TryStringArray("PSRULE_BINDING_TARGETNAME", out var targetName)) option.Binding.TargetName = targetName; - if (env.TryStringArray("PSRULE_BINDING_TARGETTYPE", out var targetType)) + if (Environment.TryStringArray("PSRULE_BINDING_TARGETTYPE", out var targetType)) option.Binding.TargetType = targetType; - if (env.TryBool("PSRULE_BINDING_USEQUALIFIEDNAME", out var useQualifiedName)) + if (Environment.TryBool("PSRULE_BINDING_USEQUALIFIEDNAME", out var useQualifiedName)) option.Binding.UseQualifiedName = useQualifiedName; - if (env.TryString("PSRULE_RULE_BASELINE", out var baseline)) + if (Environment.TryString("PSRULE_RULE_BASELINE", out var baseline)) option.Rule.Baseline = baseline; - if (env.TryStringArray("PSRULE_RULE_EXCLUDE", out var exclude)) + if (Environment.TryStringArray("PSRULE_RULE_EXCLUDE", out var exclude)) option.Rule.Exclude = exclude; - if (env.TryBool("PSRULE_RULE_INCLUDELOCAL", out var includeLocal)) + if (Environment.TryBool("PSRULE_RULE_INCLUDELOCAL", out var includeLocal)) option.Rule.IncludeLocal = includeLocal; - if (env.TryStringArray("PSRULE_RULE_INCLUDE", out var include)) + if (Environment.TryStringArray("PSRULE_RULE_INCLUDE", out var include)) option.Rule.Include = include; // Rule.Tag - currently not supported // Process configuration values - option.Configuration.Load(env); + option.Configuration.Load(); } /// diff --git a/src/PSRule/Configuration/BindingOption.cs b/src/PSRule/Configuration/BindingOption.cs index 5f955b4941..638f500235 100644 --- a/src/PSRule/Configuration/BindingOption.cs +++ b/src/PSRule/Configuration/BindingOption.cs @@ -114,42 +114,63 @@ internal static BindingOption Combine(BindingOption o1, BindingOption o2) /// /// One or more custom fields to bind. /// + /// + /// See . + /// [DefaultValue(null)] public FieldMap Field { get; set; } /// /// Determines if custom binding uses ignores case when matching properties. /// + /// + /// See . + /// [DefaultValue(null)] public bool? IgnoreCase { get; set; } /// /// Configures the separator to use for building a qualified name. /// + /// + /// See . + /// [DefaultValue(null)] public string NameSeparator { get; set; } /// /// Determines if binding prefers target info provided by the object over custom configuration. /// + /// + /// See . + /// [DefaultValue(null)] public bool? PreferTargetInfo { get; set; } /// /// Property names to use to bind TargetName. /// + /// + /// See . + /// [DefaultValue(null)] public string[] TargetName { get; set; } /// /// Property names to use to bind TargetType. /// + /// + /// See . + /// [DefaultValue(null)] public string[] TargetType { get; set; } /// /// Determines if a qualified TargetName is used. /// + /// + /// See . + /// [DefaultValue(null)] public bool? UseQualifiedName { get; set; } } diff --git a/src/PSRule/Configuration/ConfigurationOption.cs b/src/PSRule/Configuration/ConfigurationOption.cs index 308cbd37ee..b9376773ca 100644 --- a/src/PSRule/Configuration/ConfigurationOption.cs +++ b/src/PSRule/Configuration/ConfigurationOption.cs @@ -57,9 +57,9 @@ internal static ConfigurationOption Combine(ConfigurationOption o1, Configuratio /// Load values from environment variables into the configuration option. /// Keys that appear in both will replaced by environment variable values. /// - internal void Load(EnvironmentHelper env) + internal void Load() { - Load(ENVIRONMENT_PREFIX, env); + Load(ENVIRONMENT_PREFIX); } /// diff --git a/src/PSRule/Configuration/ConventionOption.cs b/src/PSRule/Configuration/ConventionOption.cs index 12f6f741a6..fb2f48afc1 100644 --- a/src/PSRule/Configuration/ConventionOption.cs +++ b/src/PSRule/Configuration/ConventionOption.cs @@ -76,9 +76,9 @@ internal static ConventionOption Combine(ConventionOption o1, ConventionOption o [DefaultValue(null)] public string[] Include { get; set; } - internal void Load(EnvironmentHelper env) + internal void Load() { - if (env.TryStringArray("PSRULE_CONVENTION_INCLUDE", out var include)) + if (Environment.TryStringArray("PSRULE_CONVENTION_INCLUDE", out var include)) Include = include; } diff --git a/src/PSRule/Configuration/ExecutionOption.cs b/src/PSRule/Configuration/ExecutionOption.cs index 268016db95..0d6ba26fa0 100644 --- a/src/PSRule/Configuration/ExecutionOption.cs +++ b/src/PSRule/Configuration/ExecutionOption.cs @@ -330,53 +330,53 @@ internal static ExecutionOption Combine(ExecutionOption o1, ExecutionOption o2) /// /// Load from environment variables. /// - internal void Load(EnvironmentHelper env) + internal void Load() { #pragma warning disable CS0618 // Type or member is obsolete - if (env.TryBool("PSRULE_EXECUTION_ALIASREFERENCEWARNING", out var bvalue)) + if (Environment.TryBool("PSRULE_EXECUTION_ALIASREFERENCEWARNING", out var bvalue)) AliasReferenceWarning = bvalue; - if (env.TryEnum("PSRULE_EXECUTION_DUPLICATERESOURCEID", out ExecutionActionPreference duplicateResourceId)) + if (Environment.TryEnum("PSRULE_EXECUTION_DUPLICATERESOURCEID", out ExecutionActionPreference duplicateResourceId)) DuplicateResourceId = duplicateResourceId; - if (env.TryEnum("PSRULE_EXECUTION_LANGUAGEMODE", out LanguageMode languageMode)) + if (Environment.TryEnum("PSRULE_EXECUTION_LANGUAGEMODE", out LanguageMode languageMode)) LanguageMode = languageMode; - if (env.TryBool("PSRULE_EXECUTION_INCONCLUSIVEWARNING", out bvalue)) + if (Environment.TryBool("PSRULE_EXECUTION_INCONCLUSIVEWARNING", out bvalue)) InconclusiveWarning = bvalue; - if (env.TryBool("PSRULE_EXECUTION_INVARIANTCULTUREWARNING", out bvalue)) + if (Environment.TryBool("PSRULE_EXECUTION_INVARIANTCULTUREWARNING", out bvalue)) InvariantCultureWarning = bvalue; - if (env.TryEnum("PSRULE_EXECUTION_INITIALSESSIONSTATE", out SessionState initialSessionState)) + if (Environment.TryEnum("PSRULE_EXECUTION_INITIALSESSIONSTATE", out SessionState initialSessionState)) InitialSessionState = initialSessionState; - if (env.TryBool("PSRULE_EXECUTION_NOTPROCESSEDWARNING", out bvalue)) + if (Environment.TryBool("PSRULE_EXECUTION_NOTPROCESSEDWARNING", out bvalue)) NotProcessedWarning = bvalue; - if (env.TryBool("PSRULE_EXECUTION_SUPPRESSEDRULEWARNING", out bvalue)) + if (Environment.TryBool("PSRULE_EXECUTION_SUPPRESSEDRULEWARNING", out bvalue)) SuppressedRuleWarning = bvalue; #pragma warning restore CS0618 // Type or member is obsolete - if (env.TryEnum("PSRULE_EXECUTION_SUPPRESSIONGROUPEXPIRED", out ExecutionActionPreference suppressionGroupExpired)) + if (Environment.TryEnum("PSRULE_EXECUTION_SUPPRESSIONGROUPEXPIRED", out ExecutionActionPreference suppressionGroupExpired)) SuppressionGroupExpired = suppressionGroupExpired; - if (env.TryEnum("PSRULE_EXECUTION_RULEEXCLUDED", out ExecutionActionPreference ruleExcluded)) + if (Environment.TryEnum("PSRULE_EXECUTION_RULEEXCLUDED", out ExecutionActionPreference ruleExcluded)) RuleExcluded = ruleExcluded; - if (env.TryEnum("PSRULE_EXECUTION_RULESUPPRESSED", out ExecutionActionPreference ruleSuppressed)) + if (Environment.TryEnum("PSRULE_EXECUTION_RULESUPPRESSED", out ExecutionActionPreference ruleSuppressed)) RuleSuppressed = ruleSuppressed; - if (env.TryEnum("PSRULE_EXECUTION_ALIASREFERENCE", out ExecutionActionPreference aliasReference)) + if (Environment.TryEnum("PSRULE_EXECUTION_ALIASREFERENCE", out ExecutionActionPreference aliasReference)) AliasReference = aliasReference; - if (env.TryEnum("PSRULE_EXECUTION_RULEINCONCLUSIVE", out ExecutionActionPreference ruleInconclusive)) + if (Environment.TryEnum("PSRULE_EXECUTION_RULEINCONCLUSIVE", out ExecutionActionPreference ruleInconclusive)) RuleInconclusive = ruleInconclusive; - if (env.TryEnum("PSRULE_EXECUTION_INVARIANTCULTURE", out ExecutionActionPreference invariantCulture)) + if (Environment.TryEnum("PSRULE_EXECUTION_INVARIANTCULTURE", out ExecutionActionPreference invariantCulture)) InvariantCulture = invariantCulture; - if (env.TryEnum("PSRULE_EXECUTION_UNPROCESSEDOBJECT", out ExecutionActionPreference unprocessedObject)) + if (Environment.TryEnum("PSRULE_EXECUTION_UNPROCESSEDOBJECT", out ExecutionActionPreference unprocessedObject)) UnprocessedObject = unprocessedObject; } diff --git a/src/PSRule/Configuration/IncludeOption.cs b/src/PSRule/Configuration/IncludeOption.cs index 456bfb7991..2581fc1237 100644 --- a/src/PSRule/Configuration/IncludeOption.cs +++ b/src/PSRule/Configuration/IncludeOption.cs @@ -91,12 +91,12 @@ internal static IncludeOption Combine(IncludeOption o1, IncludeOption o2) [DefaultValue(null)] public string[] Module { get; set; } - internal void Load(EnvironmentHelper env) + internal void Load() { - if (env.TryStringArray("PSRULE_INCLUDE_PATH", out var path)) + if (Environment.TryStringArray("PSRULE_INCLUDE_PATH", out var path)) Path = path; - if (env.TryStringArray("PSRULE_INCLUDE_MODULE", out var module)) + if (Environment.TryStringArray("PSRULE_INCLUDE_MODULE", out var module)) Module = module; } diff --git a/src/PSRule/Configuration/InputOption.cs b/src/PSRule/Configuration/InputOption.cs index 9758aaab42..3508318bcc 100644 --- a/src/PSRule/Configuration/InputOption.cs +++ b/src/PSRule/Configuration/InputOption.cs @@ -173,30 +173,30 @@ internal static InputOption Combine(InputOption o1, InputOption o2) [DefaultValue(null)] public string[] TargetType { get; set; } - internal void Load(EnvironmentHelper env) + internal void Load() { - if (env.TryEnum("PSRULE_INPUT_FORMAT", out InputFormat format)) + if (Environment.TryEnum("PSRULE_INPUT_FORMAT", out InputFormat format)) Format = format; - if (env.TryBool("PSRULE_INPUT_IGNOREGITPATH", out var ignoreGitPath)) + if (Environment.TryBool("PSRULE_INPUT_IGNOREGITPATH", out var ignoreGitPath)) IgnoreGitPath = ignoreGitPath; - if (env.TryBool("PSRULE_INPUT_IGNOREOBJECTSOURCE", out var ignoreObjectSource)) + if (Environment.TryBool("PSRULE_INPUT_IGNOREOBJECTSOURCE", out var ignoreObjectSource)) IgnoreObjectSource = ignoreObjectSource; - if (env.TryBool("PSRULE_INPUT_IGNOREREPOSITORYCOMMON", out var ignoreRepositoryCommon)) + if (Environment.TryBool("PSRULE_INPUT_IGNOREREPOSITORYCOMMON", out var ignoreRepositoryCommon)) IgnoreRepositoryCommon = ignoreRepositoryCommon; - if (env.TryBool("PSRULE_INPUT_IGNOREUNCHANGEDPATH", out var ignoreUnchangedPath)) + if (Environment.TryBool("PSRULE_INPUT_IGNOREUNCHANGEDPATH", out var ignoreUnchangedPath)) IgnoreUnchangedPath = ignoreUnchangedPath; - if (env.TryString("PSRULE_INPUT_OBJECTPATH", out var objectPath)) + if (Environment.TryString("PSRULE_INPUT_OBJECTPATH", out var objectPath)) ObjectPath = objectPath; - if (env.TryStringArray("PSRULE_INPUT_PATHIGNORE", out var pathIgnore)) + if (Environment.TryStringArray("PSRULE_INPUT_PATHIGNORE", out var pathIgnore)) PathIgnore = pathIgnore; - if (env.TryStringArray("PSRULE_INPUT_TARGETTYPE", out var targetType)) + if (Environment.TryStringArray("PSRULE_INPUT_TARGETTYPE", out var targetType)) TargetType = targetType; } diff --git a/src/PSRule/Configuration/LoggingOption.cs b/src/PSRule/Configuration/LoggingOption.cs index 67281e8d65..b8ab77eb04 100644 --- a/src/PSRule/Configuration/LoggingOption.cs +++ b/src/PSRule/Configuration/LoggingOption.cs @@ -115,18 +115,18 @@ internal static LoggingOption Combine(LoggingOption o1, LoggingOption o2) [DefaultValue(null)] public OutcomeLogStream? RulePass { get; set; } - internal void Load(EnvironmentHelper env) + internal void Load() { - if (env.TryStringArray("PSRULE_LOGGING_LIMITDEBUG", out var limitDebug)) + if (Environment.TryStringArray("PSRULE_LOGGING_LIMITDEBUG", out var limitDebug)) LimitDebug = limitDebug; - if (env.TryStringArray("PSRULE_LOGGING_LIMITVERBOSE", out var limitVerbose)) + if (Environment.TryStringArray("PSRULE_LOGGING_LIMITVERBOSE", out var limitVerbose)) LimitVerbose = limitVerbose; - if (env.TryEnum("PSRULE_LOGGING_RULEFAIL", out OutcomeLogStream ruleFail)) + if (Environment.TryEnum("PSRULE_LOGGING_RULEFAIL", out OutcomeLogStream ruleFail)) RuleFail = ruleFail; - if (env.TryEnum("PSRULE_LOGGING_RULEPASS", out OutcomeLogStream rulePass)) + if (Environment.TryEnum("PSRULE_LOGGING_RULEPASS", out OutcomeLogStream rulePass)) RulePass = rulePass; } diff --git a/src/PSRule/Configuration/OutputOption.cs b/src/PSRule/Configuration/OutputOption.cs index 5cb94a05b6..980c065052 100644 --- a/src/PSRule/Configuration/OutputOption.cs +++ b/src/PSRule/Configuration/OutputOption.cs @@ -215,42 +215,42 @@ internal static OutputOption Combine(OutputOption o1, OutputOption o2) [DefaultValue(null)] public bool? SarifProblemsOnly { get; set; } - internal void Load(EnvironmentHelper env) + internal void Load() { - if (env.TryEnum("PSRULE_OUTPUT_AS", out ResultFormat value)) + if (Environment.TryEnum("PSRULE_OUTPUT_AS", out ResultFormat value)) As = value; - if (env.TryEnum("PSRULE_OUTPUT_BANNER", out BannerFormat banner)) + if (Environment.TryEnum("PSRULE_OUTPUT_BANNER", out BannerFormat banner)) Banner = banner; - if (env.TryStringArray("PSRULE_OUTPUT_CULTURE", out var culture)) + if (Environment.TryStringArray("PSRULE_OUTPUT_CULTURE", out var culture)) Culture = culture; - if (env.TryEnum("PSRULE_OUTPUT_ENCODING", out OutputEncoding encoding)) + if (Environment.TryEnum("PSRULE_OUTPUT_ENCODING", out OutputEncoding encoding)) Encoding = encoding; - if (env.TryEnum("PSRULE_OUTPUT_FOOTER", out FooterFormat footer)) + if (Environment.TryEnum("PSRULE_OUTPUT_FOOTER", out FooterFormat footer)) Footer = footer; - if (env.TryEnum("PSRULE_OUTPUT_FORMAT", out OutputFormat format)) + if (Environment.TryEnum("PSRULE_OUTPUT_FORMAT", out OutputFormat format)) Format = format; - if (env.TryString("PSRULE_OUTPUT_JOBSUMMARYPATH", out var jobSummaryPath)) + if (Environment.TryString("PSRULE_OUTPUT_JOBSUMMARYPATH", out var jobSummaryPath)) JobSummaryPath = jobSummaryPath; - if (env.TryInt("PSRULE_OUTPUT_JSONINDENT", out var jsonIndent)) + if (Environment.TryInt("PSRULE_OUTPUT_JSONINDENT", out var jsonIndent)) JsonIndent = jsonIndent; - if (env.TryEnum("PSRULE_OUTPUT_OUTCOME", out RuleOutcome outcome)) + if (Environment.TryEnum("PSRULE_OUTPUT_OUTCOME", out RuleOutcome outcome)) Outcome = outcome; - if (env.TryString("PSRULE_OUTPUT_PATH", out var path)) + if (Environment.TryString("PSRULE_OUTPUT_PATH", out var path)) Path = path; - if (env.TryEnum("PSRULE_OUTPUT_STYLE", out OutputStyle style)) + if (Environment.TryEnum("PSRULE_OUTPUT_STYLE", out OutputStyle style)) Style = style; - if (env.TryBool("PSRULE_OUTPUT_SARIFPROBLEMSONLY", out var sarifProblemsOnly)) + if (Environment.TryBool("PSRULE_OUTPUT_SARIFPROBLEMSONLY", out var sarifProblemsOnly)) SarifProblemsOnly = sarifProblemsOnly; } diff --git a/src/PSRule/Configuration/PSRuleOption.cs b/src/PSRule/Configuration/PSRuleOption.cs index bc01003651..3b470277b2 100644 --- a/src/PSRule/Configuration/PSRuleOption.cs +++ b/src/PSRule/Configuration/PSRuleOption.cs @@ -10,6 +10,7 @@ using System.Management.Automation; using System.Threading; using Newtonsoft.Json; +using PSRule.Converters.Yaml; using PSRule.Definitions.Baselines; using PSRule.Pipeline; using PSRule.Resources; @@ -44,6 +45,7 @@ public sealed class PSRuleOption : IEquatable, IBaselineV1Spec private static readonly PSRuleOption Default = new() { + Baseline = Options.BaselineOption.Default, Binding = BindingOption.Default, Convention = ConventionOption.Default, Execution = ExecutionOption.Default, @@ -70,6 +72,7 @@ public sealed class PSRuleOption : IEquatable, IBaselineV1Spec public PSRuleOption() { // Set defaults + Baseline = new Options.BaselineOption(); Binding = new BindingOption(); Configuration = new ConfigurationOption(); Convention = new ConventionOption(); @@ -90,6 +93,7 @@ private PSRuleOption(string sourcePath, PSRuleOption option) _SourcePath = sourcePath; // Set from existing option instance + Baseline = new Options.BaselineOption(option?.Baseline); Binding = new BindingOption(option?.Binding); Configuration = new ConfigurationOption(option?.Configuration); Convention = new ConventionOption(option?.Convention); @@ -105,6 +109,11 @@ private PSRuleOption(string sourcePath, PSRuleOption option) Suppression = new SuppressionOption(option?.Suppression); } + /// + /// Options that configure baselines. + /// + public Options.BaselineOption Baseline { get; set; } + /// /// Options that affect property binding. /// @@ -189,7 +198,7 @@ public string ToYaml() Thread.CurrentThread.CurrentCulture, PSRuleResources.OptionsSourceComment, _SourcePath), - Environment.NewLine, + System.Environment.NewLine, yaml); } @@ -219,6 +228,7 @@ public static PSRuleOption FromDefault() private static PSRuleOption Combine(PSRuleOption o1, PSRuleOption o2) { var result = new PSRuleOption(o1?._SourcePath ?? o2?._SourcePath, o1); + result.Baseline = Options.BaselineOption.Combine(result.Baseline, o2?.Baseline); result.Binding = BindingOption.Combine(result.Binding, o2?.Binding); result.Configuration = ConfigurationOption.Combine(result.Configuration, o2?.Configuration); result.Convention = ConventionOption.Combine(result.Convention, o2?.Convention); @@ -318,6 +328,7 @@ private static PSRuleOption FromYaml(string path, string yaml) .IgnoreUnmatchedProperties() .WithNamingConvention(CamelCaseNamingConvention.Instance) .WithTypeConverter(new FieldMapYamlTypeConverter()) + .WithTypeConverter(new StringArrayMapConverter()) .WithTypeConverter(new SuppressionRuleYamlTypeConverter()) .WithTypeConverter(new PSObjectYamlTypeConverter()) .WithNodeTypeResolver(new PSOptionYamlTypeResolver()) @@ -346,16 +357,16 @@ private static PSRuleOption FromEnvironment(PSRuleOption option) option ??= new PSRuleOption(); // Start loading matching values - var env = EnvironmentHelper.Default; - option.Convention.Load(env); - option.Execution.Load(env); - option.Include.Load(env); - option.Input.Load(env); - option.Logging.Load(env); - option.Output.Load(env); - option.Repository.Load(env); - option.Requires.Load(env); - BaselineOption.Load(option, env); + option.Baseline.Load(); + option.Convention.Load(); + option.Execution.Load(); + option.Include.Load(); + option.Input.Load(); + option.Logging.Load(); + option.Output.Load(); + option.Repository.Load(); + option.Requires.Load(); + BaselineOption.Load(option); return option; } @@ -375,6 +386,7 @@ public static PSRuleOption FromHashtable(Hashtable hashtable) // Start loading matching values var index = BuildIndex(hashtable); + option.Baseline.Load(index); option.Convention.Load(index); option.Execution.Load(index); option.Include.Load(index); @@ -493,6 +505,7 @@ public override bool Equals(object obj) public bool Equals(PSRuleOption other) { return other != null && + Baseline == other.Baseline && Binding == other.Binding && Configuration == other.Configuration && Convention == other.Convention && @@ -513,6 +526,7 @@ public override int GetHashCode() unchecked // Overflow is fine { var hash = 17; + hash = hash * 23 + (Baseline != null ? Baseline.GetHashCode() : 0); hash = hash * 23 + (Binding != null ? Binding.GetHashCode() : 0); hash = hash * 23 + (Configuration != null ? Configuration.GetHashCode() : 0); hash = hash * 23 + (Convention != null ? Convention.GetHashCode() : 0); @@ -637,6 +651,7 @@ private string GetYaml() var s = new SerializerBuilder() .WithNamingConvention(CamelCaseNamingConvention.Instance) .WithTypeConverter(new FieldMapYamlTypeConverter()) + .WithTypeConverter(new StringArrayMapConverter()) .Build(); return s.Serialize(this); } diff --git a/src/PSRule/Configuration/RepositoryOption.cs b/src/PSRule/Configuration/RepositoryOption.cs index c9a5d0d1c1..caf0ef35e8 100644 --- a/src/PSRule/Configuration/RepositoryOption.cs +++ b/src/PSRule/Configuration/RepositoryOption.cs @@ -96,12 +96,12 @@ internal static RepositoryOption Combine(RepositoryOption o1, RepositoryOption o /// Load options from environment variables into repository option. /// Options that appear in both will replaced by environment variable values. /// - internal void Load(EnvironmentHelper env) + internal void Load() { - if (env.TryString("PSRULE_REPOSITORY_BASEREF", out var baseRef)) + if (Environment.TryString("PSRULE_REPOSITORY_BASEREF", out var baseRef)) BaseRef = baseRef; - if (env.TryString("PSRULE_REPOSITORY_URL", out var url)) + if (Environment.TryString("PSRULE_REPOSITORY_URL", out var url)) Url = url; } diff --git a/src/PSRule/Configuration/RequiresOption.cs b/src/PSRule/Configuration/RequiresOption.cs index b26c46b7e3..6bae994c8f 100644 --- a/src/PSRule/Configuration/RequiresOption.cs +++ b/src/PSRule/Configuration/RequiresOption.cs @@ -48,9 +48,9 @@ public ModuleConstraint[] ToArray() /// /// Load Requires option from environment variables. /// - internal void Load(EnvironmentHelper env) + internal void Load() { - Load(ENVIRONMENT_PREFIX, env, ConvertUnderscore); + Load(ENVIRONMENT_PREFIX, ConvertUnderscore); } /// diff --git a/src/PSRule/Definitions/Expressions/LanguageExpressions.cs b/src/PSRule/Definitions/Expressions/LanguageExpressions.cs index 7dce3d47d8..3e1c6a999f 100644 --- a/src/PSRule/Definitions/Expressions/LanguageExpressions.cs +++ b/src/PSRule/Definitions/Expressions/LanguageExpressions.cs @@ -318,7 +318,7 @@ private LanguageExpressionOuterFn Operator(string path, LanguageOperator express /// /// Returns a quantifier function if set for the expression. /// - private Func GetQuantifier(LanguageOperator expression) + private static Func GetQuantifier(LanguageOperator expression) { if (expression.Property.TryGetLong(GREATEROREQUAL, out var q)) return (number) => number >= q.Value; @@ -339,7 +339,7 @@ private Func GetQuantifier(LanguageOperator expression) } [DebuggerStepThrough] - private string Value(ExpressionContext context, object v) + private static string Value(ExpressionContext context, object v) { return v as string; } diff --git a/src/PSRule/Definitions/Resource.cs b/src/PSRule/Definitions/Resource.cs index 6da6ed1953..6c7af13ba3 100644 --- a/src/PSRule/Definitions/Resource.cs +++ b/src/PSRule/Definitions/Resource.cs @@ -7,6 +7,7 @@ using System.Diagnostics; using System.IO; using System.Text; +using PSRule.Converters.Yaml; using PSRule.Definitions.Rules; using PSRule.Pipeline; using PSRule.Runtime; @@ -195,7 +196,8 @@ internal ResourceBuilder() .IgnoreUnmatchedProperties() .WithNamingConvention(CamelCaseNamingConvention.Instance) .WithTypeConverter(new FieldMapYamlTypeConverter()) - .WithTypeConverter(new StringArrayYamlTypeConverter()) + .WithTypeConverter(new StringArrayMapConverter()) + .WithTypeConverter(new StringArrayConverter()) .WithNodeDeserializer( inner => new ResourceNodeDeserializer(new LanguageExpressionDeserializer(inner)), s => s.InsteadOf()) @@ -380,7 +382,7 @@ public string ToViewString() foreach (var kv in this) { if (i > 0) - sb.Append(Environment.NewLine); + sb.Append(System.Environment.NewLine); sb.Append(kv.Key); sb.Append('='); diff --git a/src/PSRule/Help/HelpLexer.cs b/src/PSRule/Help/HelpLexer.cs index 98616cb609..3bfdd752c1 100644 --- a/src/PSRule/Help/HelpLexer.cs +++ b/src/PSRule/Help/HelpLexer.cs @@ -169,9 +169,9 @@ private static void AppendEnding(StringBuilder stringBuilder, MarkdownToken toke preserveEnding = true; if (token.IsDoubleLineEnding()) - stringBuilder.Append(preserveEnding ? string.Concat(Environment.NewLine, Environment.NewLine) : Environment.NewLine); + stringBuilder.Append(preserveEnding ? string.Concat(System.Environment.NewLine, System.Environment.NewLine) : System.Environment.NewLine); else if (token.IsSingleLineEnding()) - stringBuilder.Append(preserveEnding ? Environment.NewLine : Space); + stringBuilder.Append(preserveEnding ? System.Environment.NewLine : Space); } } } diff --git a/src/PSRule/Help/MarkdownStream.cs b/src/PSRule/Help/MarkdownStream.cs index b2f62f0813..ba52edcc89 100644 --- a/src/PSRule/Help/MarkdownStream.cs +++ b/src/PSRule/Help/MarkdownStream.cs @@ -470,7 +470,7 @@ public string CaptureUntil(CharacterMatchDelegate match, bool ignoreEscaping = f private string Substring(int start, int length, bool ignoreEscaping = false) { - var newLine = Environment.NewLine.ToCharArray(); + var newLine = System.Environment.NewLine.ToCharArray(); var position = start; var i = 0; diff --git a/src/PSRule/Host/Host.cs b/src/PSRule/Host/Host.cs index 5e54d9b242..2db27a6738 100644 --- a/src/PSRule/Host/Host.cs +++ b/src/PSRule/Host/Host.cs @@ -93,7 +93,7 @@ public TargetObjectVariable() } - public override object Value => RunspaceContext.CurrentThread.TargetObject.Value; + public override object Value => RunspaceContext.CurrentThread.TargetObject?.Value; } internal sealed class ConfigurationVariable : PSVariable @@ -169,7 +169,7 @@ public static InitialSessionState CreateSessionState(Configuration.SessionState private static void SetExecutionPolicy(InitialSessionState state, Microsoft.PowerShell.ExecutionPolicy executionPolicy) { // Only set execution policy on Windows - if (Environment.OSVersion.Platform == PlatformID.Win32NT) + if (System.Environment.OSVersion.Platform == PlatformID.Win32NT) state.ExecutionPolicy = executionPolicy; } } diff --git a/src/PSRule/Host/HostHelper.cs b/src/PSRule/Host/HostHelper.cs index f7ff1de213..ee1ebf6798 100644 --- a/src/PSRule/Host/HostHelper.cs +++ b/src/PSRule/Host/HostHelper.cs @@ -10,6 +10,7 @@ using System.Management.Automation; using Newtonsoft.Json; using PSRule.Annotations; +using PSRule.Converters.Yaml; using PSRule.Definitions; using PSRule.Definitions.Baselines; using PSRule.Definitions.Conventions; @@ -252,7 +253,8 @@ private static ILanguageBlock[] GetYamlLanguageBlocks(Source[] sources, Runspace .IgnoreUnmatchedProperties() .WithNamingConvention(CamelCaseNamingConvention.Instance) .WithTypeConverter(new FieldMapYamlTypeConverter()) - .WithTypeConverter(new StringArrayYamlTypeConverter()) + .WithTypeConverter(new StringArrayMapConverter()) + .WithTypeConverter(new Converters.Yaml.StringArrayConverter()) .WithTypeConverter(new PSObjectYamlTypeConverter()) .WithNodeTypeResolver(new PSOptionYamlTypeResolver()) .WithNodeDeserializer( diff --git a/src/PSRule/PSRule.psm1 b/src/PSRule/PSRule.psm1 index 55f844e86c..c4d35a9f6f 100644 --- a/src/PSRule/PSRule.psm1 +++ b/src/PSRule/PSRule.psm1 @@ -708,7 +708,7 @@ function Get-PSRule { # Check that some matching script files were found if ($Null -eq $sourceFiles) { - Write-Verbose -Message "[Get-PSRule] -- Could not find any .Rule.ps1 script files in the path"; + Write-Verbose -Message "[Get-PSRule] -- Could not find any .Rule.ps1 script files in the path."; return; # continue causes issues with Pester } @@ -737,8 +737,6 @@ function Get-PSRule { $builder.IncludeDependencies(); } - # $builder.UseCommandRuntime($PSCmdlet); - # $builder.UseExecutionContext($ExecutionContext); try { $pipeline = $builder.Build(); if ($Null -ne $pipeline) { @@ -828,7 +826,7 @@ function Get-PSRuleBaseline { # Check that some matching script files were found if ($Null -eq $sourceFiles) { - Write-Verbose -Message "[Get-PSRuleBaseline] -- Could not find any .Rule.ps1 script files in the path"; + Write-Verbose -Message "[Get-PSRuleBaseline] -- Could not find any .Rule.ps1 script files in the path."; return; # continue causes issues with Pester } @@ -935,7 +933,7 @@ function Export-PSRuleBaseline { # Check that some matching script files were found if ($Null -eq $sourceFiles) { - Write-Verbose -Message "[Export-PSRuleBaseline] -- Could not find any .Rule.ps1 script files in the path"; + Write-Verbose -Message "[Export-PSRuleBaseline] -- Could not find any .Rule.ps1 script files in the path."; return; # continue causes issues with Pester } @@ -1043,7 +1041,7 @@ function Get-PSRuleHelp { # Check that some matching script files were found if ($Null -eq $sourceFiles) { - Write-Verbose -Message "[Get-PSRuleHelp] -- Could not find any .Rule.ps1 script files in the path"; + Write-Verbose -Message "[Get-PSRuleHelp] -- Could not find any .Rule.ps1 script files in the path."; return; # continue causes issues with Pester } @@ -1129,6 +1127,10 @@ function New-PSRuleOption { # Options + # Sets the Baseline.Group option + [Parameter(Mandatory = $False)] + [Hashtable]$BaselineGroup, + # Sets the Binding.IgnoreCase option [Parameter(Mandatory = $False)] [System.Boolean]$BindingIgnoreCase = $True, @@ -1455,6 +1457,10 @@ function Set-PSRuleOption { # Options + # Sets the Baseline.Group option + [Parameter(Mandatory = $False)] + [Hashtable]$BaselineGroup, + # Sets the Binding.IgnoreCase option [Parameter(Mandatory = $False)] [System.Boolean]$BindingIgnoreCase = $True, @@ -2228,6 +2234,10 @@ function SetOptions { # Options + # Sets the Baseline.Group option + [Parameter(Mandatory = $False)] + [Hashtable]$BaselineGroup, + # Sets the Binding.IgnoreCase option [Parameter(Mandatory = $False)] [System.Boolean]$BindingIgnoreCase = $True, @@ -2459,6 +2469,11 @@ function SetOptions { process { # Options + # Sets option Baseline.Group + if ($PSBoundParameters.ContainsKey('BaselineGroup')) { + $Option.Baseline.Group = $BaselineGroup; + } + # Sets option Binding.IgnoreCase if ($PSBoundParameters.ContainsKey('BindingIgnoreCase')) { $Option.Binding.IgnoreCase = $BindingIgnoreCase; diff --git a/src/PSRule/Pipeline/ExportBaselinePipelineBuilder.cs b/src/PSRule/Pipeline/ExportBaselinePipelineBuilder.cs index 59a84db420..f4bd2e29d6 100644 --- a/src/PSRule/Pipeline/ExportBaselinePipelineBuilder.cs +++ b/src/PSRule/Pipeline/ExportBaselinePipelineBuilder.cs @@ -29,20 +29,19 @@ public override IPipelineBuilder Configure(PSRuleOption option) if (option == null) return this; + Option.Baseline = new Options.BaselineOption(option.Baseline); Option.Output.As = ResultFormat.Detail; Option.Output.Culture = GetCulture(option.Output.Culture); Option.Output.Format = option.Output.Format ?? OutputOption.Default.Format; Option.Output.Encoding = option.Output.Encoding ?? OutputOption.Default.Encoding; Option.Output.Path = option.Output.Path ?? OutputOption.Default.Path; Option.Output.JsonIndent = NormalizeJsonIndentRange(option.Output.JsonIndent); - return this; } public override IPipeline Build(IPipelineWriter writer = null) { var filter = new BaselineFilter(_Name); - return new GetBaselinePipeline( pipeline: PrepareContext( bindTargetName: null, @@ -56,4 +55,4 @@ public override IPipeline Build(IPipelineWriter writer = null) ); } } -} \ No newline at end of file +} diff --git a/src/PSRule/Pipeline/Formatters/AssertFormatter.cs b/src/PSRule/Pipeline/Formatters/AssertFormatter.cs index 5f515e08ba..9aa711c942 100644 --- a/src/PSRule/Pipeline/Formatters/AssertFormatter.cs +++ b/src/PSRule/Pipeline/Formatters/AssertFormatter.cs @@ -325,7 +325,7 @@ private void Banner() if (!Option.Output.Banner.GetValueOrDefault(BannerFormat.Default).HasFlag(BannerFormat.Title)) return; - WriteLine(FormatterStrings.Banner.Replace("\\n", Environment.NewLine)); + WriteLine(FormatterStrings.Banner.Replace("\\n", System.Environment.NewLine)); LineBreak(); } @@ -551,7 +551,7 @@ protected void WriteLines(string message, string prefix = null, ConsoleColor? fo if (string.IsNullOrEmpty(message)) return; - var lines = message.Split(new string[] { Environment.NewLine }, StringSplitOptions.None); + var lines = message.Split(new string[] { System.Environment.NewLine }, StringSplitOptions.None); for (var i = 0; i < lines.Length; i++) WriteLine(lines[i], prefix, forgroundColor); } diff --git a/src/PSRule/Pipeline/GetBaselinePipelineBuilder.cs b/src/PSRule/Pipeline/GetBaselinePipelineBuilder.cs index 2464b6830b..765b5e6fc7 100644 --- a/src/PSRule/Pipeline/GetBaselinePipelineBuilder.cs +++ b/src/PSRule/Pipeline/GetBaselinePipelineBuilder.cs @@ -29,18 +29,17 @@ public override IPipelineBuilder Configure(PSRuleOption option) if (option == null) return this; + Option.Baseline = new Options.BaselineOption(option.Baseline); Option.Output.As = ResultFormat.Detail; Option.Output.Culture = GetCulture(option.Output.Culture); Option.Output.Format = SuppressFormat(option.Output.Format); Option.Output.JsonIndent = NormalizeJsonIndentRange(option.Output.JsonIndent); - return this; } public override IPipeline Build(IPipelineWriter writer = null) { - var filter = new BaselineFilter(_Name); - + var filter = new BaselineFilter(ResolveBaselineGroup(_Name)); return new GetBaselinePipeline( pipeline: PrepareContext( bindTargetName: null, @@ -61,4 +60,4 @@ private static OutputFormat SuppressFormat(OutputFormat? format) format == OutputFormat.Json) ? OutputFormat.None : format.Value; } } -} \ No newline at end of file +} diff --git a/src/PSRule/Pipeline/GetRulePipelineBuiler.cs b/src/PSRule/Pipeline/GetRulePipelineBuilder.cs similarity index 97% rename from src/PSRule/Pipeline/GetRulePipelineBuiler.cs rename to src/PSRule/Pipeline/GetRulePipelineBuilder.cs index e3981aae8b..5ac72e6430 100644 --- a/src/PSRule/Pipeline/GetRulePipelineBuiler.cs +++ b/src/PSRule/Pipeline/GetRulePipelineBuilder.cs @@ -32,12 +32,12 @@ public override IPipelineBuilder Configure(PSRuleOption option) if (option == null) return this; + Option.Baseline = new Options.BaselineOption(option.Baseline); Option.Execution = GetExecutionOption(option.Execution); Option.Output.Culture = GetCulture(option.Output.Culture); Option.Output.Format = SuppressFormat(option.Output.Format); Option.Requires = new RequiresOption(option.Requires); Option.Output.JsonIndent = NormalizeJsonIndentRange(option.Output.JsonIndent); - if (option.Rule != null) Option.Rule = new RuleOption(option.Rule); diff --git a/src/PSRule/Pipeline/InvokePipelineBuilder.cs b/src/PSRule/Pipeline/InvokePipelineBuilder.cs index ef0bdc2569..be2cb2a9f0 100644 --- a/src/PSRule/Pipeline/InvokePipelineBuilder.cs +++ b/src/PSRule/Pipeline/InvokePipelineBuilder.cs @@ -71,7 +71,7 @@ public void ResultVariable(string variableName) public void UnblockPublisher(string publisher) { - if (Environment.OSVersion.Platform != PlatformID.Win32NT) + if (System.Environment.OSVersion.Platform != PlatformID.Win32NT) return; _TrustedPublishers ??= new List(); @@ -124,7 +124,7 @@ protected void Unblock(IPipelineWriter writer) { if (Source == null || Source.Length == 0 || _TrustedPublishers == null || _TrustedPublishers.Count == 0 || - Environment.OSVersion.Platform != PlatformID.Win32NT) + System.Environment.OSVersion.Platform != PlatformID.Win32NT) return; var files = new List(); diff --git a/src/PSRule/Pipeline/Output/CsvOutputWriter.cs b/src/PSRule/Pipeline/Output/CsvOutputWriter.cs index c04b49c03a..c14856d094 100644 --- a/src/PSRule/Pipeline/Output/CsvOutputWriter.cs +++ b/src/PSRule/Pipeline/Output/CsvOutputWriter.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Text; using PSRule.Configuration; using PSRule.Definitions; @@ -79,7 +78,7 @@ private void WriteHeader() _Builder.Append(ViewStrings.Synopsis); _Builder.Append(COMMA); _Builder.Append(ViewStrings.Recommendation); - _Builder.Append(Environment.NewLine); + _Builder.Append(System.Environment.NewLine); } private void VisitRecord(RuleRecord record) @@ -100,7 +99,7 @@ private void VisitRecord(RuleRecord record) WriteColumn(record.Info.Synopsis); _Builder.Append(COMMA); WriteColumn(record.Info.Recommendation); - _Builder.Append(Environment.NewLine); + _Builder.Append(System.Environment.NewLine); } private void VisitSummaryRecord(RuleSummaryRecord record) @@ -119,7 +118,7 @@ private void VisitSummaryRecord(RuleSummaryRecord record) WriteColumn(record.Info.Synopsis); _Builder.Append(COMMA); WriteColumn(record.Info.Recommendation); - _Builder.Append(Environment.NewLine); + _Builder.Append(System.Environment.NewLine); } private void WriteColumn(string value) diff --git a/src/PSRule/Pipeline/Output/NUnit3OutputWriter.cs b/src/PSRule/Pipeline/Output/NUnit3OutputWriter.cs index 63b2ec75e7..6b154a4333 100644 --- a/src/PSRule/Pipeline/Output/NUnit3OutputWriter.cs +++ b/src/PSRule/Pipeline/Output/NUnit3OutputWriter.cs @@ -56,14 +56,14 @@ protected override string Serialize(InvokeResult[] o) xml.WriteAttributeString("time", TimeSpan.FromMilliseconds(time).ToString()); xml.WriteStartElement("environment"); - xml.WriteAttributeString("user", Environment.UserName); - xml.WriteAttributeString("machine-name", Environment.MachineName); + xml.WriteAttributeString("user", System.Environment.UserName); + xml.WriteAttributeString("machine-name", System.Environment.MachineName); xml.WriteAttributeString("cwd", PSRuleOption.GetWorkingPath()); - xml.WriteAttributeString("user-domain", Environment.UserDomainName); - xml.WriteAttributeString("platform", Environment.OSVersion.Platform.ToString()); + xml.WriteAttributeString("user-domain", System.Environment.UserDomainName); + xml.WriteAttributeString("platform", System.Environment.OSVersion.Platform.ToString()); xml.WriteAttributeString("nunit-version", "2.5.8.0"); - xml.WriteAttributeString("os-version", Environment.OSVersion.Version.ToString()); - xml.WriteAttributeString("clr-version", Environment.Version.ToString()); + xml.WriteAttributeString("os-version", System.Environment.OSVersion.Version.ToString()); + xml.WriteAttributeString("clr-version", System.Environment.Version.ToString()); xml.WriteEndElement(); xml.WriteStartElement("culture-info"); diff --git a/src/PSRule/Pipeline/PipelineBuilder.cs b/src/PSRule/Pipeline/PipelineBuilder.cs index 672b0dca9e..248237aa4e 100644 --- a/src/PSRule/Pipeline/PipelineBuilder.cs +++ b/src/PSRule/Pipeline/PipelineBuilder.cs @@ -5,7 +5,6 @@ using System.Collections; using System.Collections.Generic; using System.Globalization; -using System.Linq; using System.Management.Automation; using System.Threading; using PSRule.Configuration; @@ -348,6 +347,7 @@ public virtual IPipelineBuilder Configure(PSRuleOption option) if (option == null) return this; + Option.Baseline = new Options.BaselineOption(option.Baseline); Option.Binding = new BindingOption(option.Binding); Option.Convention = new ConventionOption(option.Convention); Option.Execution = GetExecutionOption(option.Execution); @@ -441,16 +441,7 @@ protected PipelineContext PrepareContext(BindTargetMethod bindTargetName, BindTa { var unresolved = new List(); if (_Baseline is BaselineOption.BaselineRef baselineRef) - unresolved.Add(new BaselineRef(baselineRef.Name, OptionContext.ScopeType.Explicit)); - - for (var i = 0; Source != null && i < Source.Length; i++) - { - if (Source[i].Module != null && Source[i].Module.Baseline != null && !unresolved.Any(u => ResourceIdEqualityComparer.IdEquals(u.Id, Source[i].Module.Baseline))) - { - unresolved.Add(new BaselineRef(Source[i].Module.Baseline, OptionContext.ScopeType.Module)); - PrepareWriter().WarnModuleManifestBaseline(Source[i].Module.Name); - } - } + unresolved.Add(new BaselineRef(ResolveBaselineGroup(baselineRef.Name), OptionContext.ScopeType.Explicit)); return PipelineContext.New( option: Option, @@ -464,6 +455,30 @@ protected PipelineContext PrepareContext(BindTargetMethod bindTargetName, BindTa ); } + protected string[] ResolveBaselineGroup(string[] name) + { + for (var i = 0; name != null && i < name.Length; i++) + name[i] = ResolveBaselineGroup(name[i]); + + return name; + } + + protected string ResolveBaselineGroup(string name) + { + if (name == null || name.Length < 2 || !name.StartsWith("@") || + Option == null || Option.Baseline == null || Option.Baseline.Group == null || + Option.Baseline.Group.Count == 0) + return name; + + var key = name.Substring(1); + if (!Option.Baseline.Group.TryGetValue(key, out var baselines) || baselines.Length == 0) + throw new PipelineConfigurationException("Baseline.Group", string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.PSR0003, key)); + + var writer = PrepareWriter(); + writer.WriteVerbose($"Using baseline group '{key}': {baselines[0]}"); + return baselines[0]; + } + protected virtual PipelineReader PrepareReader() { return new PipelineReader(null, null, GetInputObjectSourceFilter()); @@ -676,13 +691,13 @@ protected static OutputStyle GetStyle(OutputStyle style) if (style != OutputStyle.Detect) return style; - if (EnvironmentHelper.Default.IsAzurePipelines()) + if (Environment.IsAzurePipelines()) return OutputStyle.AzurePipelines; - if (EnvironmentHelper.Default.IsGitHubActions()) + if (Environment.IsGitHubActions()) return OutputStyle.GitHubActions; - return EnvironmentHelper.Default.IsVisualStudioCode() ? + return Environment.IsVisualStudioCode() ? OutputStyle.VisualStudioCode : OutputStyle.Client; } diff --git a/src/PSRule/Pipeline/PipelineContext.cs b/src/PSRule/Pipeline/PipelineContext.cs index 68937bb7a6..ffa6412fc6 100644 --- a/src/PSRule/Pipeline/PipelineContext.cs +++ b/src/PSRule/Pipeline/PipelineContext.cs @@ -17,6 +17,7 @@ using PSRule.Definitions.Selectors; using PSRule.Definitions.SuppressionGroups; using PSRule.Host; +using PSRule.Resources; using PSRule.Runtime; using PSRule.Runtime.ObjectPath; @@ -89,7 +90,7 @@ private PipelineContext(PSRuleOption option, IHostContext hostContext, PipelineR Baseline = baseline; _Unresolved = unresolved ?? new List(); _TrackedIssues = new List(); - RunId = EnvironmentHelper.Default.GetRunId() ?? ObjectHashAlgorithm.GetDigest(Guid.NewGuid().ToByteArray()); + RunId = Environment.GetRunId() ?? ObjectHashAlgorithm.GetDigest(Guid.NewGuid().ToByteArray()); RunTime = Stopwatch.StartNew(); } @@ -171,7 +172,7 @@ internal void Import(RunspaceContext context, IResource resource) TrackIssue(resource); if (TryBaseline(resource, out var baseline) && TryBaselineRef(resource.Id, out var baselineRef)) { - _Unresolved.Remove(baselineRef); + RemoveBaselineRef(resource.Id); Baseline.Add(new OptionContext.BaselineScope(baselineRef.Type, baseline.BaselineId, resource.Source.Module, baseline.Spec, baseline.Obsolete)); } else if (resource.Kind == ResourceKind.Selector && resource is SelectorV1 selector) @@ -222,6 +223,15 @@ private bool TryBaselineRef(ResourceId resourceId, out BaselineRef baselineRef) return true; } + private void RemoveBaselineRef(ResourceId resourceId) + { + foreach (var r in _Unresolved.ToArray()) + { + if (ResourceIdEqualityComparer.IdEquals(r.Id, resourceId.Value)) + _Unresolved.Remove(r); + } + } + private static bool TryBaseline(IResource resource, out Baseline baseline) { baseline = null; @@ -249,10 +259,20 @@ private static bool TryModuleConfig(IResource resource, out ModuleConfigV1 modul internal void Begin(RunspaceContext runspaceContext) { + ReportUnresolved(runspaceContext); ReportIssue(runspaceContext); Baseline.CheckObsolete(runspaceContext); } + private void ReportUnresolved(RunspaceContext runspaceContext) + { + foreach (var unresolved in _Unresolved) + runspaceContext.ErrorResourceUnresolved(unresolved.Kind, unresolved.Id); + + if (_Unresolved.Count > 0) + throw new PipelineBuilderException(PSRuleResources.ErrorPipelineException); + } + /// /// Report any tracked issues. /// diff --git a/src/PSRule/Pipeline/PipelineWriterExtensions.cs b/src/PSRule/Pipeline/PipelineWriterExtensions.cs index 4b3dcdc327..96263c715e 100644 --- a/src/PSRule/Pipeline/PipelineWriterExtensions.cs +++ b/src/PSRule/Pipeline/PipelineWriterExtensions.cs @@ -40,14 +40,6 @@ internal static void WarnRulePathNotFound(this IPipelineWriter writer) writer.WriteWarning(PSRuleResources.RulePathNotFound); } - internal static void WarnModuleManifestBaseline(this IPipelineWriter writer, string moduleName) - { - if (writer == null || !writer.ShouldWriteWarning()) - return; - - writer.WriteWarning(PSRuleResources.ModuleManifestBaseline, moduleName); - } - internal static void WriteWarning(this IPipelineWriter writer, string message, params object[] args) { if (writer == null || !writer.ShouldWriteWarning() || string.IsNullOrEmpty(message)) diff --git a/src/PSRule/Pipeline/Source.cs b/src/PSRule/Pipeline/Source.cs index 2be5ec15d9..ac7d809f6c 100644 --- a/src/PSRule/Pipeline/Source.cs +++ b/src/PSRule/Pipeline/Source.cs @@ -140,7 +140,6 @@ internal sealed class ModuleInfo public readonly string Path; public readonly string Name; - public readonly string Baseline; public readonly string Version; public readonly string ProjectUri; public readonly string Guid; diff --git a/src/PSRule/Pipeline/SourcePipeline.cs b/src/PSRule/Pipeline/SourcePipeline.cs index 3b2c8abdc0..0495322996 100644 --- a/src/PSRule/Pipeline/SourcePipeline.cs +++ b/src/PSRule/Pipeline/SourcePipeline.cs @@ -258,7 +258,7 @@ private bool TryPackagedModule(string name, out string path) private bool TryInstalledModule(string name, out string path) { path = null; - if (!EnvironmentHelper.Default.TryPathEnvironmentVariable("PSModulePath", out var searchPaths)) + if (!Environment.TryPathEnvironmentVariable("PSModulePath", out var searchPaths)) return false; var unsorted = new List(); diff --git a/src/PSRule/Resources/PSRuleResources.Designer.cs b/src/PSRule/Resources/PSRuleResources.Designer.cs index 48f47895db..f31ce2c785 100644 --- a/src/PSRule/Resources/PSRuleResources.Designer.cs +++ b/src/PSRule/Resources/PSRuleResources.Designer.cs @@ -429,15 +429,6 @@ internal static string MatchTrue { } } - /// - /// Looks up a localized string similar to Update module '{0}' to set the default baseline using a module configuration resource instead. Configuring the default baseline via manifest will be removed in the next major version. See https://aka.ms/ps-rule/module-config.. - /// - internal static string ModuleManifestBaseline { - get { - return ResourceManager.GetString("ModuleManifestBaseline", resourceCulture); - } - } - /// /// Looks up a localized string similar to No valid module can be found with that name.. /// @@ -582,6 +573,24 @@ internal static string PSR0002 { } } + /// + /// Looks up a localized string similar to PSR0003: The specified baseline group '{0}' is not known.. + /// + internal static string PSR0003 { + get { + return ResourceManager.GetString("PSR0003", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to PSR0004: The specified {0} resource '{1}' is not known.. + /// + internal static string PSR0004 { + get { + return ResourceManager.GetString("PSR0004", resourceCulture); + } + } + /// /// Looks up a localized string similar to Failed to deserialize the file '{0}': {1}. /// diff --git a/src/PSRule/Resources/PSRuleResources.resx b/src/PSRule/Resources/PSRuleResources.resx index 55a97d3820..78e1c273fd 100644 --- a/src/PSRule/Resources/PSRuleResources.resx +++ b/src/PSRule/Resources/PSRuleResources.resx @@ -226,9 +226,6 @@ Matches: {0} - - Update module '{0}' to set the default baseline using a module configuration resource instead. Configuring the default baseline via manifest will be removed in the next major version. See https://aka.ms/ps-rule/module-config. - Target object '{0}' has not been processed because no matching rules were found. @@ -412,4 +409,10 @@ Failed to read the path '{0}': {1} + + PSR0003: The specified baseline group '{0}' is not known. + + + PSR0004: The specified {0} resource '{1}' is not known. + \ No newline at end of file diff --git a/src/PSRule/Rules/RuleHelpInfo.cs b/src/PSRule/Rules/RuleHelpInfo.cs index d238b35bf2..9b86a6c049 100644 --- a/src/PSRule/Rules/RuleHelpInfo.cs +++ b/src/PSRule/Rules/RuleHelpInfo.cs @@ -217,7 +217,7 @@ public string GetLinkString() sb.Append(": "); sb.Append(Links[i].Uri); } - sb.Append(Environment.NewLine); + sb.Append(System.Environment.NewLine); } return sb.ToString(); } diff --git a/src/PSRule/Rules/RuleRecord.cs b/src/PSRule/Rules/RuleRecord.cs index 5a47c23c6d..60293eb1ed 100644 --- a/src/PSRule/Rules/RuleRecord.cs +++ b/src/PSRule/Rules/RuleRecord.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Collections; using System.ComponentModel; using System.Diagnostics; @@ -214,7 +213,7 @@ public bool IsProcessed() /// public string GetReasonViewString() { - return Reason == null || Reason.Length == 0 ? string.Empty : string.Join(Environment.NewLine, Reason); + return Reason == null || Reason.Length == 0 ? string.Empty : string.Join(System.Environment.NewLine, Reason); } internal bool HasSource() diff --git a/src/PSRule/Runtime/ILogger.cs b/src/PSRule/Runtime/ILogger.cs index b1f99e98d5..ed41b4bc8e 100644 --- a/src/PSRule/Runtime/ILogger.cs +++ b/src/PSRule/Runtime/ILogger.cs @@ -42,5 +42,12 @@ internal interface ILogger /// The warning message write. /// Any arguments to format the string with. void Warning(string message, params object[] args); + + /// + /// Write an error from an exception. + /// + /// The exception to write. + /// A string identififer for the error. + void Error(Exception exception, string errorId = null); } } diff --git a/src/PSRule/Runtime/RunspaceContext.cs b/src/PSRule/Runtime/RunspaceContext.cs index 8a3302013e..9f7560eb1f 100644 --- a/src/PSRule/Runtime/RunspaceContext.cs +++ b/src/PSRule/Runtime/RunspaceContext.cs @@ -527,7 +527,7 @@ private string GetStackTrace(ErrorRecord record) ? record.ScriptStackTrace : string.Concat( record.ScriptStackTrace, - Environment.NewLine, + System.Environment.NewLine, string.Format( Thread.CurrentThread.CurrentCulture, PSRuleResources.RuleStackTrace, @@ -578,7 +578,7 @@ private static int GetPositionMessageOffset(string positionMessage) if (string.IsNullOrEmpty(positionMessage)) return 0; - var lines = positionMessage.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries); + var lines = positionMessage.Split(new string[] { System.Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries); return lines.Length != 3 ? 0 : lines[2].LastIndexOf('~') - 1; } @@ -931,6 +931,15 @@ public void Warning(string message, params object[] args) Writer.WriteWarning(message, args); } + /// + public void Error(Exception exception, string errorId = null) + { + if (Writer == null || exception == null) + return; + + Writer.WriteError(new ErrorRecord(exception, errorId, ErrorCategory.InvalidOperation, null)); + } + #endregion ILogger #region IDisposable diff --git a/tests/PSRule.Tests/OutputWriterTests.cs b/tests/PSRule.Tests/OutputWriterTests.cs index 570d5e1365..990df8ff65 100644 --- a/tests/PSRule.Tests/OutputWriterTests.cs +++ b/tests/PSRule.Tests/OutputWriterTests.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Collections; using System.IO; using System.Linq; @@ -302,7 +301,7 @@ public void NUnit3() var declaration = doc.ChildNodes.Item(0) as XmlDeclaration; Assert.Equal("utf-8", declaration.Encoding); - var xml = doc["test-results"]["test-suite"].OuterXml.Replace(Environment.NewLine, "\r\n"); + var xml = doc["test-results"]["test-suite"].OuterXml.Replace(System.Environment.NewLine, "\r\n"); Assert.Equal("", xml); } @@ -325,7 +324,7 @@ public void JobSummary() stream.Seek(0, SeekOrigin.Begin); using var reader = new StreamReader(stream); - var s = reader.ReadToEnd().Replace(Environment.NewLine, "\r\n"); + var s = reader.ReadToEnd().Replace(System.Environment.NewLine, "\r\n"); Assert.Equal($"# PSRule result summary\r\n\r\n❌ PSRule completed with an overall result of 'Fail' with 3 rule(s) and 1 target(s) in {context.RunTime.Elapsed}.\r\n\r\n## Analysis\r\n\r\nThe following results were reported with fail or error results.\r\n\r\nName | Target name | Synopsis\r\n---- | ----------- | --------\r\nrule-002 | TestObject1 | This is rule 002.\r\nRule-003 | TestObject1 | This is rule 002.\r\n", s); } diff --git a/tests/PSRule.Tests/PSRule.Baseline.Tests.ps1 b/tests/PSRule.Tests/PSRule.Baseline.Tests.ps1 index b67ad6b8f6..70c78cd174 100644 --- a/tests/PSRule.Tests/PSRule.Baseline.Tests.ps1 +++ b/tests/PSRule.Tests/PSRule.Baseline.Tests.ps1 @@ -380,6 +380,16 @@ Describe 'Get-PSRuleBaseline' -Tag 'Baseline','Get-PSRuleBaseline' { $result | Should -Not -BeNullOrEmpty; $result.Length | Should -Be 1; $result[0].Name | Should -Be 'TestBaseline1'; + + # Use a baseline group by name + $result = @(Get-PSRuleBaseline -Path $baselineFilePath -Name '@latest' -Option @{ + 'Baseline.Group' = @{ + latest = 'TestBaseline1' + } + }); + $result | Should -Not -BeNullOrEmpty; + $result.Length | Should -Be 1; + $result[0].Name | Should -Be 'TestBaseline1'; } It 'With -Module' { @@ -696,6 +706,20 @@ Describe 'Baseline' -Tag 'Baseline' { $result[0].TargetName | Should -Be 'TestObject1'; $result[0].TargetType | Should -Be 'TestObjectType'; $result[0].Field.kind | Should -Be 'TestObjectType'; + + # Use a baseline group by name + $result = @($testObject | Invoke-PSRule -Path $ruleFilePath,$baselineFilePath -Baseline '@latest' -Option @{ + 'Baseline.Group' = @{ + latest = 'TestBaseline1' + } + }); + $result | Should -Not -BeNullOrEmpty; + $result.Length | Should -Be 1; + $result[0].RuleName | Should -Be 'WithBaseline'; + $result[0].Outcome | Should -Be 'Pass'; + $result[0].TargetName | Should -Be 'TestObject1'; + $result[0].TargetType | Should -Be 'TestObjectType'; + $result[0].Field.kind | Should -Be 'TestObjectType'; } It 'With -Module' { diff --git a/tests/PSRule.Tests/PSRule.Options.Tests.ps1 b/tests/PSRule.Tests/PSRule.Options.Tests.ps1 index 17452206e1..0ba4396a01 100644 --- a/tests/PSRule.Tests/PSRule.Options.Tests.ps1 +++ b/tests/PSRule.Tests/PSRule.Options.Tests.ps1 @@ -255,6 +255,47 @@ Describe 'New-PSRuleOption' -Tag 'Option','New-PSRuleOption' { } } + Context 'Read Baseline.Group' { + It 'from default' { + $option = New-PSRuleOption -Default; + $option.Baseline.Group | Should -BeNullOrEmpty; + } + + It 'from Hashtable' { + $option = New-PSRuleOption -Option @{ 'Baseline.Group' = @{ 'test' = 'Test123' } }; + $option.Baseline.Group.Count | Should -Be 1; + $option.Baseline.Group['test'] | Should -Not -BeNullOrEmpty; + $option.Baseline.Group['test'][0] | Should -Be 'Test123'; + } + + It 'from YAML' { + $option = New-PSRuleOption -Option (Join-Path -Path $here -ChildPath 'PSRule.Tests.yml'); + $option.Baseline.Group.Count | Should -Be 1; + $option.Baseline.Group['latest'] | Should -Not -BeNullOrEmpty; + $option.Baseline.Group['latest'][0] | Should -Be '.\TestBaseline1'; + } + + It 'from Environment' { + try { + $Env:PSRULE_BASELINE_GROUP = 'latest=YourBaseline'; + $option = New-PSRuleOption; + $option.Baseline.Group.Count | Should -Be 1; + $option.Baseline.Group['latest'] | Should -Not -BeNullOrEmpty; + $option.Baseline.Group['latest'][0] | Should -Be 'YourBaseline'; + } + finally { + Remove-Item 'Env:PSRULE_BASELINE_GROUP' -Force; + } + } + + It 'from parameter' { + $option = New-PSRuleOption -BaselineGroup @{ test = 'Test123' } -Path $emptyOptionsFilePath; + $option.Baseline.Group.Count | Should -Be 1; + $option.Baseline.Group['test'] | Should -Not -BeNullOrEmpty; + $option.Baseline.Group['test'][0] | Should -Be 'Test123'; + } + } + Context 'Read Binding.Field' { It 'from default' { $option = New-PSRuleOption -Default; diff --git a/tests/PSRule.Tests/PSRule.Tests.csproj b/tests/PSRule.Tests/PSRule.Tests.csproj index 327ed15505..39f19b3f75 100644 --- a/tests/PSRule.Tests/PSRule.Tests.csproj +++ b/tests/PSRule.Tests/PSRule.Tests.csproj @@ -63,6 +63,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest diff --git a/tests/PSRule.Tests/PSRule.Tests.yml b/tests/PSRule.Tests/PSRule.Tests.yml index 16a83ec8e3..543ad61d1a 100644 --- a/tests/PSRule.Tests/PSRule.Tests.yml +++ b/tests/PSRule.Tests/PSRule.Tests.yml @@ -4,6 +4,10 @@ repository: url: 'https://github.com/microsoft/PSRule.UnitTest' baseRef: dev +baseline: + group: + latest: .\TestBaseline1 + # Configure baseline rule: include: diff --git a/tests/PSRule.Tests/PSRuleOptionTests.cs b/tests/PSRule.Tests/PSRuleOptionTests.cs index 5c6ba4d2c5..f3ff57b7cd 100644 --- a/tests/PSRule.Tests/PSRuleOptionTests.cs +++ b/tests/PSRule.Tests/PSRuleOptionTests.cs @@ -74,6 +74,17 @@ public void GetObjectArrayFromYaml() Assert.Equal("East US", pso.PropertyValue("location")); } + [Fact] + public void GetBaselineGroupFromYaml() + { + var option = GetOption(); + var actual = option.Baseline.Group; + Assert.NotNull(actual); + Assert.Single(actual); + Assert.True(actual.TryGetValue("latest", out var latest)); + Assert.Equal(new string[] { ".\\TestBaseline1" }, latest); + } + #region Helper methods private static Runtime.Configuration GetConfigurationHelper(PSRuleOption option) @@ -94,12 +105,7 @@ private static Source[] GetSource() private static PSRuleOption GetOption() { - var loaded = PSRuleOption.FromFile(GetSourcePath("PSRule.Tests.yml")); - var option = new PSRuleOption - { - Configuration = loaded.Configuration - }; - return option; + return PSRuleOption.FromFile(GetSourcePath("PSRule.Tests.yml")); } private static string GetSourcePath(string fileName) diff --git a/tests/PSRule.Tests/PipelineTests.cs b/tests/PSRule.Tests/PipelineTests.cs index 5f9b385a74..c7bb4e0d1a 100644 --- a/tests/PSRule.Tests/PipelineTests.cs +++ b/tests/PSRule.Tests/PipelineTests.cs @@ -150,6 +150,31 @@ public void BuildGetPipeline() Assert.NotNull(builder.Build()); } + [Fact] + public void GetRuleWithBaseline() + { + var option = PSRuleOption.FromDefault(); + option.Baseline.Group = new Data.StringArrayMap(); + option.Baseline.Group["test"] = new string[] { ".\\TestBaseline1" }; + option.Execution.InvariantCulture = ExecutionActionPreference.Ignore; + Assert.Single(option.Baseline.Group); + + var builder = PipelineBuilder.Get(GetSource(new string[] + { + "Baseline.Rule.yaml", + "FromFileBaseline.Rule.ps1" + }), option, null); + builder.Baseline(BaselineOption.FromString("@test")); + var writer = new TestWriter(option); + var pipeline = builder.Build(writer); + + pipeline.Begin(); + pipeline.Process(null); + pipeline.End(); + + Assert.Single(writer.Output); + } + [Fact] public void PipelineWithInvariantCulture() { @@ -309,10 +334,13 @@ public void PipelineWithFileFormat() #region Helper methods - private static Source[] GetSource() + private static Source[] GetSource(string[] files = null) { var builder = new SourcePipelineBuilder(null, null); builder.Directory(GetSourcePath("FromFile.Rule.ps1")); + for (var i = 0; files != null && i < files.Length; i++) + builder.Directory(GetSourcePath(files[i])); + return builder.Build(); } diff --git a/tests/PSRule.Tests/SelectorTests.cs b/tests/PSRule.Tests/SelectorTests.cs index 2282da2529..25fd0cb04c 100644 --- a/tests/PSRule.Tests/SelectorTests.cs +++ b/tests/PSRule.Tests/SelectorTests.cs @@ -1673,7 +1673,7 @@ public void AllOf(string type, string path) GetObject((name: "name", value: "log1")), GetObject((name: "name", value: "log2")) })))); - actual3 = GetObject((name: "Name", value: "TargetObject1"), (name: "properties", value: GetObject((name: "logs", value: new object[] { })))); + actual3 = GetObject((name: "Name", value: "TargetObject1"), (name: "properties", value: GetObject((name: "logs", value: Array.Empty())))); Assert.True(allOf.Match(actual1)); Assert.True(allOf.Match(actual2)); From cf3574978a9ab68ac55f2d806360425a363cf757 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 4 Jun 2023 14:00:18 +1000 Subject: [PATCH 023/177] Bump Microsoft.CodeAnalysis.Common from 4.5.0 to 4.6.0 (#1534) * Bump Microsoft.CodeAnalysis.Common from 4.5.0 to 4.6.0 Bumps [Microsoft.CodeAnalysis.Common](https://github.com/dotnet/roslyn) from 4.5.0 to 4.6.0. - [Release notes](https://github.com/dotnet/roslyn/releases) - [Changelog](https://github.com/dotnet/roslyn/blob/main/docs/Breaking%20API%20Changes.md) - [Commits](https://github.com/dotnet/roslyn/commits) --- updated-dependencies: - dependency-name: Microsoft.CodeAnalysis.Common dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * Bump change log --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Bernie White --- docs/CHANGELOG-v2.md | 2 ++ src/PSRule.BuildTask/PSRule.BuildTask.csproj | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/CHANGELOG-v2.md b/docs/CHANGELOG-v2.md index 8fd6d44888..2ae4797f9b 100644 --- a/docs/CHANGELOG-v2.md +++ b/docs/CHANGELOG-v2.md @@ -47,6 +47,8 @@ What's changed since pre-release v2.9.0-B0033: - Engineering: - Bump Microsoft.NET.Test.Sdk to v17.6.1. [#1540](https://github.com/microsoft/PSRule/pull/1540) + - Bump Microsoft.CodeAnalysis.Common to v4.6.0. + [#1534](https://github.com/microsoft/PSRule/pull/1534) ## v2.9.0-B0033 (pre-release) diff --git a/src/PSRule.BuildTask/PSRule.BuildTask.csproj b/src/PSRule.BuildTask/PSRule.BuildTask.csproj index 274da4185d..0883dab190 100644 --- a/src/PSRule.BuildTask/PSRule.BuildTask.csproj +++ b/src/PSRule.BuildTask/PSRule.BuildTask.csproj @@ -7,7 +7,7 @@ - + - + all diff --git a/tests/PSRule.Tests/PSRuleOptionTests.cs b/tests/PSRule.Tests/PSRuleOptionTests.cs index f3ff57b7cd..caed4320b4 100644 --- a/tests/PSRule.Tests/PSRuleOptionTests.cs +++ b/tests/PSRule.Tests/PSRuleOptionTests.cs @@ -6,7 +6,6 @@ using System.Management.Automation; using PSRule.Configuration; using PSRule.Pipeline; -using Xunit; namespace PSRule { diff --git a/tests/PSRule.Tests/PathExpressionTests.cs b/tests/PSRule.Tests/PathExpressionTests.cs index 1cd3267374..7cab6a5dcb 100644 --- a/tests/PSRule.Tests/PathExpressionTests.cs +++ b/tests/PSRule.Tests/PathExpressionTests.cs @@ -7,7 +7,6 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; using PSRule.Runtime.ObjectPath; -using Xunit; namespace PSRule { diff --git a/tests/PSRule.Tests/PathFilterTests.cs b/tests/PSRule.Tests/PathFilterTests.cs index ce41a8bc1e..3d22a10f8b 100644 --- a/tests/PSRule.Tests/PathFilterTests.cs +++ b/tests/PSRule.Tests/PathFilterTests.cs @@ -3,7 +3,6 @@ using System; using PSRule.Pipeline; -using Xunit; namespace PSRule { diff --git a/tests/PSRule.Tests/PathTokenizerTests.cs b/tests/PSRule.Tests/PathTokenizerTests.cs index f8372175a2..33366ebf75 100644 --- a/tests/PSRule.Tests/PathTokenizerTests.cs +++ b/tests/PSRule.Tests/PathTokenizerTests.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. using PSRule.Runtime.ObjectPath; -using Xunit; namespace PSRule { diff --git a/tests/PSRule.Tests/PipelineTests.cs b/tests/PSRule.Tests/PipelineTests.cs index c7bb4e0d1a..cb6e39d5c4 100644 --- a/tests/PSRule.Tests/PipelineTests.cs +++ b/tests/PSRule.Tests/PipelineTests.cs @@ -12,7 +12,6 @@ using PSRule.Pipeline; using PSRule.Resources; using PSRule.Rules; -using Xunit; namespace PSRule { diff --git a/tests/PSRule.Tests/ResourceHelperTests.cs b/tests/PSRule.Tests/ResourceHelperTests.cs index 50d123bdd4..b65711e6ab 100644 --- a/tests/PSRule.Tests/ResourceHelperTests.cs +++ b/tests/PSRule.Tests/ResourceHelperTests.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. using PSRule.Definitions; -using Xunit; namespace PSRule { diff --git a/tests/PSRule.Tests/ResourceValidatorTests.cs b/tests/PSRule.Tests/ResourceValidatorTests.cs index cdba2b96ed..7a1911adc5 100644 --- a/tests/PSRule.Tests/ResourceValidatorTests.cs +++ b/tests/PSRule.Tests/ResourceValidatorTests.cs @@ -8,7 +8,6 @@ using PSRule.Host; using PSRule.Pipeline; using PSRule.Runtime; -using Xunit; using Assert = Xunit.Assert; namespace PSRule diff --git a/tests/PSRule.Tests/RuleDocumentTests.cs b/tests/PSRule.Tests/RuleDocumentTests.cs index 81a9e4d216..17d3bbd02e 100644 --- a/tests/PSRule.Tests/RuleDocumentTests.cs +++ b/tests/PSRule.Tests/RuleDocumentTests.cs @@ -7,7 +7,6 @@ using System.IO; using PSRule.Definitions; using PSRule.Help; -using Xunit; namespace PSRule { diff --git a/tests/PSRule.Tests/RuleFilterTests.cs b/tests/PSRule.Tests/RuleFilterTests.cs index a93547cbdb..a9b17794a1 100644 --- a/tests/PSRule.Tests/RuleFilterTests.cs +++ b/tests/PSRule.Tests/RuleFilterTests.cs @@ -4,7 +4,6 @@ using System.Collections; using PSRule.Definitions; using PSRule.Definitions.Rules; -using Xunit; namespace PSRule { diff --git a/tests/PSRule.Tests/RuleLanguageAstTests.cs b/tests/PSRule.Tests/RuleLanguageAstTests.cs index 05158c66c1..0ca2862fd3 100644 --- a/tests/PSRule.Tests/RuleLanguageAstTests.cs +++ b/tests/PSRule.Tests/RuleLanguageAstTests.cs @@ -4,7 +4,6 @@ using System; using System.IO; using PSRule.Host; -using Xunit; namespace PSRule { diff --git a/tests/PSRule.Tests/RulesTests.cs b/tests/PSRule.Tests/RulesTests.cs index 5102999f01..94f3eab95b 100644 --- a/tests/PSRule.Tests/RulesTests.cs +++ b/tests/PSRule.Tests/RulesTests.cs @@ -11,7 +11,6 @@ using PSRule.Pipeline; using PSRule.Rules; using PSRule.Runtime; -using Xunit; using Assert = Xunit.Assert; namespace PSRule diff --git a/tests/PSRule.Tests/SelectorTests.cs b/tests/PSRule.Tests/SelectorTests.cs index 25fd0cb04c..8a540668f7 100644 --- a/tests/PSRule.Tests/SelectorTests.cs +++ b/tests/PSRule.Tests/SelectorTests.cs @@ -11,7 +11,6 @@ using PSRule.Host; using PSRule.Pipeline; using PSRule.Runtime; -using Xunit; using Assert = Xunit.Assert; namespace PSRule diff --git a/tests/PSRule.Tests/SemanticBreakTests.cs b/tests/PSRule.Tests/SemanticBreakTests.cs index 9f6c758561..b3154b21b4 100644 --- a/tests/PSRule.Tests/SemanticBreakTests.cs +++ b/tests/PSRule.Tests/SemanticBreakTests.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using Xunit; - namespace PSRule { public sealed class SemanticBreakTests diff --git a/tests/PSRule.Tests/SemanticVersionTests.cs b/tests/PSRule.Tests/SemanticVersionTests.cs index 6e8efea1c3..50d5a138c5 100644 --- a/tests/PSRule.Tests/SemanticVersionTests.cs +++ b/tests/PSRule.Tests/SemanticVersionTests.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. using PSRule.Data; -using Xunit; namespace PSRule { diff --git a/tests/PSRule.Tests/StringExtensionsTests.cs b/tests/PSRule.Tests/StringExtensionsTests.cs index b3bc833010..5bf37c32e6 100644 --- a/tests/PSRule.Tests/StringExtensionsTests.cs +++ b/tests/PSRule.Tests/StringExtensionsTests.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using Xunit; - namespace PSRule { public sealed class StringExtensionsTests diff --git a/tests/PSRule.Tests/SuppressionFilterTests.cs b/tests/PSRule.Tests/SuppressionFilterTests.cs index 373d0b24e4..bb8747a74c 100644 --- a/tests/PSRule.Tests/SuppressionFilterTests.cs +++ b/tests/PSRule.Tests/SuppressionFilterTests.cs @@ -9,7 +9,6 @@ using PSRule.Pipeline; using PSRule.Rules; using PSRule.Runtime; -using Xunit; using Assert = Xunit.Assert; namespace PSRule diff --git a/tests/PSRule.Tests/SuppressionGroupTests.cs b/tests/PSRule.Tests/SuppressionGroupTests.cs index bf633580a7..059e43c87a 100644 --- a/tests/PSRule.Tests/SuppressionGroupTests.cs +++ b/tests/PSRule.Tests/SuppressionGroupTests.cs @@ -9,7 +9,6 @@ using PSRule.Host; using PSRule.Pipeline; using PSRule.Runtime; -using Xunit; using Assert = Xunit.Assert; namespace PSRule diff --git a/tests/PSRule.Tests/TargetBinderTests.cs b/tests/PSRule.Tests/TargetBinderTests.cs index b065bb4e88..394b5da6a6 100644 --- a/tests/PSRule.Tests/TargetBinderTests.cs +++ b/tests/PSRule.Tests/TargetBinderTests.cs @@ -6,7 +6,6 @@ using PSRule.Configuration; using PSRule.Definitions.Baselines; using PSRule.Pipeline; -using Xunit; namespace PSRule { diff --git a/tests/PSRule.Tests/TargetInfoTests.cs b/tests/PSRule.Tests/TargetInfoTests.cs index d28b0630b2..8ca4bdab97 100644 --- a/tests/PSRule.Tests/TargetInfoTests.cs +++ b/tests/PSRule.Tests/TargetInfoTests.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. using System.Management.Automation; -using Xunit; namespace PSRule { diff --git a/tests/PSRule.Tests/TargetNameBindingTests.cs b/tests/PSRule.Tests/TargetNameBindingTests.cs index 6eeb86da82..575cf1a1f6 100644 --- a/tests/PSRule.Tests/TargetNameBindingTests.cs +++ b/tests/PSRule.Tests/TargetNameBindingTests.cs @@ -5,7 +5,6 @@ using PSRule.Configuration; using PSRule.Data; using PSRule.Pipeline; -using Xunit; namespace PSRule { @@ -56,7 +55,7 @@ public void PreferTargetInfo() PipelineContext.CurrentThread = PipelineContext.New(GetOption(), null, null, null, null, null, null, null); - var actual = PipelineHookActions.BindTargetName(new string[] { "Name" }, false, false, pso1, out string path); + var actual = PipelineHookActions.BindTargetName(new string[] { "Name" }, false, false, pso1, out var path); Assert.Equal("OtherName", actual); Assert.Equal("Name", path); diff --git a/tests/PSRule.Tests/Usings.cs b/tests/PSRule.Tests/Usings.cs new file mode 100644 index 0000000000..8c07c6cf4c --- /dev/null +++ b/tests/PSRule.Tests/Usings.cs @@ -0,0 +1,4 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +global using Xunit; diff --git a/tests/PSRule.Tool.Tests/CommandTests.cs b/tests/PSRule.Tool.Tests/CommandTests.cs new file mode 100644 index 0000000000..aaffd77d51 --- /dev/null +++ b/tests/PSRule.Tool.Tests/CommandTests.cs @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.CommandLine; +using System.CommandLine.IO; +using System.Linq; +using System.Threading.Tasks; + +namespace PSRule.Tool; + +public sealed class CommandTests +{ + [Fact] + public async Task Analyze() + { + var console = new TestConsole(); + var builder = ClientBuilder.New(); + Assert.NotNull(builder.Subcommands.FirstOrDefault(c => c.Name == "analyze")); + + await builder.InvokeAsync("analyze", console); + var output = console.Out.ToString(); + + Assert.NotNull(output); + Assert.Contains($"Using PSRule v0.0.1{System.Environment.NewLine}", output); + } + + [Fact] + public async Task Restore() + { + var console = new TestConsole(); + var builder = ClientBuilder.New(); + Assert.NotNull(builder.Subcommands.FirstOrDefault(c => c.Name == "restore")); + + await builder.InvokeAsync("restore", console); + + var output = console.Out.ToString(); + Assert.NotNull(output); + } +} diff --git a/tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj b/tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj new file mode 100644 index 0000000000..0f203c118f --- /dev/null +++ b/tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj @@ -0,0 +1,29 @@ + + + + net6.0 + {d3488ce2-779f-4474-b38a-f894a4b689f7} + true + false + PSRule.Tool + Full + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + diff --git a/tests/PSRule.Tool.Tests/Usings.cs b/tests/PSRule.Tool.Tests/Usings.cs new file mode 100644 index 0000000000..8c07c6cf4c --- /dev/null +++ b/tests/PSRule.Tool.Tests/Usings.cs @@ -0,0 +1,4 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +global using Xunit; diff --git a/tests/PSRule.Tool.Tests/xunit.runner.json b/tests/PSRule.Tool.Tests/xunit.runner.json new file mode 100644 index 0000000000..2fe8c33bcc --- /dev/null +++ b/tests/PSRule.Tool.Tests/xunit.runner.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://xunit.net/schema/current/xunit.runner.schema.json", + "diagnosticMessages": true +} From 54bfb2d82f959d58c49dad477642ef19e6eebcc6 Mon Sep 17 00:00:00 2001 From: Bernie White Date: Wed, 7 Jun 2023 02:51:31 +1000 Subject: [PATCH 025/177] Fixes includeLocal for default baseline #1506 (#1545) --- docs/CHANGELOG-v2.md | 3 + src/PSRule/Definitions/Rules/RuleFilter.cs | 48 +++---- src/PSRule/PSRule.psm1 | 9 +- src/PSRule/Pipeline/GetRuleHelpPipeline.cs | 5 + src/PSRule/Pipeline/OptionContext.cs | 48 ++++--- tests/PSRule.Tests/OptionContextTests.cs | 123 ++++++++++++++++++ tests/PSRule.Tests/PipelineTests.cs | 1 + .../PSRule.Tool.Tests.csproj | 2 +- 8 files changed, 187 insertions(+), 52 deletions(-) diff --git a/docs/CHANGELOG-v2.md b/docs/CHANGELOG-v2.md index c4a3d59dce..444520fe9e 100644 --- a/docs/CHANGELOG-v2.md +++ b/docs/CHANGELOG-v2.md @@ -52,6 +52,9 @@ What's changed since pre-release v2.9.0-B0033: [#1540](https://github.com/microsoft/PSRule/pull/1540) - Bump Microsoft.CodeAnalysis.Common to v4.6.0. [#1534](https://github.com/microsoft/PSRule/pull/1534) +- Bug fixes: + - Fixed include local not automatically being enabled for default module baseline by @BernieWhite. + [#1506](https://github.com/microsoft/PSRule/issues/1506) ## v2.9.0-B0033 (pre-release) diff --git a/src/PSRule/Definitions/Rules/RuleFilter.cs b/src/PSRule/Definitions/Rules/RuleFilter.cs index 1d373f58e5..a148dc37d2 100644 --- a/src/PSRule/Definitions/Rules/RuleFilter.cs +++ b/src/PSRule/Definitions/Rules/RuleFilter.cs @@ -15,12 +15,12 @@ namespace PSRule.Definitions.Rules /// internal sealed class RuleFilter : IResourceFilter { - private readonly string[] _Include; - private readonly string[] _Excluded; - private readonly Hashtable _Tag; - private readonly ResourceLabels _Labels; - private readonly bool _IncludeLocal; - private readonly WildcardPattern _WildcardMatch; + internal readonly string[] Include; + internal readonly string[] Excluded; + internal readonly Hashtable Tag; + internal readonly ResourceLabels Labels; + internal readonly bool IncludeLocal; + internal readonly WildcardPattern WildcardMatch; /// /// Filter rules by id or tag. @@ -32,19 +32,19 @@ internal sealed class RuleFilter : IResourceFilter /// Only accept rules that have these labels. public RuleFilter(string[] include, Hashtable tag, string[] exclude, bool? includeLocal, ResourceLabels labels) { - _Include = include == null || include.Length == 0 ? null : include; - _Excluded = exclude == null || exclude.Length == 0 ? null : exclude; - _Tag = tag ?? null; - _Labels = labels ?? null; - _IncludeLocal = includeLocal ?? RuleOption.Default.IncludeLocal.Value; - _WildcardMatch = null; + Include = include == null || include.Length == 0 ? null : include; + Excluded = exclude == null || exclude.Length == 0 ? null : exclude; + Tag = tag ?? null; + Labels = labels ?? null; + IncludeLocal = includeLocal ?? RuleOption.Default.IncludeLocal.Value; + WildcardMatch = null; if (include != null && include.Length > 0 && WildcardPattern.ContainsWildcardCharacters(include[0])) { if (include.Length > 1) throw new NotSupportedException(PSRuleResources.MatchSingleName); - _WildcardMatch = new WildcardPattern(include[0]); + WildcardMatch = new WildcardPattern(include[0]); } } @@ -63,17 +63,17 @@ internal bool Match(string name, ResourceTags tag, ResourceLabels labels) public bool Match(IResource resource) { var ids = resource.GetIds(); - return !IsExcluded(ids) && (_IncludeLocal && resource.IsLocalScope() || IsIncluded(ids, resource.Tags, resource.Labels)); + return !IsExcluded(ids) && (IncludeLocal && resource.IsLocalScope() || IsIncluded(ids, resource.Tags, resource.Labels)); } private bool IsExcluded(IEnumerable ids) { - if (_Excluded == null) + if (Excluded == null) return false; foreach (var id in ids) { - if (Contains(id, _Excluded)) + if (Contains(id, Excluded)) return true; } return false; @@ -83,7 +83,7 @@ private bool IsIncluded(IEnumerable ids, ResourceTags tag, ResourceL { foreach (var id in ids) { - if (_Include == null || Contains(id, _Include) || MatchWildcard(id.Name)) + if (Include == null || Contains(id, Include) || MatchWildcard(id.Name)) return TagEquals(tag) && LabelEquals(labels); } return false; @@ -91,13 +91,13 @@ private bool IsIncluded(IEnumerable ids, ResourceTags tag, ResourceL private bool TagEquals(ResourceTags tag) { - if (_Tag == null) + if (Tag == null) return true; - if (tag == null || _Tag.Count > tag.Count) + if (tag == null || Tag.Count > tag.Count) return false; - foreach (DictionaryEntry entry in _Tag) + foreach (DictionaryEntry entry in Tag) { if (!tag.Contains(entry.Key, entry.Value)) return false; @@ -107,13 +107,13 @@ private bool TagEquals(ResourceTags tag) private bool LabelEquals(ResourceLabels labels) { - if (_Labels == null) + if (Labels == null) return true; - if (labels == null || _Labels.Count > labels.Count) + if (labels == null || Labels.Count > labels.Count) return false; - foreach (var taxon in _Labels) + foreach (var taxon in Labels) { if (!labels.Contains(taxon.Key, taxon.Value)) return false; @@ -133,7 +133,7 @@ private static bool Contains(ResourceId id, string[] set) private bool MatchWildcard(string name) { - return _WildcardMatch != null && _WildcardMatch.IsMatch(name); + return WildcardMatch != null && WildcardMatch.IsMatch(name); } } } diff --git a/src/PSRule/PSRule.psm1 b/src/PSRule/PSRule.psm1 index c4d35a9f6f..afd13d6a7b 100644 --- a/src/PSRule/PSRule.psm1 +++ b/src/PSRule/PSRule.psm1 @@ -994,7 +994,7 @@ function Get-PSRuleHelp { [Parameter(Position = 0, Mandatory = $False)] [Alias('n')] [SupportsWildcards()] - [String]$Name, + [String[]]$Name, # A path to check documentation for. [Parameter(Mandatory = $False)] @@ -1053,16 +1053,13 @@ function Get-PSRuleHelp { if ($isDeviceGuard) { $Option.Execution.LanguageMode = [PSRule.Configuration.LanguageMode]::ConstrainedLanguage; } - if ($PSBoundParameters.ContainsKey('Name')) { - $Option.Rule.Include = $Name; - } if ($PSBoundParameters.ContainsKey('Culture')) { $Option.Output.Culture = $Culture; } $hostContext = [PSRule.Pipeline.PSHostContext]::new($PSCmdlet, $ExecutionContext); - $builder = [PSRule.Pipeline.PipelineBuilder]::GetHelp($sourceFiles, $Option, $hostContext);; - + $builder = [PSRule.Pipeline.PipelineBuilder]::GetHelp($sourceFiles, $Option, $hostContext); + $builder.Name($Name); if ($Online) { $builder.Online(); } diff --git a/src/PSRule/Pipeline/GetRuleHelpPipeline.cs b/src/PSRule/Pipeline/GetRuleHelpPipeline.cs index 10bcab7afe..ded0f85113 100644 --- a/src/PSRule/Pipeline/GetRuleHelpPipeline.cs +++ b/src/PSRule/Pipeline/GetRuleHelpPipeline.cs @@ -26,6 +26,11 @@ public interface IHelpPipelineBuilder : IPipelineBuilder /// Open or show online help for a rule if it exists. /// void Online(); + + /// + /// Filter by name. + /// + void Name(string[] name); } internal sealed class GetRuleHelpPipelineBuilder : PipelineBuilderBase, IHelpPipelineBuilder diff --git a/src/PSRule/Pipeline/OptionContext.cs b/src/PSRule/Pipeline/OptionContext.cs index fe86ff9a81..d975cfd7fd 100644 --- a/src/PSRule/Pipeline/OptionContext.cs +++ b/src/PSRule/Pipeline/OptionContext.cs @@ -24,7 +24,14 @@ internal sealed class OptionContext private readonly List _ConventionOrder; private readonly string[] _DefaultCulture; + /// + /// 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; @@ -33,12 +40,6 @@ internal sealed class OptionContext private BaselineScope _ModuleBaseline; private ConfigScope _ModuleConfig; - //private BindingOption _Binding; - //private RuleFilter _Filter; - //private Dictionary _Configuration; - //private string[] _Culture; - //private ConventionFilter _ConventionFilter; - internal OptionContext() { _ModuleBaselineScope = new Dictionary(); @@ -51,9 +52,24 @@ internal OptionContext() 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 } @@ -358,31 +374,24 @@ 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; - //_Binding = null; - //_Configuration = null; - //_Filter = null; - //_Culture = null; - //_ConventionFilter = null; } private IResourceFilter GetRuleFilter() { - // if (_Filter != null) - // return _Filter; - 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?.IncludeLocal ?? _WorkspaceBaseline?.IncludeLocal ?? _ModuleBaseline?.IncludeLocal; + 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() { - // if (_ConventionFilter != null) - // return _ConventionFilter; - var include = new List(); for (var i = 0; _Parameter?.Convention?.Include != null && i < _Parameter.Convention.Include.Length; i++) include.Add(_Parameter.Convention.Include[i]); @@ -398,9 +407,6 @@ private IResourceFilter GetConventionFilter() private IBindingOption GetTargetBinding() { - // if (_Binding != null) - // return _Binding; - 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; diff --git a/tests/PSRule.Tests/OptionContextTests.cs b/tests/PSRule.Tests/OptionContextTests.cs index 790dc7b538..f3dd9be016 100644 --- a/tests/PSRule.Tests/OptionContextTests.cs +++ b/tests/PSRule.Tests/OptionContextTests.cs @@ -1,11 +1,17 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System.Collections.Generic; using PSRule.Configuration; +using PSRule.Definitions; +using PSRule.Definitions.Rules; using PSRule.Pipeline; namespace PSRule { + /// + /// Tests for . + /// public sealed class OptionContextTests { [Fact] @@ -23,8 +29,125 @@ public void Build() Assert.Equal(new string[] { "en-ZZ" }, testScope.Culture); } + [Fact] + 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 ruleFilter = localScope.GetFilter(ResourceKind.Rule) as RuleFilter; + Assert.NotNull(ruleFilter); + Assert.True(ruleFilter.IncludeLocal); + + // 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); + 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); + 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); + ruleFilter = localScope.GetFilter(ResourceKind.Rule) as RuleFilter; + Assert.NotNull(ruleFilter); + Assert.True(ruleFilter.IncludeLocal); + } + #region Helper methods + internal sealed class MockScope : Runtime.ILanguageScope + { + internal RuleFilter RuleFilter; + + public MockScope(string name) + { + Name = name; + } + + public string Name { get; } + + public IBindingOption Binding => throw new System.NotImplementedException(); + + public string[] Culture => throw new System.NotImplementedException(); + + public void AddService(string name, object service) + { + + } + + public void Configure(Dictionary configuration) + { + + } + + 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 WithBinding(IBindingOption bindingOption) + { + + } + + public void WithCulture(string[] strings) + { + + } + + public void WithFilter(IResourceFilter resourceFilter) + { + if (resourceFilter is RuleFilter ruleFilter) + RuleFilter = ruleFilter; + } + } + private static PSRuleOption GetOption(string[] culture = null) { var option = new PSRuleOption(); diff --git a/tests/PSRule.Tests/PipelineTests.cs b/tests/PSRule.Tests/PipelineTests.cs index cb6e39d5c4..81e6a601ff 100644 --- a/tests/PSRule.Tests/PipelineTests.cs +++ b/tests/PSRule.Tests/PipelineTests.cs @@ -346,6 +346,7 @@ private static Source[] GetSource(string[] files = null) private static PSRuleOption GetOption(string path = null, ExecutionActionPreference ruleExcludedAction = ExecutionActionPreference.None) { var option = path == null ? new PSRuleOption() : PSRuleOption.FromFile(path); + option.Rule.IncludeLocal = false; option.Execution.RuleExcluded = ruleExcludedAction; return option; } diff --git a/tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj b/tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj index 0f203c118f..33878395c9 100644 --- a/tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj +++ b/tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj @@ -2,7 +2,7 @@ net6.0 - {d3488ce2-779f-4474-b38a-f894a4b689f7} + {DA46C891-08F1-4D01-9F98-1F8BB10CAFEC} true false PSRule.Tool From e1ce7dd70fe3a1c1a238e535a8b8958f8e45f1f0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 7 Jun 2023 03:01:56 +1000 Subject: [PATCH 026/177] Bump Microsoft.NET.Test.Sdk from 17.6.1 to 17.6.2 (#1544) * Bump Microsoft.NET.Test.Sdk from 17.6.1 to 17.6.2 Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 17.6.1 to 17.6.2. - [Release notes](https://github.com/microsoft/vstest/releases) - [Changelog](https://github.com/microsoft/vstest/blob/main/docs/releases.md) - [Commits](https://github.com/microsoft/vstest/compare/v17.6.1...v17.6.2) --- updated-dependencies: - dependency-name: Microsoft.NET.Test.Sdk dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * Bump change log --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Bernie White --- docs/CHANGELOG-v2.md | 4 ++-- tests/PSRule.Tests/PSRule.Tests.csproj | 2 +- tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/CHANGELOG-v2.md b/docs/CHANGELOG-v2.md index 444520fe9e..b8cd05d1cf 100644 --- a/docs/CHANGELOG-v2.md +++ b/docs/CHANGELOG-v2.md @@ -48,8 +48,8 @@ What's changed since pre-release v2.9.0-B0033: - Added style and improved handling for restore command by @BernieWhite. [#1152](https://github.com/microsoft/PSRule/issues/1152) - Engineering: - - Bump Microsoft.NET.Test.Sdk to v17.6.1. - [#1540](https://github.com/microsoft/PSRule/pull/1540) + - Bump Microsoft.NET.Test.Sdk to v17.6.2. + [#1544](https://github.com/microsoft/PSRule/pull/1544) - Bump Microsoft.CodeAnalysis.Common to v4.6.0. [#1534](https://github.com/microsoft/PSRule/pull/1534) - Bug fixes: diff --git a/tests/PSRule.Tests/PSRule.Tests.csproj b/tests/PSRule.Tests/PSRule.Tests.csproj index dd1e8e9a09..0ee36bc8e0 100644 --- a/tests/PSRule.Tests/PSRule.Tests.csproj +++ b/tests/PSRule.Tests/PSRule.Tests.csproj @@ -10,7 +10,7 @@ - + diff --git a/tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj b/tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj index 33878395c9..314b6be16c 100644 --- a/tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj +++ b/tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj @@ -10,7 +10,7 @@ - + all From ed4a9788f2d448ca88bcd3bd25ecb5f094bfd185 Mon Sep 17 00:00:00 2001 From: Bernie White Date: Wed, 7 Jun 2023 07:33:29 +1000 Subject: [PATCH 027/177] Bump change log (#1546) --- docs/CHANGELOG-v2.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/CHANGELOG-v2.md b/docs/CHANGELOG-v2.md index b8cd05d1cf..b00f744e59 100644 --- a/docs/CHANGELOG-v2.md +++ b/docs/CHANGELOG-v2.md @@ -35,6 +35,8 @@ See [upgrade notes][1] for helpful information when upgrading from previous vers ## Unreleased +## v2.9.0-B0068 (pre-release) + What's changed since pre-release v2.9.0-B0033: - New features: From 24eda55e317e99b3921c62c27e2acfc9970e0dfa Mon Sep 17 00:00:00 2001 From: Bernie White Date: Thu, 8 Jun 2023 22:47:26 +1000 Subject: [PATCH 028/177] Release v2.9.0 (#1547) --- docs/CHANGELOG-v2.md | 53 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/docs/CHANGELOG-v2.md b/docs/CHANGELOG-v2.md index b00f744e59..78a43ec3c6 100644 --- a/docs/CHANGELOG-v2.md +++ b/docs/CHANGELOG-v2.md @@ -35,6 +35,59 @@ See [upgrade notes][1] for helpful information when upgrading from previous vers ## Unreleased +## v2.9.0 + +What's changed since release v2.8.1: + +- New features: + - Added sub-selector quantifiers for `allOf` or `anyOf` operators by @BernieWhite. + [#1423](https://github.com/microsoft/PSRule/issues/1423) + - Quantifiers allow you to specify the number of matches with `count`, `less`, `lessOrEqual`, `greater`, or `greaterOrEqual`. + - See [Sub-selectors][4] for more information. + - Added support for new functions by @BernieWhite. + [#1422](https://github.com/microsoft/PSRule/issues/1422) + - Added support for `padLeft`, and `padRight`. + - **Experimental**: Added support for baseline groups by @BernieWhite. + [#1541](https://github.com/microsoft/PSRule/issues/1541) + - Baseline groups allow you to reference a baseline by a friendly name. + - Update the baseline group to point to a new baseline. + - Currently only a single baseline can be referenced by a baseline group. + - See [baselines][6] for more information. +- General improvements: + - Added style and improved handling for restore command by @BernieWhite. + [#1152](https://github.com/microsoft/PSRule/issues/1152) + - **Important change**: Rename of execution options by @BernieWhite. + [#1456](https://github.com/microsoft/PSRule/issues/1456) + - Renamed options allow configuration of output level as `Ignore`, `Warn`, `Error`, or `Debug`. + - `Execution.AliasReferenceWarning` is replaced with `Execution.AliasReference`. + - `Execution.InconclusiveWarning` is replaced with `Execution.RuleInconclusive`. + - `Execution.InvariantCultureWarning` is replaced with `Execution.InvariantCulture`. + - `Execution.NotProcessedWarning` is replaced with `Execution.UnprocessedObject`. + - Deprecated `AliasReferenceWarning` option, which will be removed in v3. + - Deprecated `InconclusiveWarning` option, which will be removed in v3. + - Deprecated `InvariantCultureWarning` option, which will be removed in v3. + - Deprecated `NotProcessedWarning` option, which will be removed in v3. + - Improved schema display names by @BernieWhite. + [#1488](https://github.com/microsoft/PSRule/issues/1488) +- Engineering: + - Bump Pester to v5.4.1. + [#1510](https://github.com/microsoft/PSRule/pull/1510) + - Bump Microsoft.NET.Test.Sdk to v17.6.2. + [#1544](https://github.com/microsoft/PSRule/pull/1544) + - Bump Microsoft.CodeAnalysis.Common to v4.6.0. + [#1534](https://github.com/microsoft/PSRule/pull/1534) +- Bug fixes: + - Fixed tool output on access denied to path by @BernieWhite. + [#1490](https://github.com/microsoft/PSRule/issues/1490) + - Fixed tool exit code on error or failure by @BernieWhite. + [#1491](https://github.com/microsoft/PSRule/issues/1491) + - Fixed include local not automatically being enabled for default module baseline by @BernieWhite. + [#1506](https://github.com/microsoft/PSRule/issues/1506) + +What's changed since pre-release v2.9.0-B0068: + +- No additional changes. + ## v2.9.0-B0068 (pre-release) What's changed since pre-release v2.9.0-B0033: From f9619f2e983264c5617de40b370e61ed85625891 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 17 Jun 2023 20:19:59 +1000 Subject: [PATCH 029/177] Bump mkdocs-material from 9.1.15 to 9.1.16 (#1551) Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.1.15 to 9.1.16. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.1.15...9.1.16) --- updated-dependencies: - dependency-name: mkdocs-material dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements-docs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-docs.txt b/requirements-docs.txt index 0e8b590205..1ff12f3682 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -1,5 +1,5 @@ mkdocs==1.4.3 -mkdocs-material==9.1.15 +mkdocs-material==9.1.16 pymdown-extensions==10.0.1 mike==1.1.2 mkdocs-simple-hooks==0.1.5 From 98c515bd28684ae409a4843d957bc197de15596c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 22 Jun 2023 00:22:27 +1000 Subject: [PATCH 030/177] Bump Microsoft.CodeAnalysis.NetAnalyzers from 7.0.1 to 7.0.3 (#1550) * Bump Microsoft.CodeAnalysis.NetAnalyzers from 7.0.1 to 7.0.3 Bumps [Microsoft.CodeAnalysis.NetAnalyzers](https://github.com/dotnet/roslyn-analyzers) from 7.0.1 to 7.0.3. - [Release notes](https://github.com/dotnet/roslyn-analyzers/releases) - [Changelog](https://github.com/dotnet/roslyn-analyzers/blob/main/PostReleaseActivities.md) - [Commits](https://github.com/dotnet/roslyn-analyzers/commits) --- updated-dependencies: - dependency-name: Microsoft.CodeAnalysis.NetAnalyzers dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * Bump change log --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Bernie White --- docs/CHANGELOG-v2.md | 6 ++++++ src/PSRule.Tool/PSRule.Tool.csproj | 2 +- src/PSRule.Types/PSRule.Types.csproj | 2 +- src/PSRule/PSRule.csproj | 2 +- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/docs/CHANGELOG-v2.md b/docs/CHANGELOG-v2.md index 78a43ec3c6..a653e4c7fe 100644 --- a/docs/CHANGELOG-v2.md +++ b/docs/CHANGELOG-v2.md @@ -35,6 +35,12 @@ See [upgrade notes][1] for helpful information when upgrading from previous vers ## Unreleased +What's changed since release v2.9.0: + +- Engineering: + - Bump Microsoft.CodeAnalysis.NetAnalyzers to v7.0.3. + [#1550](https://github.com/microsoft/PSRule/pull/1550) + ## v2.9.0 What's changed since release v2.8.1: diff --git a/src/PSRule.Tool/PSRule.Tool.csproj b/src/PSRule.Tool/PSRule.Tool.csproj index 34f0358e23..14a9c9d670 100644 --- a/src/PSRule.Tool/PSRule.Tool.csproj +++ b/src/PSRule.Tool/PSRule.Tool.csproj @@ -18,7 +18,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/PSRule.Types/PSRule.Types.csproj b/src/PSRule.Types/PSRule.Types.csproj index 8b99a1db53..a5e376deb3 100644 --- a/src/PSRule.Types/PSRule.Types.csproj +++ b/src/PSRule.Types/PSRule.Types.csproj @@ -11,7 +11,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/PSRule/PSRule.csproj b/src/PSRule/PSRule.csproj index 9dd14fb46b..6621a9adb4 100644 --- a/src/PSRule/PSRule.csproj +++ b/src/PSRule/PSRule.csproj @@ -13,7 +13,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive From e6be24d3656e165718b9a0cc965103b503be2dc9 Mon Sep 17 00:00:00 2001 From: Bernie White Date: Mon, 26 Jun 2023 00:25:47 +1000 Subject: [PATCH 031/177] Expanded support for FileHeader assertion #1521 (#1555) --- docs/CHANGELOG-v2.md | 4 ++++ docs/concepts/PSRule/en-US/about_PSRule_Assert.md | 6 +++--- src/PSRule/Runtime/Assert.cs | 6 ++++++ 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/docs/CHANGELOG-v2.md b/docs/CHANGELOG-v2.md index a653e4c7fe..7d009f7786 100644 --- a/docs/CHANGELOG-v2.md +++ b/docs/CHANGELOG-v2.md @@ -37,6 +37,10 @@ See [upgrade notes][1] for helpful information when upgrading from previous vers What's changed since release v2.9.0: +- General improvements: + - Expanded support for `FileHeader` assertion by @BernieWhite. + [#1521](https://github.com/microsoft/PSRule/issues/1521) + - Added support for `.bicepparam`, `.tsp`, `.tsx`, `.editorconfig`, `.ipynb`, and `.toml` files. - Engineering: - Bump Microsoft.CodeAnalysis.NetAnalyzers to v7.0.3. [#1550](https://github.com/microsoft/PSRule/pull/1550) diff --git a/docs/concepts/PSRule/en-US/about_PSRule_Assert.md b/docs/concepts/PSRule/en-US/about_PSRule_Assert.md index 0195b60777..c154e412ee 100644 --- a/docs/concepts/PSRule/en-US/about_PSRule_Assert.md +++ b/docs/concepts/PSRule/en-US/about_PSRule_Assert.md @@ -335,11 +335,11 @@ When set, detection by file extension is skipped. Prefix detection for line comments is supported with the following file extensions: -- `.bicep`, `.cs`, `.csx` `.ts`, `.js`, `.jsx`, +- `.bicep`, `.bicepparam`, `.cs`, `.csx`, `.ts`, `.tsp`, `.tsx`, `.js`, `.jsx`, `.fs`, `.go`, `.groovy`, `.php`, `.cpp`, `.h`, `.java`, `.json`, `.jsonc`, `.scala`, `Jenkinsfile` - Use a prefix of (`// `). -- `.ps1`, `.psd1`, `.psm1`, `.yaml`, `.yml`, -`.r`, `.py`, `.sh`, `.tf`, `.tfvars`, `.gitignore`, +- `.editorconfig`, `.ipynb`, `.ps1`, `.psd1`, `.psm1`, `.yaml`, `.yml`, +`.r`, `.py`, `.sh`, `.tf`, `.tfvars`, `.toml`, `.gitignore`, `.pl`, `.rb`, `Dockerfile` - Use a prefix of (`# `). - `.sql`, `.lau` - Use a prefix of (`-- `). - `.bat`, `.cmd` - Use a prefix of (`:: `). diff --git a/src/PSRule/Runtime/Assert.cs b/src/PSRule/Runtime/Assert.cs index fae6ab7271..4dfd60748a 100644 --- a/src/PSRule/Runtime/Assert.cs +++ b/src/PSRule/Runtime/Assert.cs @@ -1532,9 +1532,12 @@ private static string DetectLinePrefix(string extension) switch (extension) { case ".bicep": + case ".bicepparam": case ".cs": case ".csx": case ".ts": + case ".tsp": + case ".tsx": case ".js": case ".jsx": case ".fs": @@ -1550,6 +1553,8 @@ private static string DetectLinePrefix(string extension) case "jenkinsfile": return "// "; + case ".editorconfig": + case ".ipynb": case ".ps1": case ".psd1": case ".psm1": @@ -1560,6 +1565,7 @@ private static string DetectLinePrefix(string extension) case ".sh": case ".tf": case ".tfvars": + case ".toml": case ".gitignore": case ".pl": case ".rb": From 06b1d0ccefa74232d9bfca12eeab2e5d478a63fc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 27 Jun 2023 16:51:01 +1000 Subject: [PATCH 032/177] Bump mkdocs-material from 9.1.16 to 9.1.17 (#1556) Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.1.16 to 9.1.17. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.1.16...9.1.17) --- updated-dependencies: - dependency-name: mkdocs-material dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements-docs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-docs.txt b/requirements-docs.txt index 1ff12f3682..d5cb39ebe4 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -1,5 +1,5 @@ mkdocs==1.4.3 -mkdocs-material==9.1.16 +mkdocs-material==9.1.17 pymdown-extensions==10.0.1 mike==1.1.2 mkdocs-simple-hooks==0.1.5 From 6b1b8cfec2246cdecf9e3ac67e30433054ccc0c5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 5 Jul 2023 18:13:56 +1000 Subject: [PATCH 033/177] Bump mkdocs-material from 9.1.17 to 9.1.18 (#1560) Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.1.17 to 9.1.18. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.1.17...9.1.18) --- updated-dependencies: - dependency-name: mkdocs-material dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements-docs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-docs.txt b/requirements-docs.txt index d5cb39ebe4..fbba40e92f 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -1,5 +1,5 @@ mkdocs==1.4.3 -mkdocs-material==9.1.17 +mkdocs-material==9.1.18 pymdown-extensions==10.0.1 mike==1.1.2 mkdocs-simple-hooks==0.1.5 From 8c77dc31a793b4edb2f433a1b514422a1d805609 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Jul 2023 12:54:50 +1000 Subject: [PATCH 034/177] Bump Microsoft.NET.Test.Sdk from 17.6.2 to 17.6.3 (#1557) * Bump Microsoft.NET.Test.Sdk from 17.6.2 to 17.6.3 Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 17.6.2 to 17.6.3. - [Release notes](https://github.com/microsoft/vstest/releases) - [Changelog](https://github.com/microsoft/vstest/blob/main/docs/releases.md) - [Commits](https://github.com/microsoft/vstest/compare/v17.6.2...v17.6.3) --- updated-dependencies: - dependency-name: Microsoft.NET.Test.Sdk dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * Bump change log --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Bernie White --- docs/CHANGELOG-v2.md | 2 ++ tests/PSRule.Tests/PSRule.Tests.csproj | 2 +- tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/CHANGELOG-v2.md b/docs/CHANGELOG-v2.md index 7d009f7786..aadaf35322 100644 --- a/docs/CHANGELOG-v2.md +++ b/docs/CHANGELOG-v2.md @@ -44,6 +44,8 @@ What's changed since release v2.9.0: - Engineering: - Bump Microsoft.CodeAnalysis.NetAnalyzers to v7.0.3. [#1550](https://github.com/microsoft/PSRule/pull/1550) + - Bump Microsoft.NET.Test.Sdk to v17.6.3. + [#1557](https://github.com/microsoft/PSRule/pull/1557) ## v2.9.0 diff --git a/tests/PSRule.Tests/PSRule.Tests.csproj b/tests/PSRule.Tests/PSRule.Tests.csproj index 0ee36bc8e0..1a51a54d36 100644 --- a/tests/PSRule.Tests/PSRule.Tests.csproj +++ b/tests/PSRule.Tests/PSRule.Tests.csproj @@ -10,7 +10,7 @@ - + diff --git a/tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj b/tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj index 314b6be16c..2a62c25af4 100644 --- a/tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj +++ b/tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj @@ -10,7 +10,7 @@ - + all From b1c2949ba40e7adb2148a12c066461006db971b7 Mon Sep 17 00:00:00 2001 From: Bernie White Date: Mon, 10 Jul 2023 13:06:12 +1000 Subject: [PATCH 035/177] Bump YamlDotNet to v13.1.1 #1399 (#1563) --- docs/CHANGELOG-v2.md | 2 ++ src/PSRule.Types/PSRule.Types.csproj | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/CHANGELOG-v2.md b/docs/CHANGELOG-v2.md index aadaf35322..ad4045841e 100644 --- a/docs/CHANGELOG-v2.md +++ b/docs/CHANGELOG-v2.md @@ -46,6 +46,8 @@ What's changed since release v2.9.0: [#1550](https://github.com/microsoft/PSRule/pull/1550) - Bump Microsoft.NET.Test.Sdk to v17.6.3. [#1557](https://github.com/microsoft/PSRule/pull/1557) + - Bump YamlDotNet to v13.1.1. + [#1399](https://github.com/microsoft/PSRule/issues/1399) ## v2.9.0 diff --git a/src/PSRule.Types/PSRule.Types.csproj b/src/PSRule.Types/PSRule.Types.csproj index a5e376deb3..5aa2f5bcfb 100644 --- a/src/PSRule.Types/PSRule.Types.csproj +++ b/src/PSRule.Types/PSRule.Types.csproj @@ -16,7 +16,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + From 92e3ee2bd0a6e98a3a9667638dd45b8fd29c2ebc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Jul 2023 13:24:20 +1000 Subject: [PATCH 036/177] Bump xunit from 2.4.2 to 2.5.0 (#1562) * Bump xunit from 2.4.2 to 2.5.0 Bumps [xunit](https://github.com/xunit/xunit) from 2.4.2 to 2.5.0. - [Commits](https://github.com/xunit/xunit/compare/2.4.2...2.5.0) --- updated-dependencies: - dependency-name: xunit dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * Bump change log --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Bernie White --- docs/CHANGELOG-v2.md | 2 ++ tests/PSRule.Tests/PSRule.Tests.csproj | 2 +- tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/CHANGELOG-v2.md b/docs/CHANGELOG-v2.md index ad4045841e..8d2ed826da 100644 --- a/docs/CHANGELOG-v2.md +++ b/docs/CHANGELOG-v2.md @@ -48,6 +48,8 @@ What's changed since release v2.9.0: [#1557](https://github.com/microsoft/PSRule/pull/1557) - Bump YamlDotNet to v13.1.1. [#1399](https://github.com/microsoft/PSRule/issues/1399) + - Bump xunit to v2.5.0. + [#1562](https://github.com/microsoft/PSRule/pull/1562) ## v2.9.0 diff --git a/tests/PSRule.Tests/PSRule.Tests.csproj b/tests/PSRule.Tests/PSRule.Tests.csproj index 1a51a54d36..d601900cee 100644 --- a/tests/PSRule.Tests/PSRule.Tests.csproj +++ b/tests/PSRule.Tests/PSRule.Tests.csproj @@ -12,7 +12,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj b/tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj index 2a62c25af4..a18d889815 100644 --- a/tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj +++ b/tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj @@ -11,7 +11,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive From bab884ef258e672eb0cd586fa547d199bbdb208d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Jul 2023 13:42:20 +1000 Subject: [PATCH 037/177] Bump xunit.runner.visualstudio from 2.4.5 to 2.5.0 (#1561) * Bump xunit.runner.visualstudio from 2.4.5 to 2.5.0 Bumps [xunit.runner.visualstudio](https://github.com/xunit/visualstudio.xunit) from 2.4.5 to 2.5.0. - [Release notes](https://github.com/xunit/visualstudio.xunit/releases) - [Commits](https://github.com/xunit/visualstudio.xunit/compare/v2.4.5...2.5.0) --- updated-dependencies: - dependency-name: xunit.runner.visualstudio dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * Bump change log --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Bernie White --- docs/CHANGELOG-v2.md | 2 ++ tests/PSRule.Tests/PSRule.Tests.csproj | 2 +- tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/CHANGELOG-v2.md b/docs/CHANGELOG-v2.md index 8d2ed826da..67d1229501 100644 --- a/docs/CHANGELOG-v2.md +++ b/docs/CHANGELOG-v2.md @@ -50,6 +50,8 @@ What's changed since release v2.9.0: [#1399](https://github.com/microsoft/PSRule/issues/1399) - Bump xunit to v2.5.0. [#1562](https://github.com/microsoft/PSRule/pull/1562) + - Bump xunit.runner.visualstudio to v2.5.0. + [#1561](https://github.com/microsoft/PSRule/pull/1561) ## v2.9.0 diff --git a/tests/PSRule.Tests/PSRule.Tests.csproj b/tests/PSRule.Tests/PSRule.Tests.csproj index d601900cee..4efa61a9a3 100644 --- a/tests/PSRule.Tests/PSRule.Tests.csproj +++ b/tests/PSRule.Tests/PSRule.Tests.csproj @@ -13,7 +13,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj b/tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj index a18d889815..092c38fb8d 100644 --- a/tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj +++ b/tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj @@ -12,7 +12,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive From f4ea121d1770c945fb45fdfa63135779e106413f Mon Sep 17 00:00:00 2001 From: Bernie White Date: Mon, 10 Jul 2023 15:34:20 +1000 Subject: [PATCH 038/177] Code quality updates (#1564) --- src/PSRule.Badges/BadgeResources.cs | 2 -- src/PSRule.Badges/SvgBuilder.cs | 1 - src/PSRule.Benchmark/PSRule.cs | 3 --- src/PSRule.Benchmark/Program.cs | 1 - src/PSRule.BuildTool/BadgeResource.cs | 2 -- src/PSRule.BuildTool/Program.cs | 1 - src/PSRule.Common.props | 4 +++- src/PSRule.Tool/ClientBuilder.cs | 1 - src/PSRule.Tool/ClientContext.cs | 2 -- src/PSRule.Tool/ClientHost.cs | 2 -- src/PSRule.Tool/Program.cs | 1 - src/PSRule.Types/Converters/TypeConverter.cs | 4 ---- .../Converters/Yaml/StringArrayConverter.cs | 2 -- .../Converters/Yaml/StringArrayMapConverter.cs | 2 -- src/PSRule.Types/Data/DateVersion.cs | 3 --- src/PSRule.Types/Data/SemanticVersion.cs | 3 --- src/PSRule.Types/Data/StringArrayMap.cs | 2 -- src/PSRule.Types/DictionaryExtensions.cs | 2 -- src/PSRule.Types/Environment.cs | 2 -- src/PSRule.Types/HashtableExtensions.cs | 2 -- src/PSRule.Types/Options/BaselineOption.cs | 2 -- src/PSRule/Badges/BadgeBuilder.cs | 3 --- src/PSRule/Commands/AssertExistsCommand.cs | 1 - src/PSRule/Commands/AssertMatchCommand.cs | 1 - src/PSRule/Commands/AssertTypeOfCommand.cs | 1 - src/PSRule/Commands/AssertWithinCommand.cs | 1 - src/PSRule/Commands/LanguageBlock.cs | 1 - src/PSRule/Commands/NewRuleDefinitionCommand.cs | 2 -- src/PSRule/Commands/RuleKeyword.cs | 2 -- src/PSRule/Common/ArrayExtensions.cs | 2 -- src/PSRule/Common/BaselineJsonSerializationMapper.cs | 3 --- src/PSRule/Common/BaselineYamlSerializationMapper.cs | 3 --- src/PSRule/Common/ExpressionHelpers.cs | 5 ----- src/PSRule/Common/ExternalToolHelper.cs | 4 ---- src/PSRule/Common/GitHelper.cs | 2 -- src/PSRule/Common/HashAlgorithmExtensions.cs | 1 - src/PSRule/Common/HashSetExtensions.cs | 1 - src/PSRule/Common/HashtableExtensions.cs | 2 -- src/PSRule/Common/JsonCommentWriter.cs | 1 - src/PSRule/Common/JsonConverters.cs | 4 ---- src/PSRule/Common/JsonReaderExtensions.cs | 1 - src/PSRule/Common/KeyMapDictionary.cs | 11 ++++------- src/PSRule/Common/ListExtensions.cs | 2 -- src/PSRule/Common/LoggerExtensions.cs | 2 -- src/PSRule/Common/PSObjectExtensions.cs | 3 --- src/PSRule/Common/ReasonExtensions.cs | 2 -- src/PSRule/Common/ResourceExtensions.cs | 1 - .../Common/RunspaceContextDiagnosticExtensions.cs | 1 - src/PSRule/Common/StringExtensions.cs | 2 -- src/PSRule/Common/YamlConverters.cs | 3 --- src/PSRule/Configuration/BannerFormat.cs | 1 - src/PSRule/Configuration/BaselineOption.cs | 1 - src/PSRule/Configuration/BindingOption.cs | 1 - src/PSRule/Configuration/ConfigurationOption.cs | 1 - src/PSRule/Configuration/ConventionOption.cs | 2 -- src/PSRule/Configuration/ExecutionOption.cs | 2 -- src/PSRule/Configuration/FieldMap.cs | 2 -- src/PSRule/Configuration/FooterFormat.cs | 1 - src/PSRule/Configuration/IncludeOption.cs | 2 -- src/PSRule/Configuration/InputOption.cs | 2 -- src/PSRule/Configuration/JobSummaryFormat.cs | 1 - src/PSRule/Configuration/LoggingOption.cs | 2 -- src/PSRule/Configuration/OutputOption.cs | 2 -- src/PSRule/Configuration/PSRuleOption.cs | 4 ---- src/PSRule/Configuration/PipelineHook.cs | 2 -- src/PSRule/Configuration/RepositoryOption.cs | 2 -- src/PSRule/Configuration/RequiresOption.cs | 1 - src/PSRule/Configuration/RuleOption.cs | 1 - src/PSRule/Configuration/SuppressionOption.cs | 2 -- src/PSRule/Configuration/SuppressionRule.cs | 2 -- src/PSRule/Data/ITargetIssueCollection.cs | 4 ---- src/PSRule/Data/ITargetSourceCollection.cs | 3 --- src/PSRule/Data/InputFileInfo.cs | 2 -- src/PSRule/Data/InputFileInfoCollection.cs | 3 --- src/PSRule/Data/TargetIssueInfo.cs | 1 - src/PSRule/Data/TargetSourceInfo.cs | 2 -- src/PSRule/Definitions/Baselines/Baseline.cs | 2 -- src/PSRule/Definitions/Conventions/BaseConvention.cs | 2 -- .../Definitions/Conventions/ConventionComparer.cs | 1 - .../Definitions/Conventions/ScriptBlockConvention.cs | 10 +++------- src/PSRule/Definitions/DependencyGraph.cs | 3 --- src/PSRule/Definitions/DependencyGraphBuilder.cs | 4 ---- src/PSRule/Definitions/DependencyTargetCollection.cs | 2 -- src/PSRule/Definitions/Expressions/Exceptions.cs | 1 - .../Definitions/Expressions/ExpressionContext.cs | 2 -- .../Definitions/Expressions/FunctionBuilder.cs | 2 -- src/PSRule/Definitions/Expressions/Functions.cs | 5 +---- .../Definitions/Expressions/LanguageExpressions.cs | 9 ++------- src/PSRule/Definitions/Expressions/Primitives.cs | 1 - src/PSRule/Definitions/ICondition.cs | 1 - src/PSRule/Definitions/ILanguageBlock.cs | 1 - src/PSRule/Definitions/IRuleResultV2.cs | 1 - src/PSRule/Definitions/ISuppressionInfo.cs | 2 -- src/PSRule/Definitions/Resource.cs | 5 +---- src/PSRule/Definitions/ResourceId.cs | 4 +--- src/PSRule/Definitions/ResourceIndex.cs | 2 -- src/PSRule/Definitions/ResourceValidator.cs | 2 -- src/PSRule/Definitions/ResultDetail.cs | 2 -- src/PSRule/Definitions/ResultReason.cs | 1 - src/PSRule/Definitions/Rules/Rule.cs | 1 - src/PSRule/Definitions/Rules/RuleFilter.cs | 2 -- src/PSRule/Definitions/Rules/RuleVisitor.cs | 1 - src/PSRule/Definitions/Selectors/SelectorVisitor.cs | 1 - src/PSRule/Definitions/Spec.cs | 2 +- src/PSRule/Definitions/SpecFactory.cs | 2 -- .../SuppressionGroups/SuppressionGroup.cs | 1 - .../SuppressionGroups/SuppressionGroupVisitor.cs | 1 - src/PSRule/Help/HelpLexer.cs | 3 --- src/PSRule/Help/MarkdownLexer.cs | 3 --- src/PSRule/Help/MarkdownReader.cs | 2 +- src/PSRule/Help/MarkdownStream.cs | 4 +--- src/PSRule/Help/MarkdownToken.cs | 1 - src/PSRule/Help/MetadataLexer.cs | 2 -- src/PSRule/Help/Models.cs | 1 - src/PSRule/Help/TokenStream.cs | 3 --- src/PSRule/Host/Host.cs | 5 ++--- src/PSRule/Host/HostHelper.cs | 4 ---- src/PSRule/Host/RuleLanguageAst.cs | 3 --- src/PSRule/Pipeline/AssertPipeline.cs | 10 +++------- src/PSRule/Pipeline/CommandLineBuilder.cs | 1 - src/PSRule/Pipeline/Exceptions.cs | 2 -- src/PSRule/Pipeline/Formatters/AssertFormatter.cs | 3 --- src/PSRule/Pipeline/Formatters/ClientFormatter.cs | 1 - .../Pipeline/Formatters/VisualStudioCodeFormatter.cs | 1 - src/PSRule/Pipeline/GetBaselinePipeline.cs | 1 - src/PSRule/Pipeline/GetRuleHelpPipeline.cs | 2 -- src/PSRule/Pipeline/GetTargetPipeline.cs | 1 - src/PSRule/Pipeline/HostContext.cs | 1 - src/PSRule/Pipeline/IBindingOption.cs | 1 - src/PSRule/Pipeline/InvokePipelineBuilder.cs | 4 ---- src/PSRule/Pipeline/InvokeResult.cs | 1 - src/PSRule/Pipeline/InvokeRulePipeline.cs | 3 --- src/PSRule/Pipeline/ModulePathComparer.cs | 2 -- src/PSRule/Pipeline/OptionContext.cs | 2 -- src/PSRule/Pipeline/Output/FileOutputWriter.cs | 2 -- src/PSRule/Pipeline/Output/HostPipelineWriter.cs | 3 --- src/PSRule/Pipeline/Output/JobSummaryWriter.cs | 4 ---- src/PSRule/Pipeline/Output/JsonOutputWriter.cs | 2 -- src/PSRule/Pipeline/Output/MarkdownOutputWriter.cs | 1 - src/PSRule/Pipeline/Output/NUnit3OutputWriter.cs | 3 --- src/PSRule/Pipeline/Output/OutputStringWriter.cs | 1 - src/PSRule/Pipeline/Output/SarifOutputWriter.cs | 3 --- src/PSRule/Pipeline/Output/WideOutputWriter.cs | 1 - src/PSRule/Pipeline/Output/YamlOutputWriter.cs | 2 -- src/PSRule/Pipeline/PathBuilder.cs | 3 --- src/PSRule/Pipeline/PathFilter.cs | 3 --- src/PSRule/Pipeline/PipelineBuilder.cs | 3 --- src/PSRule/Pipeline/PipelineContext.cs | 3 --- src/PSRule/Pipeline/PipelineHookActions.cs | 2 -- src/PSRule/Pipeline/PipelineLogger.cs | 3 --- src/PSRule/Pipeline/PipelineReader.cs | 1 - src/PSRule/Pipeline/PipelineReciever.cs | 3 --- src/PSRule/Pipeline/PipelineWriter.cs | 4 ---- src/PSRule/Pipeline/PipelineWriterExtensions.cs | 2 -- src/PSRule/Pipeline/RulePipeline.cs | 1 - src/PSRule/Pipeline/Source.cs | 2 -- src/PSRule/Pipeline/SourcePipeline.cs | 5 ----- src/PSRule/Pipeline/TargetBinder.cs | 2 -- src/PSRule/Pipeline/TargetObject.cs | 2 -- src/PSRule/Pipeline/TestPipeline.cs | 2 +- src/PSRule/Rules/ErrorInfo.cs | 1 - src/PSRule/Rules/PowerShellCondition.cs | 1 - src/PSRule/Rules/Rule.cs | 1 - src/PSRule/Rules/RuleBlock.cs | 1 - src/PSRule/Rules/RuleHelpInfo.cs | 1 - src/PSRule/Rules/RuleOutcome.cs | 1 - src/PSRule/Rules/SuppressionFilter.cs | 3 --- src/PSRule/Runtime/Assert.cs | 3 --- src/PSRule/Runtime/AssertResult.cs | 9 ++------- src/PSRule/Runtime/Configuration.cs | 1 - src/PSRule/Runtime/ILogger.cs | 2 -- src/PSRule/Runtime/LanguageScope.cs | 2 -- src/PSRule/Runtime/LanguageScriptBlock.cs | 1 - src/PSRule/Runtime/ObjectHelper.cs | 3 +-- src/PSRule/Runtime/ObjectPath/Exceptions.cs | 1 - src/PSRule/Runtime/ObjectPath/PathExpression.cs | 2 -- .../Runtime/ObjectPath/PathExpressionBuilder.cs | 4 ---- src/PSRule/Runtime/ObjectPath/PathTokenizer.cs | 2 -- src/PSRule/Runtime/ObjectPath/Tokens.cs | 4 ++-- src/PSRule/Runtime/Operand.cs | 2 -- src/PSRule/Runtime/PSRule.cs | 4 ---- src/PSRule/Runtime/PSRuleMemberInfo.cs | 1 - src/PSRule/Runtime/RuleConditionResult.cs | 3 +-- src/PSRule/Runtime/RunspaceContext.cs | 5 ----- tests/PSRule.Tests/OptionContextTests.cs | 12 ++++++------ 185 files changed, 36 insertions(+), 401 deletions(-) diff --git a/src/PSRule.Badges/BadgeResources.cs b/src/PSRule.Badges/BadgeResources.cs index 2370b6d3a1..6582a27a27 100644 --- a/src/PSRule.Badges/BadgeResources.cs +++ b/src/PSRule.Badges/BadgeResources.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; -using System.IO; using System.Reflection; using Newtonsoft.Json; diff --git a/src/PSRule.Badges/SvgBuilder.cs b/src/PSRule.Badges/SvgBuilder.cs index 3f561bc787..10ac464ae6 100644 --- a/src/PSRule.Badges/SvgBuilder.cs +++ b/src/PSRule.Badges/SvgBuilder.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Text; namespace PSRule.Badges diff --git a/src/PSRule.Benchmark/PSRule.cs b/src/PSRule.Benchmark/PSRule.cs index 8b4e149d83..4a934d3ce4 100644 --- a/src/PSRule.Benchmark/PSRule.cs +++ b/src/PSRule.Benchmark/PSRule.cs @@ -1,9 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; -using System.Collections.Generic; -using System.IO; using System.Management.Automation; using System.Reflection; using BenchmarkDotNet.Attributes; diff --git a/src/PSRule.Benchmark/Program.cs b/src/PSRule.Benchmark/Program.cs index f975f64086..dfabdf14fa 100644 --- a/src/PSRule.Benchmark/Program.cs +++ b/src/PSRule.Benchmark/Program.cs @@ -11,7 +11,6 @@ #endif -using System; using System.Diagnostics; using Microsoft.Extensions.CommandLineUtils; diff --git a/src/PSRule.BuildTool/BadgeResource.cs b/src/PSRule.BuildTool/BadgeResource.cs index 00652a4e07..9b68428b68 100644 --- a/src/PSRule.BuildTool/BadgeResource.cs +++ b/src/PSRule.BuildTool/BadgeResource.cs @@ -1,10 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.CommandLine.Invocation; using System.Drawing; -using System.IO; using System.Runtime.InteropServices; using Newtonsoft.Json; diff --git a/src/PSRule.BuildTool/Program.cs b/src/PSRule.BuildTool/Program.cs index 32971787b9..a7a2c0fe44 100644 --- a/src/PSRule.BuildTool/Program.cs +++ b/src/PSRule.BuildTool/Program.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. using System.CommandLine; -using System.Threading.Tasks; namespace PSRule.BuildTool { diff --git a/src/PSRule.Common.props b/src/PSRule.Common.props index 68024b2602..ea49a5e6f2 100644 --- a/src/PSRule.Common.props +++ b/src/PSRule.Common.props @@ -3,7 +3,9 @@ netstandard2.0 - 9.0 + 11.0 + + enable en-US true portable diff --git a/src/PSRule.Tool/ClientBuilder.cs b/src/PSRule.Tool/ClientBuilder.cs index b5051d7e62..9ae4eb5c7d 100644 --- a/src/PSRule.Tool/ClientBuilder.cs +++ b/src/PSRule.Tool/ClientBuilder.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. using System.CommandLine; -using System.IO; using System.Reflection; using PSRule.Tool.Resources; diff --git a/src/PSRule.Tool/ClientContext.cs b/src/PSRule.Tool/ClientContext.cs index 5d2eebfa5c..0d2ab8675f 100644 --- a/src/PSRule.Tool/ClientContext.cs +++ b/src/PSRule.Tool/ClientContext.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; - namespace PSRule.Tool { internal sealed class ClientContext diff --git a/src/PSRule.Tool/ClientHost.cs b/src/PSRule.Tool/ClientHost.cs index dcaf311d67..21ac7620b6 100644 --- a/src/PSRule.Tool/ClientHost.cs +++ b/src/PSRule.Tool/ClientHost.cs @@ -1,11 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.CommandLine; using System.CommandLine.Invocation; using System.CommandLine.IO; -using System.IO; using System.Management.Automation; using PSRule.Pipeline; diff --git a/src/PSRule.Tool/Program.cs b/src/PSRule.Tool/Program.cs index e1cd73bdde..6bdbef79fa 100644 --- a/src/PSRule.Tool/Program.cs +++ b/src/PSRule.Tool/Program.cs @@ -3,7 +3,6 @@ using System.CommandLine; using System.CommandLine.Parsing; -using System.Threading.Tasks; namespace PSRule.Tool { diff --git a/src/PSRule.Types/Converters/TypeConverter.cs b/src/PSRule.Types/Converters/TypeConverter.cs index 4722c27efe..a4641e74b2 100644 --- a/src/PSRule.Types/Converters/TypeConverter.cs +++ b/src/PSRule.Types/Converters/TypeConverter.cs @@ -1,11 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Threading; using Newtonsoft.Json.Linq; namespace PSRule.Converters diff --git a/src/PSRule.Types/Converters/Yaml/StringArrayConverter.cs b/src/PSRule.Types/Converters/Yaml/StringArrayConverter.cs index e22d544195..4bd8a99ac1 100644 --- a/src/PSRule.Types/Converters/Yaml/StringArrayConverter.cs +++ b/src/PSRule.Types/Converters/Yaml/StringArrayConverter.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; -using System.Collections.Generic; using YamlDotNet.Core; using YamlDotNet.Core.Events; using YamlDotNet.Serialization; diff --git a/src/PSRule.Types/Converters/Yaml/StringArrayMapConverter.cs b/src/PSRule.Types/Converters/Yaml/StringArrayMapConverter.cs index aa0b814fc0..3546e418fb 100644 --- a/src/PSRule.Types/Converters/Yaml/StringArrayMapConverter.cs +++ b/src/PSRule.Types/Converters/Yaml/StringArrayMapConverter.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; -using System.Collections.Generic; using PSRule.Data; using YamlDotNet.Core; using YamlDotNet.Core.Events; diff --git a/src/PSRule.Types/Data/DateVersion.cs b/src/PSRule.Types/Data/DateVersion.cs index 4ee7151db5..249103b987 100644 --- a/src/PSRule.Types/Data/DateVersion.cs +++ b/src/PSRule.Types/Data/DateVersion.cs @@ -1,10 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; -using System.Collections.Generic; using System.Diagnostics; -using System.Threading; namespace PSRule.Data { diff --git a/src/PSRule.Types/Data/SemanticVersion.cs b/src/PSRule.Types/Data/SemanticVersion.cs index 3d0fa64352..b14d57357f 100644 --- a/src/PSRule.Types/Data/SemanticVersion.cs +++ b/src/PSRule.Types/Data/SemanticVersion.cs @@ -1,10 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; -using System.Collections.Generic; using System.Diagnostics; -using System.Threading; namespace PSRule.Data { diff --git a/src/PSRule.Types/Data/StringArrayMap.cs b/src/PSRule.Types/Data/StringArrayMap.cs index b37d178bc8..ee475cee03 100644 --- a/src/PSRule.Types/Data/StringArrayMap.cs +++ b/src/PSRule.Types/Data/StringArrayMap.cs @@ -1,9 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Collections; -using System.Collections.Generic; using PSRule.Converters; namespace PSRule.Data diff --git a/src/PSRule.Types/DictionaryExtensions.cs b/src/PSRule.Types/DictionaryExtensions.cs index 0471647c0b..0f78e4d2df 100644 --- a/src/PSRule.Types/DictionaryExtensions.cs +++ b/src/PSRule.Types/DictionaryExtensions.cs @@ -1,9 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Collections; -using System.Collections.Generic; using System.Diagnostics; using PSRule.Converters; using PSRule.Data; diff --git a/src/PSRule.Types/Environment.cs b/src/PSRule.Types/Environment.cs index ac1256dbcc..dcf3afb62b 100644 --- a/src/PSRule.Types/Environment.cs +++ b/src/PSRule.Types/Environment.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; -using System.Collections.Generic; using System.Net; using System.Security; using PSRule.Data; diff --git a/src/PSRule.Types/HashtableExtensions.cs b/src/PSRule.Types/HashtableExtensions.cs index 4cb8ea9667..a0eaf3af87 100644 --- a/src/PSRule.Types/HashtableExtensions.cs +++ b/src/PSRule.Types/HashtableExtensions.cs @@ -1,9 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Collections; -using System.Collections.Generic; using System.Diagnostics; namespace PSRule diff --git a/src/PSRule.Types/Options/BaselineOption.cs b/src/PSRule.Types/Options/BaselineOption.cs index 77047fbecc..e36b62db7e 100644 --- a/src/PSRule.Types/Options/BaselineOption.cs +++ b/src/PSRule.Types/Options/BaselineOption.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; -using System.Collections.Generic; using System.ComponentModel; using PSRule.Data; diff --git a/src/PSRule/Badges/BadgeBuilder.cs b/src/PSRule/Badges/BadgeBuilder.cs index 00bd7e738b..4b9828aaef 100644 --- a/src/PSRule/Badges/BadgeBuilder.cs +++ b/src/PSRule/Badges/BadgeBuilder.cs @@ -1,9 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; -using System.Collections.Generic; -using System.IO; using PSRule.Configuration; using PSRule.Pipeline; using PSRule.Resources; diff --git a/src/PSRule/Commands/AssertExistsCommand.cs b/src/PSRule/Commands/AssertExistsCommand.cs index d4a1db3e6e..43dad8f642 100644 --- a/src/PSRule/Commands/AssertExistsCommand.cs +++ b/src/PSRule/Commands/AssertExistsCommand.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Collections.Generic; using System.Management.Automation; using PSRule.Pipeline; using PSRule.Resources; diff --git a/src/PSRule/Commands/AssertMatchCommand.cs b/src/PSRule/Commands/AssertMatchCommand.cs index 4d2afc602d..b995db943f 100644 --- a/src/PSRule/Commands/AssertMatchCommand.cs +++ b/src/PSRule/Commands/AssertMatchCommand.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Management.Automation; using System.Text.RegularExpressions; using PSRule.Pipeline; diff --git a/src/PSRule/Commands/AssertTypeOfCommand.cs b/src/PSRule/Commands/AssertTypeOfCommand.cs index 352a1e3a83..f6408d1c28 100644 --- a/src/PSRule/Commands/AssertTypeOfCommand.cs +++ b/src/PSRule/Commands/AssertTypeOfCommand.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Linq; using System.Management.Automation; using PSRule.Resources; using PSRule.Runtime; diff --git a/src/PSRule/Commands/AssertWithinCommand.cs b/src/PSRule/Commands/AssertWithinCommand.cs index 3c18907f32..1c5ba0afa4 100644 --- a/src/PSRule/Commands/AssertWithinCommand.cs +++ b/src/PSRule/Commands/AssertWithinCommand.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Management.Automation; using PSRule.Pipeline; using PSRule.Resources; diff --git a/src/PSRule/Commands/LanguageBlock.cs b/src/PSRule/Commands/LanguageBlock.cs index a8ab022cd5..d016e96584 100644 --- a/src/PSRule/Commands/LanguageBlock.cs +++ b/src/PSRule/Commands/LanguageBlock.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Collections; using System.Management.Automation; using PSRule.Annotations; diff --git a/src/PSRule/Commands/NewRuleDefinitionCommand.cs b/src/PSRule/Commands/NewRuleDefinitionCommand.cs index 671bf31191..7cd9cbe6dc 100644 --- a/src/PSRule/Commands/NewRuleDefinitionCommand.cs +++ b/src/PSRule/Commands/NewRuleDefinitionCommand.cs @@ -1,10 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Collections; using System.Management.Automation; -using System.Threading; using PSRule.Definitions; using PSRule.Definitions.Rules; using PSRule.Pipeline; diff --git a/src/PSRule/Commands/RuleKeyword.cs b/src/PSRule/Commands/RuleKeyword.cs index fe9651c634..f83a6cb662 100644 --- a/src/PSRule/Commands/RuleKeyword.cs +++ b/src/PSRule/Commands/RuleKeyword.cs @@ -1,10 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Collections; using System.Management.Automation; -using System.Threading; using PSRule.Definitions; using PSRule.Pipeline; using PSRule.Resources; diff --git a/src/PSRule/Common/ArrayExtensions.cs b/src/PSRule/Common/ArrayExtensions.cs index 2caf42f5eb..f1c85277c7 100644 --- a/src/PSRule/Common/ArrayExtensions.cs +++ b/src/PSRule/Common/ArrayExtensions.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; - namespace PSRule.Common { /// diff --git a/src/PSRule/Common/BaselineJsonSerializationMapper.cs b/src/PSRule/Common/BaselineJsonSerializationMapper.cs index 274b9683b2..fce90ff665 100644 --- a/src/PSRule/Common/BaselineJsonSerializationMapper.cs +++ b/src/PSRule/Common/BaselineJsonSerializationMapper.cs @@ -1,9 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; -using System.Collections.Generic; -using System.Linq; using System.Management.Automation; using Newtonsoft.Json; using PSRule.Configuration; diff --git a/src/PSRule/Common/BaselineYamlSerializationMapper.cs b/src/PSRule/Common/BaselineYamlSerializationMapper.cs index 0b57d496aa..ef2cb5f8af 100644 --- a/src/PSRule/Common/BaselineYamlSerializationMapper.cs +++ b/src/PSRule/Common/BaselineYamlSerializationMapper.cs @@ -1,9 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; -using System.Collections.Generic; -using System.Linq; using System.Management.Automation; using PSRule.Configuration; using PSRule.Definitions; diff --git a/src/PSRule/Common/ExpressionHelpers.cs b/src/PSRule/Common/ExpressionHelpers.cs index 07d348130c..cad3139c6d 100644 --- a/src/PSRule/Common/ExpressionHelpers.cs +++ b/src/PSRule/Common/ExpressionHelpers.cs @@ -1,15 +1,10 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Collections; -using System.Collections.Generic; -using System.IO; -using System.Linq; using System.Management.Automation; using System.Reflection; using System.Text.RegularExpressions; -using System.Threading; using Newtonsoft.Json.Linq; using PSRule.Configuration; using PSRule.Converters; diff --git a/src/PSRule/Common/ExternalToolHelper.cs b/src/PSRule/Common/ExternalToolHelper.cs index 00880027ea..a4856a7e66 100644 --- a/src/PSRule/Common/ExternalToolHelper.cs +++ b/src/PSRule/Common/ExternalToolHelper.cs @@ -1,12 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; -using System.Collections.Generic; using System.Diagnostics; -using System.IO; using System.Text; -using System.Threading; using PSRule.Configuration; namespace PSRule diff --git a/src/PSRule/Common/GitHelper.cs b/src/PSRule/Common/GitHelper.cs index f74f2afbd5..6acc7227ea 100644 --- a/src/PSRule/Common/GitHelper.cs +++ b/src/PSRule/Common/GitHelper.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; -using System.IO; using System.Runtime.InteropServices; using PSRule.Configuration; diff --git a/src/PSRule/Common/HashAlgorithmExtensions.cs b/src/PSRule/Common/HashAlgorithmExtensions.cs index 2bc30fd35a..1428edacd8 100644 --- a/src/PSRule/Common/HashAlgorithmExtensions.cs +++ b/src/PSRule/Common/HashAlgorithmExtensions.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Linq; using System.Security.Cryptography; namespace PSRule diff --git a/src/PSRule/Common/HashSetExtensions.cs b/src/PSRule/Common/HashSetExtensions.cs index fb065f898e..5d729481fb 100644 --- a/src/PSRule/Common/HashSetExtensions.cs +++ b/src/PSRule/Common/HashSetExtensions.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Collections.Generic; using PSRule.Definitions; namespace PSRule diff --git a/src/PSRule/Common/HashtableExtensions.cs b/src/PSRule/Common/HashtableExtensions.cs index e9dd8ee3d8..15c7bc0b9d 100644 --- a/src/PSRule/Common/HashtableExtensions.cs +++ b/src/PSRule/Common/HashtableExtensions.cs @@ -2,9 +2,7 @@ // Licensed under the MIT License. using System.Collections; -using System.Collections.Generic; using System.Diagnostics; -using System.Linq; namespace PSRule { diff --git a/src/PSRule/Common/JsonCommentWriter.cs b/src/PSRule/Common/JsonCommentWriter.cs index fe6d6648e1..1b385eb886 100644 --- a/src/PSRule/Common/JsonCommentWriter.cs +++ b/src/PSRule/Common/JsonCommentWriter.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.IO; using Newtonsoft.Json; namespace PSRule diff --git a/src/PSRule/Common/JsonConverters.cs b/src/PSRule/Common/JsonConverters.cs index cce5cc7d21..12dad92c5e 100644 --- a/src/PSRule/Common/JsonConverters.cs +++ b/src/PSRule/Common/JsonConverters.cs @@ -1,10 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; using System.Management.Automation; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; diff --git a/src/PSRule/Common/JsonReaderExtensions.cs b/src/PSRule/Common/JsonReaderExtensions.cs index 5d6c513cb1..604e0a6d21 100644 --- a/src/PSRule/Common/JsonReaderExtensions.cs +++ b/src/PSRule/Common/JsonReaderExtensions.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Diagnostics; using Newtonsoft.Json; using PSRule.Definitions; diff --git a/src/PSRule/Common/KeyMapDictionary.cs b/src/PSRule/Common/KeyMapDictionary.cs index cbea199dd9..35be6cf7ab 100644 --- a/src/PSRule/Common/KeyMapDictionary.cs +++ b/src/PSRule/Common/KeyMapDictionary.cs @@ -1,11 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Collections; -using System.Collections.Generic; using System.Dynamic; -using System.Linq; namespace PSRule { @@ -20,7 +17,7 @@ public abstract class KeyMapDictionary : DynamicObject, IDictionary /// Create an empty map. /// - internal protected KeyMapDictionary() + protected internal KeyMapDictionary() { _Map = new Dictionary(StringComparer.OrdinalIgnoreCase); } @@ -30,7 +27,7 @@ internal protected KeyMapDictionary() /// /// An existing instance to copy key/ values from. /// Is raised if the map is null. - internal protected KeyMapDictionary(KeyMapDictionary map) + protected internal KeyMapDictionary(KeyMapDictionary map) { if (map == null) throw new ArgumentNullException(nameof(map)); @@ -42,7 +39,7 @@ internal protected KeyMapDictionary(KeyMapDictionary map) /// Create a map intially populated with values copied from a dictionary. /// /// An existing dictionary to copy key/ values from. - internal protected KeyMapDictionary(IDictionary dictionary) + protected internal KeyMapDictionary(IDictionary dictionary) { _Map = dictionary == null ? new Dictionary(StringComparer.OrdinalIgnoreCase) : @@ -53,7 +50,7 @@ internal protected KeyMapDictionary(IDictionary dictionary) /// Create a map intially populated with values copied from a hashtable. /// /// An existing hashtable to copy key/ values from. - internal protected KeyMapDictionary(Hashtable hashtable) + protected internal KeyMapDictionary(Hashtable hashtable) : this() { Load(hashtable); diff --git a/src/PSRule/Common/ListExtensions.cs b/src/PSRule/Common/ListExtensions.cs index 63bf7e7f0d..4150442863 100644 --- a/src/PSRule/Common/ListExtensions.cs +++ b/src/PSRule/Common/ListExtensions.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Collections.Generic; - namespace PSRule { internal static class ListExtensions diff --git a/src/PSRule/Common/LoggerExtensions.cs b/src/PSRule/Common/LoggerExtensions.cs index 901882c529..eb0ff19857 100644 --- a/src/PSRule/Common/LoggerExtensions.cs +++ b/src/PSRule/Common/LoggerExtensions.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; -using System.Threading; using PSRule.Definitions; using PSRule.Pipeline; using PSRule.Resources; diff --git a/src/PSRule/Common/PSObjectExtensions.cs b/src/PSRule/Common/PSObjectExtensions.cs index 9102cea7df..294eb78c9f 100644 --- a/src/PSRule/Common/PSObjectExtensions.cs +++ b/src/PSRule/Common/PSObjectExtensions.cs @@ -1,12 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Collections; using System.Globalization; -using System.IO; using System.Management.Automation; -using System.Threading; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using PSRule.Data; diff --git a/src/PSRule/Common/ReasonExtensions.cs b/src/PSRule/Common/ReasonExtensions.cs index 4441ad7aaf..9f39c999fe 100644 --- a/src/PSRule/Common/ReasonExtensions.cs +++ b/src/PSRule/Common/ReasonExtensions.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; -using System.Collections.Generic; using PSRule.Definitions; namespace PSRule diff --git a/src/PSRule/Common/ResourceExtensions.cs b/src/PSRule/Common/ResourceExtensions.cs index 54545c657c..eef9850253 100644 --- a/src/PSRule/Common/ResourceExtensions.cs +++ b/src/PSRule/Common/ResourceExtensions.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Collections.Generic; using PSRule.Definitions; using PSRule.Definitions.Baselines; diff --git a/src/PSRule/Common/RunspaceContextDiagnosticExtensions.cs b/src/PSRule/Common/RunspaceContextDiagnosticExtensions.cs index 65ea4cfe30..7c402e1ce7 100644 --- a/src/PSRule/Common/RunspaceContextDiagnosticExtensions.cs +++ b/src/PSRule/Common/RunspaceContextDiagnosticExtensions.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Threading; using PSRule.Configuration; using PSRule.Definitions; using PSRule.Pipeline; diff --git a/src/PSRule/Common/StringExtensions.cs b/src/PSRule/Common/StringExtensions.cs index d2a4af20fc..2657cf4daa 100644 --- a/src/PSRule/Common/StringExtensions.cs +++ b/src/PSRule/Common/StringExtensions.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; -using System.Collections.Generic; using System.Text; namespace PSRule diff --git a/src/PSRule/Common/YamlConverters.cs b/src/PSRule/Common/YamlConverters.cs index 5b1452d71a..b9fd1a8344 100644 --- a/src/PSRule/Common/YamlConverters.cs +++ b/src/PSRule/Common/YamlConverters.cs @@ -1,10 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Collections; -using System.Collections.Generic; -using System.Linq; using System.Management.Automation; using System.Reflection; using PSRule.Annotations; diff --git a/src/PSRule/Configuration/BannerFormat.cs b/src/PSRule/Configuration/BannerFormat.cs index eb891e4b69..dca343bb95 100644 --- a/src/PSRule/Configuration/BannerFormat.cs +++ b/src/PSRule/Configuration/BannerFormat.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using Newtonsoft.Json; using Newtonsoft.Json.Converters; diff --git a/src/PSRule/Configuration/BaselineOption.cs b/src/PSRule/Configuration/BaselineOption.cs index a48f3b0bfd..0636b94454 100644 --- a/src/PSRule/Configuration/BaselineOption.cs +++ b/src/PSRule/Configuration/BaselineOption.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. using System.Collections; -using System.Collections.Generic; using PSRule.Definitions.Baselines; namespace PSRule.Configuration diff --git a/src/PSRule/Configuration/BindingOption.cs b/src/PSRule/Configuration/BindingOption.cs index 638f500235..fb2ed7a064 100644 --- a/src/PSRule/Configuration/BindingOption.cs +++ b/src/PSRule/Configuration/BindingOption.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.ComponentModel; namespace PSRule.Configuration diff --git a/src/PSRule/Configuration/ConfigurationOption.cs b/src/PSRule/Configuration/ConfigurationOption.cs index b9376773ca..7a50d91bb1 100644 --- a/src/PSRule/Configuration/ConfigurationOption.cs +++ b/src/PSRule/Configuration/ConfigurationOption.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. using System.Collections; -using System.Collections.Generic; namespace PSRule.Configuration { diff --git a/src/PSRule/Configuration/ConventionOption.cs b/src/PSRule/Configuration/ConventionOption.cs index fb2f48afc1..1fcdafd205 100644 --- a/src/PSRule/Configuration/ConventionOption.cs +++ b/src/PSRule/Configuration/ConventionOption.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; -using System.Collections.Generic; using System.ComponentModel; namespace PSRule.Configuration diff --git a/src/PSRule/Configuration/ExecutionOption.cs b/src/PSRule/Configuration/ExecutionOption.cs index 0d6ba26fa0..d2467b500c 100644 --- a/src/PSRule/Configuration/ExecutionOption.cs +++ b/src/PSRule/Configuration/ExecutionOption.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; -using System.Collections.Generic; using System.ComponentModel; namespace PSRule.Configuration diff --git a/src/PSRule/Configuration/FieldMap.cs b/src/PSRule/Configuration/FieldMap.cs index 47b567a3d7..a35af3a905 100644 --- a/src/PSRule/Configuration/FieldMap.cs +++ b/src/PSRule/Configuration/FieldMap.cs @@ -1,9 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Collections; -using System.Collections.Generic; using System.Dynamic; namespace PSRule.Configuration diff --git a/src/PSRule/Configuration/FooterFormat.cs b/src/PSRule/Configuration/FooterFormat.cs index 6ad37103f4..f91a7bcae7 100644 --- a/src/PSRule/Configuration/FooterFormat.cs +++ b/src/PSRule/Configuration/FooterFormat.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using Newtonsoft.Json; using Newtonsoft.Json.Converters; diff --git a/src/PSRule/Configuration/IncludeOption.cs b/src/PSRule/Configuration/IncludeOption.cs index 2581fc1237..82cdc5f871 100644 --- a/src/PSRule/Configuration/IncludeOption.cs +++ b/src/PSRule/Configuration/IncludeOption.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; -using System.Collections.Generic; using System.ComponentModel; namespace PSRule.Configuration diff --git a/src/PSRule/Configuration/InputOption.cs b/src/PSRule/Configuration/InputOption.cs index 3508318bcc..66bfeebb6e 100644 --- a/src/PSRule/Configuration/InputOption.cs +++ b/src/PSRule/Configuration/InputOption.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; -using System.Collections.Generic; using System.ComponentModel; namespace PSRule.Configuration diff --git a/src/PSRule/Configuration/JobSummaryFormat.cs b/src/PSRule/Configuration/JobSummaryFormat.cs index e6edb4830b..6ce30f3ef4 100644 --- a/src/PSRule/Configuration/JobSummaryFormat.cs +++ b/src/PSRule/Configuration/JobSummaryFormat.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using Newtonsoft.Json; using Newtonsoft.Json.Converters; diff --git a/src/PSRule/Configuration/LoggingOption.cs b/src/PSRule/Configuration/LoggingOption.cs index b8ab77eb04..0e58bff3dc 100644 --- a/src/PSRule/Configuration/LoggingOption.cs +++ b/src/PSRule/Configuration/LoggingOption.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; -using System.Collections.Generic; using System.ComponentModel; namespace PSRule.Configuration diff --git a/src/PSRule/Configuration/OutputOption.cs b/src/PSRule/Configuration/OutputOption.cs index 2e1ed3a79d..39d9e2e0d6 100644 --- a/src/PSRule/Configuration/OutputOption.cs +++ b/src/PSRule/Configuration/OutputOption.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; -using System.Collections.Generic; using System.ComponentModel; using PSRule.Rules; diff --git a/src/PSRule/Configuration/PSRuleOption.cs b/src/PSRule/Configuration/PSRuleOption.cs index 3b470277b2..e31bd07d6f 100644 --- a/src/PSRule/Configuration/PSRuleOption.cs +++ b/src/PSRule/Configuration/PSRuleOption.cs @@ -1,14 +1,10 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Collections; -using System.Collections.Generic; using System.Diagnostics; using System.Globalization; -using System.IO; using System.Management.Automation; -using System.Threading; using Newtonsoft.Json; using PSRule.Converters.Yaml; using PSRule.Definitions.Baselines; diff --git a/src/PSRule/Configuration/PipelineHook.cs b/src/PSRule/Configuration/PipelineHook.cs index 11bf22589a..b10b1b9945 100644 --- a/src/PSRule/Configuration/PipelineHook.cs +++ b/src/PSRule/Configuration/PipelineHook.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Collections.Generic; - namespace PSRule.Configuration { /// diff --git a/src/PSRule/Configuration/RepositoryOption.cs b/src/PSRule/Configuration/RepositoryOption.cs index caf0ef35e8..92c2bcf1de 100644 --- a/src/PSRule/Configuration/RepositoryOption.cs +++ b/src/PSRule/Configuration/RepositoryOption.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; -using System.Collections.Generic; using System.ComponentModel; namespace PSRule.Configuration diff --git a/src/PSRule/Configuration/RequiresOption.cs b/src/PSRule/Configuration/RequiresOption.cs index 6bae994c8f..b26c695a24 100644 --- a/src/PSRule/Configuration/RequiresOption.cs +++ b/src/PSRule/Configuration/RequiresOption.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Collections.Generic; using PSRule.Data; namespace PSRule.Configuration diff --git a/src/PSRule/Configuration/RuleOption.cs b/src/PSRule/Configuration/RuleOption.cs index 84a471d5da..f465f41a54 100644 --- a/src/PSRule/Configuration/RuleOption.cs +++ b/src/PSRule/Configuration/RuleOption.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Collections; using System.ComponentModel; using PSRule.Definitions; diff --git a/src/PSRule/Configuration/SuppressionOption.cs b/src/PSRule/Configuration/SuppressionOption.cs index 8c78075f04..c94b6f1485 100644 --- a/src/PSRule/Configuration/SuppressionOption.cs +++ b/src/PSRule/Configuration/SuppressionOption.cs @@ -1,9 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Collections; -using System.Collections.Generic; namespace PSRule.Configuration { diff --git a/src/PSRule/Configuration/SuppressionRule.cs b/src/PSRule/Configuration/SuppressionRule.cs index 2ed1ae9f89..a7c5a66261 100644 --- a/src/PSRule/Configuration/SuppressionRule.cs +++ b/src/PSRule/Configuration/SuppressionRule.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Linq; - namespace PSRule.Configuration { /// diff --git a/src/PSRule/Data/ITargetIssueCollection.cs b/src/PSRule/Data/ITargetIssueCollection.cs index 71a6b44bcd..ccdf59f3b2 100644 --- a/src/PSRule/Data/ITargetIssueCollection.cs +++ b/src/PSRule/Data/ITargetIssueCollection.cs @@ -1,10 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; -using System.Collections.Generic; -using System.Linq; - namespace PSRule.Data { /// diff --git a/src/PSRule/Data/ITargetSourceCollection.cs b/src/PSRule/Data/ITargetSourceCollection.cs index a20ead9a97..3ba6a4eceb 100644 --- a/src/PSRule/Data/ITargetSourceCollection.cs +++ b/src/PSRule/Data/ITargetSourceCollection.cs @@ -1,9 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; -using System.Collections.Generic; - namespace PSRule.Data { /// diff --git a/src/PSRule/Data/InputFileInfo.cs b/src/PSRule/Data/InputFileInfo.cs index b935e3bade..17f151f1ea 100644 --- a/src/PSRule/Data/InputFileInfo.cs +++ b/src/PSRule/Data/InputFileInfo.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.IO; - namespace PSRule.Data { /// diff --git a/src/PSRule/Data/InputFileInfoCollection.cs b/src/PSRule/Data/InputFileInfoCollection.cs index cc51259603..52b9363609 100644 --- a/src/PSRule/Data/InputFileInfoCollection.cs +++ b/src/PSRule/Data/InputFileInfoCollection.cs @@ -1,10 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Collections; -using System.Collections.Generic; -using System.Linq; namespace PSRule.Data { diff --git a/src/PSRule/Data/TargetIssueInfo.cs b/src/PSRule/Data/TargetIssueInfo.cs index e41518fcdf..9f22238482 100644 --- a/src/PSRule/Data/TargetIssueInfo.cs +++ b/src/PSRule/Data/TargetIssueInfo.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Management.Automation; using Newtonsoft.Json; diff --git a/src/PSRule/Data/TargetSourceInfo.cs b/src/PSRule/Data/TargetSourceInfo.cs index d1d4208fd9..677f24a48e 100644 --- a/src/PSRule/Data/TargetSourceInfo.cs +++ b/src/PSRule/Data/TargetSourceInfo.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; -using System.IO; using System.Management.Automation; using Newtonsoft.Json; using PSRule.Configuration; diff --git a/src/PSRule/Definitions/Baselines/Baseline.cs b/src/PSRule/Definitions/Baselines/Baseline.cs index ad463c7846..b28146e57e 100644 --- a/src/PSRule/Definitions/Baselines/Baseline.cs +++ b/src/PSRule/Definitions/Baselines/Baseline.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; -using System.Collections.Generic; using System.Management.Automation; using Newtonsoft.Json; using PSRule.Configuration; diff --git a/src/PSRule/Definitions/Conventions/BaseConvention.cs b/src/PSRule/Definitions/Conventions/BaseConvention.cs index 9773c88e11..dac0fbd488 100644 --- a/src/PSRule/Definitions/Conventions/BaseConvention.cs +++ b/src/PSRule/Definitions/Conventions/BaseConvention.cs @@ -1,9 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Collections; -using System.Collections.Generic; using System.Diagnostics; using System.Management.Automation; using PSRule.Pipeline; diff --git a/src/PSRule/Definitions/Conventions/ConventionComparer.cs b/src/PSRule/Definitions/Conventions/ConventionComparer.cs index e8f4b35810..aae17b4c57 100644 --- a/src/PSRule/Definitions/Conventions/ConventionComparer.cs +++ b/src/PSRule/Definitions/Conventions/ConventionComparer.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Collections.Generic; using PSRule.Runtime; namespace PSRule.Definitions.Conventions diff --git a/src/PSRule/Definitions/Conventions/ScriptBlockConvention.cs b/src/PSRule/Definitions/Conventions/ScriptBlockConvention.cs index a935dff0dd..4db8f4b86b 100644 --- a/src/PSRule/Definitions/Conventions/ScriptBlockConvention.cs +++ b/src/PSRule/Definitions/Conventions/ScriptBlockConvention.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Collections; using System.Management.Automation; using PSRule.Pipeline; @@ -106,14 +105,11 @@ private void Dispose(bool disposing) { if (disposing) { - if (_Begin != null) - _Begin.Dispose(); + _Begin?.Dispose(); - if (_Process != null) - _Process.Dispose(); + _Process?.Dispose(); - if (_End != null) - _End.Dispose(); + _End?.Dispose(); } _Disposed = true; } diff --git a/src/PSRule/Definitions/DependencyGraph.cs b/src/PSRule/Definitions/DependencyGraph.cs index ae1577f348..7041194184 100644 --- a/src/PSRule/Definitions/DependencyGraph.cs +++ b/src/PSRule/Definitions/DependencyGraph.cs @@ -1,9 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; -using System.Collections.Generic; - namespace PSRule.Definitions { internal sealed class DependencyGraph : IDisposable where T : IDependencyTarget diff --git a/src/PSRule/Definitions/DependencyGraphBuilder.cs b/src/PSRule/Definitions/DependencyGraphBuilder.cs index 9cff623e2d..6f8cc94391 100644 --- a/src/PSRule/Definitions/DependencyGraphBuilder.cs +++ b/src/PSRule/Definitions/DependencyGraphBuilder.cs @@ -1,10 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; using PSRule.Pipeline; using PSRule.Resources; using PSRule.Rules; diff --git a/src/PSRule/Definitions/DependencyTargetCollection.cs b/src/PSRule/Definitions/DependencyTargetCollection.cs index 38df977ad3..73765e714c 100644 --- a/src/PSRule/Definitions/DependencyTargetCollection.cs +++ b/src/PSRule/Definitions/DependencyTargetCollection.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Collections.Generic; - namespace PSRule.Definitions { internal sealed class DependencyTargetCollection where T : IDependencyTarget diff --git a/src/PSRule/Definitions/Expressions/Exceptions.cs b/src/PSRule/Definitions/Expressions/Exceptions.cs index da85f400bb..ab3842f81f 100644 --- a/src/PSRule/Definitions/Expressions/Exceptions.cs +++ b/src/PSRule/Definitions/Expressions/Exceptions.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Runtime.Serialization; using System.Security.Permissions; using PSRule.Pipeline; diff --git a/src/PSRule/Definitions/Expressions/ExpressionContext.cs b/src/PSRule/Definitions/Expressions/ExpressionContext.cs index 8950e634ef..31a8f8dae9 100644 --- a/src/PSRule/Definitions/Expressions/ExpressionContext.cs +++ b/src/PSRule/Definitions/Expressions/ExpressionContext.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; -using System.Collections.Generic; using System.Diagnostics; using PSRule.Pipeline; using PSRule.Runtime; diff --git a/src/PSRule/Definitions/Expressions/FunctionBuilder.cs b/src/PSRule/Definitions/Expressions/FunctionBuilder.cs index 2f1e2aac11..ff437156c9 100644 --- a/src/PSRule/Definitions/Expressions/FunctionBuilder.cs +++ b/src/PSRule/Definitions/Expressions/FunctionBuilder.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; -using System.Collections.Generic; using System.Diagnostics; namespace PSRule.Definitions.Expressions diff --git a/src/PSRule/Definitions/Expressions/Functions.cs b/src/PSRule/Definitions/Expressions/Functions.cs index 7c8a8bdc10..dc6426691c 100644 --- a/src/PSRule/Definitions/Expressions/Functions.cs +++ b/src/PSRule/Definitions/Expressions/Functions.cs @@ -1,10 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; -using System.Linq; using System.Text; -using System.Threading; using PSRule.Common; using PSRule.Resources; using PSRule.Runtime; @@ -44,7 +41,7 @@ internal static class Functions /// /// The available built-in functions. /// - internal readonly static IFunctionDescriptor[] Builtin = new IFunctionDescriptor[] + internal static readonly IFunctionDescriptor[] Builtin = new IFunctionDescriptor[] { new FunctionDescriptor(CONFIGURATION, Configuration), new FunctionDescriptor(PATH, Path), diff --git a/src/PSRule/Definitions/Expressions/LanguageExpressions.cs b/src/PSRule/Definitions/Expressions/LanguageExpressions.cs index 3e1c6a999f..69332563c3 100644 --- a/src/PSRule/Definitions/Expressions/LanguageExpressions.cs +++ b/src/PSRule/Definitions/Expressions/LanguageExpressions.cs @@ -1,11 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; -using System.Collections.Generic; using System.Diagnostics; -using System.Linq; -using System.Threading; using PSRule.Configuration; using PSRule.Data; using PSRule.Pipeline; @@ -249,8 +245,7 @@ private static LanguageExpressionOuterFn Scope(LanguageExpressionOuterFn fn) { return (context, o) => { - if (RunspaceContext.CurrentThread != null) - RunspaceContext.CurrentThread.EnterLanguageScope(context.Source); + RunspaceContext.CurrentThread?.EnterLanguageScope(context.Source); return fn(context, o); }; @@ -486,7 +481,7 @@ internal sealed class LanguageExpressions private const string DOT = "."; // Define built-ins - internal readonly static ILanguageExpresssionDescriptor[] Builtin = new ILanguageExpresssionDescriptor[] + internal static readonly ILanguageExpresssionDescriptor[] Builtin = new ILanguageExpresssionDescriptor[] { // Operators new LanguageExpresssionDescriptor(IF, LanguageExpressionType.Operator, If), diff --git a/src/PSRule/Definitions/Expressions/Primitives.cs b/src/PSRule/Definitions/Expressions/Primitives.cs index 581c9e0d7b..34b9250c4c 100644 --- a/src/PSRule/Definitions/Expressions/Primitives.cs +++ b/src/PSRule/Definitions/Expressions/Primitives.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Collections.Generic; using System.Diagnostics; using PSRule.Pipeline; diff --git a/src/PSRule/Definitions/ICondition.cs b/src/PSRule/Definitions/ICondition.cs index 47b8167cb0..e23f6553aa 100644 --- a/src/PSRule/Definitions/ICondition.cs +++ b/src/PSRule/Definitions/ICondition.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Management.Automation; namespace PSRule.Definitions diff --git a/src/PSRule/Definitions/ILanguageBlock.cs b/src/PSRule/Definitions/ILanguageBlock.cs index ffa3491bd1..ff34caf7f2 100644 --- a/src/PSRule/Definitions/ILanguageBlock.cs +++ b/src/PSRule/Definitions/ILanguageBlock.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using PSRule.Pipeline; namespace PSRule.Definitions diff --git a/src/PSRule/Definitions/IRuleResultV2.cs b/src/PSRule/Definitions/IRuleResultV2.cs index eff51db125..8b94f97668 100644 --- a/src/PSRule/Definitions/IRuleResultV2.cs +++ b/src/PSRule/Definitions/IRuleResultV2.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. using System.Collections; -using System.Collections.Generic; using PSRule.Definitions.Rules; using PSRule.Rules; diff --git a/src/PSRule/Definitions/ISuppressionInfo.cs b/src/PSRule/Definitions/ISuppressionInfo.cs index 16585883fc..cf6f5d723a 100644 --- a/src/PSRule/Definitions/ISuppressionInfo.cs +++ b/src/PSRule/Definitions/ISuppressionInfo.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Collections.Generic; - namespace PSRule.Definitions { /// diff --git a/src/PSRule/Definitions/Resource.cs b/src/PSRule/Definitions/Resource.cs index 6c7af13ba3..ad3fd769d4 100644 --- a/src/PSRule/Definitions/Resource.cs +++ b/src/PSRule/Definitions/Resource.cs @@ -1,11 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Collections; -using System.Collections.Generic; using System.Diagnostics; -using System.IO; using System.Text; using PSRule.Converters.Yaml; using PSRule.Definitions.Rules; @@ -485,7 +482,7 @@ public sealed class ResourceExtent /// /// Create a resource. /// - internal protected Resource(ResourceKind kind, string apiVersion, SourceFile source, ResourceMetadata metadata, IResourceHelpInfo info, ISourceExtent extent, TSpec spec) + protected internal Resource(ResourceKind kind, string apiVersion, SourceFile source, ResourceMetadata metadata, IResourceHelpInfo info, ISourceExtent extent, TSpec spec) { Kind = kind; ApiVersion = apiVersion; diff --git a/src/PSRule/Definitions/ResourceId.cs b/src/PSRule/Definitions/ResourceId.cs index 9aad18aad9..da8850fcf0 100644 --- a/src/PSRule/Definitions/ResourceId.cs +++ b/src/PSRule/Definitions/ResourceId.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; -using System.Collections.Generic; using System.Diagnostics; using PSRule.Runtime; @@ -191,7 +189,7 @@ private static bool TryParse(string id, out string scope, out string name) /// internal sealed class ResourceIdEqualityComparer : IEqualityComparer, IEqualityComparer { - public readonly static ResourceIdEqualityComparer Default = new(); + public static readonly ResourceIdEqualityComparer Default = new(); public static bool IdEquals(ResourceId x, ResourceId y) { diff --git a/src/PSRule/Definitions/ResourceIndex.cs b/src/PSRule/Definitions/ResourceIndex.cs index 3afbb76a9b..ab1b881ede 100644 --- a/src/PSRule/Definitions/ResourceIndex.cs +++ b/src/PSRule/Definitions/ResourceIndex.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Collections.Generic; - namespace PSRule.Definitions { internal sealed class ResourceIndex diff --git a/src/PSRule/Definitions/ResourceValidator.cs b/src/PSRule/Definitions/ResourceValidator.cs index 9bbfe8e220..05d81311d3 100644 --- a/src/PSRule/Definitions/ResourceValidator.cs +++ b/src/PSRule/Definitions/ResourceValidator.cs @@ -1,10 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Management.Automation; using System.Text.RegularExpressions; -using System.Threading; using PSRule.Resources; using PSRule.Runtime; diff --git a/src/PSRule/Definitions/ResultDetail.cs b/src/PSRule/Definitions/ResultDetail.cs index 4ac030cc36..32fbf6d6b4 100644 --- a/src/PSRule/Definitions/ResultDetail.cs +++ b/src/PSRule/Definitions/ResultDetail.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Collections.Generic; - namespace PSRule.Definitions { internal sealed class ResultDetail : IResultDetailV2 diff --git a/src/PSRule/Definitions/ResultReason.cs b/src/PSRule/Definitions/ResultReason.cs index c6193a4c22..4c3525c6fd 100644 --- a/src/PSRule/Definitions/ResultReason.cs +++ b/src/PSRule/Definitions/ResultReason.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Threading; using PSRule.Runtime; namespace PSRule.Definitions diff --git a/src/PSRule/Definitions/Rules/Rule.cs b/src/PSRule/Definitions/Rules/Rule.cs index 958cfcc0b2..85ecc8e954 100644 --- a/src/PSRule/Definitions/Rules/Rule.cs +++ b/src/PSRule/Definitions/Rules/Rule.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using Newtonsoft.Json; using PSRule.Definitions.Expressions; using PSRule.Pipeline; diff --git a/src/PSRule/Definitions/Rules/RuleFilter.cs b/src/PSRule/Definitions/Rules/RuleFilter.cs index a148dc37d2..3ac4f5b10f 100644 --- a/src/PSRule/Definitions/Rules/RuleFilter.cs +++ b/src/PSRule/Definitions/Rules/RuleFilter.cs @@ -1,9 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Collections; -using System.Collections.Generic; using System.Management.Automation; using PSRule.Configuration; using PSRule.Resources; diff --git a/src/PSRule/Definitions/Rules/RuleVisitor.cs b/src/PSRule/Definitions/Rules/RuleVisitor.cs index 511689b482..0bc5975e68 100644 --- a/src/PSRule/Definitions/Rules/RuleVisitor.cs +++ b/src/PSRule/Definitions/Rules/RuleVisitor.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Diagnostics; using System.Management.Automation; using PSRule.Definitions.Expressions; diff --git a/src/PSRule/Definitions/Selectors/SelectorVisitor.cs b/src/PSRule/Definitions/Selectors/SelectorVisitor.cs index 34e766de3e..57e1fda7f1 100644 --- a/src/PSRule/Definitions/Selectors/SelectorVisitor.cs +++ b/src/PSRule/Definitions/Selectors/SelectorVisitor.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Diagnostics; using PSRule.Definitions.Expressions; using PSRule.Pipeline; diff --git a/src/PSRule/Definitions/Spec.cs b/src/PSRule/Definitions/Spec.cs index 734869656d..d6983bf877 100644 --- a/src/PSRule/Definitions/Spec.cs +++ b/src/PSRule/Definitions/Spec.cs @@ -50,7 +50,7 @@ internal static class Specs /// /// The built-in resource types. /// - public readonly static ISpecDescriptor[] BuiltinTypes = new ISpecDescriptor[] + public static readonly ISpecDescriptor[] BuiltinTypes = new ISpecDescriptor[] { new SpecDescriptor(V1, Rule), new SpecDescriptor(V1, Baseline), diff --git a/src/PSRule/Definitions/SpecFactory.cs b/src/PSRule/Definitions/SpecFactory.cs index d042b1e100..3c1ef3bc08 100644 --- a/src/PSRule/Definitions/SpecFactory.cs +++ b/src/PSRule/Definitions/SpecFactory.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; -using System.Collections.Generic; using PSRule.Annotations; using PSRule.Pipeline; diff --git a/src/PSRule/Definitions/SuppressionGroups/SuppressionGroup.cs b/src/PSRule/Definitions/SuppressionGroups/SuppressionGroup.cs index 6c674ddaea..00c21d821d 100644 --- a/src/PSRule/Definitions/SuppressionGroups/SuppressionGroup.cs +++ b/src/PSRule/Definitions/SuppressionGroups/SuppressionGroup.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using PSRule.Definitions.Expressions; using PSRule.Pipeline; diff --git a/src/PSRule/Definitions/SuppressionGroups/SuppressionGroupVisitor.cs b/src/PSRule/Definitions/SuppressionGroups/SuppressionGroupVisitor.cs index 3e5c0a7785..abaca8de81 100644 --- a/src/PSRule/Definitions/SuppressionGroups/SuppressionGroupVisitor.cs +++ b/src/PSRule/Definitions/SuppressionGroups/SuppressionGroupVisitor.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using PSRule.Definitions.Expressions; using PSRule.Pipeline; using PSRule.Resources; diff --git a/src/PSRule/Help/HelpLexer.cs b/src/PSRule/Help/HelpLexer.cs index 3bfdd752c1..b2fd2e4730 100644 --- a/src/PSRule/Help/HelpLexer.cs +++ b/src/PSRule/Help/HelpLexer.cs @@ -1,12 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; -using System.Collections.Generic; using System.Globalization; using System.Resources; using System.Text; -using System.Threading; using PSRule.Definitions; using PSRule.Resources; diff --git a/src/PSRule/Help/MarkdownLexer.cs b/src/PSRule/Help/MarkdownLexer.cs index 3e12e4f198..56ff737df6 100644 --- a/src/PSRule/Help/MarkdownLexer.cs +++ b/src/PSRule/Help/MarkdownLexer.cs @@ -1,9 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; -using System.Collections.Generic; - namespace PSRule.Help { internal abstract class MarkdownLexer diff --git a/src/PSRule/Help/MarkdownReader.cs b/src/PSRule/Help/MarkdownReader.cs index ebc721397a..f978884b78 100644 --- a/src/PSRule/Help/MarkdownReader.cs +++ b/src/PSRule/Help/MarkdownReader.cs @@ -29,7 +29,7 @@ internal sealed class MarkdownReader /// /// Line ending characters: \r, \n /// - private readonly static char[] LineEndingCharacters = new char[] { '\r', '\n' }; + private static readonly char[] LineEndingCharacters = new char[] { '\r', '\n' }; private const char Hash = '#'; private const char Asterix = '*'; diff --git a/src/PSRule/Help/MarkdownStream.cs b/src/PSRule/Help/MarkdownStream.cs index ba52edcc89..1a2056e6b1 100644 --- a/src/PSRule/Help/MarkdownStream.cs +++ b/src/PSRule/Help/MarkdownStream.cs @@ -1,9 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Diagnostics; -using System.Linq; namespace PSRule.Help { @@ -92,7 +90,7 @@ private sealed class StreamCursor private const char AngleOpen = '<'; private const char AngleClose = '>'; private const char Backslash = '\\'; - private readonly static char[] NewLineStopCharacters = new char[] { '\r', '\n' }; + private static readonly char[] NewLineStopCharacters = new char[] { '\r', '\n' }; public MarkdownStream(string markdown) { diff --git a/src/PSRule/Help/MarkdownToken.cs b/src/PSRule/Help/MarkdownToken.cs index 2f90561047..97d58ae837 100644 --- a/src/PSRule/Help/MarkdownToken.cs +++ b/src/PSRule/Help/MarkdownToken.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Diagnostics; namespace PSRule.Help diff --git a/src/PSRule/Help/MetadataLexer.cs b/src/PSRule/Help/MetadataLexer.cs index cd5a79a177..10ceabeb00 100644 --- a/src/PSRule/Help/MetadataLexer.cs +++ b/src/PSRule/Help/MetadataLexer.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Collections.Generic; - namespace PSRule.Help { internal sealed class MetadataLexer : MarkdownLexer diff --git a/src/PSRule/Help/Models.cs b/src/PSRule/Help/Models.cs index 9f1af4ebf4..d8186e150c 100644 --- a/src/PSRule/Help/Models.cs +++ b/src/PSRule/Help/Models.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using PSRule.Definitions; namespace PSRule.Help diff --git a/src/PSRule/Help/TokenStream.cs b/src/PSRule/Help/TokenStream.cs index c93e33ddd1..923f5e3fbd 100644 --- a/src/PSRule/Help/TokenStream.cs +++ b/src/PSRule/Help/TokenStream.cs @@ -1,11 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Collections; -using System.Collections.Generic; using System.Diagnostics; -using System.Linq; namespace PSRule.Help { diff --git a/src/PSRule/Host/Host.cs b/src/PSRule/Host/Host.cs index 2db27a6738..622a3da961 100644 --- a/src/PSRule/Host/Host.cs +++ b/src/PSRule/Host/Host.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Management.Automation; using System.Management.Automation.Runspaces; using PSRule.Commands; @@ -115,7 +114,7 @@ internal static class HostState /// /// Define language commands. /// - private readonly static SessionStateCmdletEntry[] BuiltInCmdlets = new SessionStateCmdletEntry[] + private static readonly SessionStateCmdletEntry[] BuiltInCmdlets = new SessionStateCmdletEntry[] { new SessionStateCmdletEntry("New-RuleDefinition", typeof(NewRuleDefinitionCommand), null), new SessionStateCmdletEntry("Export-PSRuleConvention", typeof(ExportConventionCommand), null), @@ -132,7 +131,7 @@ internal static class HostState /// /// Define language aliases. /// - private readonly static SessionStateAliasEntry[] BuiltInAliases = new SessionStateAliasEntry[] + private static readonly SessionStateAliasEntry[] BuiltInAliases = new SessionStateAliasEntry[] { new SessionStateAliasEntry(LanguageKeywords.Rule, "New-RuleDefinition", string.Empty, ScopedItemOptions.ReadOnly), new SessionStateAliasEntry(LanguageKeywords.Recommend, "Write-Recommendation", string.Empty, ScopedItemOptions.ReadOnly), diff --git a/src/PSRule/Host/HostHelper.cs b/src/PSRule/Host/HostHelper.cs index ee1ebf6798..e6bd60c062 100644 --- a/src/PSRule/Host/HostHelper.cs +++ b/src/PSRule/Host/HostHelper.cs @@ -1,12 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Collections; -using System.Collections.Generic; using System.Collections.ObjectModel; -using System.IO; -using System.Linq; using System.Management.Automation; using Newtonsoft.Json; using PSRule.Annotations; diff --git a/src/PSRule/Host/RuleLanguageAst.cs b/src/PSRule/Host/RuleLanguageAst.cs index 9235680c9a..e6fd52fb6b 100644 --- a/src/PSRule/Host/RuleLanguageAst.cs +++ b/src/PSRule/Host/RuleLanguageAst.cs @@ -1,11 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; -using System.Collections.Generic; using System.Management.Automation; using System.Management.Automation.Language; -using System.Threading; using PSRule.Definitions; using PSRule.Resources; diff --git a/src/PSRule/Pipeline/AssertPipeline.cs b/src/PSRule/Pipeline/AssertPipeline.cs index 126a9ecc1a..928be7b58c 100644 --- a/src/PSRule/Pipeline/AssertPipeline.cs +++ b/src/PSRule/Pipeline/AssertPipeline.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Collections.Generic; using System.Management.Automation; using PSRule.Configuration; using PSRule.Definitions.Rules; @@ -72,8 +71,7 @@ public override void WriteObject(object sendToPipeline, bool enumerateCollection return; ProcessResult(result); - if (_InnerWriter != null) - _InnerWriter.WriteObject(sendToPipeline, enumerateCollection); + _InnerWriter?.WriteObject(sendToPipeline, enumerateCollection); } public override void WriteWarning(string message) @@ -152,8 +150,7 @@ public override void End() } finally { - if (_InnerWriter != null) - _InnerWriter.End(); + _InnerWriter?.End(); } } @@ -164,8 +161,7 @@ private void ProcessResult(InvokeResult result) _ErrorCount += result.Error; _TotalCount += result.Total; _Level = _Level.GetWorstCase(result.Level); - if (_Results != null) - _Results.AddRange(result.AsRecord()); + _Results?.AddRange(result.AsRecord()); } } diff --git a/src/PSRule/Pipeline/CommandLineBuilder.cs b/src/PSRule/Pipeline/CommandLineBuilder.cs index cea81918af..21904539cf 100644 --- a/src/PSRule/Pipeline/CommandLineBuilder.cs +++ b/src/PSRule/Pipeline/CommandLineBuilder.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using PSRule.Configuration; namespace PSRule.Pipeline diff --git a/src/PSRule/Pipeline/Exceptions.cs b/src/PSRule/Pipeline/Exceptions.cs index bdabe56ea0..a501ff0a55 100644 --- a/src/PSRule/Pipeline/Exceptions.cs +++ b/src/PSRule/Pipeline/Exceptions.cs @@ -1,11 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Management.Automation; using System.Runtime.Serialization; using System.Security.Permissions; -using System.Threading; namespace PSRule.Pipeline { diff --git a/src/PSRule/Pipeline/Formatters/AssertFormatter.cs b/src/PSRule/Pipeline/Formatters/AssertFormatter.cs index 9aa711c942..3687836ae2 100644 --- a/src/PSRule/Pipeline/Formatters/AssertFormatter.cs +++ b/src/PSRule/Pipeline/Formatters/AssertFormatter.cs @@ -1,10 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; -using System.Collections.Generic; using System.Management.Automation; -using System.Threading; using PSRule.Configuration; using PSRule.Definitions; using PSRule.Resources; diff --git a/src/PSRule/Pipeline/Formatters/ClientFormatter.cs b/src/PSRule/Pipeline/Formatters/ClientFormatter.cs index 936f87dfff..d9e35bc99f 100644 --- a/src/PSRule/Pipeline/Formatters/ClientFormatter.cs +++ b/src/PSRule/Pipeline/Formatters/ClientFormatter.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using PSRule.Configuration; using PSRule.Resources; using PSRule.Rules; diff --git a/src/PSRule/Pipeline/Formatters/VisualStudioCodeFormatter.cs b/src/PSRule/Pipeline/Formatters/VisualStudioCodeFormatter.cs index 680703cc30..96ec9ad517 100644 --- a/src/PSRule/Pipeline/Formatters/VisualStudioCodeFormatter.cs +++ b/src/PSRule/Pipeline/Formatters/VisualStudioCodeFormatter.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using PSRule.Configuration; using PSRule.Resources; using PSRule.Rules; diff --git a/src/PSRule/Pipeline/GetBaselinePipeline.cs b/src/PSRule/Pipeline/GetBaselinePipeline.cs index 3055f2d9ae..5b4c871635 100644 --- a/src/PSRule/Pipeline/GetBaselinePipeline.cs +++ b/src/PSRule/Pipeline/GetBaselinePipeline.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Linq; using PSRule.Definitions; using PSRule.Definitions.Baselines; using PSRule.Host; diff --git a/src/PSRule/Pipeline/GetRuleHelpPipeline.cs b/src/PSRule/Pipeline/GetRuleHelpPipeline.cs index ded0f85113..3f5c5b5abc 100644 --- a/src/PSRule/Pipeline/GetRuleHelpPipeline.cs +++ b/src/PSRule/Pipeline/GetRuleHelpPipeline.cs @@ -1,10 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Diagnostics; using System.Management.Automation; -using System.Threading; using PSRule.Configuration; using PSRule.Host; using PSRule.Resources; diff --git a/src/PSRule/Pipeline/GetTargetPipeline.cs b/src/PSRule/Pipeline/GetTargetPipeline.cs index 6b757134e0..9c4bd46e44 100644 --- a/src/PSRule/Pipeline/GetTargetPipeline.cs +++ b/src/PSRule/Pipeline/GetTargetPipeline.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Management.Automation; using PSRule.Configuration; diff --git a/src/PSRule/Pipeline/HostContext.cs b/src/PSRule/Pipeline/HostContext.cs index fe199fb004..5bc49983df 100644 --- a/src/PSRule/Pipeline/HostContext.cs +++ b/src/PSRule/Pipeline/HostContext.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.IO; using System.Management.Automation; using PSRule.Definitions; diff --git a/src/PSRule/Pipeline/IBindingOption.cs b/src/PSRule/Pipeline/IBindingOption.cs index 0e3228ca9b..7bfc0bd85b 100644 --- a/src/PSRule/Pipeline/IBindingOption.cs +++ b/src/PSRule/Pipeline/IBindingOption.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Diagnostics; using PSRule.Configuration; diff --git a/src/PSRule/Pipeline/InvokePipelineBuilder.cs b/src/PSRule/Pipeline/InvokePipelineBuilder.cs index be2cb2a9f0..b1da569cd2 100644 --- a/src/PSRule/Pipeline/InvokePipelineBuilder.cs +++ b/src/PSRule/Pipeline/InvokePipelineBuilder.cs @@ -1,10 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; using PSRule.Configuration; using PSRule.Host; diff --git a/src/PSRule/Pipeline/InvokeResult.cs b/src/PSRule/Pipeline/InvokeResult.cs index 36be3d9ac7..1ced61d474 100644 --- a/src/PSRule/Pipeline/InvokeResult.cs +++ b/src/PSRule/Pipeline/InvokeResult.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Collections.Generic; using PSRule.Definitions.Rules; using PSRule.Rules; diff --git a/src/PSRule/Pipeline/InvokeRulePipeline.cs b/src/PSRule/Pipeline/InvokeRulePipeline.cs index b06fb14888..9ff40a82f2 100644 --- a/src/PSRule/Pipeline/InvokeRulePipeline.cs +++ b/src/PSRule/Pipeline/InvokeRulePipeline.cs @@ -1,9 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; -using System.Collections.Generic; -using System.Linq; using System.Management.Automation; using PSRule.Configuration; using PSRule.Definitions; diff --git a/src/PSRule/Pipeline/ModulePathComparer.cs b/src/PSRule/Pipeline/ModulePathComparer.cs index 409f6ba3c1..af0b270b60 100644 --- a/src/PSRule/Pipeline/ModulePathComparer.cs +++ b/src/PSRule/Pipeline/ModulePathComparer.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Collections.Generic; -using System.IO; using PSRule.Data; namespace PSRule.Pipeline diff --git a/src/PSRule/Pipeline/OptionContext.cs b/src/PSRule/Pipeline/OptionContext.cs index d975cfd7fd..9d21ee2684 100644 --- a/src/PSRule/Pipeline/OptionContext.cs +++ b/src/PSRule/Pipeline/OptionContext.cs @@ -1,9 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Collections; -using System.Collections.Generic; using PSRule.Configuration; using PSRule.Definitions; using PSRule.Definitions.Baselines; diff --git a/src/PSRule/Pipeline/Output/FileOutputWriter.cs b/src/PSRule/Pipeline/Output/FileOutputWriter.cs index c7ffcdd91f..2ddbc367fc 100644 --- a/src/PSRule/Pipeline/Output/FileOutputWriter.cs +++ b/src/PSRule/Pipeline/Output/FileOutputWriter.cs @@ -1,10 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.IO; using System.Management.Automation; using System.Text; -using System.Threading; using PSRule.Configuration; using PSRule.Resources; diff --git a/src/PSRule/Pipeline/Output/HostPipelineWriter.cs b/src/PSRule/Pipeline/Output/HostPipelineWriter.cs index eecbab3e4d..c9c7d89957 100644 --- a/src/PSRule/Pipeline/Output/HostPipelineWriter.cs +++ b/src/PSRule/Pipeline/Output/HostPipelineWriter.cs @@ -1,10 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; -using System.Collections.Generic; using System.Management.Automation; -using System.Threading; using PSRule.Configuration; using PSRule.Rules; diff --git a/src/PSRule/Pipeline/Output/JobSummaryWriter.cs b/src/PSRule/Pipeline/Output/JobSummaryWriter.cs index fe44643b4d..2050a5fec9 100644 --- a/src/PSRule/Pipeline/Output/JobSummaryWriter.cs +++ b/src/PSRule/Pipeline/Output/JobSummaryWriter.cs @@ -1,10 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; using System.Text; using PSRule.Configuration; using PSRule.Resources; diff --git a/src/PSRule/Pipeline/Output/JsonOutputWriter.cs b/src/PSRule/Pipeline/Output/JsonOutputWriter.cs index 744378267b..0ffe1416b0 100644 --- a/src/PSRule/Pipeline/Output/JsonOutputWriter.cs +++ b/src/PSRule/Pipeline/Output/JsonOutputWriter.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Collections.Generic; -using System.IO; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using PSRule.Configuration; diff --git a/src/PSRule/Pipeline/Output/MarkdownOutputWriter.cs b/src/PSRule/Pipeline/Output/MarkdownOutputWriter.cs index 6105914ea8..17dd451668 100644 --- a/src/PSRule/Pipeline/Output/MarkdownOutputWriter.cs +++ b/src/PSRule/Pipeline/Output/MarkdownOutputWriter.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Linq; using System.Text; using PSRule.Configuration; using PSRule.Definitions; diff --git a/src/PSRule/Pipeline/Output/NUnit3OutputWriter.cs b/src/PSRule/Pipeline/Output/NUnit3OutputWriter.cs index 6b154a4333..725c114395 100644 --- a/src/PSRule/Pipeline/Output/NUnit3OutputWriter.cs +++ b/src/PSRule/Pipeline/Output/NUnit3OutputWriter.cs @@ -1,10 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; -using System.Linq; using System.Text; -using System.Threading; using System.Xml; using PSRule.Configuration; using PSRule.Resources; diff --git a/src/PSRule/Pipeline/Output/OutputStringWriter.cs b/src/PSRule/Pipeline/Output/OutputStringWriter.cs index 8df6adfb0f..5ad81605b6 100644 --- a/src/PSRule/Pipeline/Output/OutputStringWriter.cs +++ b/src/PSRule/Pipeline/Output/OutputStringWriter.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.IO; using System.Text; using PSRule.Configuration; diff --git a/src/PSRule/Pipeline/Output/SarifOutputWriter.cs b/src/PSRule/Pipeline/Output/SarifOutputWriter.cs index d7ff1cd6d2..65e2557e40 100644 --- a/src/PSRule/Pipeline/Output/SarifOutputWriter.cs +++ b/src/PSRule/Pipeline/Output/SarifOutputWriter.cs @@ -1,9 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; -using System.Collections.Generic; -using System.IO; using System.Text; using Microsoft.CodeAnalysis.Sarif; using PSRule.Configuration; diff --git a/src/PSRule/Pipeline/Output/WideOutputWriter.cs b/src/PSRule/Pipeline/Output/WideOutputWriter.cs index 50db32363a..0af8c03fe8 100644 --- a/src/PSRule/Pipeline/Output/WideOutputWriter.cs +++ b/src/PSRule/Pipeline/Output/WideOutputWriter.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Collections.Generic; using System.Management.Automation; using PSRule.Configuration; using PSRule.Definitions.Rules; diff --git a/src/PSRule/Pipeline/Output/YamlOutputWriter.cs b/src/PSRule/Pipeline/Output/YamlOutputWriter.cs index 8b6ab8879a..2191d33717 100644 --- a/src/PSRule/Pipeline/Output/YamlOutputWriter.cs +++ b/src/PSRule/Pipeline/Output/YamlOutputWriter.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Collections.Generic; -using System.IO; using PSRule.Configuration; using PSRule.Definitions.Baselines; using YamlDotNet.Core; diff --git a/src/PSRule/Pipeline/PathBuilder.cs b/src/PSRule/Pipeline/PathBuilder.cs index d28f8fa2da..9620357ca9 100644 --- a/src/PSRule/Pipeline/PathBuilder.cs +++ b/src/PSRule/Pipeline/PathBuilder.cs @@ -1,10 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; -using System.Collections.Generic; using System.Diagnostics; -using System.IO; using System.Management.Automation; using PSRule.Configuration; using PSRule.Data; diff --git a/src/PSRule/Pipeline/PathFilter.cs b/src/PSRule/Pipeline/PathFilter.cs index 2e78cb3093..b7488d3f6f 100644 --- a/src/PSRule/Pipeline/PathFilter.cs +++ b/src/PSRule/Pipeline/PathFilter.cs @@ -1,10 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; -using System.Collections.Generic; using System.Diagnostics; -using System.IO; namespace PSRule.Pipeline { diff --git a/src/PSRule/Pipeline/PipelineBuilder.cs b/src/PSRule/Pipeline/PipelineBuilder.cs index 248237aa4e..562ae0b1a5 100644 --- a/src/PSRule/Pipeline/PipelineBuilder.cs +++ b/src/PSRule/Pipeline/PipelineBuilder.cs @@ -1,12 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Collections; -using System.Collections.Generic; using System.Globalization; using System.Management.Automation; -using System.Threading; using PSRule.Configuration; using PSRule.Data; using PSRule.Definitions; diff --git a/src/PSRule/Pipeline/PipelineContext.cs b/src/PSRule/Pipeline/PipelineContext.cs index ffa6412fc6..bf9287b3ff 100644 --- a/src/PSRule/Pipeline/PipelineContext.cs +++ b/src/PSRule/Pipeline/PipelineContext.cs @@ -1,11 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Collections; -using System.Collections.Generic; using System.Diagnostics; -using System.Linq; using System.Management.Automation; using System.Management.Automation.Runspaces; using System.Security.Cryptography; diff --git a/src/PSRule/Pipeline/PipelineHookActions.cs b/src/PSRule/Pipeline/PipelineHookActions.cs index 2118de5a63..2d6afdddb1 100644 --- a/src/PSRule/Pipeline/PipelineHookActions.cs +++ b/src/PSRule/Pipeline/PipelineHookActions.cs @@ -1,9 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Globalization; -using System.Linq; using System.Management.Automation; using System.Text; using Newtonsoft.Json; diff --git a/src/PSRule/Pipeline/PipelineLogger.cs b/src/PSRule/Pipeline/PipelineLogger.cs index 642e072785..a5562a0613 100644 --- a/src/PSRule/Pipeline/PipelineLogger.cs +++ b/src/PSRule/Pipeline/PipelineLogger.cs @@ -1,10 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; -using System.Collections.Generic; using System.Management.Automation; -using System.Threading; using PSRule.Configuration; namespace PSRule.Pipeline diff --git a/src/PSRule/Pipeline/PipelineReader.cs b/src/PSRule/Pipeline/PipelineReader.cs index 6a648878b5..3e6c9b4db1 100644 --- a/src/PSRule/Pipeline/PipelineReader.cs +++ b/src/PSRule/Pipeline/PipelineReader.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Collections.Concurrent; using System.Management.Automation; diff --git a/src/PSRule/Pipeline/PipelineReciever.cs b/src/PSRule/Pipeline/PipelineReciever.cs index 95c4bfaef5..f163be024e 100644 --- a/src/PSRule/Pipeline/PipelineReciever.cs +++ b/src/PSRule/Pipeline/PipelineReciever.cs @@ -1,10 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Collections; -using System.Collections.Generic; -using System.IO; using System.Management.Automation; using System.Net; using Newtonsoft.Json; diff --git a/src/PSRule/Pipeline/PipelineWriter.cs b/src/PSRule/Pipeline/PipelineWriter.cs index 42be0a8df3..9ab64fea95 100644 --- a/src/PSRule/Pipeline/PipelineWriter.cs +++ b/src/PSRule/Pipeline/PipelineWriter.cs @@ -1,11 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; -using System.Collections.Generic; -using System.IO; using System.Management.Automation; -using System.Threading; using PSRule.Configuration; using PSRule.Resources; using PSRule.Rules; diff --git a/src/PSRule/Pipeline/PipelineWriterExtensions.cs b/src/PSRule/Pipeline/PipelineWriterExtensions.cs index 96263c715e..631421dec0 100644 --- a/src/PSRule/Pipeline/PipelineWriterExtensions.cs +++ b/src/PSRule/Pipeline/PipelineWriterExtensions.cs @@ -1,9 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Management.Automation; -using System.Threading; using PSRule.Resources; namespace PSRule.Pipeline diff --git a/src/PSRule/Pipeline/RulePipeline.cs b/src/PSRule/Pipeline/RulePipeline.cs index f048e7b2aa..e62a9e01a3 100644 --- a/src/PSRule/Pipeline/RulePipeline.cs +++ b/src/PSRule/Pipeline/RulePipeline.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Management.Automation; using PSRule.Runtime; diff --git a/src/PSRule/Pipeline/Source.cs b/src/PSRule/Pipeline/Source.cs index ac7d809f6c..6d4b13cb29 100644 --- a/src/PSRule/Pipeline/Source.cs +++ b/src/PSRule/Pipeline/Source.cs @@ -1,10 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Collections; using System.Diagnostics; -using System.IO; using System.Management.Automation; using Newtonsoft.Json; using PSRule.Runtime; diff --git a/src/PSRule/Pipeline/SourcePipeline.cs b/src/PSRule/Pipeline/SourcePipeline.cs index 0495322996..0b472771dd 100644 --- a/src/PSRule/Pipeline/SourcePipeline.cs +++ b/src/PSRule/Pipeline/SourcePipeline.cs @@ -1,14 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Collections; -using System.Collections.Generic; -using System.IO; -using System.Linq; using System.Management.Automation; using System.Reflection; -using System.Threading; using PSRule.Configuration; using PSRule.Pipeline.Output; using PSRule.Resources; diff --git a/src/PSRule/Pipeline/TargetBinder.cs b/src/PSRule/Pipeline/TargetBinder.cs index 962772b7a5..d47232cd30 100644 --- a/src/PSRule/Pipeline/TargetBinder.cs +++ b/src/PSRule/Pipeline/TargetBinder.cs @@ -1,9 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Collections; -using System.Collections.Generic; using PSRule.Configuration; using PSRule.Runtime; using static PSRule.Pipeline.TargetBinder; diff --git a/src/PSRule/Pipeline/TargetObject.cs b/src/PSRule/Pipeline/TargetObject.cs index a7a0e6e804..970c75447e 100644 --- a/src/PSRule/Pipeline/TargetObject.cs +++ b/src/PSRule/Pipeline/TargetObject.cs @@ -1,9 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Collections; -using System.Collections.Generic; using System.Diagnostics; using System.Management.Automation; using Newtonsoft.Json.Linq; diff --git a/src/PSRule/Pipeline/TestPipeline.cs b/src/PSRule/Pipeline/TestPipeline.cs index 82ab93f675..41b9d7e06f 100644 --- a/src/PSRule/Pipeline/TestPipeline.cs +++ b/src/PSRule/Pipeline/TestPipeline.cs @@ -41,7 +41,7 @@ protected override PipelineWriter PrepareWriter() return new BooleanWriter(GetOutput(), Option.Output.Outcome.Value, ShouldProcess); } - private new static bool ShouldProcess(string target, string action) + private static new bool ShouldProcess(string target, string action) { return true; } diff --git a/src/PSRule/Rules/ErrorInfo.cs b/src/PSRule/Rules/ErrorInfo.cs index 4e416da6a2..8dffd4e404 100644 --- a/src/PSRule/Rules/ErrorInfo.cs +++ b/src/PSRule/Rules/ErrorInfo.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Management.Automation; using System.Management.Automation.Language; using Newtonsoft.Json; diff --git a/src/PSRule/Rules/PowerShellCondition.cs b/src/PSRule/Rules/PowerShellCondition.cs index b28faa6471..0a58ab2158 100644 --- a/src/PSRule/Rules/PowerShellCondition.cs +++ b/src/PSRule/Rules/PowerShellCondition.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Collections.ObjectModel; using System.Management.Automation; using PSRule.Definitions; diff --git a/src/PSRule/Rules/Rule.cs b/src/PSRule/Rules/Rule.cs index 43043a7393..d060cc2ca6 100644 --- a/src/PSRule/Rules/Rule.cs +++ b/src/PSRule/Rules/Rule.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.ComponentModel; using Newtonsoft.Json; using PSRule.Data; diff --git a/src/PSRule/Rules/RuleBlock.cs b/src/PSRule/Rules/RuleBlock.cs index 0b575cfdca..91c9bc3336 100644 --- a/src/PSRule/Rules/RuleBlock.cs +++ b/src/PSRule/Rules/RuleBlock.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Collections; using System.Diagnostics; using Newtonsoft.Json; diff --git a/src/PSRule/Rules/RuleHelpInfo.cs b/src/PSRule/Rules/RuleHelpInfo.cs index 9b86a6c049..2e050edba3 100644 --- a/src/PSRule/Rules/RuleHelpInfo.cs +++ b/src/PSRule/Rules/RuleHelpInfo.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Collections; using System.Text; using Newtonsoft.Json; diff --git a/src/PSRule/Rules/RuleOutcome.cs b/src/PSRule/Rules/RuleOutcome.cs index 24d222b4ae..fbaf3da370 100644 --- a/src/PSRule/Rules/RuleOutcome.cs +++ b/src/PSRule/Rules/RuleOutcome.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using Newtonsoft.Json; using Newtonsoft.Json.Converters; diff --git a/src/PSRule/Rules/SuppressionFilter.cs b/src/PSRule/Rules/SuppressionFilter.cs index 3c492cf55b..a0deb33e20 100644 --- a/src/PSRule/Rules/SuppressionFilter.cs +++ b/src/PSRule/Rules/SuppressionFilter.cs @@ -1,10 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; -using System.Collections.Generic; using System.Diagnostics; -using System.Threading; using PSRule.Configuration; using PSRule.Definitions; using PSRule.Definitions.SuppressionGroups; diff --git a/src/PSRule/Runtime/Assert.cs b/src/PSRule/Runtime/Assert.cs index 4dfd60748a..74e52a577e 100644 --- a/src/PSRule/Runtime/Assert.cs +++ b/src/PSRule/Runtime/Assert.cs @@ -1,11 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; -using System.IO; using System.Management.Automation; using System.Net; -using System.Threading; using Manatee.Json; using Manatee.Json.Schema; using Manatee.Json.Serialization; diff --git a/src/PSRule/Runtime/AssertResult.cs b/src/PSRule/Runtime/AssertResult.cs index 9c5f79dd6f..53a322c1f7 100644 --- a/src/PSRule/Runtime/AssertResult.cs +++ b/src/PSRule/Runtime/AssertResult.cs @@ -1,9 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; -using System.Collections.Generic; -using System.Threading; using PSRule.Definitions; using PSRule.Pipeline; using PSRule.Resources; @@ -108,8 +105,7 @@ public AssertResult PathPrefix(string prefix) /// Replacement arguments for the format string. public AssertResult Reason(string text, params object[] args) { - if (_Reason != null) - _Reason.Clear(); + _Reason?.Clear(); AddReason(Operand.FromTarget(), text, args); return this; @@ -123,8 +119,7 @@ public AssertResult Reason(string text, params object[] args) /// Replacement arguments for the format string. public AssertResult ReasonFrom(string path, string text, params object[] args) { - if (_Reason != null) - _Reason.Clear(); + _Reason?.Clear(); AddReason(Operand.FromPath(path), text, args); return this; diff --git a/src/PSRule/Runtime/Configuration.cs b/src/PSRule/Runtime/Configuration.cs index e16a6569ef..c23cc03db8 100644 --- a/src/PSRule/Runtime/Configuration.cs +++ b/src/PSRule/Runtime/Configuration.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. using System.Collections; -using System.Collections.Generic; using System.Dynamic; namespace PSRule.Runtime diff --git a/src/PSRule/Runtime/ILogger.cs b/src/PSRule/Runtime/ILogger.cs index ed41b4bc8e..86e3828b0b 100644 --- a/src/PSRule/Runtime/ILogger.cs +++ b/src/PSRule/Runtime/ILogger.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; - namespace PSRule.Runtime { /// diff --git a/src/PSRule/Runtime/LanguageScope.cs b/src/PSRule/Runtime/LanguageScope.cs index bed92a081f..1259d18202 100644 --- a/src/PSRule/Runtime/LanguageScope.cs +++ b/src/PSRule/Runtime/LanguageScope.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; -using System.Collections.Generic; using System.Diagnostics; using PSRule.Definitions; using PSRule.Pipeline; diff --git a/src/PSRule/Runtime/LanguageScriptBlock.cs b/src/PSRule/Runtime/LanguageScriptBlock.cs index 227298f272..cae792f2c5 100644 --- a/src/PSRule/Runtime/LanguageScriptBlock.cs +++ b/src/PSRule/Runtime/LanguageScriptBlock.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Management.Automation; namespace PSRule.Runtime diff --git a/src/PSRule/Runtime/ObjectHelper.cs b/src/PSRule/Runtime/ObjectHelper.cs index 81d9726d87..99f90247d9 100644 --- a/src/PSRule/Runtime/ObjectHelper.cs +++ b/src/PSRule/Runtime/ObjectHelper.cs @@ -39,8 +39,7 @@ private static PathExpression GetPathExpression(IBindingContext bindingContext, if (bindingContext == null || !bindingContext.GetPathExpression(path, out var expression)) { expression = PathExpression.Create(path); - if (bindingContext != null) - bindingContext.CachePathExpression(path, expression); + bindingContext?.CachePathExpression(path, expression); } return expression; } diff --git a/src/PSRule/Runtime/ObjectPath/Exceptions.cs b/src/PSRule/Runtime/ObjectPath/Exceptions.cs index f8918e6c38..9e5e99fe08 100644 --- a/src/PSRule/Runtime/ObjectPath/Exceptions.cs +++ b/src/PSRule/Runtime/ObjectPath/Exceptions.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Runtime.Serialization; using System.Security.Permissions; using PSRule.Pipeline; diff --git a/src/PSRule/Runtime/ObjectPath/PathExpression.cs b/src/PSRule/Runtime/ObjectPath/PathExpression.cs index f42ce4abc0..f02b62b178 100644 --- a/src/PSRule/Runtime/ObjectPath/PathExpression.cs +++ b/src/PSRule/Runtime/ObjectPath/PathExpression.cs @@ -1,9 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Collections.Generic; using System.Diagnostics; -using System.Linq; namespace PSRule.Runtime.ObjectPath { diff --git a/src/PSRule/Runtime/ObjectPath/PathExpressionBuilder.cs b/src/PSRule/Runtime/ObjectPath/PathExpressionBuilder.cs index 1f22582bec..d0ef2413ab 100644 --- a/src/PSRule/Runtime/ObjectPath/PathExpressionBuilder.cs +++ b/src/PSRule/Runtime/ObjectPath/PathExpressionBuilder.cs @@ -1,15 +1,11 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Collections; -using System.Collections.Generic; using System.ComponentModel; using System.Dynamic; -using System.Linq; using System.Management.Automation; using System.Reflection; -using System.Threading; using Newtonsoft.Json.Linq; using PSRule.Resources; diff --git a/src/PSRule/Runtime/ObjectPath/PathTokenizer.cs b/src/PSRule/Runtime/ObjectPath/PathTokenizer.cs index 8fd15af457..52623e7d05 100644 --- a/src/PSRule/Runtime/ObjectPath/PathTokenizer.cs +++ b/src/PSRule/Runtime/ObjectPath/PathTokenizer.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; -using System.Collections.Generic; using System.Diagnostics; namespace PSRule.Runtime.ObjectPath diff --git a/src/PSRule/Runtime/ObjectPath/Tokens.cs b/src/PSRule/Runtime/ObjectPath/Tokens.cs index 1ec8789bfa..53eee45387 100644 --- a/src/PSRule/Runtime/ObjectPath/Tokens.cs +++ b/src/PSRule/Runtime/ObjectPath/Tokens.cs @@ -104,8 +104,8 @@ internal interface IPathToken [DebuggerDisplay("Type = {Type}, Arg = {Arg}")] internal sealed class PathToken : IPathToken { - public readonly static PathToken RootRef = new(PathTokenType.RootRef); - public readonly static PathToken CurrentRef = new(PathTokenType.CurrentRef); + public static readonly PathToken RootRef = new(PathTokenType.RootRef); + public static readonly PathToken CurrentRef = new(PathTokenType.CurrentRef); public PathTokenType Type { get; } diff --git a/src/PSRule/Runtime/Operand.cs b/src/PSRule/Runtime/Operand.cs index 911edbfdf6..4734fe2159 100644 --- a/src/PSRule/Runtime/Operand.cs +++ b/src/PSRule/Runtime/Operand.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; - namespace PSRule.Runtime { /// diff --git a/src/PSRule/Runtime/PSRule.cs b/src/PSRule/Runtime/PSRule.cs index edee4c87c0..98d8108b0d 100644 --- a/src/PSRule/Runtime/PSRule.cs +++ b/src/PSRule/Runtime/PSRule.cs @@ -1,11 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Collections; -using System.Collections.Generic; -using System.IO; -using System.Linq; using System.Management.Automation; using PSRule.Badges; using PSRule.Configuration; diff --git a/src/PSRule/Runtime/PSRuleMemberInfo.cs b/src/PSRule/Runtime/PSRuleMemberInfo.cs index afd5aab203..00b1333f9f 100644 --- a/src/PSRule/Runtime/PSRuleMemberInfo.cs +++ b/src/PSRule/Runtime/PSRuleMemberInfo.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Collections.Generic; using System.Diagnostics; using System.Management.Automation; using Newtonsoft.Json; diff --git a/src/PSRule/Runtime/RuleConditionResult.cs b/src/PSRule/Runtime/RuleConditionResult.cs index 307e6747ea..a6baf29000 100644 --- a/src/PSRule/Runtime/RuleConditionResult.cs +++ b/src/PSRule/Runtime/RuleConditionResult.cs @@ -1,14 +1,13 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Collections.Generic; using PSRule.Definitions; namespace PSRule.Runtime { internal static class RuleConditionHelper { - private readonly static RuleConditionResult Empty = new(pass: 0, count: 0, hadErrors: false); + private static readonly RuleConditionResult Empty = new(pass: 0, count: 0, hadErrors: false); internal static RuleConditionResult Create(IEnumerable value) { diff --git a/src/PSRule/Runtime/RunspaceContext.cs b/src/PSRule/Runtime/RunspaceContext.cs index 9f7560eb1f..a43dbed8e1 100644 --- a/src/PSRule/Runtime/RunspaceContext.cs +++ b/src/PSRule/Runtime/RunspaceContext.cs @@ -1,14 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; -using System.Collections.Generic; using System.Diagnostics; -using System.IO; -using System.Linq; using System.Management.Automation; using System.Management.Automation.Language; -using System.Threading; using PSRule.Configuration; using PSRule.Definitions; using PSRule.Pipeline; diff --git a/tests/PSRule.Tests/OptionContextTests.cs b/tests/PSRule.Tests/OptionContextTests.cs index f3dd9be016..10205c33e5 100644 --- a/tests/PSRule.Tests/OptionContextTests.cs +++ b/tests/PSRule.Tests/OptionContextTests.cs @@ -46,7 +46,7 @@ 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.Add(new OptionContext.BaselineScope(OptionContext.ScopeType.Explicit, new string[] { "abc" }, null, null)); optionContext.UpdateLanguageScope(localScope); ruleFilter = localScope.GetFilter(ResourceKind.Rule) as RuleFilter; Assert.NotNull(ruleFilter); @@ -88,17 +88,17 @@ public MockScope(string name) public void AddService(string name, object service) { - + } public void Configure(Dictionary configuration) { - + } public void Dispose() { - + } public IResourceFilter GetFilter(ResourceKind kind) @@ -133,12 +133,12 @@ public bool TryGetType(object o, out string type, out string path) public void WithBinding(IBindingOption bindingOption) { - + } public void WithCulture(string[] strings) { - + } public void WithFilter(IResourceFilter resourceFilter) From ab8faedca56dd54b7cc32c768fcafe300913e3e9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 15 Jul 2023 14:40:11 +1000 Subject: [PATCH 039/177] Bump pymdown-extensions from 10.0.1 to 10.1 (#1567) Bumps [pymdown-extensions](https://github.com/facelessuser/pymdown-extensions) from 10.0.1 to 10.1. - [Release notes](https://github.com/facelessuser/pymdown-extensions/releases) - [Commits](https://github.com/facelessuser/pymdown-extensions/compare/10.0.1...10.1.0) --- updated-dependencies: - dependency-name: pymdown-extensions dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements-docs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-docs.txt b/requirements-docs.txt index fbba40e92f..49c1dd3446 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -1,6 +1,6 @@ mkdocs==1.4.3 mkdocs-material==9.1.18 -pymdown-extensions==10.0.1 +pymdown-extensions==10.1 mike==1.1.2 mkdocs-simple-hooks==0.1.5 mkdocs-git-revision-date-plugin==0.3.2 From 57e0c6d8ab60cc4d0adaf3e9ee2098be15853226 Mon Sep 17 00:00:00 2001 From: Bernie White Date: Sun, 16 Jul 2023 23:10:44 +1000 Subject: [PATCH 040/177] Quality updates (#1568) * Quality updates * Fix path reporting in devskim * Updates --- .github/workflows/analyze.yaml | 2 +- src/PSRule.Tool/ClientHelper.cs | 2 +- src/PSRule.Types/Data/IFileInfo.cs | 21 +++ src/PSRule.Types/Data/ISourceInfo.cs | 12 ++ .../Data/ITargetInfo.cs | 0 src/PSRule.Types/Data/ITargetObject.cs | 12 ++ .../Data/InputFileInfo.cs | 8 +- .../Data/TargetIssueInfo.cs | 30 ----- .../Data/TargetSourceInfo.cs | 46 ++----- src/PSRule.Types/Environment.cs | 126 +++++++++++++++++- src/PSRule.Types/Expressions/Helpers.cs | 26 ++++ src/PSRule.Types/PSRule.Types.csproj | 19 +++ .../Resources/Messages.Designer.cs | 63 +++++++++ src/PSRule.Types/Resources/Messages.resx | 120 +++++++++++++++++ .../StringExtensions.cs | 90 ++++++++++--- src/PSRule/Badges/BadgeBuilder.cs | 2 +- src/PSRule/Common/ExpressionHelpers.cs | 18 +-- src/PSRule/Common/ExternalToolHelper.cs | 2 +- src/PSRule/Common/GitHelper.cs | 2 +- src/PSRule/Common/PSObjectExtensions.cs | 54 +++++++- src/PSRule/Common/YamlConverters.cs | 8 +- src/PSRule/Configuration/PSRuleOption.cs | 106 +++------------ .../Expressions/LanguageExpressions.cs | 8 +- src/PSRule/PSRule.psm1 | 2 +- src/PSRule/Pipeline/CommandLineBuilder.cs | 2 +- src/PSRule/Pipeline/GetTargetPipeline.cs | 4 +- src/PSRule/Pipeline/InvokePipelineBuilder.cs | 4 +- src/PSRule/Pipeline/OptionContext.cs | 2 +- .../Pipeline/Output/FileOutputWriter.cs | 2 +- .../Pipeline/Output/JobSummaryWriter.cs | 2 +- .../Pipeline/Output/NUnit3OutputWriter.cs | 2 +- src/PSRule/Pipeline/PathBuilder.cs | 4 +- src/PSRule/Pipeline/PipelineBuilder.cs | 2 +- src/PSRule/Pipeline/PipelineContext.cs | 2 +- src/PSRule/Pipeline/SourcePipeline.cs | 8 +- src/PSRule/Pipeline/TargetObject.cs | 2 +- src/PSRule/Runtime/Assert.cs | 12 +- src/PSRule/Runtime/PSRule.cs | 2 +- tests/PSRule.Tests/PSRuleOptionTests.cs | 8 +- tests/PSRule.Tests/PipelineTests.cs | 12 +- tests/PSRule.Tests/RulesTests.cs | 8 +- 41 files changed, 619 insertions(+), 238 deletions(-) create mode 100644 src/PSRule.Types/Data/IFileInfo.cs create mode 100644 src/PSRule.Types/Data/ISourceInfo.cs rename src/{PSRule => PSRule.Types}/Data/ITargetInfo.cs (100%) create mode 100644 src/PSRule.Types/Data/ITargetObject.cs rename src/{PSRule => PSRule.Types}/Data/InputFileInfo.cs (92%) rename src/{PSRule => PSRule.Types}/Data/TargetIssueInfo.cs (72%) rename src/{PSRule => PSRule.Types}/Data/TargetSourceInfo.cs (75%) create mode 100644 src/PSRule.Types/Expressions/Helpers.cs create mode 100644 src/PSRule.Types/Resources/Messages.Designer.cs create mode 100644 src/PSRule.Types/Resources/Messages.resx rename src/{PSRule/Common => PSRule.Types}/StringExtensions.cs (51%) diff --git a/.github/workflows/analyze.yaml b/.github/workflows/analyze.yaml index 6940569784..2a736100d6 100644 --- a/.github/workflows/analyze.yaml +++ b/.github/workflows/analyze.yaml @@ -59,7 +59,7 @@ jobs: - name: Run DevSkim scanner uses: microsoft/DevSkim-Action@v1 with: - directory-to-scan: src/ + directory-to-scan: . - name: Upload results to security tab uses: github/codeql-action/upload-sarif@v2 diff --git a/src/PSRule.Tool/ClientHelper.cs b/src/PSRule.Tool/ClientHelper.cs index 2c1fd51a3b..3982177ce1 100644 --- a/src/PSRule.Tool/ClientHelper.cs +++ b/src/PSRule.Tool/ClientHelper.cs @@ -44,7 +44,7 @@ public static int RunAnalyze(AnalyzerOptions operationOptions, ClientContext cli var host = new ClientHost(invocation, operationOptions.Verbose, operationOptions.Debug); var option = GetOption(host); var inputPath = operationOptions.InputPath == null || operationOptions.InputPath.Length == 0 ? - new string[] { PSRuleOption.GetWorkingPath() } : operationOptions.InputPath; + new string[] { Environment.GetWorkingPath() } : operationOptions.InputPath; if (operationOptions.Path != null) option.Include.Path = operationOptions.Path; diff --git a/src/PSRule.Types/Data/IFileInfo.cs b/src/PSRule.Types/Data/IFileInfo.cs new file mode 100644 index 0000000000..f04094e592 --- /dev/null +++ b/src/PSRule.Types/Data/IFileInfo.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace PSRule.Data +{ + /// + /// An object with information about an input file. + /// + public interface IFileInfo + { + /// + /// The full path to the file. + /// + string Path { get; } + + /// + /// The extension for the file. + /// + string Extension { get; } + } +} diff --git a/src/PSRule.Types/Data/ISourceInfo.cs b/src/PSRule.Types/Data/ISourceInfo.cs new file mode 100644 index 0000000000..5fe3eaa594 --- /dev/null +++ b/src/PSRule.Types/Data/ISourceInfo.cs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace PSRule.Data +{ + /// + /// Information about the source of an object. + /// + public interface ISourceInfo + { + } +} diff --git a/src/PSRule/Data/ITargetInfo.cs b/src/PSRule.Types/Data/ITargetInfo.cs similarity index 100% rename from src/PSRule/Data/ITargetInfo.cs rename to src/PSRule.Types/Data/ITargetInfo.cs diff --git a/src/PSRule.Types/Data/ITargetObject.cs b/src/PSRule.Types/Data/ITargetObject.cs new file mode 100644 index 0000000000..232e095303 --- /dev/null +++ b/src/PSRule.Types/Data/ITargetObject.cs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace PSRule.Data +{ + /// + /// An instance of a target object. + /// + public interface ITargetObject + { + } +} diff --git a/src/PSRule/Data/InputFileInfo.cs b/src/PSRule.Types/Data/InputFileInfo.cs similarity index 92% rename from src/PSRule/Data/InputFileInfo.cs rename to src/PSRule.Types/Data/InputFileInfo.cs index 17f151f1ea..252685b82d 100644 --- a/src/PSRule/Data/InputFileInfo.cs +++ b/src/PSRule.Types/Data/InputFileInfo.cs @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using PSRule.Expressions; + namespace PSRule.Data { /// @@ -17,7 +19,7 @@ internal InputFileInfo(string basePath, string path) { FullName = path; _Source = new TargetSourceInfo(this); - if (path.IsUri()) + if (path.IsURL()) { IsUrl = true; return; @@ -26,8 +28,8 @@ internal InputFileInfo(string basePath, string path) Name = System.IO.Path.GetFileName(path); Extension = System.IO.Path.GetExtension(path); DirectoryName = System.IO.Path.GetDirectoryName(path); - DisplayName = ExpressionHelpers.NormalizePath(basePath, FullName); - Path = ExpressionHelpers.NormalizePath(basePath, FullName); + DisplayName = Helpers.NormalizePath(basePath, FullName); + Path = Helpers.NormalizePath(basePath, FullName); _TargetType = string.IsNullOrEmpty(Extension) ? System.IO.Path.GetFileNameWithoutExtension(path) : Extension; } diff --git a/src/PSRule/Data/TargetIssueInfo.cs b/src/PSRule.Types/Data/TargetIssueInfo.cs similarity index 72% rename from src/PSRule/Data/TargetIssueInfo.cs rename to src/PSRule.Types/Data/TargetIssueInfo.cs index 9f22238482..8992a81e5d 100644 --- a/src/PSRule/Data/TargetIssueInfo.cs +++ b/src/PSRule.Types/Data/TargetIssueInfo.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Management.Automation; using Newtonsoft.Json; namespace PSRule.Data @@ -77,34 +76,5 @@ public override int GetHashCode() return hash; } } - - /// - /// Create an issue from a structured object. - /// - public static TargetIssueInfo Create(object o) - { - return o is PSObject pso ? Create(pso) : null; - } - - /// - /// Create an issue from a structured object. - /// - public static TargetIssueInfo Create(PSObject o) - { - var result = new TargetIssueInfo(); - if (o.TryProperty(PROPERTY_TYPE, out string type)) - result.Type = type; - - if (o.TryProperty(PROPERTY_NAME, out string name)) - result.Name = name; - - if (o.TryProperty(PROPERTY_PATH, out string path)) - result.Path = path; - - if (o.TryProperty(PROPERTY_MESSAGE, out string message)) - result.Message = message; - - return result; - } } } diff --git a/src/PSRule/Data/TargetSourceInfo.cs b/src/PSRule.Types/Data/TargetSourceInfo.cs similarity index 75% rename from src/PSRule/Data/TargetSourceInfo.cs rename to src/PSRule.Types/Data/TargetSourceInfo.cs index 677f24a48e..5840ddf00f 100644 --- a/src/PSRule/Data/TargetSourceInfo.cs +++ b/src/PSRule.Types/Data/TargetSourceInfo.cs @@ -1,9 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Management.Automation; using Newtonsoft.Json; -using PSRule.Configuration; +using PSRule.Expressions; using PSRule.Resources; namespace PSRule.Data @@ -11,7 +10,7 @@ namespace PSRule.Data /// /// An object source location reported by a downstream tool. /// - public sealed class TargetSourceInfo : IEquatable + public sealed class TargetSourceInfo : IEquatable, IFileInfo { private const string PROPERTY_FILE = "file"; private const string PROPERTY_LINE = "line"; @@ -32,19 +31,19 @@ public TargetSourceInfo() internal TargetSourceInfo(InputFileInfo info) { File = info.FullName; - Type = PSRuleResources.FileSourceType; + Type = "File"; } internal TargetSourceInfo(FileInfo info) { File = info.FullName; - Type = PSRuleResources.FileSourceType; + Type = "File"; } internal TargetSourceInfo(Uri uri) { File = uri.AbsoluteUri; - Type = PSRuleResources.FileSourceType; + Type = "File"; } /// @@ -71,6 +70,10 @@ internal TargetSourceInfo(Uri uri) [JsonProperty(PropertyName = PROPERTY_TYPE)] public string Type { get; internal set; } + string IFileInfo.Path => File; + + string IFileInfo.Extension => Path.GetExtension(File); + /// public bool Equals(TargetSourceInfo other) { @@ -124,36 +127,7 @@ public string ToString(string defaultType, bool useRelativePath) internal string GetPath(bool useRelativePath) { - return useRelativePath ? ExpressionHelpers.NormalizePath(PSRuleOption.GetWorkingPath(), File) : File; - } - - /// - /// Create source information from a structured object. - /// - public static TargetSourceInfo Create(object o) - { - return o is PSObject pso ? Create(pso) : null; - } - - /// - /// Create source information from a structured object. - /// - public static TargetSourceInfo Create(PSObject o) - { - var result = new TargetSourceInfo(); - if (o.TryProperty(PROPERTY_FILE, out string file)) - result.File = file; - - if (o.TryProperty(PROPERTY_LINE, out int line)) - result.Line = line; - - if (o.TryProperty(PROPERTY_POSITION, out int position)) - result.Position = position; - - if (o.TryProperty(PROPERTY_TYPE, out string type)) - result.Type = type; - - return result; + return useRelativePath ? Helpers.NormalizePath(Environment.GetWorkingPath(), File) : File; } } } diff --git a/src/PSRule.Types/Environment.cs b/src/PSRule.Types/Environment.cs index dcf3afb62b..338f772eae 100644 --- a/src/PSRule.Types/Environment.cs +++ b/src/PSRule.Types/Environment.cs @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System.Diagnostics; +using System.Globalization; using System.Net; using System.Security; using PSRule.Data; @@ -8,7 +10,7 @@ namespace PSRule { /// - /// A helper for accessing environment variables. + /// A helper for accessing environment and runtime variables. /// public static class Environment { @@ -17,12 +19,123 @@ public static class Environment private static readonly char[] LINUX_PATH_ENV_SEPARATOR = new char[] { ':' }; private static readonly char[] WINDOWS_PATH_ENV_SEPARATOR = new char[] { ';' }; + private const char BACKSLASH = '\\'; + private const char SLASH = '/'; + private const char STRINGARRYAMAP_PAIRSEPARATOR = '='; private const string PATH_ENV = "PATH"; private const string DEFAULT_CREDENTIAL_USERNAME = "na"; private const string TF_BUILD = "TF_BUILD"; private const string GITHUB_ACTIONS = "GITHUB_ACTIONS"; + /// + /// A callback that is overridden by PowerShell so that the current working path can be retrieved. + /// + private static WorkingPathResolver _GetWorkingPath = () => Directory.GetCurrentDirectory(); + + /// + /// Sets the current culture to use when processing rules unless otherwise specified. + /// + private static CultureInfo _CurrentCulture = Thread.CurrentThread.CurrentCulture; + + /// + /// A delgate to allow callback get current working path. + /// + public delegate string WorkingPathResolver(); + + /// + /// Configures PSRule to use the culture of the current thread at runtime. + /// + [DebuggerStepThrough] + public static void UseCurrentCulture() + { + UseCurrentCulture(Thread.CurrentThread.CurrentCulture); + } + + /// + /// Configures PSRule to use the specified culture at runtime. + /// + /// A valid culture. + [DebuggerStepThrough] + public static void UseCurrentCulture(string culture) + { + UseCurrentCulture(CultureInfo.CreateSpecificCulture(culture)); + } + + /// + /// Configures PSRule to use the specified culture at runtime. + /// + /// A valid culture. + public static void UseCurrentCulture(CultureInfo culture) + { + _CurrentCulture = culture; + } + + /// + /// Configures PSRule to use the specified resolver to determine the current working path. + /// + /// A method that can be used to resolve the current working path. + internal static void UseWorkingPathResolver(WorkingPathResolver resolver) + { + _GetWorkingPath = resolver; + } + + /// + /// Gets the current working path being used by PSRule. + /// + /// The current working path. + public static string GetWorkingPath() + { + return _GetWorkingPath(); + } + + /// + /// Get the current culture being used by PSRule. + /// + /// The current culture. + public static CultureInfo GetCurrentCulture() + { + return _CurrentCulture; + } + + /// + /// Get a full path instead of a relative path that may be passed from PowerShell. + /// + /// A full or relative path. + /// When set to true the returned path uses forward slashes instead of backslashes. + /// The base path to use. When null of unspecified, the current working path will be used. + /// A absolute path. + internal static string GetRootedPath(string path, bool normalize = false, string basePath = null) + { + if (string.IsNullOrEmpty(path)) + path = string.Empty; + + basePath ??= GetWorkingPath(); + var rootedPath = Path.IsPathRooted(path) ? Path.GetFullPath(path) : Path.GetFullPath(Path.Combine(basePath, path)); + return normalize ? rootedPath.Replace(BACKSLASH, SLASH) : rootedPath; + } + + /// + /// Get a full base path instead of a relative path that may be passed from PowerShell. + /// + /// A full or relative path. + /// When set to true the returned path uses forward slashes instead of backslashes. + /// A absolute base path. + /// + /// A base path always includes a trailing /. + /// + internal static string GetRootedBasePath(string path, bool normalize = false) + { + if (string.IsNullOrEmpty(path)) + path = string.Empty; + + var rootedPath = GetRootedPath(path); + var basePath = rootedPath.Length > 0 && IsPathSeparator(rootedPath[rootedPath.Length - 1]) + ? rootedPath + : string.Concat(rootedPath, Path.DirectorySeparatorChar); + return normalize ? basePath.Replace(BACKSLASH, SLASH) : basePath; + } + /// /// Determine if the environment is running within Azure Pipelines. /// @@ -205,5 +318,16 @@ private static bool TryParseBool(string variable, out bool value) } return false; } + + /// + /// Determine if the is a path separator character. + /// + /// The character to check. + /// Returns true if the charater is a path separator. Otherwise false is returned. + [DebuggerStepThrough] + private static bool IsPathSeparator(char c) + { + return c == Path.DirectorySeparatorChar || c == Path.AltDirectorySeparatorChar || c == SLASH || c == BACKSLASH; + } } } diff --git a/src/PSRule.Types/Expressions/Helpers.cs b/src/PSRule.Types/Expressions/Helpers.cs new file mode 100644 index 0000000000..066d3a9827 --- /dev/null +++ b/src/PSRule.Types/Expressions/Helpers.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace PSRule.Expressions +{ + internal static class Helpers + { + private const char Backslash = '\\'; + private const char Slash = '/'; + + internal static bool WithinPath(string actualPath, string expectedPath, bool caseSensitive) + { + var expected = Environment.GetRootedBasePath(expectedPath, normalize: true); + var actual = Environment.GetRootedPath(actualPath, normalize: true); + return actual.StartsWith(expected, ignoreCase: !caseSensitive, Thread.CurrentThread.CurrentCulture); + } + + internal static string NormalizePath(string basePath, string path, bool caseSensitive = true) + { + path = Environment.GetRootedPath(path, normalize: true, basePath: basePath); + basePath = Environment.GetRootedBasePath(basePath, normalize: true); + return path.Length >= basePath.Length && + path.StartsWith(basePath, caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase) ? path.Substring(basePath.Length).Replace(Backslash, Slash) : path; + } + } +} diff --git a/src/PSRule.Types/PSRule.Types.csproj b/src/PSRule.Types/PSRule.Types.csproj index 5aa2f5bcfb..35eefb0ea2 100644 --- a/src/PSRule.Types/PSRule.Types.csproj +++ b/src/PSRule.Types/PSRule.Types.csproj @@ -19,4 +19,23 @@ + + + True + True + Messages.resx + + + + + + ResXFileCodeGenerator + Messages.Designer.cs + + + + + + + diff --git a/src/PSRule.Types/Resources/Messages.Designer.cs b/src/PSRule.Types/Resources/Messages.Designer.cs new file mode 100644 index 0000000000..bf3e505ea0 --- /dev/null +++ b/src/PSRule.Types/Resources/Messages.Designer.cs @@ -0,0 +1,63 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace PSRule.Resources { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Messages { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Messages() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("PSRule.Resources.Messages", typeof(Messages).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + } +} diff --git a/src/PSRule.Types/Resources/Messages.resx b/src/PSRule.Types/Resources/Messages.resx new file mode 100644 index 0000000000..1af7de150c --- /dev/null +++ b/src/PSRule.Types/Resources/Messages.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/PSRule/Common/StringExtensions.cs b/src/PSRule.Types/StringExtensions.cs similarity index 51% rename from src/PSRule/Common/StringExtensions.cs rename to src/PSRule.Types/StringExtensions.cs index 2657cf4daa..54f18c0354 100644 --- a/src/PSRule/Common/StringExtensions.cs +++ b/src/PSRule.Types/StringExtensions.cs @@ -5,19 +5,39 @@ namespace PSRule { - internal static class StringExtensions + /// + /// Extension methods for strings. + /// + public static class StringExtensions { - public static bool IsUri(this string s) + private const string HTTP_SCHEME = "http://"; + private const string HTTPS_SCHEME = "https://"; + + private static readonly char[] LINE_STOPCHARACTERS = new char[] { '\r', '\n' }; + + /// + /// Determine if the string is a URL. + /// + /// The string to check. + /// Returns true if the string starts with a http:// or https://. + public static bool IsURL(this string s) { - return s.StartsWith("http://", StringComparison.OrdinalIgnoreCase) || s.StartsWith("https://", StringComparison.OrdinalIgnoreCase); + return s.StartsWith(HTTP_SCHEME, StringComparison.OrdinalIgnoreCase) || + s.StartsWith(HTTPS_SCHEME, StringComparison.OrdinalIgnoreCase); } + /// + /// Split a string semantically based on a maximum width. + /// + /// The string to split. + /// The maximum width to split lines along. + /// Returns an array of strings that have been semantically split. public static string[] SplitSemantic(this string s, int width = 80) { if (s == null) return null; - if (s.Length <= 80) + if (s.Length <= width) return new string[] { s }; var result = new List(); @@ -28,9 +48,11 @@ public static string[] SplitSemantic(this string s, int width = 80) if (i >= s.Length) i = s.Length - 1; - var breaks = s.IndexOfAny(new char[] { '\r', '\n' }, pos, i - pos); + var breaks = s.IndexOfAny(LINE_STOPCHARACTERS, pos, i - pos); if (breaks > -1) + { i = breaks; + } else { while (!IsSemanticStopChar(s[i]) && i > pos) @@ -58,26 +80,36 @@ public static string[] SplitSemantic(this string s, int width = 80) return result.ToArray(); } - private static bool IsSemanticStopChar(char c) - { - return char.IsWhiteSpace(c) || (char.IsPunctuation(c) && char.GetUnicodeCategory(c) != System.Globalization.UnicodeCategory.DashPunctuation); - } - - private static bool IsLineBreak(char c) - { - return char.GetUnicodeCategory(c) == System.Globalization.UnicodeCategory.LineSeparator; - } - - public static string ToCamelCase(this string str) + /// + /// Convert a string to camel case. + /// + /// The input string to convert. + /// The converted string. + public static string ToCamelCase(this string s) { - return !string.IsNullOrEmpty(str) ? char.ToLowerInvariant(str[0]) + str.Substring(1) : string.Empty; + return !string.IsNullOrEmpty(s) ? char.ToLowerInvariant(s[0]) + s.Substring(1) : string.Empty; } - public static bool Contains(this string source, string value, StringComparison comparison) + /// + /// Determine if the string contains a specific substring. + /// + /// + /// + /// + /// + public static bool Contains(this string s, string value, StringComparison comparison) { - return source?.IndexOf(value, comparison) >= 0; + return s?.IndexOf(value, comparison) >= 0; } + /// + /// Replace an old string with a new string. + /// + /// + /// + /// + /// + /// public static string Replace(this string s, string oldString, string newString, bool caseSensitive) { if (string.IsNullOrEmpty(s) || string.IsNullOrEmpty(oldString) || s.Length < oldString.Length) @@ -103,5 +135,25 @@ public static string Replace(this string s, string oldString, string newString, return sb.ToString(); } + + /// + /// + /// + /// + /// + private static bool IsSemanticStopChar(char c) + { + return char.IsWhiteSpace(c) || (char.IsPunctuation(c) && char.GetUnicodeCategory(c) != System.Globalization.UnicodeCategory.DashPunctuation); + } + + /// + /// + /// + /// + /// + private static bool IsLineBreak(char c) + { + return char.GetUnicodeCategory(c) == System.Globalization.UnicodeCategory.LineSeparator; + } } } diff --git a/src/PSRule/Badges/BadgeBuilder.cs b/src/PSRule/Badges/BadgeBuilder.cs index 4b9828aaef..b0484a2163 100644 --- a/src/PSRule/Badges/BadgeBuilder.cs +++ b/src/PSRule/Badges/BadgeBuilder.cs @@ -129,7 +129,7 @@ public string ToSvg() /// public void ToFile(string path) { - path = PSRuleOption.GetRootedPath(path); + path = Environment.GetRootedPath(path); var parentPath = Directory.GetParent(path); if (!parentPath.Exists) Directory.CreateDirectory(path: parentPath.FullName); diff --git a/src/PSRule/Common/ExpressionHelpers.cs b/src/PSRule/Common/ExpressionHelpers.cs index cad3139c6d..658100e44c 100644 --- a/src/PSRule/Common/ExpressionHelpers.cs +++ b/src/PSRule/Common/ExpressionHelpers.cs @@ -418,15 +418,15 @@ internal static bool CountValue(object actualValue, object expectedValue, bool c internal static bool WithinPath(string actualPath, string expectedPath, bool caseSensitive) { - var expected = PSRuleOption.GetRootedBasePath(expectedPath, normalize: true); - var actual = PSRuleOption.GetRootedPath(actualPath, normalize: true); + var expected = Environment.GetRootedBasePath(expectedPath, normalize: true); + var actual = Environment.GetRootedPath(actualPath, normalize: true); return actual.StartsWith(expected, ignoreCase: !caseSensitive, Thread.CurrentThread.CurrentCulture); } internal static string NormalizePath(string basePath, string path, bool caseSensitive = true) { - path = PSRuleOption.GetRootedPath(path, normalize: true, basePath: basePath); - basePath = PSRuleOption.GetRootedBasePath(basePath, normalize: true); + path = Environment.GetRootedPath(path, normalize: true, basePath: basePath); + basePath = Environment.GetRootedBasePath(basePath, normalize: true); return path.Length >= basePath.Length && path.StartsWith(basePath, caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase) ? path.Substring(basePath.Length).Replace(Backslash, Slash) : path; } @@ -437,23 +437,23 @@ internal static string GetObjectOriginPath(object o) var targetInfo = GetTargetInfo(o); if (baseObject is InputFileInfo inputFileInfo) { - return PSRuleOption.GetRootedPath(inputFileInfo.FullName, normalize: true); + return Environment.GetRootedPath(inputFileInfo.FullName, normalize: true); } else if (baseObject is FileInfo fileInfo) { - return PSRuleOption.GetRootedPath(fileInfo.FullName, normalize: true); + return Environment.GetRootedPath(fileInfo.FullName, normalize: true); } else if (baseObject is TargetSourceInfo sourceInfo && !string.IsNullOrEmpty(sourceInfo.File)) { - return PSRuleOption.GetRootedPath(sourceInfo.File, normalize: true); + return Environment.GetRootedPath(sourceInfo.File, normalize: true); } else if (targetInfo != null) { - return PSRuleOption.GetRootedPath(targetInfo.File, normalize: true); + return Environment.GetRootedPath(targetInfo.File, normalize: true); } else if (baseObject is string s) { - return PSRuleOption.GetRootedPath(s, normalize: true); + return Environment.GetRootedPath(s, normalize: true); } return null; } diff --git a/src/PSRule/Common/ExternalToolHelper.cs b/src/PSRule/Common/ExternalToolHelper.cs index a4856a7e66..421a3ca653 100644 --- a/src/PSRule/Common/ExternalToolHelper.cs +++ b/src/PSRule/Common/ExternalToolHelper.cs @@ -54,7 +54,7 @@ public bool WaitForExit(string args, out int exitCode) RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, - WorkingDirectory = PSRuleOption.GetWorkingPath(), + WorkingDirectory = Environment.GetWorkingPath(), }; _Process = Process.Start(startInfo); _Process.ErrorDataReceived += Bicep_ErrorDataReceived; diff --git a/src/PSRule/Common/GitHelper.cs b/src/PSRule/Common/GitHelper.cs index 6acc7227ea..5088e8fd08 100644 --- a/src/PSRule/Common/GitHelper.cs +++ b/src/PSRule/Common/GitHelper.cs @@ -183,7 +183,7 @@ private static bool TryReadCommit(string path, out string value) private static bool TryGitFile(string path, string file, out string filePath) { - path ??= PSRuleOption.GetRootedBasePath(GIT_DEFAULT_PATH); + path ??= Environment.GetRootedBasePath(GIT_DEFAULT_PATH); filePath = Path.Combine(path, file); return File.Exists(filePath); } diff --git a/src/PSRule/Common/PSObjectExtensions.cs b/src/PSRule/Common/PSObjectExtensions.cs index 294eb78c9f..cf979690f2 100644 --- a/src/PSRule/Common/PSObjectExtensions.cs +++ b/src/PSRule/Common/PSObjectExtensions.cs @@ -19,6 +19,10 @@ internal static class PSObjectExtensions private const string PROPERTY_TYPE = "type"; private const string PROPERTY_SCOPE = "scope"; private const string PROPERTY_PATH = "path"; + private const string PROPERTY_FILE = "file"; + private const string PROPERTY_LINE = "line"; + private const string PROPERTY_POSITION = "position"; + private const string PROPERTY_MESSAGE = "message"; public static T PropertyValue(this PSObject o, string propertyName) { @@ -161,7 +165,7 @@ public static void ConvertTargetInfoProperty(this PSObject o) { for (var i = 0; i < sources.Length; i++) { - var source = TargetSourceInfo.Create(sources.GetValue(i)); + var source = CreateSourceInfo(sources.GetValue(i)); targetInfo.WithSource(source); } } @@ -169,7 +173,7 @@ public static void ConvertTargetInfoProperty(this PSObject o) { for (var i = 0; i < issues.Length; i++) { - var issue = TargetIssueInfo.Create(issues.GetValue(i)); + var issue = CreateIssueInfo(issues.GetValue(i)); targetInfo.WithIssue(issue); } } @@ -190,6 +194,52 @@ public static void ConvertTargetInfoType(this PSObject o) } } + private static TargetIssueInfo CreateIssueInfo(object o) + { + return o is PSObject pso ? CreateIssueInfo(pso) : null; + } + + private static TargetIssueInfo CreateIssueInfo(PSObject o) + { + var result = new TargetIssueInfo(); + if (o.TryProperty(PROPERTY_TYPE, out string type)) + result.Type = type; + + if (o.TryProperty(PROPERTY_NAME, out string name)) + result.Name = name; + + if (o.TryProperty(PROPERTY_PATH, out string path)) + result.Path = path; + + if (o.TryProperty(PROPERTY_MESSAGE, out string message)) + result.Message = message; + + return result; + } + + private static TargetSourceInfo CreateSourceInfo(object o) + { + return o is PSObject pso ? CreateSourceInfo(pso) : null; + } + + private static TargetSourceInfo CreateSourceInfo(PSObject o) + { + var result = new TargetSourceInfo(); + if (o.TryProperty(PROPERTY_FILE, out string file)) + result.File = file; + + if (o.TryProperty(PROPERTY_LINE, out int line)) + result.Line = line; + + if (o.TryProperty(PROPERTY_POSITION, out int position)) + result.Position = position; + + if (o.TryProperty(PROPERTY_TYPE, out string type)) + result.Type = type; + + return result; + } + private static T ConvertValue(object value) { if (value == null) diff --git a/src/PSRule/Common/YamlConverters.cs b/src/PSRule/Common/YamlConverters.cs index b9fd1a8344..0633eed95f 100644 --- a/src/PSRule/Common/YamlConverters.cs +++ b/src/PSRule/Common/YamlConverters.cs @@ -872,13 +872,13 @@ internal sealed class PSObjectYamlDeserializer : INodeDeserializer { private readonly INodeDeserializer _Next; private readonly PSObjectYamlTypeConverter _Converter; - private readonly TargetSourceInfo _SourceInfo; + private readonly IFileInfo _FileInfo; - public PSObjectYamlDeserializer(INodeDeserializer next, TargetSourceInfo sourceInfo) + public PSObjectYamlDeserializer(INodeDeserializer next, IFileInfo sourceInfo) { _Next = next; _Converter = new PSObjectYamlTypeConverter(); - _SourceInfo = sourceInfo; + _FileInfo = sourceInfo; } bool INodeDeserializer.Deserialize(IParser reader, Type expectedType, Func nestedObjectDeserializer, out object value) @@ -891,7 +891,7 @@ bool INodeDeserializer.Deserialize(IParser reader, Type expectedType, Func - /// A delgate to allow callback to PowerShell to get current working path. - /// - internal delegate string PathDelegate(); - /// /// A structure that stores PSRule configuration options. /// @@ -31,9 +26,6 @@ public sealed class PSRuleOption : IEquatable, IBaselineV1Spec { private const string DEFAULT_FILENAME = "ps-rule.yaml"; - private const char Backslash = '\\'; - private const char Slash = '/'; - /// /// The original source path the options were loaded from if applicable. /// @@ -52,16 +44,6 @@ public sealed class PSRuleOption : IEquatable, IBaselineV1Spec Rule = RuleOption.Default, }; - /// - /// A callback that is overridden by PowerShell so that the current working path can be retrieved. - /// - private static PathDelegate _GetWorkingPath = () => Directory.GetCurrentDirectory(); - - /// - /// Sets the current culture to use when processing rules unless otherwise specified. - /// - private static CultureInfo _CurrentCulture = Thread.CurrentThread.CurrentCulture; - /// /// Create an empty PSRule options object. /// @@ -295,7 +277,7 @@ public static PSRuleOption FromFileOrEmpty(string path) /// public static PSRuleOption FromFileOrEmpty() { - return FromFileOrEmpty(GetWorkingPath()); + return FromFileOrEmpty(Environment.GetWorkingPath()); } /// @@ -404,12 +386,7 @@ public static PSRuleOption FromHashtable(Hashtable hashtable) /// public static void UseExecutionContext(EngineIntrinsics executionContext) { - if (executionContext == null) - { - _GetWorkingPath = () => Directory.GetCurrentDirectory(); - return; - } - _GetWorkingPath = () => executionContext.SessionState.Path.CurrentFileSystemLocation.Path; + Environment.UseWorkingPathResolver(executionContext == null ? () => Directory.GetCurrentDirectory() : () => executionContext.SessionState.Path.CurrentFileSystemLocation.Path); } /// @@ -417,58 +394,61 @@ public static void UseExecutionContext(EngineIntrinsics executionContext) /// public static void UseHostContext(IHostContext hostContext) { - if (hostContext == null) - { - _GetWorkingPath = () => Directory.GetCurrentDirectory(); - return; - } - _GetWorkingPath = () => hostContext.GetWorkingPath(); + Environment.UseWorkingPathResolver(hostContext == null ? () => Directory.GetCurrentDirectory() : () => hostContext.GetWorkingPath()); } /// /// Configures PSRule to use the culture of the current thread at runtime. + /// This method is deprecated. Use instead. /// - [DebuggerStepThrough] + [Obsolete("Use PSRule.Environment instead.")] public static void UseCurrentCulture() { - UseCurrentCulture(Thread.CurrentThread.CurrentCulture); + Environment.UseCurrentCulture(); } /// /// Configures PSRule to use the specified culture at runtime. + /// This method is deprecated. Use instead. /// /// A valid culture. - [DebuggerStepThrough] + [Obsolete("Use PSRule.Environment instead.")] public static void UseCurrentCulture(string culture) { - UseCurrentCulture(CultureInfo.CreateSpecificCulture(culture)); + Environment.UseCurrentCulture(culture); } /// /// Configures PSRule to use the specified culture at runtime. + /// This method is deprecated. Use instead. /// /// A valid culture. + [Obsolete("Use PSRule.Environment instead.")] public static void UseCurrentCulture(CultureInfo culture) { - _CurrentCulture = culture; + Environment.UseCurrentCulture(culture); } /// /// Gets the current working path being used by PSRule. + /// This method is deprecated. Use instead. /// /// The current working path. + [Obsolete("Use PSRule.Environment instead.")] public static string GetWorkingPath() { - return _GetWorkingPath(); + return Environment.GetWorkingPath(); } /// /// Get the current culture being used by PSRule. + /// This method is deprecated. Use instead. /// /// The current culture. + [Obsolete("Use PSRule.Environment instead.")] public static CultureInfo GetCurrentCulture() { - return _CurrentCulture; + return Environment.GetCurrentCulture(); } /// @@ -546,7 +526,7 @@ public override int GetHashCode() /// public static string GetFilePath(string path) { - var rootedPath = GetRootedPath(path); + var rootedPath = Environment.GetRootedPath(path); if (Path.HasExtension(rootedPath)) { var ext = Path.GetExtension(rootedPath); @@ -565,44 +545,6 @@ public static string GetFilePath(string path) Path.Combine(rootedPath, DEFAULT_FILENAME); } - /// - /// Get a full path instead of a relative path that may be passed from PowerShell. - /// - /// A full or relative path. - /// When set to true the returned path uses forward slashes instead of backslashes. - /// The base path to use. When null of unspecified, the current working path will be used. - /// A absolute path. - internal static string GetRootedPath(string path, bool normalize = false, string basePath = null) - { - if (string.IsNullOrEmpty(path)) - path = string.Empty; - - basePath ??= GetWorkingPath(); - var rootedPath = Path.IsPathRooted(path) ? Path.GetFullPath(path) : Path.GetFullPath(Path.Combine(basePath, path)); - return normalize ? rootedPath.Replace(Backslash, Slash) : rootedPath; - } - - /// - /// Get a full base path instead of a relative path that may be passed from PowerShell. - /// - /// A full or relative path. - /// When set to true the returned path uses forward slashes instead of backslashes. - /// A absolute base path. - /// - /// A base path always includes a trailing /. - /// - internal static string GetRootedBasePath(string path, bool normalize = false) - { - if (string.IsNullOrEmpty(path)) - path = string.Empty; - - var rootedPath = GetRootedPath(path); - var basePath = rootedPath.Length > 0 && IsSeparator(rootedPath[rootedPath.Length - 1]) - ? rootedPath - : string.Concat(rootedPath, Path.DirectorySeparatorChar); - return normalize ? basePath.Replace(Backslash, Slash) : basePath; - } - /// /// Build index to allow mapping values. /// @@ -622,11 +564,11 @@ internal static Dictionary BuildIndex(Hashtable hashtable) [DebuggerStepThrough] internal static bool IsCaseSentitive() { - var lower = GetWorkingPath().ToLower(Thread.CurrentThread.CurrentCulture); + var lower = Environment.GetWorkingPath().ToLower(Thread.CurrentThread.CurrentCulture); if (!Directory.Exists(lower)) return true; - var upper = GetWorkingPath().ToUpper(Thread.CurrentThread.CurrentCulture); + var upper = Environment.GetWorkingPath().ToUpper(Thread.CurrentThread.CurrentCulture); return !Directory.Exists(upper); } @@ -651,11 +593,5 @@ private string GetYaml() .Build(); return s.Serialize(this); } - - [DebuggerStepThrough] - private static bool IsSeparator(char c) - { - return c == Path.DirectorySeparatorChar || c == Path.AltDirectorySeparatorChar || c == '/' || c == '\\'; - } } } diff --git a/src/PSRule/Definitions/Expressions/LanguageExpressions.cs b/src/PSRule/Definitions/Expressions/LanguageExpressions.cs index 69332563c3..c530646fd7 100644 --- a/src/PSRule/Definitions/Expressions/LanguageExpressions.cs +++ b/src/PSRule/Definitions/Expressions/LanguageExpressions.cs @@ -1325,7 +1325,7 @@ internal static bool WithinPath(ExpressionContext context, ExpressionInfo info, context, operand, ReasonStrings.WithinPath, - ExpressionHelpers.NormalizePath(PSRuleOption.GetWorkingPath(), source), + ExpressionHelpers.NormalizePath(Environment.GetWorkingPath(), source), StringJoinNormalizedPath(path) ); } @@ -1351,8 +1351,8 @@ internal static bool NotWithinPath(ExpressionContext context, ExpressionInfo inf context, operand, ReasonStrings.NotWithinPath, - ExpressionHelpers.NormalizePath(PSRuleOption.GetWorkingPath(), source), - ExpressionHelpers.NormalizePath(PSRuleOption.GetWorkingPath(), path[i]) + ExpressionHelpers.NormalizePath(Environment.GetWorkingPath(), source), + ExpressionHelpers.NormalizePath(Environment.GetWorkingPath(), path[i]) ); } } @@ -1855,7 +1855,7 @@ private static string StringJoin(Array propertyValue) private static string StringJoinNormalizedPath(string[] path) { - var normalizedPath = path.Select(p => ExpressionHelpers.NormalizePath(PSRuleOption.GetWorkingPath(), p)); + var normalizedPath = path.Select(p => ExpressionHelpers.NormalizePath(Environment.GetWorkingPath(), p)); return StringJoin(normalizedPath.ToArray()); } diff --git a/src/PSRule/PSRule.psm1 b/src/PSRule/PSRule.psm1 index afd13d6a7b..cb70c293a5 100644 --- a/src/PSRule/PSRule.psm1 +++ b/src/PSRule/PSRule.psm1 @@ -8,7 +8,7 @@ Set-StrictMode -Version latest; [PSRule.Configuration.PSRuleOption]::UseExecutionContext($ExecutionContext); -[PSRule.Configuration.PSRuleOption]::UseCurrentCulture(); +[PSRule.Environment]::UseCurrentCulture(); # # Localization diff --git a/src/PSRule/Pipeline/CommandLineBuilder.cs b/src/PSRule/Pipeline/CommandLineBuilder.cs index 21904539cf..91cec22404 100644 --- a/src/PSRule/Pipeline/CommandLineBuilder.cs +++ b/src/PSRule/Pipeline/CommandLineBuilder.cs @@ -62,7 +62,7 @@ public static IInvokePipelineBuilder Assert(string[] module, PSRuleOption option internal static string GetLocalPath() { - return string.IsNullOrEmpty(AppContext.BaseDirectory) ? null : PSRuleOption.GetRootedBasePath(AppContext.BaseDirectory); + return string.IsNullOrEmpty(AppContext.BaseDirectory) ? null : Environment.GetRootedBasePath(AppContext.BaseDirectory); } } } diff --git a/src/PSRule/Pipeline/GetTargetPipeline.cs b/src/PSRule/Pipeline/GetTargetPipeline.cs index 9c4bd46e44..199c739cc7 100644 --- a/src/PSRule/Pipeline/GetTargetPipeline.cs +++ b/src/PSRule/Pipeline/GetTargetPipeline.cs @@ -56,11 +56,11 @@ public void InputPath(string[] path) PathFilter required = null; if (TryChangedFiles(out var files)) { - required = PathFilter.Create(PSRuleOption.GetWorkingPath(), path); + required = PathFilter.Create(Environment.GetWorkingPath(), path); path = files; } - var builder = new InputPathBuilder(GetOutput(), PSRuleOption.GetWorkingPath(), "*", GetInputFilter(), required); + var builder = new InputPathBuilder(GetOutput(), Environment.GetWorkingPath(), "*", GetInputFilter(), required); builder.Add(path); _InputPath = builder; } diff --git a/src/PSRule/Pipeline/InvokePipelineBuilder.cs b/src/PSRule/Pipeline/InvokePipelineBuilder.cs index b1da569cd2..d78121f90c 100644 --- a/src/PSRule/Pipeline/InvokePipelineBuilder.cs +++ b/src/PSRule/Pipeline/InvokePipelineBuilder.cs @@ -51,11 +51,11 @@ public void InputPath(string[] path) PathFilter required = null; if (TryChangedFiles(out var files)) { - required = PathFilter.Create(PSRuleOption.GetWorkingPath(), path); + required = PathFilter.Create(Environment.GetWorkingPath(), path); path = files; } - var builder = new InputPathBuilder(PrepareWriter(), PSRuleOption.GetWorkingPath(), "*", GetInputFilter(), required); + var builder = new InputPathBuilder(PrepareWriter(), Environment.GetWorkingPath(), "*", GetInputFilter(), required); builder.Add(path); _InputPath = builder; } diff --git a/src/PSRule/Pipeline/OptionContext.cs b/src/PSRule/Pipeline/OptionContext.cs index 9d21ee2684..7dd1548985 100644 --- a/src/PSRule/Pipeline/OptionContext.cs +++ b/src/PSRule/Pipeline/OptionContext.cs @@ -448,7 +448,7 @@ private static string[] GetDefaultCulture() var set = new HashSet(); // Fallback to current culture - var current = PSRuleOption.GetCurrentCulture(); + var current = Environment.GetCurrentCulture(); if (!set.Contains(current.Name) && !string.IsNullOrEmpty(current.Name)) { result.Add(current.Name); diff --git a/src/PSRule/Pipeline/Output/FileOutputWriter.cs b/src/PSRule/Pipeline/Output/FileOutputWriter.cs index 2ddbc367fc..e84f3e576b 100644 --- a/src/PSRule/Pipeline/Output/FileOutputWriter.cs +++ b/src/PSRule/Pipeline/Output/FileOutputWriter.cs @@ -34,7 +34,7 @@ public override void WriteObject(object sendToPipeline, bool enumerateCollection private void WriteToFile(object o) { - var rootedPath = PSRuleOption.GetRootedPath(_Path); + var rootedPath = Environment.GetRootedPath(_Path); if (CreateFile(rootedPath)) { File.WriteAllText(path: rootedPath, contents: o.ToString(), encoding: _Encoding); diff --git a/src/PSRule/Pipeline/Output/JobSummaryWriter.cs b/src/PSRule/Pipeline/Output/JobSummaryWriter.cs index 2050a5fec9..d7eb66b881 100644 --- a/src/PSRule/Pipeline/Output/JobSummaryWriter.cs +++ b/src/PSRule/Pipeline/Output/JobSummaryWriter.cs @@ -31,7 +31,7 @@ internal sealed class JobSummaryWriter : ResultOutputWriter public JobSummaryWriter(IPipelineWriter inner, PSRuleOption option, ShouldProcess shouldProcess, string outputPath = null, Stream stream = null, Source[] source = null) : base(inner, option, shouldProcess) { - _OutputPath = outputPath ?? PSRuleOption.GetRootedPath(Option.Output.JobSummaryPath); + _OutputPath = outputPath ?? Environment.GetRootedPath(Option.Output.JobSummaryPath); _Encoding = option.Output.GetEncoding(); _JobSummary = JobSummaryFormat.Default; _Stream = stream; diff --git a/src/PSRule/Pipeline/Output/NUnit3OutputWriter.cs b/src/PSRule/Pipeline/Output/NUnit3OutputWriter.cs index 725c114395..3c2064bd5d 100644 --- a/src/PSRule/Pipeline/Output/NUnit3OutputWriter.cs +++ b/src/PSRule/Pipeline/Output/NUnit3OutputWriter.cs @@ -55,7 +55,7 @@ protected override string Serialize(InvokeResult[] o) xml.WriteStartElement("environment"); xml.WriteAttributeString("user", System.Environment.UserName); xml.WriteAttributeString("machine-name", System.Environment.MachineName); - xml.WriteAttributeString("cwd", PSRuleOption.GetWorkingPath()); + xml.WriteAttributeString("cwd", Environment.GetWorkingPath()); xml.WriteAttributeString("user-domain", System.Environment.UserDomainName); xml.WriteAttributeString("platform", System.Environment.OSVersion.Platform.ToString()); xml.WriteAttributeString("nunit-version", "2.5.8.0"); diff --git a/src/PSRule/Pipeline/PathBuilder.cs b/src/PSRule/Pipeline/PathBuilder.cs index 9620357ca9..e5740b82ac 100644 --- a/src/PSRule/Pipeline/PathBuilder.cs +++ b/src/PSRule/Pipeline/PathBuilder.cs @@ -45,7 +45,7 @@ protected PathBuilder(IPipelineWriter logger, string basePath, string searchPatt _Logger = logger; _Files = new List(); _Paths = new HashSet(); - _BasePath = NormalizePath(PSRuleOption.GetRootedBasePath(basePath)); + _BasePath = NormalizePath(Environment.GetRootedBasePath(basePath)); _DefaultSearchPattern = searchPattern; _GlobalFilter = filter; _Required = required; @@ -113,7 +113,7 @@ private void FindFiles(string path) private bool TryUrl(string path) { - if (!path.IsUri()) + if (!path.IsURL()) return false; AddFile(path); diff --git a/src/PSRule/Pipeline/PipelineBuilder.cs b/src/PSRule/Pipeline/PipelineBuilder.cs index 562ae0b1a5..1e34f97a78 100644 --- a/src/PSRule/Pipeline/PipelineBuilder.cs +++ b/src/PSRule/Pipeline/PipelineBuilder.cs @@ -576,7 +576,7 @@ protected PathFilter GetInputFilter() { if (_InputFilter == null) { - var basePath = PSRuleOption.GetWorkingPath(); + var basePath = Environment.GetWorkingPath(); var ignoreGitPath = Option.Input.IgnoreGitPath ?? InputOption.Default.IgnoreGitPath.Value; var ignoreRepositoryCommon = Option.Input.IgnoreRepositoryCommon ?? InputOption.Default.IgnoreRepositoryCommon.Value; var builder = PathFilterBuilder.Create(basePath, Option.Input.PathIgnore, ignoreGitPath, ignoreRepositoryCommon); diff --git a/src/PSRule/Pipeline/PipelineContext.cs b/src/PSRule/Pipeline/PipelineContext.cs index bf9287b3ff..da6a895b88 100644 --- a/src/PSRule/Pipeline/PipelineContext.cs +++ b/src/PSRule/Pipeline/PipelineContext.cs @@ -159,7 +159,7 @@ internal Runspace GetRunspace() _Runspace.SessionStateProxy.PSVariable.Set(WarningPreference, ActionPreference.Continue); _Runspace.SessionStateProxy.PSVariable.Set(VerbosePreference, ActionPreference.Continue); _Runspace.SessionStateProxy.PSVariable.Set(DebugPreference, ActionPreference.Continue); - _Runspace.SessionStateProxy.Path.SetLocation(PSRuleOption.GetWorkingPath()); + _Runspace.SessionStateProxy.Path.SetLocation(Environment.GetWorkingPath()); } return _Runspace; } diff --git a/src/PSRule/Pipeline/SourcePipeline.cs b/src/PSRule/Pipeline/SourcePipeline.cs index 0b472771dd..5b8711c670 100644 --- a/src/PSRule/Pipeline/SourcePipeline.cs +++ b/src/PSRule/Pipeline/SourcePipeline.cs @@ -186,7 +186,7 @@ public void Directory(string path, bool excludeDefaultRulePath = false) return; VerboseScanSource(path); - path = PSRuleOption.GetRootedPath(path); + path = Environment.GetRootedPath(path); var files = GetFiles(path, null, excludeDefaultRulePath); if (files == null || files.Length == 0) return; @@ -243,7 +243,7 @@ private bool TryPackagedModule(string name, out string path) return false; Log($"Looking for modules in: {_LocalPath}"); - path = PSRuleOption.GetRootedBasePath(Path.Combine(_LocalPath, "Modules", name)); + path = Environment.GetRootedBasePath(Path.Combine(_LocalPath, "Modules", name)); return System.IO.Directory.Exists(path); } @@ -261,7 +261,7 @@ private bool TryInstalledModule(string name, out string path) for (var i = 0; i < searchPaths.Length; i++) { Debug($"Looking for modules search paths: {searchPaths[i]}"); - var searchPath = PSRuleOption.GetRootedBasePath(Path.Combine(searchPaths[i], name)); + var searchPath = Environment.GetRootedBasePath(Path.Combine(searchPaths[i], name)); if (System.IO.Directory.Exists(searchPath)) { foreach (var versionPath in System.IO.Directory.EnumerateDirectories(searchPath)) @@ -405,7 +405,7 @@ private void Source(Source source) private static SourceFile[] GetFiles(string path, string helpPath, bool excludeDefaultRulePath, string moduleName = null) { - var rootedPath = PSRuleOption.GetRootedPath(path); + var rootedPath = Environment.GetRootedPath(path); var extension = Path.GetExtension(rootedPath); if (IsSourceFile(extension)) { diff --git a/src/PSRule/Pipeline/TargetObject.cs b/src/PSRule/Pipeline/TargetObject.cs index 970c75447e..03cf396e19 100644 --- a/src/PSRule/Pipeline/TargetObject.cs +++ b/src/PSRule/Pipeline/TargetObject.cs @@ -38,7 +38,7 @@ public void SetSelectorResult(SelectorVisitor selector, bool result) /// /// An object processed by PSRule. /// - public sealed class TargetObject + public sealed class TargetObject : ITargetObject { private readonly Dictionary _Annotations; diff --git a/src/PSRule/Runtime/Assert.cs b/src/PSRule/Runtime/Assert.cs index 74e52a577e..eec65e9e85 100644 --- a/src/PSRule/Runtime/Assert.cs +++ b/src/PSRule/Runtime/Assert.cs @@ -1230,8 +1230,8 @@ public AssertResult WithinPath(PSObject inputObject, string field, string[] path return Pass(); result.AddReason(Operand.FromPath(field), ReasonStrings.WithinPath, - ExpressionHelpers.NormalizePath(PSRuleOption.GetWorkingPath(), fieldValuePath), - ExpressionHelpers.NormalizePath(PSRuleOption.GetWorkingPath(), path[i]) + ExpressionHelpers.NormalizePath(Environment.GetWorkingPath(), fieldValuePath), + ExpressionHelpers.NormalizePath(Environment.GetWorkingPath(), path[i]) ); } return result; @@ -1254,8 +1254,8 @@ public AssertResult NotWithinPath(PSObject inputObject, string field, string[] p { if (ExpressionHelpers.WithinPath(fieldValuePath, path[i], caseSensitive.GetValueOrDefault(PSRuleOption.IsCaseSentitive()))) return Fail(Operand.FromPath(field), ReasonStrings.NotWithinPath, - ExpressionHelpers.NormalizePath(PSRuleOption.GetWorkingPath(), fieldValuePath), - ExpressionHelpers.NormalizePath(PSRuleOption.GetWorkingPath(), path[i]) + ExpressionHelpers.NormalizePath(Environment.GetWorkingPath(), fieldValuePath), + ExpressionHelpers.NormalizePath(Environment.GetWorkingPath(), path[i]) ); } return Pass(); @@ -1485,7 +1485,7 @@ private static bool TryReadJson(string uri, out string json) { return false; } - else if (uri.IsUri()) + else if (uri.IsURL()) { using var webClient = new WebClient(); json = webClient.DownloadString(uri); @@ -1502,7 +1502,7 @@ private static bool TryReadJson(string uri, out string json) private static bool TryFilePath(string path, out string rootedPath) { - rootedPath = PSRuleOption.GetRootedPath(path); + rootedPath = Environment.GetRootedPath(path); return File.Exists(rootedPath); } diff --git a/src/PSRule/Runtime/PSRule.cs b/src/PSRule/Runtime/PSRule.cs index 98d8108b0d..83bf987153 100644 --- a/src/PSRule/Runtime/PSRule.cs +++ b/src/PSRule/Runtime/PSRule.cs @@ -93,7 +93,7 @@ internal PSRuleRepository(RunspaceContext context) /// public IInputFileInfoCollection GetChangedFiles() { - _ChangedFiles ??= new InputFileInfoCollection(PSRuleOption.GetWorkingPath(), GitHelper.TryGetChangedFiles(BaseRef, "d", null, out var files) ? files : null); + _ChangedFiles ??= new InputFileInfoCollection(Environment.GetWorkingPath(), GitHelper.TryGetChangedFiles(BaseRef, "d", null, out var files) ? files : null); return _ChangedFiles; } } diff --git a/tests/PSRule.Tests/PSRuleOptionTests.cs b/tests/PSRule.Tests/PSRuleOptionTests.cs index caed4320b4..9040ac042d 100644 --- a/tests/PSRule.Tests/PSRuleOptionTests.cs +++ b/tests/PSRule.Tests/PSRuleOptionTests.cs @@ -16,10 +16,10 @@ public void GetRootedBasePath() { var pwd = Directory.GetCurrentDirectory(); var basePwd = $"{pwd}{Path.DirectorySeparatorChar}"; - Assert.Equal(basePwd, PSRuleOption.GetRootedBasePath(null)); - Assert.Equal(basePwd, PSRuleOption.GetRootedBasePath(pwd)); - Assert.Equal(pwd, PSRuleOption.GetRootedPath(null)); - Assert.Equal(pwd, PSRuleOption.GetRootedPath(pwd)); + Assert.Equal(basePwd, Environment.GetRootedBasePath(null)); + Assert.Equal(basePwd, Environment.GetRootedBasePath(pwd)); + Assert.Equal(pwd, Environment.GetRootedPath(null)); + Assert.Equal(pwd, Environment.GetRootedPath(pwd)); } [Fact] diff --git a/tests/PSRule.Tests/PipelineTests.cs b/tests/PSRule.Tests/PipelineTests.cs index 81e6a601ff..e6ec105cbd 100644 --- a/tests/PSRule.Tests/PipelineTests.cs +++ b/tests/PSRule.Tests/PipelineTests.cs @@ -177,7 +177,7 @@ public void GetRuleWithBaseline() [Fact] public void PipelineWithInvariantCulture() { - PSRuleOption.UseCurrentCulture(CultureInfo.InvariantCulture); + Environment.UseCurrentCulture(CultureInfo.InvariantCulture); var context = PipelineContext.New(GetOption(), null, null, null, null, null, new OptionContext(), null); var writer = new TestWriter(GetOption()); var pipeline = new GetRulePipeline(context, GetSource(), new PipelineReader(null, null, null), writer, false); @@ -190,14 +190,14 @@ public void PipelineWithInvariantCulture() } finally { - PSRuleOption.UseCurrentCulture(); + Environment.UseCurrentCulture(); } } [Fact] public void PipelineWithInvariantCultureDisabled() { - PSRuleOption.UseCurrentCulture(CultureInfo.InvariantCulture); + 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); @@ -212,7 +212,7 @@ public void PipelineWithInvariantCultureDisabled() } finally { - PSRuleOption.UseCurrentCulture(); + Environment.UseCurrentCulture(); } } @@ -361,7 +361,7 @@ private static PSObject GetTestObject() var info = new PSObject(); info.Properties.Add(new PSNoteProperty("path", "resources[0]")); var source = new PSObject(); - source.Properties.Add(new PSNoteProperty("file", PSRuleOption.GetRootedPath("./ObjectFromFileNotFile.json"))); + source.Properties.Add(new PSNoteProperty("file", Environment.GetRootedPath("./ObjectFromFileNotFile.json"))); source.Properties.Add(new PSNoteProperty("type", "example")); info.Properties.Add(new PSNoteProperty("source", new PSObject[] { source })); var o = new PSObject(); @@ -372,7 +372,7 @@ private static PSObject GetTestObject() private static PSObject GetFileObject() { - var info = new FileInfo(PSRuleOption.GetRootedPath("./ObjectFromFileSingle.json")); + var info = new FileInfo(Environment.GetRootedPath("./ObjectFromFileSingle.json")); return new PSObject(info); } diff --git a/tests/PSRule.Tests/RulesTests.cs b/tests/PSRule.Tests/RulesTests.cs index 94f3eab95b..243b2e3d8f 100644 --- a/tests/PSRule.Tests/RulesTests.cs +++ b/tests/PSRule.Tests/RulesTests.cs @@ -33,14 +33,14 @@ public void ReadYamlRule() var rule = HostHelper.GetRule(GetSource(), context, includeDependencies: false); Assert.NotNull(rule); Assert.Equal("YamlBasicRule", rule[0].Name); - Assert.Equal(PSRuleOption.GetRootedPath(""), rule[0].Source.HelpPath); + Assert.Equal(Environment.GetRootedPath(""), rule[0].Source.HelpPath); Assert.Equal(10, rule[0].Extent.Line); // From relative path rule = HostHelper.GetRule(GetSource("../../../FromFile.Rule.yaml"), context, includeDependencies: false); Assert.NotNull(rule); Assert.Equal("YamlBasicRule", rule[0].Name); - Assert.Equal(PSRuleOption.GetRootedPath("../../.."), rule[0].Source.HelpPath); + Assert.Equal(Environment.GetRootedPath("../../.."), rule[0].Source.HelpPath); var hashtable = rule[0].Tag.ToHashtable(); Assert.Equal("tag", hashtable["feature"]); @@ -236,14 +236,14 @@ public void ReadJsonRule() var rule = HostHelper.GetRule(GetSource("FromFile.Rule.jsonc"), context, includeDependencies: false); Assert.NotNull(rule); Assert.Equal("JsonBasicRule", rule[0].Name); - Assert.Equal(PSRuleOption.GetRootedPath(""), rule[0].Source.HelpPath); + Assert.Equal(Environment.GetRootedPath(""), rule[0].Source.HelpPath); Assert.Equal(7, rule[0].Extent.Line); // From relative path rule = HostHelper.GetRule(GetSource("../../../FromFile.Rule.jsonc"), context, includeDependencies: false); Assert.NotNull(rule); Assert.Equal("JsonBasicRule", rule[0].Name); - Assert.Equal(PSRuleOption.GetRootedPath("../../.."), rule[0].Source.HelpPath); + Assert.Equal(Environment.GetRootedPath("../../.."), rule[0].Source.HelpPath); var hashtable = rule[0].Tag.ToHashtable(); Assert.Equal("tag", hashtable["feature"]); From d18204669bce17946956e3ff6ba1a81966e89929 Mon Sep 17 00:00:00 2001 From: Bernie White Date: Mon, 17 Jul 2023 00:19:06 +1000 Subject: [PATCH 041/177] Bump docs for v3 (#1569) --- CHANGELOG.md | 7 ++++++ docs/CHANGELOG-v2.md | 23 ++++--------------- docs/CHANGELOG-v3.md | 54 ++++++++++++++++++++++++++++++++++++++++++++ mkdocs.yml | 1 + 4 files changed, 67 insertions(+), 18 deletions(-) create mode 100644 docs/CHANGELOG-v3.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 56bf3fd4ff..8de5e824a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,13 @@ See [upgrade notes][1] for helpful information when upgrading from previous vers [1]: https://aka.ms/ps-rule/upgrade +## Next release + +We are currently working towards the next release of PSRule. +PSRule v3 is currently in development and not suitable for production use. + +- [v3](https://microsoft.github.io/PSRule/latest/CHANGELOG-v3/) + ## Current release - [v2](https://microsoft.github.io/PSRule/latest/CHANGELOG-v2/) diff --git a/docs/CHANGELOG-v2.md b/docs/CHANGELOG-v2.md index 67d1229501..19c4838782 100644 --- a/docs/CHANGELOG-v2.md +++ b/docs/CHANGELOG-v2.md @@ -33,25 +33,12 @@ See [upgrade notes][1] for helpful information when upgrading from previous vers [5]: creating-your-pipeline.md#processing-changed-files-only [6]: concepts/baselines.md -## Unreleased +!!! Attention + We are currently working towards the next release of PSRule. + For more information see [v3][7] release notes. + Please check out our [upgrade notes][1] to get prepared for upgrading to the latest version. -What's changed since release v2.9.0: - -- General improvements: - - Expanded support for `FileHeader` assertion by @BernieWhite. - [#1521](https://github.com/microsoft/PSRule/issues/1521) - - Added support for `.bicepparam`, `.tsp`, `.tsx`, `.editorconfig`, `.ipynb`, and `.toml` files. -- Engineering: - - Bump Microsoft.CodeAnalysis.NetAnalyzers to v7.0.3. - [#1550](https://github.com/microsoft/PSRule/pull/1550) - - Bump Microsoft.NET.Test.Sdk to v17.6.3. - [#1557](https://github.com/microsoft/PSRule/pull/1557) - - Bump YamlDotNet to v13.1.1. - [#1399](https://github.com/microsoft/PSRule/issues/1399) - - Bump xunit to v2.5.0. - [#1562](https://github.com/microsoft/PSRule/pull/1562) - - Bump xunit.runner.visualstudio to v2.5.0. - [#1561](https://github.com/microsoft/PSRule/pull/1561) + [7]: https://microsoft.github.io/PSRule/latest/CHANGELOG-v3/ ## v2.9.0 diff --git a/docs/CHANGELOG-v3.md b/docs/CHANGELOG-v3.md new file mode 100644 index 0000000000..aed2ac3824 --- /dev/null +++ b/docs/CHANGELOG-v3.md @@ -0,0 +1,54 @@ +--- +discussion: false +--- + +# Change log + +See [upgrade notes][1] for helpful information when upgrading from previous versions. + + [1]: https://aka.ms/ps-rule/upgrade + +**Important notes**: + +- Several options have been renamed and the old names will be removed in v3. + See [deprecations][2] for details. +- Several properties of rule and language block elements will be removed from v3. + See [deprecations][2] for details. + + [2]: https://aka.ms/ps-rule/deprecations#deprecations-for-v3 + +**Experimental features**: + +- Baseline groups allow you to use a friendly name to reference baselines. + See [baselines][6] for more information. +- Functions within YAML and JSON expressions can be used to perform manipulation prior to testing a condition. + See [functions][3] for more information. +- Sub-selectors within YAML and JSON expressions can be used to filter rules and list properties. + See [sub-selectors][4] for more information. +- Processing of changes files only within a pipeline. + See [creating your pipeline][5] for more information. + + [3]: expressions/functions.md + [4]: expressions/sub-selectors.md + [5]: creating-your-pipeline.md#processing-changed-files-only + [6]: concepts/baselines.md + +## Unreleased + +What's changed since release v2.9.0: + +- General improvements: + - Expanded support for `FileHeader` assertion by @BernieWhite. + [#1521](https://github.com/microsoft/PSRule/issues/1521) + - Added support for `.bicepparam`, `.tsp`, `.tsx`, `.editorconfig`, `.ipynb`, and `.toml` files. +- Engineering: + - Bump Microsoft.CodeAnalysis.NetAnalyzers to v7.0.3. + [#1550](https://github.com/microsoft/PSRule/pull/1550) + - Bump Microsoft.NET.Test.Sdk to v17.6.3. + [#1557](https://github.com/microsoft/PSRule/pull/1557) + - Bump YamlDotNet to v13.1.1. + [#1399](https://github.com/microsoft/PSRule/issues/1399) + - Bump xunit to v2.5.0. + [#1562](https://github.com/microsoft/PSRule/pull/1562) + - Bump xunit.runner.visualstudio to v2.5.0. + [#1561](https://github.com/microsoft/PSRule/pull/1561) diff --git a/mkdocs.yml b/mkdocs.yml index fc7af9826d..d46de65b24 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -72,6 +72,7 @@ nav: - Related projects: related-projects.md - Releases: - Change log: + - v3: 'CHANGELOG-v3.md' - v2: 'CHANGELOG-v2.md' - v1: 'CHANGELOG-v1.md' - v0: 'CHANGELOG-v0.md' From bc72b13f35f27f91099eae1bc48eba3ed54fe04f Mon Sep 17 00:00:00 2001 From: Bernie White Date: Mon, 17 Jul 2023 00:52:28 +1000 Subject: [PATCH 042/177] Update global usings in tests (#1570) --- src/PSRule.Types/Data/TargetSourceInfo.cs | 1 - src/PSRule/Badges/BadgeBuilder.cs | 1 - src/PSRule/Common/ExpressionHelpers.cs | 1 - src/PSRule/Common/ExternalToolHelper.cs | 1 - src/PSRule/Common/GitHelper.cs | 1 - src/PSRule/Definitions/Expressions/LanguageExpressions.cs | 1 - src/PSRule/Pipeline/PathBuilder.cs | 1 - src/PSRule/Runtime/PSRule.cs | 1 - tests/PSRule.Tests/AssertTests.cs | 1 - tests/PSRule.Tests/BaselineTests.cs | 1 - tests/PSRule.Tests/FunctionBuilderTests.cs | 1 - tests/PSRule.Tests/ModuleConfigTests.cs | 1 - tests/PSRule.Tests/ResourceValidatorTests.cs | 1 - tests/PSRule.Tests/RulesTests.cs | 1 - tests/PSRule.Tests/SelectorTests.cs | 1 - tests/PSRule.Tests/SuppressionFilterTests.cs | 1 - tests/PSRule.Tests/SuppressionGroupTests.cs | 1 - tests/PSRule.Tests/Usings.cs | 1 + 18 files changed, 1 insertion(+), 17 deletions(-) diff --git a/src/PSRule.Types/Data/TargetSourceInfo.cs b/src/PSRule.Types/Data/TargetSourceInfo.cs index 5840ddf00f..f263b9c18e 100644 --- a/src/PSRule.Types/Data/TargetSourceInfo.cs +++ b/src/PSRule.Types/Data/TargetSourceInfo.cs @@ -3,7 +3,6 @@ using Newtonsoft.Json; using PSRule.Expressions; -using PSRule.Resources; namespace PSRule.Data { diff --git a/src/PSRule/Badges/BadgeBuilder.cs b/src/PSRule/Badges/BadgeBuilder.cs index b0484a2163..2826580ad1 100644 --- a/src/PSRule/Badges/BadgeBuilder.cs +++ b/src/PSRule/Badges/BadgeBuilder.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using PSRule.Configuration; using PSRule.Pipeline; using PSRule.Resources; using PSRule.Rules; diff --git a/src/PSRule/Common/ExpressionHelpers.cs b/src/PSRule/Common/ExpressionHelpers.cs index 658100e44c..97f2445593 100644 --- a/src/PSRule/Common/ExpressionHelpers.cs +++ b/src/PSRule/Common/ExpressionHelpers.cs @@ -6,7 +6,6 @@ using System.Reflection; using System.Text.RegularExpressions; using Newtonsoft.Json.Linq; -using PSRule.Configuration; using PSRule.Converters; using PSRule.Data; using PSRule.Pipeline; diff --git a/src/PSRule/Common/ExternalToolHelper.cs b/src/PSRule/Common/ExternalToolHelper.cs index 421a3ca653..c94065f051 100644 --- a/src/PSRule/Common/ExternalToolHelper.cs +++ b/src/PSRule/Common/ExternalToolHelper.cs @@ -3,7 +3,6 @@ using System.Diagnostics; using System.Text; -using PSRule.Configuration; namespace PSRule { diff --git a/src/PSRule/Common/GitHelper.cs b/src/PSRule/Common/GitHelper.cs index 5088e8fd08..a54d98c506 100644 --- a/src/PSRule/Common/GitHelper.cs +++ b/src/PSRule/Common/GitHelper.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. using System.Runtime.InteropServices; -using PSRule.Configuration; namespace PSRule { diff --git a/src/PSRule/Definitions/Expressions/LanguageExpressions.cs b/src/PSRule/Definitions/Expressions/LanguageExpressions.cs index c530646fd7..4ee09a779c 100644 --- a/src/PSRule/Definitions/Expressions/LanguageExpressions.cs +++ b/src/PSRule/Definitions/Expressions/LanguageExpressions.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. using System.Diagnostics; -using PSRule.Configuration; using PSRule.Data; using PSRule.Pipeline; using PSRule.Resources; diff --git a/src/PSRule/Pipeline/PathBuilder.cs b/src/PSRule/Pipeline/PathBuilder.cs index e5740b82ac..d402099197 100644 --- a/src/PSRule/Pipeline/PathBuilder.cs +++ b/src/PSRule/Pipeline/PathBuilder.cs @@ -3,7 +3,6 @@ using System.Diagnostics; using System.Management.Automation; -using PSRule.Configuration; using PSRule.Data; namespace PSRule.Pipeline diff --git a/src/PSRule/Runtime/PSRule.cs b/src/PSRule/Runtime/PSRule.cs index 83bf987153..0d4f7078f8 100644 --- a/src/PSRule/Runtime/PSRule.cs +++ b/src/PSRule/Runtime/PSRule.cs @@ -4,7 +4,6 @@ using System.Collections; using System.Management.Automation; using PSRule.Badges; -using PSRule.Configuration; using PSRule.Data; using PSRule.Pipeline; diff --git a/tests/PSRule.Tests/AssertTests.cs b/tests/PSRule.Tests/AssertTests.cs index f04c231d67..7614d195a9 100644 --- a/tests/PSRule.Tests/AssertTests.cs +++ b/tests/PSRule.Tests/AssertTests.cs @@ -10,7 +10,6 @@ using PSRule.Pipeline; using PSRule.Runtime; using Xunit.Abstractions; -using Assert = Xunit.Assert; namespace PSRule { diff --git a/tests/PSRule.Tests/BaselineTests.cs b/tests/PSRule.Tests/BaselineTests.cs index ffc9549ed3..15455545c8 100644 --- a/tests/PSRule.Tests/BaselineTests.cs +++ b/tests/PSRule.Tests/BaselineTests.cs @@ -14,7 +14,6 @@ using PSRule.Pipeline.Output; using PSRule.Runtime; using YamlDotNet.Serialization; -using Assert = Xunit.Assert; namespace PSRule { diff --git a/tests/PSRule.Tests/FunctionBuilderTests.cs b/tests/PSRule.Tests/FunctionBuilderTests.cs index ddb37e54fb..1ba1432cd0 100644 --- a/tests/PSRule.Tests/FunctionBuilderTests.cs +++ b/tests/PSRule.Tests/FunctionBuilderTests.cs @@ -9,7 +9,6 @@ using PSRule.Host; using PSRule.Pipeline; using PSRule.Runtime; -using Assert = Xunit.Assert; namespace PSRule { diff --git a/tests/PSRule.Tests/ModuleConfigTests.cs b/tests/PSRule.Tests/ModuleConfigTests.cs index af50c1b9cb..6ec0ccb9dd 100644 --- a/tests/PSRule.Tests/ModuleConfigTests.cs +++ b/tests/PSRule.Tests/ModuleConfigTests.cs @@ -8,7 +8,6 @@ using PSRule.Host; using PSRule.Pipeline; using PSRule.Runtime; -using Assert = Xunit.Assert; namespace PSRule { diff --git a/tests/PSRule.Tests/ResourceValidatorTests.cs b/tests/PSRule.Tests/ResourceValidatorTests.cs index 7a1911adc5..f753226cdb 100644 --- a/tests/PSRule.Tests/ResourceValidatorTests.cs +++ b/tests/PSRule.Tests/ResourceValidatorTests.cs @@ -8,7 +8,6 @@ using PSRule.Host; using PSRule.Pipeline; using PSRule.Runtime; -using Assert = Xunit.Assert; namespace PSRule { diff --git a/tests/PSRule.Tests/RulesTests.cs b/tests/PSRule.Tests/RulesTests.cs index 243b2e3d8f..594c51ac32 100644 --- a/tests/PSRule.Tests/RulesTests.cs +++ b/tests/PSRule.Tests/RulesTests.cs @@ -11,7 +11,6 @@ using PSRule.Pipeline; using PSRule.Rules; using PSRule.Runtime; -using Assert = Xunit.Assert; namespace PSRule { diff --git a/tests/PSRule.Tests/SelectorTests.cs b/tests/PSRule.Tests/SelectorTests.cs index 8a540668f7..9d3edbc397 100644 --- a/tests/PSRule.Tests/SelectorTests.cs +++ b/tests/PSRule.Tests/SelectorTests.cs @@ -11,7 +11,6 @@ using PSRule.Host; using PSRule.Pipeline; using PSRule.Runtime; -using Assert = Xunit.Assert; namespace PSRule { diff --git a/tests/PSRule.Tests/SuppressionFilterTests.cs b/tests/PSRule.Tests/SuppressionFilterTests.cs index bb8747a74c..07b5871e70 100644 --- a/tests/PSRule.Tests/SuppressionFilterTests.cs +++ b/tests/PSRule.Tests/SuppressionFilterTests.cs @@ -9,7 +9,6 @@ using PSRule.Pipeline; using PSRule.Rules; using PSRule.Runtime; -using Assert = Xunit.Assert; namespace PSRule { diff --git a/tests/PSRule.Tests/SuppressionGroupTests.cs b/tests/PSRule.Tests/SuppressionGroupTests.cs index 059e43c87a..d95fe9b4b3 100644 --- a/tests/PSRule.Tests/SuppressionGroupTests.cs +++ b/tests/PSRule.Tests/SuppressionGroupTests.cs @@ -9,7 +9,6 @@ using PSRule.Host; using PSRule.Pipeline; using PSRule.Runtime; -using Assert = Xunit.Assert; namespace PSRule { diff --git a/tests/PSRule.Tests/Usings.cs b/tests/PSRule.Tests/Usings.cs index 8c07c6cf4c..c0d37fb5bc 100644 --- a/tests/PSRule.Tests/Usings.cs +++ b/tests/PSRule.Tests/Usings.cs @@ -2,3 +2,4 @@ // Licensed under the MIT License. global using Xunit; +global using Assert = Xunit.Assert; From 0841dea870d6ea7df03ae029dd21057810f4f1ac Mon Sep 17 00:00:00 2001 From: Bernie White Date: Mon, 17 Jul 2023 01:03:55 +1000 Subject: [PATCH 043/177] Update CI to push v3 docs (#1571) --- .github/workflows/docs.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index 8b64f701b8..6a1a147c08 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -34,7 +34,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v4 with: - python-version: '3.9' + python-version: '3.11' architecture: 'x64' - name: Install dependencies @@ -44,4 +44,4 @@ jobs: python3 -m pip install -r requirements-docs.txt - name: Deploy site - run: mike deploy --push --update-aliases v2 latest stable --title 'v2' + run: mike deploy --push --update-aliases v3 latest --title 'v3' From 1080301da9f2005039b903a1945ab94cefa66478 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Jul 2023 01:21:18 +1000 Subject: [PATCH 044/177] Bump BenchmarkDotNet from 0.13.5 to 0.13.6 (#1566) * Bump BenchmarkDotNet from 0.13.5 to 0.13.6 Bumps [BenchmarkDotNet](https://github.com/dotnet/BenchmarkDotNet) from 0.13.5 to 0.13.6. - [Release notes](https://github.com/dotnet/BenchmarkDotNet/releases) - [Commits](https://github.com/dotnet/BenchmarkDotNet/compare/v0.13.5...v0.13.6) --- updated-dependencies: - dependency-name: BenchmarkDotNet dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * Bump change log --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Bernie White --- docs/CHANGELOG-v3.md | 2 ++ src/PSRule.Benchmark/PSRule.Benchmark.csproj | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/CHANGELOG-v3.md b/docs/CHANGELOG-v3.md index aed2ac3824..2d11bcb611 100644 --- a/docs/CHANGELOG-v3.md +++ b/docs/CHANGELOG-v3.md @@ -52,3 +52,5 @@ What's changed since release v2.9.0: [#1562](https://github.com/microsoft/PSRule/pull/1562) - Bump xunit.runner.visualstudio to v2.5.0. [#1561](https://github.com/microsoft/PSRule/pull/1561) + - Bump BenchmarkDotNet to v0.13.6. + [#1566](https://github.com/microsoft/PSRule/pull/1566) diff --git a/src/PSRule.Benchmark/PSRule.Benchmark.csproj b/src/PSRule.Benchmark/PSRule.Benchmark.csproj index 21d227a4f1..c56f264fc1 100644 --- a/src/PSRule.Benchmark/PSRule.Benchmark.csproj +++ b/src/PSRule.Benchmark/PSRule.Benchmark.csproj @@ -15,7 +15,7 @@ - + From e4e2cce5d675a0c0886ad0bdcfa3024f1af7b7e2 Mon Sep 17 00:00:00 2001 From: Bernie White Date: Mon, 17 Jul 2023 01:32:00 +1000 Subject: [PATCH 045/177] Add vNext announcement in docs (#1572) --- .vscode/settings.json | 3 +++ overrides/main.html | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/.vscode/settings.json b/.vscode/settings.json index cac8e90ff0..00107e76e3 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -64,6 +64,9 @@ "editor.formatOnSave": false, "editor.tabSize": 4 }, + "[html]": { + "editor.formatOnSave": false, + }, "files.associations": { "**/.azure-pipelines/*.yaml": "azure-pipelines", "**/.azure-pipelines/jobs/*.yaml": "azure-pipelines", diff --git a/overrides/main.html b/overrides/main.html index d31b340efd..272a63b39c 100644 --- a/overrides/main.html +++ b/overrides/main.html @@ -44,3 +44,10 @@ {% include "partials/giscus.html" %} {% endblock %} + +{% block announce %} + You are viewing the next version PSRule. + + Click here to go to latest stable version. + +{% endblock %} From b72e1bd7727604a1608cf1c050de909db9a18b69 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Jul 2023 01:42:29 +1000 Subject: [PATCH 046/177] Bump BenchmarkDotNet.Diagnostics.Windows from 0.13.5 to 0.13.6 (#1565) * Bump BenchmarkDotNet.Diagnostics.Windows from 0.13.5 to 0.13.6 Bumps [BenchmarkDotNet.Diagnostics.Windows](https://github.com/dotnet/BenchmarkDotNet) from 0.13.5 to 0.13.6. - [Release notes](https://github.com/dotnet/BenchmarkDotNet/releases) - [Commits](https://github.com/dotnet/BenchmarkDotNet/compare/v0.13.5...v0.13.6) --- updated-dependencies: - dependency-name: BenchmarkDotNet.Diagnostics.Windows dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * Bump change log --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Bernie White --- docs/CHANGELOG-v3.md | 2 ++ src/PSRule.Benchmark/PSRule.Benchmark.csproj | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/CHANGELOG-v3.md b/docs/CHANGELOG-v3.md index 2d11bcb611..3491e53e81 100644 --- a/docs/CHANGELOG-v3.md +++ b/docs/CHANGELOG-v3.md @@ -54,3 +54,5 @@ What's changed since release v2.9.0: [#1561](https://github.com/microsoft/PSRule/pull/1561) - Bump BenchmarkDotNet to v0.13.6. [#1566](https://github.com/microsoft/PSRule/pull/1566) + - Bump BenchmarkDotNet.Diagnostics.Windows to v0.13.6. + [#1565](https://github.com/microsoft/PSRule/pull/1565) diff --git a/src/PSRule.Benchmark/PSRule.Benchmark.csproj b/src/PSRule.Benchmark/PSRule.Benchmark.csproj index c56f264fc1..220d74993c 100644 --- a/src/PSRule.Benchmark/PSRule.Benchmark.csproj +++ b/src/PSRule.Benchmark/PSRule.Benchmark.csproj @@ -20,7 +20,7 @@ - + From b4842018ac1e5e994aa0a7ede8c733aab88cd739 Mon Sep 17 00:00:00 2001 From: Bernie White Date: Mon, 17 Jul 2023 01:55:16 +1000 Subject: [PATCH 047/177] Update devcontainer configuration (#1573) --- .devcontainer/devcontainer.json | 15 ++++++++++++--- .vscode/extensions.json | 16 ++++++++-------- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index a821e5180e..e092fb588f 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -13,13 +13,22 @@ }, "extensions": [ "ms-dotnettools.csharp", - "bewhite.psrule-vscode-preview" + "ms-vscode.powershell", + "github.vscode-pull-request-github", + "davidanson.vscode-markdownlint", + "streetsidesoftware.code-spell-checker", + "bewhite.psrule-vscode-preview", + "eamodio.gitlens" ] } }, "features": { - "github-cli": "latest", - "powershell": "latest" + "ghcr.io/devcontainers/features/github-cli": { + "version": "latest" + }, + "ghcr.io/devcontainers/features/powershell": { + "version": "latest" + } }, "onCreateCommand": "/opt/microsoft/powershell/7/pwsh -f .devcontainer/container-build.ps1", "postStartCommand": "/opt/microsoft/powershell/7/pwsh -f .devcontainer/container-start.ps1", diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 0fb947cef9..48a037f99d 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,10 +1,10 @@ { - "recommendations": [ - "ms-azure-devops.azure-pipelines", - "github.vscode-pull-request-github", - "davidanson.vscode-markdownlint", - "streetsidesoftware.code-spell-checker", - "bewhite.psrule-vscode-preview", - "ms-dotnettools.csharp" - ] + "recommendations": [ + "ms-dotnettools.csharp", + "ms-vscode.powershell", + "github.vscode-pull-request-github", + "davidanson.vscode-markdownlint", + "streetsidesoftware.code-spell-checker", + "bewhite.psrule-vscode-preview" + ] } From ff4454552a11c7b42b475e9ef904385321b69220 Mon Sep 17 00:00:00 2001 From: Bernie White Date: Mon, 17 Jul 2023 02:36:08 +1000 Subject: [PATCH 048/177] Update banner style (#1574) --- docs/assets/stylesheets/extra.css | 36 ++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/docs/assets/stylesheets/extra.css b/docs/assets/stylesheets/extra.css index 21bdfd0edc..db7fbd603c 100644 --- a/docs/assets/stylesheets/extra.css +++ b/docs/assets/stylesheets/extra.css @@ -1,19 +1,25 @@ @keyframes heart { - 0%, 40%, 80%, 100% { + + 0%, + 40%, + 80%, + 100% { transform: scale(1); } - 20%, 60% { + + 20%, + 60% { transform: scale(1.15); } } .heart { animation: heart 1000ms infinite; - color:#e91e63; + color: #e91e63; } .experimental { - color:rgb(124, 77, 255); + color: rgb(124, 77, 255); } :root { @@ -24,18 +30,28 @@ :root { --md-admonition-icon--experimental: url('data:image/svg+xml;charset=utf-8,') } + .md-typeset .admonition.experimental, .md-typeset details.experimental { border-color: rgb(124, 77, 255) } -.md-typeset .experimental > .admonition-title, -.md-typeset .experimental > summary { - background-color: rgba(124,77,255,.1);; + +.md-typeset .experimental>.admonition-title, +.md-typeset .experimental>summary { + background-color: rgba(124, 77, 255, .1); + ; border-color: rgb(124, 77, 255); } -.md-typeset .experimental > .admonition-title::before, -.md-typeset .experimental > summary::before { + +.md-typeset .experimental>.admonition-title::before, +.md-typeset .experimental>summary::before { background-color: rgb(124, 77, 255); -webkit-mask-image: var(--md-admonition-icon--experimental); - mask-image: var(--md-admonition-icon--experimental); + mask-image: var(--md-admonition-icon--experimental); +} + +aside.md-banner { + overflow: auto; + color: var(--md-warning-fg-color); + background-color: var(--md-warning-bg-color); } From bb41e561ec4052cec388c75f77cd7da60a02bc85 Mon Sep 17 00:00:00 2001 From: Bernie White Date: Mon, 17 Jul 2023 02:47:31 +1000 Subject: [PATCH 049/177] Use SHA-512 for name generation #1155 (#1575) --- README.md | 1 + docs/CHANGELOG-v3.md | 7 ++ .../PSRule/en-US/about_PSRule_Options.md | 69 ++++++++++++++++--- docs/upgrade-notes.md | 21 ++++++ schemas/PSRule-options.schema.json | 12 ++++ src/PSRule.Tool/ClientHelper.cs | 2 +- .../Options}/ExecutionActionPreference.cs | 2 +- .../Options}/ExecutionOption.cs | 23 ++++++- src/PSRule.Types/Options/HashAlgorithm.cs | 30 ++++++++ .../Options}/LanguageMode.cs | 2 +- .../Options}/SessionState.cs | 2 +- .../RunspaceContextDiagnosticExtensions.cs | 2 +- src/PSRule/Configuration/PSRuleOption.cs | 10 +-- src/PSRule/Host/Host.cs | 4 +- src/PSRule/PSRule.psm1 | 66 +++++++++--------- src/PSRule/Pipeline/GetRuleHelpPipeline.cs | 1 + src/PSRule/Pipeline/PipelineBuilder.cs | 11 +-- src/PSRule/Pipeline/PipelineContext.cs | 23 ++++--- src/PSRule/Pipeline/PipelineHookActions.cs | 5 +- src/PSRule/Runtime/RunspaceContext.cs | 1 + tests/PSRule.Tests/PSRule.Common.Tests.ps1 | 14 ++-- tests/PSRule.Tests/PSRule.Options.Tests.ps1 | 28 ++++++++ tests/PSRule.Tests/PSRule.Tests.yml | 1 + tests/PSRule.Tests/PipelineTests.cs | 3 +- tests/PSRule.Tests/TargetNameBindingTests.cs | 22 ++++-- 25 files changed, 276 insertions(+), 86 deletions(-) rename src/{PSRule/Configuration => PSRule.Types/Options}/ExecutionActionPreference.cs (96%) rename src/{PSRule/Configuration => PSRule.Types/Options}/ExecutionOption.cs (95%) create mode 100644 src/PSRule.Types/Options/HashAlgorithm.cs rename src/{PSRule/Configuration => PSRule.Types/Options}/LanguageMode.cs (96%) rename src/{PSRule/Configuration => PSRule.Types/Options}/SessionState.cs (96%) diff --git a/README.md b/README.md index b4b8569298..2a3cce8068 100644 --- a/README.md +++ b/README.md @@ -294,6 +294,7 @@ The following conceptual topics exist in the `PSRule` module: - [Execution.AliasReference](https://aka.ms/ps-rule/options#executionaliasreference) - [Execution.AliasReferenceWarning](https://aka.ms/ps-rule/options#executionaliasreferencewarning) - [Execution.DuplicateResourceId](https://aka.ms/ps-rule/options#executionduplicateresourceid) + - [Execution.HashAlgorithm](https://aka.ms/ps-rule/options#executionhashalgorithm) - [Execution.LanguageMode](https://aka.ms/ps-rule/options#executionlanguagemode) - [Execution.InconclusiveWarning](https://aka.ms/ps-rule/options#executioninconclusivewarning) - [Execution.InvariantCulture](https://aka.ms/ps-rule/options#executioninvariantculture) diff --git a/docs/CHANGELOG-v3.md b/docs/CHANGELOG-v3.md index 3491e53e81..bdf727fbbf 100644 --- a/docs/CHANGELOG-v3.md +++ b/docs/CHANGELOG-v3.md @@ -38,6 +38,13 @@ See [upgrade notes][1] for helpful information when upgrading from previous vers What's changed since release v2.9.0: - General improvements: + - **Breaking change:** Switch to use SHA-512 for generating unbound objects by @BernieWhite. + [#1155](https://github.com/microsoft/PSRule/issues/1155) + - Objects that have no bound name will automatically be assigned a name based on the SHA-512 hash of the object. + - Previously a SHA-1 hash was used, however this is no longer considered secure. + - The name for unbound objects that are suppressed will change as a result. + - Additionally the hash can be changed by setting the `Execution.HashAlgorithm` option. + - See [upgrade notes][1] for details. - Expanded support for `FileHeader` assertion by @BernieWhite. [#1521](https://github.com/microsoft/PSRule/issues/1521) - Added support for `.bicepparam`, `.tsp`, `.tsx`, `.editorconfig`, `.ipynb`, and `.toml` files. diff --git a/docs/concepts/PSRule/en-US/about_PSRule_Options.md b/docs/concepts/PSRule/en-US/about_PSRule_Options.md index 8fc560c861..8773baf052 100644 --- a/docs/concepts/PSRule/en-US/about_PSRule_Options.md +++ b/docs/concepts/PSRule/en-US/about_PSRule_Options.md @@ -18,6 +18,7 @@ The following workspace options are available for use: - [Execution.AliasReference](#executionaliasreference) - [Execution.AliasReferenceWarning](#executionaliasreferencewarning) - [Execution.DuplicateResourceId](#executionduplicateresourceid) +- [Execution.HashAlgorithm](#executionhashalgorithm) - [Execution.LanguageMode](#executionlanguagemode) - [Execution.InconclusiveWarning](#executioninconclusivewarning) - [Execution.InvariantCulture](#executioninvariantculture) @@ -88,7 +89,8 @@ Options can be used with the following PSRule cmdlets: Each of these cmdlets support: -- Using the `-Option` parameter with an object created with the `New-PSRuleOption` cmdlet. See cmdlet help for syntax and examples. +- Using the `-Option` parameter with an object created with the `New-PSRuleOption` cmdlet. + See cmdlet help for syntax and examples. - Using the `-Option` parameter with a hashtable object. - Using the `-Option` parameter with a YAML file path. @@ -124,7 +126,8 @@ The `Set-PSRuleOption` cmdlet can be used to set options stored in YAML or the Y Set-PSRuleOption -OutputFormat Yaml; ``` -By default, PSRule will automatically look for a default YAML options file in the current working directory. Alternatively, you can specify a specific file path. +By default, PSRule will automatically look for a default YAML options file in the current working directory. +Alternatively, you can specify a specific file path. For example: @@ -426,21 +429,25 @@ _TargetName_ is used in output results to identify one object from another. Many objects could be passed down the pipeline at the same time, so using a _TargetName_ that is meaningful is important. _TargetName_ is also used for advanced features such as rule suppression. -The value that PSRule uses for _TargetName_ is configurable. PSRule uses the following logic to determine what _TargetName_ should be used: +The value that PSRule uses for _TargetName_ is configurable. +PSRule uses the following logic to determine what _TargetName_ should be used: - By default PSRule will: - Use `TargetName` or `Name` properties on the object. These property names are case insensitive. - If both `TargetName` and `Name` properties exist, `TargetName` will take precedence over `Name`. - - If neither `TargetName` or `Name` properties exist, a SHA1 hash of the object will be used as _TargetName_. + - If neither `TargetName` or `Name` properties exist, a hash of the object will be used as _TargetName_. + - The hash algorithm used can be set by the `Execution.HashAlgorithm` option. - If custom _TargetName_ binding properties are configured, the property names specified will override the defaults. - If **none** of the configured property names exist, PSRule will revert back to `TargetName` then `Name`. - If more then one property name is configured, the order they are specified in the configuration determines precedence. - i.e. The first configured property name will take precedence over the second property name. - - By default the property name will be matched ignoring case sensitivity. To use a case sensitive match, configure the [Binding.IgnoreCase](#bindingignorecase) option. + - By default the property name will be matched ignoring case sensitivity. + To use a case sensitive match, configure the [Binding.IgnoreCase](#bindingignorecase) option. - If a custom _TargetName_ binding function is specified, the function will be evaluated first before any other option. - If the function returns `$Null` then custom properties, `TargetName` and `Name` properties will be used. - The custom binding function is executed outside the PSRule engine, so PSRule keywords and variables will not be available. - - Custom binding functions are blocked in constrained language mode is used. See [language mode](#executionlanguagemode) for more information. + - Custom binding functions are blocked in constrained language mode is used. + See [language mode](#executionlanguagemode) for more information. Custom property names to use for binding can be specified using: @@ -515,11 +522,13 @@ PSRule uses the following logic to determine what _TargetType_ should be used: - If **none** of the configured property names exist, PSRule will revert back to the type presented by PowerShell. - If more then one property name is configured, the order they are specified in the configuration determines precedence. - i.e. The first configured property name will take precedence over the second property name. - - By default the property name will be matched ignoring case sensitivity. To use a case sensitive match, configure the [`Binding.IgnoreCase`](#bindingignorecase) option. + - By default the property name will be matched ignoring case sensitivity. + To use a case sensitive match, configure the [`Binding.IgnoreCase`](#bindingignorecase) option. - If a custom _TargetType_ binding function is specified, the function will be evaluated first before any other option. - If the function returns `$Null` then custom properties, or the type presented by PowerShell will be used in order instead. - The custom binding function is executed outside the PSRule engine, so PSRule keywords and variables will not be available. - - Custom binding functions are blocked in constrained language mode is used. See [language mode](#executionlanguagemode) for more information. + - Custom binding functions are blocked in constrained language mode is used. + See [language mode](#executionlanguagemode) for more information. Custom property names to use for binding can be specified using: @@ -897,6 +906,50 @@ variables: value: Warn ``` +### Execution.HashAlgorithm + +Specifies the hashing algorithm used by the PSRule runtime. +This hash algorithm is used when generating a resource identifier for an object that does not have a bound name. + +By default, the _SHA512_ algorithm is used. + +The following algorithms are available for use in PSRule: + +- SHA512 +- SHA384 +- SHA256 + +This option can be specified using: + +```powershell +# PowerShell: Using the Execution.HashAlgorithm hashtable key +$option = New-PSRuleOption -Option @{ 'Execution.HashAlgorithm' = 'SHA256' }; +``` + +```yaml +# YAML: Using the execution/hashAlgorithm property +execution: + hashAlgorithm: SHA256 +``` + +```bash +# Bash: Using environment variable +export PSRULE_EXECUTION_HASHALGORITHM=SHA256 +``` + +```yaml +# GitHub Actions: Using environment variable +env: + PSRULE_EXECUTION_HASHALGORITHM: SHA256 +``` + +```yaml +# Azure Pipelines: Using environment variable +variables: +- name: PSRULE_EXECUTION_HASHALGORITHM + value: SHA256 +``` + ### Execution.LanguageMode Unless PowerShell has been constrained, full language features of PowerShell are available to use within rule definitions. diff --git a/docs/upgrade-notes.md b/docs/upgrade-notes.md index f773404f45..8bfe545285 100644 --- a/docs/upgrade-notes.md +++ b/docs/upgrade-notes.md @@ -8,6 +8,27 @@ discussion: false This document contains notes to help upgrade from previous versions of PSRule. +## Upgrading to v3.0.0 + +### Unbound object names + +When an object is processed by PSRule, it is assigned a name. +This name is used to identify the object in the output and to suppress the object from future processing. + +Prior to _v3.0.0_, the name was generated using a SHA-1 hash of the object. +The SHA-1 algorithm is no longer considered secure and has been replaced with SHA-512. + +From _v3.0.0_, if the name of an object can not be determined, the SHA-512 hash of the object will be used. +Any objects that have previously been suppressed with a name based on a SHA-1 hash will no longer be suppressed. + +To resolve any issue caused by this change, you can: + +1. Configure binding by setting the [Binding.TargetName][1] option to set an alternative property to use as the name. _OR_ +2. Update any existing keys set with the [Suppression][2] option to use the new SHA-512 hash. + + [1]: https://aka.ms/ps-rule/options#bindingtargetname + [2]: https://aka.ms/ps-rule/options#suppression + ## Upgrading to v2.0.0 ### Resources naming restrictions diff --git a/schemas/PSRule-options.schema.json b/schemas/PSRule-options.schema.json index 0d0efa7d94..5ee9c25f23 100644 --- a/schemas/PSRule-options.schema.json +++ b/schemas/PSRule-options.schema.json @@ -328,6 +328,18 @@ ], "default": "Error" }, + "hashAlgorithm": { + "type": "string", + "title": "Hash Algorithm", + "description": "Configures the hashing algorithm used by the PSRule runtime. By default, SHA512 is used.", + "markdownDescription": "Configures the hashing algorithm used by the PSRule runtime.\n\nBy default, `SHA512` is used.\n\n[See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Options/#executionhashalgorithm)", + "enum": [ + "SHA512", + "SHA384", + "SHA256" + ], + "default": "SHA512" + }, "languageMode": { "type": "string", "title": "Language mode", diff --git a/src/PSRule.Tool/ClientHelper.cs b/src/PSRule.Tool/ClientHelper.cs index 3982177ce1..c07b5266ff 100644 --- a/src/PSRule.Tool/ClientHelper.cs +++ b/src/PSRule.Tool/ClientHelper.cs @@ -190,7 +190,7 @@ private static PSRuleOption GetOption(ClientHost host) { PSRuleOption.UseHostContext(host); var option = PSRuleOption.FromFileOrEmpty(); - option.Execution.InitialSessionState = Configuration.SessionState.Minimal; + option.Execution.InitialSessionState = Options.SessionState.Minimal; option.Input.Format = InputFormat.File; option.Output.Style ??= OutputStyle.Client; return option; diff --git a/src/PSRule/Configuration/ExecutionActionPreference.cs b/src/PSRule.Types/Options/ExecutionActionPreference.cs similarity index 96% rename from src/PSRule/Configuration/ExecutionActionPreference.cs rename to src/PSRule.Types/Options/ExecutionActionPreference.cs index 487b3bd677..13e57af5e2 100644 --- a/src/PSRule/Configuration/ExecutionActionPreference.cs +++ b/src/PSRule.Types/Options/ExecutionActionPreference.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -namespace PSRule.Configuration +namespace PSRule.Options { /// /// Determines the action to take for execution behaviors. diff --git a/src/PSRule/Configuration/ExecutionOption.cs b/src/PSRule.Types/Options/ExecutionOption.cs similarity index 95% rename from src/PSRule/Configuration/ExecutionOption.cs rename to src/PSRule.Types/Options/ExecutionOption.cs index d2467b500c..edad84eabb 100644 --- a/src/PSRule/Configuration/ExecutionOption.cs +++ b/src/PSRule.Types/Options/ExecutionOption.cs @@ -3,7 +3,7 @@ using System.ComponentModel; -namespace PSRule.Configuration +namespace PSRule.Options { /// /// Options that configure the execution sandbox. @@ -13,7 +13,7 @@ namespace PSRule.Configuration /// public sealed class ExecutionOption : IEquatable { - private const LanguageMode DEFAULT_LANGUAGEMODE = Configuration.LanguageMode.FullLanguage; + private const LanguageMode DEFAULT_LANGUAGEMODE = Options.LanguageMode.FullLanguage; private const ExecutionActionPreference DEFAULT_DUPLICATERESOURCEID = ExecutionActionPreference.Error; private const SessionState DEFAULT_INITIALSESSIONSTATE = SessionState.BuiltIn; private const ExecutionActionPreference DEFAULT_SUPPRESSIONGROUPEXPIRED = ExecutionActionPreference.Warn; @@ -23,10 +23,12 @@ public sealed class ExecutionOption : IEquatable private const ExecutionActionPreference DEFAULT_RULEINCONCLUSIVE = ExecutionActionPreference.Warn; private const ExecutionActionPreference DEFAULT_INVARIANTCULTURE = ExecutionActionPreference.Warn; private const ExecutionActionPreference DEFAULT_UNPROCESSEDOBJECT = ExecutionActionPreference.Warn; + private const HashAlgorithm DEFAULT_HASHALGORITHM = Options.HashAlgorithm.SHA512; internal static readonly ExecutionOption Default = new() { DuplicateResourceId = DEFAULT_DUPLICATERESOURCEID, + HashAlgorithm = DEFAULT_HASHALGORITHM, LanguageMode = DEFAULT_LANGUAGEMODE, InitialSessionState = DEFAULT_INITIALSESSIONSTATE, SuppressionGroupExpired = DEFAULT_SUPPRESSIONGROUPEXPIRED, @@ -46,6 +48,7 @@ public ExecutionOption() #pragma warning disable CS0618 // Type or member is obsolete AliasReferenceWarning = null; DuplicateResourceId = null; + HashAlgorithm = null; LanguageMode = null; InconclusiveWarning = null; InvariantCultureWarning = null; @@ -74,6 +77,7 @@ public ExecutionOption(ExecutionOption option) #pragma warning disable CS0618 // Type or member is obsolete AliasReferenceWarning = option.AliasReferenceWarning; DuplicateResourceId = option.DuplicateResourceId; + HashAlgorithm = option.HashAlgorithm; LanguageMode = option.LanguageMode; InconclusiveWarning = option.InconclusiveWarning; InvariantCultureWarning = option.InvariantCultureWarning; @@ -103,6 +107,7 @@ public bool Equals(ExecutionOption other) return other != null && AliasReferenceWarning == other.AliasReferenceWarning && DuplicateResourceId == other.DuplicateResourceId && + HashAlgorithm == other.HashAlgorithm && LanguageMode == other.LanguageMode && InconclusiveWarning == other.InconclusiveWarning && InvariantCultureWarning == other.InvariantCultureWarning && @@ -128,6 +133,7 @@ public override int GetHashCode() #pragma warning disable CS0618 // Type or member is obsolete hash = hash * 23 + (AliasReferenceWarning.HasValue ? AliasReferenceWarning.Value.GetHashCode() : 0); hash = hash * 23 + (DuplicateResourceId.HasValue ? DuplicateResourceId.Value.GetHashCode() : 0); + hash = hash * 23 + (HashAlgorithm.HasValue ? HashAlgorithm.Value.GetHashCode() : 0); hash = hash * 23 + (LanguageMode.HasValue ? LanguageMode.Value.GetHashCode() : 0); hash = hash * 23 + (InconclusiveWarning.HasValue ? InconclusiveWarning.Value.GetHashCode() : 0); hash = hash * 23 + (InvariantCultureWarning.HasValue ? InvariantCultureWarning.Value.GetHashCode() : 0); @@ -157,6 +163,7 @@ internal static ExecutionOption Combine(ExecutionOption o1, ExecutionOption o2) { AliasReferenceWarning = o1.AliasReferenceWarning ?? o2.AliasReferenceWarning, DuplicateResourceId = o1.DuplicateResourceId ?? o2.DuplicateResourceId, + HashAlgorithm = o1.HashAlgorithm ?? o2.HashAlgorithm, LanguageMode = o1.LanguageMode ?? o2.LanguageMode, InconclusiveWarning = o1.InconclusiveWarning ?? o2.InconclusiveWarning, NotProcessedWarning = o1.NotProcessedWarning ?? o2.NotProcessedWarning, @@ -192,6 +199,12 @@ internal static ExecutionOption Combine(ExecutionOption o1, ExecutionOption o2) [DefaultValue(null)] public ExecutionActionPreference? DuplicateResourceId { get; set; } + /// + /// Configures the hashing algorithm used by the PSRule runtime. + /// + [DefaultValue(null)] + public HashAlgorithm? HashAlgorithm { get; set; } + /// /// The langauge mode to execute PowerShell code with. /// @@ -330,6 +343,9 @@ internal static ExecutionOption Combine(ExecutionOption o1, ExecutionOption o2) /// internal void Load() { + if (Environment.TryEnum("PSRULE_EXECUTION_HASHALGORITHM", out HashAlgorithm hashAlgorithm)) + HashAlgorithm = hashAlgorithm; + #pragma warning disable CS0618 // Type or member is obsolete if (Environment.TryBool("PSRULE_EXECUTION_ALIASREFERENCEWARNING", out var bvalue)) AliasReferenceWarning = bvalue; @@ -383,6 +399,9 @@ internal void Load() /// internal void Load(Dictionary index) { + if (index.TryPopEnum("Execution.HashAlgorithm", out HashAlgorithm hashAlgorithm)) + HashAlgorithm = hashAlgorithm; + #pragma warning disable CS0618 // Type or member is obsolete if (index.TryPopBool("Execution.AliasReferenceWarning", out var bvalue)) AliasReferenceWarning = bvalue; diff --git a/src/PSRule.Types/Options/HashAlgorithm.cs b/src/PSRule.Types/Options/HashAlgorithm.cs new file mode 100644 index 0000000000..9891677f97 --- /dev/null +++ b/src/PSRule.Types/Options/HashAlgorithm.cs @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace PSRule.Options +{ + /// + /// Configures the hashing algorithm used by the PSRule runtime. + /// + [JsonConverter(typeof(StringEnumConverter))] + public enum HashAlgorithm + { + /// + /// Use SHA256. + /// + SHA256, + + /// + /// Use SHA384. + /// + SHA384, + + /// + /// Use SHA512. + /// + SHA512 + } +} diff --git a/src/PSRule/Configuration/LanguageMode.cs b/src/PSRule.Types/Options/LanguageMode.cs similarity index 96% rename from src/PSRule/Configuration/LanguageMode.cs rename to src/PSRule.Types/Options/LanguageMode.cs index 53a4653ec0..d03a30bdb8 100644 --- a/src/PSRule/Configuration/LanguageMode.cs +++ b/src/PSRule.Types/Options/LanguageMode.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -namespace PSRule.Configuration +namespace PSRule.Options { /// /// Configures the language mode PowerShell code executes as within PSRule runtime. diff --git a/src/PSRule/Configuration/SessionState.cs b/src/PSRule.Types/Options/SessionState.cs similarity index 96% rename from src/PSRule/Configuration/SessionState.cs rename to src/PSRule.Types/Options/SessionState.cs index e330b1afd8..e1c31bfd93 100644 --- a/src/PSRule/Configuration/SessionState.cs +++ b/src/PSRule.Types/Options/SessionState.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -namespace PSRule.Configuration +namespace PSRule.Options { /// /// Configures how the initial PowerShell sandbox for executing rules is created. diff --git a/src/PSRule/Common/RunspaceContextDiagnosticExtensions.cs b/src/PSRule/Common/RunspaceContextDiagnosticExtensions.cs index 7c402e1ce7..cbefaa8eaa 100644 --- a/src/PSRule/Common/RunspaceContextDiagnosticExtensions.cs +++ b/src/PSRule/Common/RunspaceContextDiagnosticExtensions.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using PSRule.Configuration; +using PSRule.Options; using PSRule.Definitions; using PSRule.Pipeline; using PSRule.Resources; diff --git a/src/PSRule/Configuration/PSRuleOption.cs b/src/PSRule/Configuration/PSRuleOption.cs index db58cb5c29..a422e0c154 100644 --- a/src/PSRule/Configuration/PSRuleOption.cs +++ b/src/PSRule/Configuration/PSRuleOption.cs @@ -36,7 +36,7 @@ public sealed class PSRuleOption : IEquatable, IBaselineV1Spec Baseline = Options.BaselineOption.Default, Binding = BindingOption.Default, Convention = ConventionOption.Default, - Execution = ExecutionOption.Default, + Execution = Options.ExecutionOption.Default, Include = IncludeOption.Default, Input = InputOption.Default, Logging = LoggingOption.Default, @@ -54,7 +54,7 @@ public PSRuleOption() Binding = new BindingOption(); Configuration = new ConfigurationOption(); Convention = new ConventionOption(); - Execution = new ExecutionOption(); + Execution = new Options.ExecutionOption(); Include = new IncludeOption(); Input = new InputOption(); Logging = new LoggingOption(); @@ -75,7 +75,7 @@ private PSRuleOption(string sourcePath, PSRuleOption option) Binding = new BindingOption(option?.Binding); Configuration = new ConfigurationOption(option?.Configuration); Convention = new ConventionOption(option?.Convention); - Execution = new ExecutionOption(option?.Execution); + Execution = new Options.ExecutionOption(option?.Execution); Include = new IncludeOption(option?.Include); Input = new InputOption(option?.Input); Logging = new LoggingOption(option?.Logging); @@ -110,7 +110,7 @@ private PSRuleOption(string sourcePath, PSRuleOption option) /// /// Options that configure the execution sandbox. /// - public ExecutionOption Execution { get; set; } + public Options.ExecutionOption Execution { get; set; } /// /// Options that affect source locations imported for execution. @@ -210,7 +210,7 @@ private static PSRuleOption Combine(PSRuleOption o1, PSRuleOption o2) result.Binding = BindingOption.Combine(result.Binding, o2?.Binding); result.Configuration = ConfigurationOption.Combine(result.Configuration, o2?.Configuration); result.Convention = ConventionOption.Combine(result.Convention, o2?.Convention); - result.Execution = ExecutionOption.Combine(result.Execution, o2?.Execution); + result.Execution = Options.ExecutionOption.Combine(result.Execution, o2?.Execution); result.Include = IncludeOption.Combine(result.Include, o2?.Include); result.Input = InputOption.Combine(result.Input, o2?.Input); result.Logging = LoggingOption.Combine(result.Logging, o2?.Logging); diff --git a/src/PSRule/Host/Host.cs b/src/PSRule/Host/Host.cs index 622a3da961..d2143fb193 100644 --- a/src/PSRule/Host/Host.cs +++ b/src/PSRule/Host/Host.cs @@ -147,9 +147,9 @@ internal static class HostState /// /// Create a default session state. /// - public static InitialSessionState CreateSessionState(Configuration.SessionState initialSessionState) + public static InitialSessionState CreateSessionState(Options.SessionState initialSessionState) { - var state = initialSessionState == Configuration.SessionState.Minimal ? + var state = initialSessionState == Options.SessionState.Minimal ? InitialSessionState.CreateDefault2() : InitialSessionState.CreateDefault(); // Add in language elements diff --git a/src/PSRule/PSRule.psm1 b/src/PSRule/PSRule.psm1 index cb70c293a5..de0e6e74fa 100644 --- a/src/PSRule/PSRule.psm1 +++ b/src/PSRule/PSRule.psm1 @@ -125,7 +125,7 @@ function Invoke-PSRule { # If DeviceGuard is enabled, force a contrained execution environment if ($isDeviceGuard) { - $Option.Execution.LanguageMode = [PSRule.Configuration.LanguageMode]::ConstrainedLanguage; + $Option.Execution.LanguageMode = [PSRule.Options.LanguageMode]::ConstrainedLanguage; } if ($PSBoundParameters.ContainsKey('Format')) { $Option.Input.Format = $Format; @@ -283,7 +283,7 @@ function Test-PSRuleTarget { # If DeviceGuard is enabled, force a contrained execution environment if ($isDeviceGuard) { - $Option.Execution.LanguageMode = [PSRule.Configuration.LanguageMode]::ConstrainedLanguage; + $Option.Execution.LanguageMode = [PSRule.Options.LanguageMode]::ConstrainedLanguage; } if ($PSBoundParameters.ContainsKey('Format')) { $Option.Input.Format = $Format; @@ -389,7 +389,7 @@ function Get-PSRuleTarget { # If DeviceGuard is enabled, force a contrained execution environment if ($isDeviceGuard) { - $Option.Execution.LanguageMode = [PSRule.Configuration.LanguageMode]::ConstrainedLanguage; + $Option.Execution.LanguageMode = [PSRule.Options.LanguageMode]::ConstrainedLanguage; } if ($PSBoundParameters.ContainsKey('Format')) { $Option.Input.Format = $Format; @@ -556,7 +556,7 @@ function Assert-PSRule { # If DeviceGuard is enabled, force a contrained execution environment if ($isDeviceGuard) { - $Option.Execution.LanguageMode = [PSRule.Configuration.LanguageMode]::ConstrainedLanguage; + $Option.Execution.LanguageMode = [PSRule.Options.LanguageMode]::ConstrainedLanguage; } if ($PSBoundParameters.ContainsKey('Format')) { $Option.Input.Format = $Format; @@ -718,7 +718,7 @@ function Get-PSRule { # If DeviceGuard is enabled, force a contrained execution environment if ($isDeviceGuard) { - $Option.Execution.LanguageMode = [PSRule.Configuration.LanguageMode]::ConstrainedLanguage; + $Option.Execution.LanguageMode = [PSRule.Options.LanguageMode]::ConstrainedLanguage; } if ($PSBoundParameters.ContainsKey('OutputFormat')) { $Option.Output.Format = $OutputFormat; @@ -1051,7 +1051,7 @@ function Get-PSRuleHelp { # If DeviceGuard is enabled, force a contrained execution environment if ($isDeviceGuard) { - $Option.Execution.LanguageMode = [PSRule.Configuration.LanguageMode]::ConstrainedLanguage; + $Option.Execution.LanguageMode = [PSRule.Options.LanguageMode]::ConstrainedLanguage; } if ($PSBoundParameters.ContainsKey('Culture')) { $Option.Output.Culture = $Culture; @@ -1171,7 +1171,7 @@ function New-PSRuleOption { # Sets the Execution.DuplicateResourceId option [Parameter(Mandatory = $False)] [Alias('ExecutionDuplicateResourceId')] - [PSRule.Configuration.ExecutionActionPreference]$DuplicateResourceId = [PSRule.Configuration.ExecutionActionPreference]::Error, + [PSRule.Options.ExecutionActionPreference]$DuplicateResourceId = [PSRule.Options.ExecutionActionPreference]::Error, # Sets the Execution.InconclusiveWarning option [Parameter(Mandatory = $False)] @@ -1186,7 +1186,7 @@ function New-PSRuleOption { # Sets the Execution.InitialSessionState option [Parameter(Mandatory = $False)] [Alias('ExecutionInitialSessionState')] - [PSRule.Configuration.SessionState]$InitialSessionState = [PSRule.Configuration.SessionState]::BuiltIn, + [PSRule.Options.SessionState]$InitialSessionState = [PSRule.Options.SessionState]::BuiltIn, # Sets the Execution.NotProcessedWarning option [Parameter(Mandatory = $False)] @@ -1202,31 +1202,31 @@ function New-PSRuleOption { # Sets the Execution.SuppressionGroupExpired option [Parameter(Mandatory = $False)] [Alias('ExecutionSuppressionGroupExpired')] - [PSRule.Configuration.ExecutionActionPreference]$SuppressionGroupExpired = [PSRule.Configuration.ExecutionActionPreference]::Warn, + [PSRule.Options.ExecutionActionPreference]$SuppressionGroupExpired = [PSRule.Options.ExecutionActionPreference]::Warn, # Sets the Execution.RuleExcluded option [Parameter(Mandatory = $False)] - [PSRule.Configuration.ExecutionActionPreference]$ExecutionRuleExcluded = [PSRule.Configuration.ExecutionActionPreference]::Ignore, + [PSRule.Options.ExecutionActionPreference]$ExecutionRuleExcluded = [PSRule.Options.ExecutionActionPreference]::Ignore, # Sets the Execution.RuleSuppressed option [Parameter(Mandatory = $False)] - [PSRule.Configuration.ExecutionActionPreference]$ExecutionRuleSuppressed = [PSRule.Configuration.ExecutionActionPreference]::Warn, + [PSRule.Options.ExecutionActionPreference]$ExecutionRuleSuppressed = [PSRule.Options.ExecutionActionPreference]::Warn, # Sets the Execution.AliasReference option [Parameter(Mandatory = $False)] - [PSRule.Configuration.ExecutionActionPreference]$ExecutionAliasReference = [PSRule.Configuration.ExecutionActionPreference]::Warn, + [PSRule.Options.ExecutionActionPreference]$ExecutionAliasReference = [PSRule.Options.ExecutionActionPreference]::Warn, # Sets the Execution.RuleInconclusive option [Parameter(Mandatory = $False)] - [PSRule.Configuration.ExecutionActionPreference]$ExecutionRuleInconclusive = [PSRule.Configuration.ExecutionActionPreference]::Warn, + [PSRule.Options.ExecutionActionPreference]$ExecutionRuleInconclusive = [PSRule.Options.ExecutionActionPreference]::Warn, # Sets the Execution.InvariantCulture option [Parameter(Mandatory = $False)] - [PSRule.Configuration.ExecutionActionPreference]$ExecutionInvariantCulture = [PSRule.Configuration.ExecutionActionPreference]::Warn, + [PSRule.Options.ExecutionActionPreference]$ExecutionInvariantCulture = [PSRule.Options.ExecutionActionPreference]::Warn, # Sets the Execution.UnprocessedObject option [Parameter(Mandatory = $False)] - [PSRule.Configuration.ExecutionActionPreference]$ExecutionUnprocessedObject = [PSRule.Configuration.ExecutionActionPreference]::Warn, + [PSRule.Options.ExecutionActionPreference]$ExecutionUnprocessedObject = [PSRule.Options.ExecutionActionPreference]::Warn, # Sets the Include.Module option [Parameter(Mandatory = $False)] @@ -1501,7 +1501,7 @@ function Set-PSRuleOption { # Sets the Execution.DuplicateResourceId option [Parameter(Mandatory = $False)] [Alias('ExecutionDuplicateResourceId')] - [PSRule.Configuration.ExecutionActionPreference]$DuplicateResourceId = [PSRule.Configuration.ExecutionActionPreference]::Error, + [PSRule.Options.ExecutionActionPreference]$DuplicateResourceId = [PSRule.Options.ExecutionActionPreference]::Error, # Sets the Execution.InconclusiveWarning option [Parameter(Mandatory = $False)] @@ -1516,7 +1516,7 @@ function Set-PSRuleOption { # Sets the Execution.InitialSessionState option [Parameter(Mandatory = $False)] [Alias('ExecutionInitialSessionState')] - [PSRule.Configuration.SessionState]$InitialSessionState = [PSRule.Configuration.SessionState]::BuiltIn, + [PSRule.Options.SessionState]$InitialSessionState = [PSRule.Options.SessionState]::BuiltIn, # Sets the Execution.NotProcessedWarning option [Parameter(Mandatory = $False)] @@ -1532,31 +1532,31 @@ function Set-PSRuleOption { # Sets the Execution.SuppressionGroupExpired option [Parameter(Mandatory = $False)] [Alias('ExecutionSuppressionGroupExpired')] - [PSRule.Configuration.ExecutionActionPreference]$SuppressionGroupExpired = [PSRule.Configuration.ExecutionActionPreference]::Warn, + [PSRule.Options.ExecutionActionPreference]$SuppressionGroupExpired = [PSRule.Options.ExecutionActionPreference]::Warn, # Sets the Execution.RuleExcluded option [Parameter(Mandatory = $False)] - [PSRule.Configuration.ExecutionActionPreference]$ExecutionRuleExcluded = [PSRule.Configuration.ExecutionActionPreference]::Ignore, + [PSRule.Options.ExecutionActionPreference]$ExecutionRuleExcluded = [PSRule.Options.ExecutionActionPreference]::Ignore, # Sets the Execution.RuleSuppressed option [Parameter(Mandatory = $False)] - [PSRule.Configuration.ExecutionActionPreference]$ExecutionRuleSuppressed = [PSRule.Configuration.ExecutionActionPreference]::Warn, + [PSRule.Options.ExecutionActionPreference]$ExecutionRuleSuppressed = [PSRule.Options.ExecutionActionPreference]::Warn, # Sets the Execution.AliasReference option [Parameter(Mandatory = $False)] - [PSRule.Configuration.ExecutionActionPreference]$ExecutionAliasReference = [PSRule.Configuration.ExecutionActionPreference]::Warn, + [PSRule.Options.ExecutionActionPreference]$ExecutionAliasReference = [PSRule.Options.ExecutionActionPreference]::Warn, # Sets the Execution.RuleInconclusive option [Parameter(Mandatory = $False)] - [PSRule.Configuration.ExecutionActionPreference]$ExecutionRuleInconclusive = [PSRule.Configuration.ExecutionActionPreference]::Warn, + [PSRule.Options.ExecutionActionPreference]$ExecutionRuleInconclusive = [PSRule.Options.ExecutionActionPreference]::Warn, # Sets the Execution.InvariantCulture option [Parameter(Mandatory = $False)] - [PSRule.Configuration.ExecutionActionPreference]$ExecutionInvariantCulture = [PSRule.Configuration.ExecutionActionPreference]::Warn, + [PSRule.Options.ExecutionActionPreference]$ExecutionInvariantCulture = [PSRule.Options.ExecutionActionPreference]::Warn, # Sets the Execution.UnprocessedObject option [Parameter(Mandatory = $False)] - [PSRule.Configuration.ExecutionActionPreference]$ExecutionUnprocessedObject = [PSRule.Configuration.ExecutionActionPreference]::Warn, + [PSRule.Options.ExecutionActionPreference]$ExecutionUnprocessedObject = [PSRule.Options.ExecutionActionPreference]::Warn, # Sets the Include.Module option [Parameter(Mandatory = $False)] @@ -2278,7 +2278,7 @@ function SetOptions { # Sets the Execution.DuplicateResourceId option [Parameter(Mandatory = $False)] [Alias('ExecutionDuplicateResourceId')] - [PSRule.Configuration.ExecutionActionPreference]$DuplicateResourceId = [PSRule.Configuration.ExecutionActionPreference]::Error, + [PSRule.Options.ExecutionActionPreference]$DuplicateResourceId = [PSRule.Options.ExecutionActionPreference]::Error, # Sets the Execution.InconclusiveWarning option [Parameter(Mandatory = $False)] @@ -2293,7 +2293,7 @@ function SetOptions { # Sets the Execution.InitialSessionState option [Parameter(Mandatory = $False)] [Alias('ExecutionInitialSessionState')] - [PSRule.Configuration.SessionState]$InitialSessionState = [PSRule.Configuration.SessionState]::BuiltIn, + [PSRule.Options.SessionState]$InitialSessionState = [PSRule.Options.SessionState]::BuiltIn, # Sets the Execution.NotProcessedWarning option [Parameter(Mandatory = $False)] @@ -2308,31 +2308,31 @@ function SetOptions { # Sets the Execution.SuppressionGroupExpired option [Parameter(Mandatory = $False)] [Alias('ExecutionSuppressionGroupExpired')] - [PSRule.Configuration.ExecutionActionPreference]$SuppressionGroupExpired = [PSRule.Configuration.ExecutionActionPreference]::Warn, + [PSRule.Options.ExecutionActionPreference]$SuppressionGroupExpired = [PSRule.Options.ExecutionActionPreference]::Warn, # Sets the Execution.RuleExcluded option [Parameter(Mandatory = $False)] - [PSRule.Configuration.ExecutionActionPreference]$ExecutionRuleExcluded = [PSRule.Configuration.ExecutionActionPreference]::Ignore, + [PSRule.Options.ExecutionActionPreference]$ExecutionRuleExcluded = [PSRule.Options.ExecutionActionPreference]::Ignore, # Sets the Execution.RuleSuppressed option [Parameter(Mandatory = $False)] - [PSRule.Configuration.ExecutionActionPreference]$ExecutionRuleSuppressed = [PSRule.Configuration.ExecutionActionPreference]::Warn, + [PSRule.Options.ExecutionActionPreference]$ExecutionRuleSuppressed = [PSRule.Options.ExecutionActionPreference]::Warn, # Sets the Execution.AliasReference option [Parameter(Mandatory = $False)] - [PSRule.Configuration.ExecutionActionPreference]$ExecutionAliasReference = [PSRule.Configuration.ExecutionActionPreference]::Warn, + [PSRule.Options.ExecutionActionPreference]$ExecutionAliasReference = [PSRule.Options.ExecutionActionPreference]::Warn, # Sets the Execution.RuleInconclusive option [Parameter(Mandatory = $False)] - [PSRule.Configuration.ExecutionActionPreference]$ExecutionRuleInconclusive = [PSRule.Configuration.ExecutionActionPreference]::Warn, + [PSRule.Options.ExecutionActionPreference]$ExecutionRuleInconclusive = [PSRule.Options.ExecutionActionPreference]::Warn, # Sets the Execution.InvariantCulture option [Parameter(Mandatory = $False)] - [PSRule.Configuration.ExecutionActionPreference]$ExecutionInvariantCulture = [PSRule.Configuration.ExecutionActionPreference]::Warn, + [PSRule.Options.ExecutionActionPreference]$ExecutionInvariantCulture = [PSRule.Options.ExecutionActionPreference]::Warn, # Sets the Execution.UnprocessedObject option [Parameter(Mandatory = $False)] - [PSRule.Configuration.ExecutionActionPreference]$ExecutionUnprocessedObject = [PSRule.Configuration.ExecutionActionPreference]::Warn, + [PSRule.Options.ExecutionActionPreference]$ExecutionUnprocessedObject = [PSRule.Options.ExecutionActionPreference]::Warn, # Sets the Include.Module option [Parameter(Mandatory = $False)] diff --git a/src/PSRule/Pipeline/GetRuleHelpPipeline.cs b/src/PSRule/Pipeline/GetRuleHelpPipeline.cs index 3f5c5b5abc..15f555a6e7 100644 --- a/src/PSRule/Pipeline/GetRuleHelpPipeline.cs +++ b/src/PSRule/Pipeline/GetRuleHelpPipeline.cs @@ -5,6 +5,7 @@ using System.Management.Automation; using PSRule.Configuration; using PSRule.Host; +using PSRule.Options; using PSRule.Resources; using PSRule.Rules; diff --git a/src/PSRule/Pipeline/PipelineBuilder.cs b/src/PSRule/Pipeline/PipelineBuilder.cs index 1e34f97a78..8fdd2552ad 100644 --- a/src/PSRule/Pipeline/PipelineBuilder.cs +++ b/src/PSRule/Pipeline/PipelineBuilder.cs @@ -8,6 +8,7 @@ using PSRule.Data; using PSRule.Definitions; using PSRule.Definitions.Baselines; +using PSRule.Options; using PSRule.Pipeline.Output; using PSRule.Resources; @@ -179,7 +180,7 @@ public interface IPipelineBuilder /// Configure the pipeline to use a specific baseline. /// /// A baseline option or the name of a baseline. - void Baseline(BaselineOption baseline); + void Baseline(Configuration.BaselineOption baseline); /// /// Build the pipeline. @@ -284,7 +285,7 @@ internal abstract class PipelineBuilderBase : IPipelineBuilder private string[] _Include; private Hashtable _Tag; - private BaselineOption _Baseline; + private Configuration.BaselineOption _Baseline; private string[] _Convention; private PathFilter _InputFilter; private PipelineWriter _Writer; @@ -365,13 +366,13 @@ public virtual IPipelineBuilder Configure(PSRuleOption option) /// Use a baseline, either by name or by path. /// [Obsolete()] - public void UseBaseline(BaselineOption baseline) + public void UseBaseline(Configuration.BaselineOption baseline) { Baseline(baseline); } /// - public void Baseline(BaselineOption baseline) + public void Baseline(Configuration.BaselineOption baseline) { if (baseline == null) return; @@ -437,7 +438,7 @@ private static bool TryModuleVersion(string moduleVersion, string requiredVersio protected PipelineContext PrepareContext(BindTargetMethod bindTargetName, BindTargetMethod bindTargetType, BindTargetMethod bindField) { var unresolved = new List(); - if (_Baseline is BaselineOption.BaselineRef baselineRef) + if (_Baseline is Configuration.BaselineOption.BaselineRef baselineRef) unresolved.Add(new BaselineRef(ResolveBaselineGroup(baselineRef.Name), OptionContext.ScopeType.Explicit)); return PipelineContext.New( diff --git a/src/PSRule/Pipeline/PipelineContext.cs b/src/PSRule/Pipeline/PipelineContext.cs index da6a895b88..73badf6bdc 100644 --- a/src/PSRule/Pipeline/PipelineContext.cs +++ b/src/PSRule/Pipeline/PipelineContext.cs @@ -14,6 +14,7 @@ using PSRule.Definitions.Selectors; using PSRule.Definitions.SuppressionGroups; using PSRule.Host; +using PSRule.Options; using PSRule.Resources; using PSRule.Runtime; using PSRule.Runtime.ObjectPath; @@ -38,7 +39,6 @@ internal sealed class PipelineContext : IDisposable, IBindingContext // Objects kept for caching and disposal private Runspace _Runspace; - private SHA1Managed _Hash; // Track whether Dispose has been called. private bool _Disposed; @@ -60,14 +60,7 @@ internal sealed class PipelineContext : IDisposable, IBindingContext internal readonly Stopwatch RunTime; - public HashAlgorithm ObjectHashAlgorithm - { - get - { - _Hash ??= new SHA1Managed(); - return _Hash; - } - } + 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) { @@ -87,6 +80,8 @@ private PipelineContext(PSRuleOption option, IHostContext hostContext, PipelineR 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(); } @@ -280,6 +275,14 @@ private void ReportIssue(RunspaceContext runspaceContext) // runspaceContext.WarnMissingApiVersion(_TrackedIssues[i].Kind, _TrackedIssues[i].Id); } + private static System.Security.Cryptography.HashAlgorithm GetHashAlgorithm(Options.HashAlgorithm hashAlgorithm) + { + if (hashAlgorithm == Options.HashAlgorithm.SHA256) + return SHA256.Create(); + + return hashAlgorithm == Options.HashAlgorithm.SHA384 ? SHA384.Create() : SHA512.Create(); + } + #region IBindingContext public bool GetPathExpression(string path, out PathExpression expression) @@ -307,7 +310,7 @@ private void Dispose(bool disposing) { if (disposing) { - _Hash?.Dispose(); + ObjectHashAlgorithm?.Dispose(); _Runspace?.Dispose(); _PathExpressionCache.Clear(); LocalizedDataCache.Clear(); diff --git a/src/PSRule/Pipeline/PipelineHookActions.cs b/src/PSRule/Pipeline/PipelineHookActions.cs index 2d6afdddb1..1eb4c34a2d 100644 --- a/src/PSRule/Pipeline/PipelineHookActions.cs +++ b/src/PSRule/Pipeline/PipelineHookActions.cs @@ -143,7 +143,7 @@ private static string NestedTargetPropertyBinding(string[] propertyNames, bool c } /// - /// Calculate a SHA1 hash for an object to use as TargetName. + /// Calculate a hash for an object to use as TargetName. /// /// A PSObject to hash. /// The TargetName of the object. @@ -159,7 +159,8 @@ private static string GetUnboundObjectTargetName(object targetObject) settings.Converters.Insert(0, new PSObjectJsonConverter()); var json = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(targetObject, settings)); - return PipelineContext.CurrentThread.ObjectHashAlgorithm.GetDigest(json); + var name = PipelineContext.CurrentThread.ObjectHashAlgorithm.GetDigest(json); + return name.Substring(0, name.Length > 50 ? 50 : name.Length); } /// diff --git a/src/PSRule/Runtime/RunspaceContext.cs b/src/PSRule/Runtime/RunspaceContext.cs index a43dbed8e1..508eb42129 100644 --- a/src/PSRule/Runtime/RunspaceContext.cs +++ b/src/PSRule/Runtime/RunspaceContext.cs @@ -6,6 +6,7 @@ using System.Management.Automation.Language; using PSRule.Configuration; using PSRule.Definitions; +using PSRule.Options; using PSRule.Pipeline; using PSRule.Resources; using PSRule.Rules; diff --git a/tests/PSRule.Tests/PSRule.Common.Tests.ps1 b/tests/PSRule.Tests/PSRule.Common.Tests.ps1 index 70303cb089..cec8c482e6 100644 --- a/tests/PSRule.Tests/PSRule.Common.Tests.ps1 +++ b/tests/PSRule.Tests/PSRule.Common.Tests.ps1 @@ -2775,13 +2775,13 @@ Describe 'Binding' -Tag Common, Binding { $result | Should -Not -BeNullOrEmpty; $result.Length | Should -Be 7; $result[0..6].IsSuccess() | Should -BeIn $True; - $result[0].TargetName | Should -BeIn 'f209c623345144be61087d91f30c17b01c6e86d2'; - $result[1].TargetName | Should -BeIn '28e156a7121bc57b0461029208daf0b48d1c4fd0'; - $result[2].TargetName | Should -BeIn '3b8eeb35831ea8f7b5de4e0cf04f32b9a1233a0d'; - $result[3].TargetName | Should -BeIn '7b3ce68b6c2f7d67dae4210eeb83be69f978e2a8'; - $result[4].TargetName | Should -BeIn '205c97d9248d2cd12db1c55ba421eb8df84b22a7'; - $result[5].TargetName | Should -BeIn '356a192b7913b04c54574d18c28d46e6395428ab'; - $result[6].TargetName | Should -BeIn 'da4b9237bacccdf19c0760cab7aec4a8359010b0'; + $result[0].TargetName | Should -BeIn 'f3d2f8ce966af96a8d320e8f5c088604324885a0d02f44b174'; + $result[1].TargetName | Should -BeIn '839b3457fca709821c89e23263a070fdca7cb8c4a86b5862f9'; + $result[2].TargetName | Should -BeIn '1c23e67aab1f653e2ead0b0e71153d02eb249f1c8382821598'; + $result[3].TargetName | Should -BeIn '72dd48c5f3cef36c66f5633955719b5cdb5f679539ec39b087'; + $result[4].TargetName | Should -BeIn '35f8cb2a8d4c26a7d53839be143c5d5b82e1543ce27adb94d4'; + $result[5].TargetName | Should -BeIn '4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8d'; + $result[6].TargetName | Should -BeIn '40b244112641dd78dd4f93b6c9190dd46e0099194d5a44257b'; } It 'Binds to custom name' { diff --git a/tests/PSRule.Tests/PSRule.Options.Tests.ps1 b/tests/PSRule.Tests/PSRule.Options.Tests.ps1 index 0ba4396a01..25e184d4ee 100644 --- a/tests/PSRule.Tests/PSRule.Options.Tests.ps1 +++ b/tests/PSRule.Tests/PSRule.Options.Tests.ps1 @@ -635,6 +635,34 @@ Describe 'New-PSRuleOption' -Tag 'Option','New-PSRuleOption' { } } + Context 'Read Execution.HashAlgorithm' { + It 'from default' { + $option = New-PSRuleOption -Default; + $option.Execution.HashAlgorithm | Should -Be 'SHA512'; + } + + It 'from Hashtable' { + $option = New-PSRuleOption -Option @{ 'Execution.HashAlgorithm' = 'SHA256' }; + $option.Execution.HashAlgorithm | Should -Be 'SHA256'; + } + + It 'from YAML' { + $option = New-PSRuleOption -Option (Join-Path -Path $here -ChildPath 'PSRule.Tests.yml'); + $option.Execution.HashAlgorithm | Should -Be 'SHA256'; + } + + It 'from Environment' { + try { + $Env:PSRULE_EXECUTION_HASHALGORITHM = 'SHA256'; + $option = New-PSRuleOption; + $option.Execution.HashAlgorithm | Should -Be 'SHA256'; + } + finally { + Remove-Item 'Env:PSRULE_EXECUTION_HASHALGORITHM' -Force; + } + } + } + Context 'Read Execution.LanguageMode' { It 'from default' { $option = New-PSRuleOption -Default; diff --git a/tests/PSRule.Tests/PSRule.Tests.yml b/tests/PSRule.Tests/PSRule.Tests.yml index 543ad61d1a..6c3d8761e0 100644 --- a/tests/PSRule.Tests/PSRule.Tests.yml +++ b/tests/PSRule.Tests/PSRule.Tests.yml @@ -56,6 +56,7 @@ execution: aliasReference: Ignore aliasReferenceWarning: false duplicateResourceId: Warn + hashAlgorithm: SHA256 languageMode: ConstrainedLanguage inconclusiveWarning: false invariantCulture: Ignore diff --git a/tests/PSRule.Tests/PipelineTests.cs b/tests/PSRule.Tests/PipelineTests.cs index e6ec105cbd..84fc350536 100644 --- a/tests/PSRule.Tests/PipelineTests.cs +++ b/tests/PSRule.Tests/PipelineTests.cs @@ -9,6 +9,7 @@ using System.Threading; using Newtonsoft.Json.Linq; using PSRule.Configuration; +using PSRule.Options; using PSRule.Pipeline; using PSRule.Resources; using PSRule.Rules; @@ -163,7 +164,7 @@ public void GetRuleWithBaseline() "Baseline.Rule.yaml", "FromFileBaseline.Rule.ps1" }), option, null); - builder.Baseline(BaselineOption.FromString("@test")); + builder.Baseline(Configuration.BaselineOption.FromString("@test")); var writer = new TestWriter(option); var pipeline = builder.Build(writer); diff --git a/tests/PSRule.Tests/TargetNameBindingTests.cs b/tests/PSRule.Tests/TargetNameBindingTests.cs index 575cf1a1f6..7ced67040b 100644 --- a/tests/PSRule.Tests/TargetNameBindingTests.cs +++ b/tests/PSRule.Tests/TargetNameBindingTests.cs @@ -4,6 +4,7 @@ using System.Management.Automation; using PSRule.Configuration; using PSRule.Data; +using PSRule.Options; using PSRule.Pipeline; namespace PSRule @@ -39,12 +40,17 @@ public void UnboundObjectTargetName() var pso1 = PSObject.AsPSObject(testObject1); var pso2 = PSObject.AsPSObject(testObject2); + // SHA512 PipelineContext.CurrentThread = PipelineContext.New(GetOption(), null, null, null, null, null, null, null); - var actual1 = PipelineHookActions.BindTargetName(null, false, false, pso1, out _); - var actual2 = PipelineHookActions.BindTargetName(null, false, false, pso2, out _); - Assert.Equal(expected: "f209c623345144be61087d91f30c17b01c6e86d2", actual: actual1); - Assert.Equal(expected: "f209c623345144be61087d91f30c17b01c6e86d2", actual: actual2); + Assert.Equal("f3d2f8ce966af96a8d320e8f5c088604324885a0d02f44b174", PipelineHookActions.BindTargetName(null, false, false, pso1, out _)); + Assert.Equal("f3d2f8ce966af96a8d320e8f5c088604324885a0d02f44b174", PipelineHookActions.BindTargetName(null, false, false, pso2, out _)); + + // SHA256 + PipelineContext.CurrentThread = PipelineContext.New(GetOption(HashAlgorithm.SHA256), null, null, null, null, null, null, null); + + Assert.Equal("67327c8cd8622d17cf1702a76cbbb685e9ef260ce39c9f6779", PipelineHookActions.BindTargetName(null, false, false, pso1, out _)); + Assert.Equal("67327c8cd8622d17cf1702a76cbbb685e9ef260ce39c9f6779", PipelineHookActions.BindTargetName(null, false, false, pso2, out _)); } [Fact] @@ -68,9 +74,13 @@ public void PreferTargetInfo() Assert.Null(path); } - private static PSRuleOption GetOption() + private static PSRuleOption GetOption(HashAlgorithm? hashAlgorithm = null) { - return new PSRuleOption(); + var option = new PSRuleOption(); + if (hashAlgorithm != null) + option.Execution.HashAlgorithm = hashAlgorithm; + + return option; } } } From c4553ba9f25cd1f4b905ab2ed4cdd87d91d9185d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 20 Jul 2023 17:48:30 +1000 Subject: [PATCH 050/177] Bump mkdocs-material from 9.1.18 to 9.1.19 (#1576) Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.1.18 to 9.1.19. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.1.18...9.1.19) --- updated-dependencies: - dependency-name: mkdocs-material dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements-docs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-docs.txt b/requirements-docs.txt index 49c1dd3446..895f741ecb 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -1,5 +1,5 @@ mkdocs==1.4.3 -mkdocs-material==9.1.18 +mkdocs-material==9.1.19 pymdown-extensions==10.1 mike==1.1.2 mkdocs-simple-hooks==0.1.5 From 9253d1d89c48dae9673c416544d79ef018d830ea Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 21 Jul 2023 21:37:01 +1000 Subject: [PATCH 051/177] Bump mkdocs-redirects from 1.2.0 to 1.2.1 (#1577) Bumps [mkdocs-redirects](https://github.com/datarobot/mkdocs-redirects) from 1.2.0 to 1.2.1. - [Release notes](https://github.com/datarobot/mkdocs-redirects/releases) - [Commits](https://github.com/datarobot/mkdocs-redirects/compare/v1.2.0...v1.2.1) --- updated-dependencies: - dependency-name: mkdocs-redirects dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements-docs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-docs.txt b/requirements-docs.txt index 895f741ecb..64197e28f7 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -5,4 +5,4 @@ mike==1.1.2 mkdocs-simple-hooks==0.1.5 mkdocs-git-revision-date-plugin==0.3.2 mdx-truly-sane-lists==1.3 -mkdocs-redirects==1.2.0 +mkdocs-redirects==1.2.1 From 5386203607ec4de3a043a5f7149edade78109944 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 28 Jul 2023 10:02:22 +1000 Subject: [PATCH 052/177] Bump mkdocs-material from 9.1.19 to 9.1.20 (#1581) Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.1.19 to 9.1.20. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.1.19...9.1.20) --- updated-dependencies: - dependency-name: mkdocs-material dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements-docs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-docs.txt b/requirements-docs.txt index 64197e28f7..6407bab93d 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -1,5 +1,5 @@ mkdocs==1.4.3 -mkdocs-material==9.1.19 +mkdocs-material==9.1.20 pymdown-extensions==10.1 mike==1.1.2 mkdocs-simple-hooks==0.1.5 From 79bbae928a63a32e1bbf51b03b29f26008f83dbb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 28 Jul 2023 10:17:10 +1000 Subject: [PATCH 053/177] Bump mkdocs from 1.4.3 to 1.5.1 (#1582) Bumps [mkdocs](https://github.com/mkdocs/mkdocs) from 1.4.3 to 1.5.1. - [Release notes](https://github.com/mkdocs/mkdocs/releases) - [Commits](https://github.com/mkdocs/mkdocs/compare/1.4.3...1.5.1) --- updated-dependencies: - dependency-name: mkdocs dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements-docs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-docs.txt b/requirements-docs.txt index 6407bab93d..5727464d7d 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -1,4 +1,4 @@ -mkdocs==1.4.3 +mkdocs==1.5.1 mkdocs-material==9.1.20 pymdown-extensions==10.1 mike==1.1.2 From 31eb97e9de5c7ec9a68b8afa8b123cf8bf78b122 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 30 Jul 2023 23:17:24 +1000 Subject: [PATCH 054/177] Bump mkdocs-material from 9.1.20 to 9.1.21 (#1583) Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.1.20 to 9.1.21. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.1.20...9.1.21) --- updated-dependencies: - dependency-name: mkdocs-material dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements-docs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-docs.txt b/requirements-docs.txt index 5727464d7d..7073d60cfc 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -1,5 +1,5 @@ mkdocs==1.5.1 -mkdocs-material==9.1.20 +mkdocs-material==9.1.21 pymdown-extensions==10.1 mike==1.1.2 mkdocs-simple-hooks==0.1.5 From 5f8a16f6323f4e71651239e2325149d49a9d7ee3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 4 Aug 2023 15:59:25 +1000 Subject: [PATCH 055/177] Bump mkdocs from 1.5.1 to 1.5.2 (#1585) Bumps [mkdocs](https://github.com/mkdocs/mkdocs) from 1.5.1 to 1.5.2. - [Release notes](https://github.com/mkdocs/mkdocs/releases) - [Commits](https://github.com/mkdocs/mkdocs/compare/1.5.1...1.5.2) --- updated-dependencies: - dependency-name: mkdocs dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements-docs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-docs.txt b/requirements-docs.txt index 7073d60cfc..ba42e92b90 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -1,4 +1,4 @@ -mkdocs==1.5.1 +mkdocs==1.5.2 mkdocs-material==9.1.21 pymdown-extensions==10.1 mike==1.1.2 From bc84facf4cfed42cca21803dcfed73975695f941 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Aug 2023 11:41:45 +1000 Subject: [PATCH 056/177] Bump BenchmarkDotNet from 0.13.6 to 0.13.7 (#1587) * Bump BenchmarkDotNet from 0.13.6 to 0.13.7 Bumps [BenchmarkDotNet](https://github.com/dotnet/BenchmarkDotNet) from 0.13.6 to 0.13.7. - [Release notes](https://github.com/dotnet/BenchmarkDotNet/releases) - [Commits](https://github.com/dotnet/BenchmarkDotNet/compare/v0.13.6...v0.13.7) --- updated-dependencies: - dependency-name: BenchmarkDotNet dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * Bump change log --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Bernie White --- docs/CHANGELOG-v3.md | 4 ++-- src/PSRule.Benchmark/PSRule.Benchmark.csproj | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/CHANGELOG-v3.md b/docs/CHANGELOG-v3.md index bdf727fbbf..62e0babbf9 100644 --- a/docs/CHANGELOG-v3.md +++ b/docs/CHANGELOG-v3.md @@ -59,7 +59,7 @@ What's changed since release v2.9.0: [#1562](https://github.com/microsoft/PSRule/pull/1562) - Bump xunit.runner.visualstudio to v2.5.0. [#1561](https://github.com/microsoft/PSRule/pull/1561) - - Bump BenchmarkDotNet to v0.13.6. - [#1566](https://github.com/microsoft/PSRule/pull/1566) + - Bump BenchmarkDotNet to v0.13.7. + [#1587](https://github.com/microsoft/PSRule/pull/1587) - Bump BenchmarkDotNet.Diagnostics.Windows to v0.13.6. [#1565](https://github.com/microsoft/PSRule/pull/1565) diff --git a/src/PSRule.Benchmark/PSRule.Benchmark.csproj b/src/PSRule.Benchmark/PSRule.Benchmark.csproj index 220d74993c..d060da0053 100644 --- a/src/PSRule.Benchmark/PSRule.Benchmark.csproj +++ b/src/PSRule.Benchmark/PSRule.Benchmark.csproj @@ -15,7 +15,7 @@ - + From 55c5cfcff6d36a722f4445412272d0ebf124c3a0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Aug 2023 13:28:03 +1000 Subject: [PATCH 057/177] Bump BenchmarkDotNet.Diagnostics.Windows from 0.13.6 to 0.13.7 (#1586) * Bump BenchmarkDotNet.Diagnostics.Windows from 0.13.6 to 0.13.7 Bumps [BenchmarkDotNet.Diagnostics.Windows](https://github.com/dotnet/BenchmarkDotNet) from 0.13.6 to 0.13.7. - [Release notes](https://github.com/dotnet/BenchmarkDotNet/releases) - [Commits](https://github.com/dotnet/BenchmarkDotNet/compare/v0.13.6...v0.13.7) --- updated-dependencies: - dependency-name: BenchmarkDotNet.Diagnostics.Windows dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * Bump change log --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Bernie White --- .vscode/settings.json | 4 ++-- docs/CHANGELOG-v3.md | 4 ++-- src/PSRule.Benchmark/PSRule.Benchmark.csproj | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 00107e76e3..1402545f85 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -99,12 +99,12 @@ "[csharp]": { "editor.tabSize": 4 }, - "omnisharp.enableImportCompletion": true, "omnisharp.organizeImportsOnFormat": true, "omnisharp.enableEditorConfigSupport": true, "omnisharp.enableRoslynAnalyzers": true, "git.branchProtection": [ "main", "release/*" - ] + ], + "dotnet.completion.showCompletionItemsFromUnimportedNamespaces": true } diff --git a/docs/CHANGELOG-v3.md b/docs/CHANGELOG-v3.md index 62e0babbf9..40706c7a23 100644 --- a/docs/CHANGELOG-v3.md +++ b/docs/CHANGELOG-v3.md @@ -61,5 +61,5 @@ What's changed since release v2.9.0: [#1561](https://github.com/microsoft/PSRule/pull/1561) - Bump BenchmarkDotNet to v0.13.7. [#1587](https://github.com/microsoft/PSRule/pull/1587) - - Bump BenchmarkDotNet.Diagnostics.Windows to v0.13.6. - [#1565](https://github.com/microsoft/PSRule/pull/1565) + - Bump BenchmarkDotNet.Diagnostics.Windows to v0.13.7. + [#1586](https://github.com/microsoft/PSRule/pull/1586) diff --git a/src/PSRule.Benchmark/PSRule.Benchmark.csproj b/src/PSRule.Benchmark/PSRule.Benchmark.csproj index d060da0053..2f20a73002 100644 --- a/src/PSRule.Benchmark/PSRule.Benchmark.csproj +++ b/src/PSRule.Benchmark/PSRule.Benchmark.csproj @@ -20,7 +20,7 @@ - + From d082587fd3f767457fb2bb4344e06772130d4de8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Aug 2023 14:27:38 +1000 Subject: [PATCH 058/177] Bump YamlDotNet from 13.1.1 to 13.2.0 (#1589) * Bump YamlDotNet from 13.1.1 to 13.2.0 Bumps [YamlDotNet](https://github.com/aaubry/YamlDotNet) from 13.1.1 to 13.2.0. - [Release notes](https://github.com/aaubry/YamlDotNet/releases) - [Commits](https://github.com/aaubry/YamlDotNet/compare/v13.1.1...v13.2.0) --- updated-dependencies: - dependency-name: YamlDotNet dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * Bump change log --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Bernie White --- docs/CHANGELOG-v3.md | 4 ++-- src/PSRule.Types/PSRule.Types.csproj | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/CHANGELOG-v3.md b/docs/CHANGELOG-v3.md index 40706c7a23..4dc7ba335e 100644 --- a/docs/CHANGELOG-v3.md +++ b/docs/CHANGELOG-v3.md @@ -53,8 +53,8 @@ What's changed since release v2.9.0: [#1550](https://github.com/microsoft/PSRule/pull/1550) - Bump Microsoft.NET.Test.Sdk to v17.6.3. [#1557](https://github.com/microsoft/PSRule/pull/1557) - - Bump YamlDotNet to v13.1.1. - [#1399](https://github.com/microsoft/PSRule/issues/1399) + - Bump YamlDotNet to v13.2.0. + [#1589](https://github.com/microsoft/PSRule/issues/1589) - Bump xunit to v2.5.0. [#1562](https://github.com/microsoft/PSRule/pull/1562) - Bump xunit.runner.visualstudio to v2.5.0. diff --git a/src/PSRule.Types/PSRule.Types.csproj b/src/PSRule.Types/PSRule.Types.csproj index 35eefb0ea2..0720a2c6a9 100644 --- a/src/PSRule.Types/PSRule.Types.csproj +++ b/src/PSRule.Types/PSRule.Types.csproj @@ -16,7 +16,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + From aa5ae237ff1b54f0d456a53e7a75a9c38d9ce265 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Aug 2023 16:18:49 +1000 Subject: [PATCH 059/177] Bump Microsoft.NET.Test.Sdk from 17.6.3 to 17.7.0 (#1588) * Bump Microsoft.NET.Test.Sdk from 17.6.3 to 17.7.0 Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 17.6.3 to 17.7.0. - [Release notes](https://github.com/microsoft/vstest/releases) - [Changelog](https://github.com/microsoft/vstest/blob/main/docs/releases.md) - [Commits](https://github.com/microsoft/vstest/compare/v17.6.3...v17.7.0) --- updated-dependencies: - dependency-name: Microsoft.NET.Test.Sdk dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * Bump change log --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Bernie White --- docs/CHANGELOG-v3.md | 4 ++-- tests/PSRule.Tests/PSRule.Tests.csproj | 2 +- tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/CHANGELOG-v3.md b/docs/CHANGELOG-v3.md index 4dc7ba335e..4af65906f2 100644 --- a/docs/CHANGELOG-v3.md +++ b/docs/CHANGELOG-v3.md @@ -51,8 +51,8 @@ What's changed since release v2.9.0: - Engineering: - Bump Microsoft.CodeAnalysis.NetAnalyzers to v7.0.3. [#1550](https://github.com/microsoft/PSRule/pull/1550) - - Bump Microsoft.NET.Test.Sdk to v17.6.3. - [#1557](https://github.com/microsoft/PSRule/pull/1557) + - Bump Microsoft.NET.Test.Sdk to v17.7.0. + [#1588](https://github.com/microsoft/PSRule/pull/1588) - Bump YamlDotNet to v13.2.0. [#1589](https://github.com/microsoft/PSRule/issues/1589) - Bump xunit to v2.5.0. diff --git a/tests/PSRule.Tests/PSRule.Tests.csproj b/tests/PSRule.Tests/PSRule.Tests.csproj index 4efa61a9a3..72dbc602a6 100644 --- a/tests/PSRule.Tests/PSRule.Tests.csproj +++ b/tests/PSRule.Tests/PSRule.Tests.csproj @@ -10,7 +10,7 @@ - + diff --git a/tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj b/tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj index 092c38fb8d..1abcdedfa3 100644 --- a/tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj +++ b/tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj @@ -10,7 +10,7 @@ - + all From a61b6c97d5a94a32d1471eafcf1571da8ada813e Mon Sep 17 00:00:00 2001 From: Bernie White Date: Tue, 15 Aug 2023 22:38:44 +1000 Subject: [PATCH 060/177] Clean up old pipelines (#1590) --- .azure-pipelines/azure-pipelines.yaml | 185 ----------------------- .azure-pipelines/jobs/test.yaml | 76 ---------- .azure-pipelines/jobs/testContainer.yaml | 76 ---------- .github/ISSUE_TEMPLATE/bug_report.md | 2 +- 4 files changed, 1 insertion(+), 338 deletions(-) delete mode 100644 .azure-pipelines/azure-pipelines.yaml delete mode 100644 .azure-pipelines/jobs/test.yaml delete mode 100644 .azure-pipelines/jobs/testContainer.yaml diff --git a/.azure-pipelines/azure-pipelines.yaml b/.azure-pipelines/azure-pipelines.yaml deleted file mode 100644 index dda19137d9..0000000000 --- a/.azure-pipelines/azure-pipelines.yaml +++ /dev/null @@ -1,185 +0,0 @@ -# Azure DevOps -# CI pipeline for PSRule - -variables: - version: '2.5.0' - buildConfiguration: 'Release' - disable.coverage.autogenerate: 'true' - imageName: 'ubuntu-22.04' - - # Use build number format, i.e. 2.5.0-B2203001 -name: $(version)-B$(date:yyMM)$(rev:rrr) - -trigger: - branches: - include: - - 'main' - - 'release/*' - -pr: - branches: - include: - - 'main' - - 'release/*' - -stages: - -# Build pipeline -- stage: Build - displayName: Build - dependsOn: [] - jobs: - - job: - pool: - vmImage: $(imageName) - displayName: 'Module' - steps: - - # Install pipeline dependencies - - powershell: ./scripts/pipeline-deps.ps1 - displayName: 'Install dependencies' - - # Build module - - powershell: Invoke-Build -Configuration $(buildConfiguration) -Build $(Build.BuildNumber) - displayName: 'Build module' - - # DotNet test results - - task: PublishTestResults@2 - displayName: 'Publish unit test results' - inputs: - testRunTitle: 'DotNet on $(imageName)' - testRunner: VSTest - testResultsFiles: 'reports/*.trx' - mergeTestResults: true - platform: $(imageName) - configuration: $(buildConfiguration) - publishRunAttachments: true - condition: succeededOrFailed() - - # PSRule results - - task: PublishTestResults@2 - displayName: 'Publish PSRule results' - inputs: - testRunTitle: 'PSRule on $(imageName)' - testRunner: NUnit - testResultsFiles: 'reports/ps-rule*.xml' - mergeTestResults: true - platform: $(imageName) - configuration: $(buildConfiguration) - publishRunAttachments: true - condition: succeededOrFailed() - - - publish: reports/ - displayName: 'Test output' - artifact: Tests - condition: succeededOrFailed() - - # Generate artifacts - - publish: out/modules/PSRule - displayName: 'Publish module' - artifact: PSRule - -# Analysis pipeline -- stage: Analysis - displayName: Analysis - dependsOn: [] - jobs: - - job: - pool: - vmImage: $(imageName) - displayName: 'SonarCloud' - condition: not(eq(variables['Build.Reason'], 'PullRequest')) - steps: - - - script: | - echo "##vso[task.setvariable variable=JAVA_HOME]$(JAVA_HOME_11_X64)" - echo "##vso[task.setvariable variable=PATH]$(JAVA_HOME_11_X64)\bin;$(PATH)" - displayName: 'Set Java version' - - # Run SonarCloud analysis - - script: dotnet tool install --global dotnet-sonarscanner - displayName: 'Install Sonar scanner' - - - script: $HOME/.dotnet/tools/dotnet-sonarscanner begin /k:"BernieWhite_PSRule" /o:"berniewhite-github" /d:sonar.host.url="https://sonarcloud.io" /d:sonar.login=$(sonarQubeToken) /v:"$(Build.BuildNumber)" /d:sonar.cs.vscoveragexml.reportsPaths="reports/" /d:sonar.cs.xunit.reportsPaths="reports/" - displayName: 'Prepare SonarCloud' - - - script: dotnet build - displayName: 'Build solution for analysis' - - - script: $HOME/.dotnet/tools/dotnet-sonarscanner end /d:sonar.login=$(sonarQubeToken) - displayName: 'Complete SonarCloud' - - - job: Secret_Scan - pool: - vmImage: 'windows-2022' - displayName: Secret scan - - steps: - - task: securedevelopmentteam.vss-secure-development-tools.build-task-credscan.CredScan@2 - displayName: 'Scan for secrets' - inputs: - debugMode: false - toolMajorVersion: V2 - - - task: securedevelopmentteam.vss-secure-development-tools.build-task-publishsecurityanalysislogs.PublishSecurityAnalysisLogs@2 - displayName: 'Publish scan logs' - continueOnError: true - - - task: securedevelopmentteam.vss-secure-development-tools.build-task-postanalysis.PostAnalysis@1 - displayName: 'Check for failures' - inputs: - CredScan: true - ToolLogsNotFoundAction: Error - -# Test pipeline -- stage: Test - dependsOn: Build - jobs: - - - template: jobs/test.yaml - parameters: - name: ubuntu_22_04_coverage - imageName: 'ubuntu-22.04' - displayName: 'PowerShell coverage' - coverage: 'true' - publishResults: 'false' - - - template: jobs/test.yaml - parameters: - name: macOS_11 - displayName: 'PowerShell 7.2 - macOS-11' - imageName: 'macOS-11' - - - template: jobs/test.yaml - parameters: - name: ps_5_1_windows_2022 - displayName: 'PowerShell 5.1 - Windows 2022' - imageName: 'windows-2022' - pwsh: 'false' - - - template: jobs/test.yaml - parameters: - name: ps_7_2_windows_2022 - displayName: 'PowerShell 7.2 - Windows 2022' - imageName: 'windows-2022' - - # - template: jobs/testContainer.yaml - # parameters: - # name: alpine_3_14 - # displayName: 'PowerShell 7.2 - alpine-3.14' - # imageName: mcr.microsoft.com/powershell/test-deps - # imageTag: alpine-3.14 - - - template: jobs/testContainer.yaml - parameters: - name: ps_7_2_ubuntu_22_04 - displayName: 'PowerShell 7.2 - ubuntu-22.04' - imageName: mcr.microsoft.com/powershell - imageTag: 7.2-ubuntu-22.04 - - - template: jobs/testContainer.yaml - parameters: - name: ps_7_3_ubuntu_22_04 - displayName: 'PowerShell 7.3 - ubuntu-22.04' - imageName: mcr.microsoft.com/powershell - imageTag: 7.3-ubuntu-22.04 diff --git a/.azure-pipelines/jobs/test.yaml b/.azure-pipelines/jobs/test.yaml deleted file mode 100644 index 0199f03530..0000000000 --- a/.azure-pipelines/jobs/test.yaml +++ /dev/null @@ -1,76 +0,0 @@ -# Azure DevOps -# CI job for running VM pipelines - -parameters: - name: '' - displayName: '' - buildConfiguration: 'Release' - imageName: '' - coverage: 'false' - publishResults: 'true' - pwsh: 'true' - -jobs: -- job: ${{ parameters.name }} - displayName: ${{ parameters.displayName }} - pool: - vmImage: ${{ parameters.imageName }} - variables: - COVERAGE: ${{ parameters.coverage }} - PUBLISHRESULTS: ${{ parameters.publishResults }} - skipComponentGovernanceDetection: true - steps: - - # Install pipeline dependencies - - powershell: ./scripts/pipeline-deps.ps1 - displayName: 'Install dependencies' - - # Download module - - task: DownloadPipelineArtifact@2 - displayName: 'Download module' - inputs: - artifact: PSRule - path: $(Build.SourcesDirectory)/out/modules/PSRule - - # Build module - - task: PowerShell@2 - inputs: - targetType: inline - script: Invoke-Build TestModule -Configuration ${{ parameters.buildConfiguration }} -Build $(Build.BuildNumber) - pwsh: ${{ eq(parameters.pwsh, 'true') }} - env: - COVERAGE: ${{ parameters.coverage }} - displayName: 'Test module' - - # Pester test results - - task: PublishTestResults@2 - displayName: 'Publish Pester results' - inputs: - testRunTitle: 'Pester on ${{ parameters.imageName }}' - testRunner: NUnit - testResultsFiles: 'reports/pester-unit.xml' - mergeTestResults: true - platform: ${{ parameters.name }} - configuration: ${{ parameters.buildConfiguration }} - publishRunAttachments: true - condition: and(succeededOrFailed(), eq(variables['PUBLISHRESULTS'], 'true')) - - # Generate Code Coverage report - - task: Palmmedia.reportgenerator.reportgenerator-build-release-task.reportgenerator@4 - displayName: 'Code coverage report generator' - inputs: - reports: 'reports/pester-coverage.xml' - targetdir: 'reports/coverage' - sourcedirs: 'src/PSRule' - reporttypes: 'HtmlInline_AzurePipelines;Cobertura;SonarQube;Badges' - tag: $(Build.BuildNumber) - condition: eq(variables['COVERAGE'], 'true') - - # Publish Code Coverage report - - task: PublishCodeCoverageResults@1 - displayName: 'Publish Pester code coverage' - inputs: - codeCoverageTool: 'Cobertura' - summaryFileLocation: 'reports/coverage/Cobertura.xml' - reportDirectory: 'reports/coverage' - condition: eq(variables['COVERAGE'], 'true') diff --git a/.azure-pipelines/jobs/testContainer.yaml b/.azure-pipelines/jobs/testContainer.yaml deleted file mode 100644 index ac3f8518d2..0000000000 --- a/.azure-pipelines/jobs/testContainer.yaml +++ /dev/null @@ -1,76 +0,0 @@ -# Azure DevOps -# CI job for running container pipelines - -parameters: - name: '' - displayName: '' - buildConfiguration: 'Release' - vmImage: 'ubuntu-20.04' - imageName: '' - imageTag: '' - coverage: 'false' - publishResults: 'true' - -jobs: -- job: ${{ parameters.name }} - displayName: ${{ parameters.displayName }} - pool: - vmImage: ${{ parameters.vmImage }} - container: - image: '${{ parameters.imageName }}:${{ parameters.imageTag }}' - env: - COVERAGE: ${{ parameters.coverage }} - PUBLISHRESULTS: ${{ parameters.publishResults }} - variables: - COVERAGE: ${{ parameters.coverage }} - PUBLISHRESULTS: ${{ parameters.publishResults }} - skipComponentGovernanceDetection: true - steps: - - # Install pipeline dependencies - - powershell: ./scripts/pipeline-deps.ps1 - displayName: 'Install dependencies' - - # Download module - - task: DownloadPipelineArtifact@2 - displayName: 'Download module' - inputs: - artifact: PSRule - path: $(Build.SourcesDirectory)/out/modules/PSRule - - # Build module - - powershell: Invoke-Build TestModule -Configuration ${{ parameters.buildConfiguration }} -Build $(Build.BuildNumber) - displayName: 'Test module' - - # Pester test results - - task: PublishTestResults@2 - displayName: 'Publish Pester results' - inputs: - testRunTitle: 'Pester on ${{ parameters.imageTag }}' - testRunner: NUnit - testResultsFiles: 'reports/pester-unit.xml' - mergeTestResults: true - platform: ${{ parameters.imageTag }} - configuration: ${{ parameters.buildConfiguration }} - publishRunAttachments: true - condition: and(succeededOrFailed(), eq(variables['PUBLISHRESULTS'], 'true')) - - # Generate Code Coverage report - - task: Palmmedia.reportgenerator.reportgenerator-build-release-task.reportgenerator@4 - displayName: 'Code coverage report generator' - inputs: - reports: 'reports/pester-coverage.xml' - targetdir: 'reports/coverage' - sourcedirs: 'src/PSRule' - reporttypes: 'HtmlInline_AzurePipelines;Cobertura;SonarQube;Badges' - tag: $(Build.BuildNumber) - condition: eq(variables['COVERAGE'], 'true') - - # Publish Code Coverage report - - task: PublishCodeCoverageResults@1 - displayName: 'Publish Pester code coverage' - inputs: - codeCoverageTool: 'Cobertura' - summaryFileLocation: 'reports/coverage/Cobertura.xml' - reportDirectory: 'reports/coverage' - condition: eq(variables['COVERAGE'], 'true') diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index b65ffebb0b..c2ac1cde4a 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -30,7 +30,7 @@ Steps to reproduce the issue: **Module in use and version:** - Module: PSRule -- Version: **[e.g. 2.4.0]** +- Version: **[e.g. 2.9.0]** Captured output from `$PSVersionTable`: From 60e7e12533a3a58730a43cfe6c64551502e4933d Mon Sep 17 00:00:00 2001 From: Bernie White Date: Tue, 15 Aug 2023 22:49:01 +1000 Subject: [PATCH 061/177] Update ./modules.json (#1591) Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> --- modules.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules.json b/modules.json index bd9a5c25d8..5aa4221ab2 100644 --- a/modules.json +++ b/modules.json @@ -2,7 +2,7 @@ "dependencies": {}, "devDependencies": { "Pester": { - "version": "5.4.1" + "version": "5.5.0" }, "platyPS": { "version": "0.14.2" From fab5a5ec8bd0abba737ddae101c6520d7b0cb0e0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 23 Aug 2023 15:25:56 +1000 Subject: [PATCH 062/177] Bump mkdocs-material from 9.1.21 to 9.2.2 (#1594) Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.1.21 to 9.2.2. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.1.21...9.2.2) --- updated-dependencies: - dependency-name: mkdocs-material dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements-docs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-docs.txt b/requirements-docs.txt index ba42e92b90..39c7bbfb19 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -1,5 +1,5 @@ mkdocs==1.5.2 -mkdocs-material==9.1.21 +mkdocs-material==9.2.2 pymdown-extensions==10.1 mike==1.1.2 mkdocs-simple-hooks==0.1.5 From 90fcb51322360c0389051f9c5731a2b4d00fda66 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 24 Aug 2023 10:42:58 +1000 Subject: [PATCH 063/177] Bump mkdocs-material from 9.2.2 to 9.2.3 (#1595) Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.2.2 to 9.2.3. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.2.2...9.2.3) --- updated-dependencies: - dependency-name: mkdocs-material dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements-docs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-docs.txt b/requirements-docs.txt index 39c7bbfb19..5961f959e9 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -1,5 +1,5 @@ mkdocs==1.5.2 -mkdocs-material==9.2.2 +mkdocs-material==9.2.3 pymdown-extensions==10.1 mike==1.1.2 mkdocs-simple-hooks==0.1.5 From 46a28ae0e43adc241fcc340cf16f3892e018f452 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 25 Aug 2023 14:41:50 +1000 Subject: [PATCH 064/177] Bump Microsoft.CodeAnalysis.Common from 4.6.0 to 4.7.0 (#1593) * Bump Microsoft.CodeAnalysis.Common from 4.6.0 to 4.7.0 Bumps [Microsoft.CodeAnalysis.Common](https://github.com/dotnet/roslyn) from 4.6.0 to 4.7.0. - [Release notes](https://github.com/dotnet/roslyn/releases) - [Changelog](https://github.com/dotnet/roslyn/blob/main/docs/Breaking%20API%20Changes.md) - [Commits](https://github.com/dotnet/roslyn/commits) --- updated-dependencies: - dependency-name: Microsoft.CodeAnalysis.Common dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * Bump change log --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Bernie White --- docs/CHANGELOG-v3.md | 2 ++ src/PSRule.BuildTask/PSRule.BuildTask.csproj | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/CHANGELOG-v3.md b/docs/CHANGELOG-v3.md index 4af65906f2..60c1bb0967 100644 --- a/docs/CHANGELOG-v3.md +++ b/docs/CHANGELOG-v3.md @@ -51,6 +51,8 @@ What's changed since release v2.9.0: - Engineering: - Bump Microsoft.CodeAnalysis.NetAnalyzers to v7.0.3. [#1550](https://github.com/microsoft/PSRule/pull/1550) + - Bump Microsoft.CodeAnalysis.Common to v4.7.0. + [#1593](https://github.com/microsoft/PSRule/pull/1593) - Bump Microsoft.NET.Test.Sdk to v17.7.0. [#1588](https://github.com/microsoft/PSRule/pull/1588) - Bump YamlDotNet to v13.2.0. diff --git a/src/PSRule.BuildTask/PSRule.BuildTask.csproj b/src/PSRule.BuildTask/PSRule.BuildTask.csproj index 83a8c8afde..d2209315e8 100644 --- a/src/PSRule.BuildTask/PSRule.BuildTask.csproj +++ b/src/PSRule.BuildTask/PSRule.BuildTask.csproj @@ -8,7 +8,7 @@ - + diff --git a/mkdocs.yml b/mkdocs.yml index d46de65b24..e28e075eac 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -65,6 +65,7 @@ nav: - Functions: expressions/functions.md - Grouping rules: concepts/grouping-rules.md - Sub-selectors: expressions/sub-selectors.md + - Lock file: concepts/lockfile.md - Scenarios: - Using within continuous integration: scenarios/validation-pipeline/validation-pipeline.md - Troubleshooting: troubleshooting.md @@ -85,7 +86,7 @@ nav: # - Configuring rule defaults: setup/configuring-rules.md # - Configuring expansion: setup/configuring-expansion.md - Reference: - - Commands: + - PowerShell cmdlets: - Assert-PSRule: commands/PSRule/en-US/Assert-PSRule.md - Export-PSRuleBaseline: commands/PSRule/en-US/Export-PSRuleBaseline.md - Get-PSRule: commands/PSRule/en-US/Get-PSRule.md @@ -96,6 +97,7 @@ nav: - New-PSRuleOption: commands/PSRule/en-US/New-PSRuleOption.md - Set-PSRuleOption: commands/PSRule/en-US/Set-PSRuleOption.md - Test-PSRuleTarget: commands/PSRule/en-US/Test-PSRuleTarget.md + - CLI commands: concepts/cli.md - Assertion methods: concepts/PSRule/en-US/about_PSRule_Assert.md - Baselines: concepts/PSRule/en-US/about_PSRule_Baseline.md - Badges: concepts/PSRule/en-US/about_PSRule_Badges.md diff --git a/ps-rule.lock.json b/ps-rule.lock.json new file mode 100644 index 0000000000..d95177725c --- /dev/null +++ b/ps-rule.lock.json @@ -0,0 +1,10 @@ +{ + "modules": { + "PSRule.Rules.MSFT.OSS": { + "version": "1.1.0" + }, + "PSRule.Rules.Azure": { + "version": "1.30.0" + } + } +} diff --git a/schemas/PSRule-lock.schema.json b/schemas/PSRule-lock.schema.json new file mode 100644 index 0000000000..4799ed66fc --- /dev/null +++ b/schemas/PSRule-lock.schema.json @@ -0,0 +1,29 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema#", + "type": "object", + "title": "PSRule lock", + "description": "A schema for the PSRule lock file.", + "properties": { + "modules": { + "type": "object", + "title": "Modules", + "additionalProperties": { + "type": "object", + "properties": { + "version": { + "type": "string", + "title": "Version" + } + }, + "required": [ + "version" + ], + "additionalProperties": false + } + } + }, + "required": [ + "modules" + ], + "additionalProperties": false +} diff --git a/src/PSRule.Tool/ClientBuilder.cs b/src/PSRule.Tool/ClientBuilder.cs index 9ae4eb5c7d..5d54948929 100644 --- a/src/PSRule.Tool/ClientBuilder.cs +++ b/src/PSRule.Tool/ClientBuilder.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System.CommandLine; +using System.CommandLine.Builder; using System.Reflection; using PSRule.Tool.Resources; @@ -15,6 +16,9 @@ internal sealed class ClientBuilder private readonly Option _Verbose; private readonly Option _Debug; private readonly Option _RestoreForce; + private readonly Option _ModuleAddVersion; + private readonly Option _ModuleAddForce; + private readonly Option _ModuleAddSkipVerification; private readonly Option _Path; private readonly Option _OutputPath; private readonly Option _OutputFormat; @@ -27,7 +31,8 @@ private ClientBuilder(RootCommand cmd) Command = cmd; _Option = new Option( new string[] { "--option" }, - CmdStrings.Options_Option_Description + getDefaultValue: () => "ps-rule.yaml", + description: CmdStrings.Options_Option_Description ); _Verbose = new Option( new string[] { "--verbose" }, @@ -38,7 +43,8 @@ private ClientBuilder(RootCommand cmd) CmdStrings.Options_Debug_Description ); _Path = new Option( - new string[] { "-p", "--path" } + new string[] { "-p", "--path" }, + CmdStrings.Options_Path_Description ); _OutputPath = new Option( new string[] { "--output-path" } @@ -50,7 +56,8 @@ private ClientBuilder(RootCommand cmd) new string[] { "-f", "--input-path" } ); _Module = new Option( - new string[] { "-m", "--module" } + new string[] { "-m", "--module" }, + CmdStrings.Options_Module_Description ); _Baseline = new Option( new string[] { "--baseline" } @@ -59,6 +66,19 @@ private ClientBuilder(RootCommand cmd) new string[] { "--force" }, CmdStrings.Restore_Force_Description ); + _ModuleAddVersion = new Option + ( + new string[] { "--version" }, + CmdStrings.Module_Add_Version_Description + ); + _ModuleAddForce = new Option( + new string[] { "--force" }, + CmdStrings.Module_Add_Force_Description + ); + _ModuleAddSkipVerification = new Option( + new string[] { "--skip-verification" }, + CmdStrings.Module_Add_SkipVerification_Description + ); cmd.AddGlobalOption(_Option); cmd.AddGlobalOption(_Verbose); @@ -75,6 +95,7 @@ public static Command New() }; var builder = new ClientBuilder(cmd); builder.AddAnalyze(); + builder.AddModule(); builder.AddRestore(); return builder.Command; } @@ -126,5 +147,91 @@ private void AddRestore() }); Command.AddCommand(cmd); } + + private void AddModule() + { + var cmd = new Command("module", CmdStrings.Module_Description); + + var moduleArg = new Argument + ( + "module", + CmdStrings.Module_Module_Description + ); + moduleArg.Arity = ArgumentArity.OneOrMore; + + // Add + var add = new Command( + "add", + CmdStrings.Module_Add_Description + ); + add.AddArgument(moduleArg); + add.AddOption(_ModuleAddVersion); + add.AddOption(_ModuleAddForce); + add.AddOption(_ModuleAddSkipVerification); + add.SetHandler((invocation) => + { + var option = new ModuleOptions + { + Path = invocation.ParseResult.GetValueForOption(_Path), + Option = invocation.ParseResult.GetValueForOption(_Option), + Verbose = invocation.ParseResult.GetValueForOption(_Verbose), + Debug = invocation.ParseResult.GetValueForOption(_Debug), + Module = invocation.ParseResult.GetValueForArgument(moduleArg), + Version = invocation.ParseResult.GetValueForOption(_ModuleAddVersion), + Force = invocation.ParseResult.GetValueForOption(_ModuleAddForce), + SkipVerification = invocation.ParseResult.GetValueForOption(_ModuleAddSkipVerification), + }; + + var client = new ClientContext(); + invocation.ExitCode = ClientHelper.AddModule(option, client, invocation); + }); + + // Remove + var remove = new Command( + "remove", + CmdStrings.Module_Remove_Description + ); + remove.AddArgument(moduleArg); + remove.SetHandler((invocation) => + { + var option = new ModuleOptions + { + Path = invocation.ParseResult.GetValueForOption(_Path), + Option = invocation.ParseResult.GetValueForOption(_Option), + Verbose = invocation.ParseResult.GetValueForOption(_Verbose), + Debug = invocation.ParseResult.GetValueForOption(_Debug), + Module = invocation.ParseResult.GetValueForArgument(moduleArg) + }; + + var client = new ClientContext(); + invocation.ExitCode = ClientHelper.RemoveModule(option, client, invocation); + }); + + // Upgrade + var upgrade = new Command( + "upgrade", + CmdStrings.Module_Upgrade_Description + ); + upgrade.SetHandler((invocation) => + { + var option = new ModuleOptions + { + Path = invocation.ParseResult.GetValueForOption(_Path), + Option = invocation.ParseResult.GetValueForOption(_Option), + Verbose = invocation.ParseResult.GetValueForOption(_Verbose), + Debug = invocation.ParseResult.GetValueForOption(_Debug) + }; + + var client = new ClientContext(); + invocation.ExitCode = ClientHelper.UpgradeModule(option, client, invocation); + }); + + cmd.AddCommand(add); + cmd.AddCommand(remove); + cmd.AddCommand(upgrade); + + cmd.AddOption(_Path); + Command.AddCommand(cmd); + } } } diff --git a/src/PSRule.Tool/ClientHelper.cs b/src/PSRule.Tool/ClientHelper.cs index c07b5266ff..a3659d69eb 100644 --- a/src/PSRule.Tool/ClientHelper.cs +++ b/src/PSRule.Tool/ClientHelper.cs @@ -4,10 +4,14 @@ using System.Collections; using System.CommandLine; using System.CommandLine.Invocation; +using System.Diagnostics; using System.Management.Automation; +using Microsoft.CodeAnalysis.Sarif; using PSRule.Configuration; using PSRule.Data; using PSRule.Pipeline; +using PSRule.Pipeline.Dependencies; +using PSRule.Tool.Resources; using SemanticVersion = PSRule.Data.SemanticVersion; namespace PSRule.Tool @@ -24,7 +28,12 @@ internal sealed class ClientHelper /// /// Failed to install a module. /// - private const int ERROR_MODUILE_FAILEDTOINSTALL = 500; + private const int ERROR_MODUILE_FAILEDTOINSTALL = 501; + + private const int ERROR_MODULE_FAILEDTOFIND = 502; + + private const int ERROR_MODULE_ADD_VIOLATES_CONSTRAINT = 503; + /// /// One or more failures occurred. @@ -43,6 +52,7 @@ public static int RunAnalyze(AnalyzerOptions operationOptions, ClientContext cli var exitCode = 0; var host = new ClientHost(invocation, operationOptions.Verbose, operationOptions.Debug); var option = GetOption(host); + var file = LockFile.Read(null); var inputPath = operationOptions.InputPath == null || operationOptions.InputPath.Length == 0 ? new string[] { Environment.GetWorkingPath() } : operationOptions.InputPath; @@ -50,7 +60,7 @@ public static int RunAnalyze(AnalyzerOptions operationOptions, ClientContext cli option.Include.Path = operationOptions.Path; // Build command - var builder = CommandLineBuilder.Assert(operationOptions.Module, option, host); + var builder = CommandLineBuilder.Assert(operationOptions.Module, file, option, host); builder.Baseline(BaselineOption.FromString(operationOptions.Baseline)); builder.InputPath(inputPath); builder.UnblockPublisher(PUBLISHER); @@ -70,47 +80,169 @@ public static int RunAnalyze(AnalyzerOptions operationOptions, ClientContext cli public static int RunRestore(RestoreOptions operationOptions, ClientContext clientContext, InvocationContext invocation) { var exitCode = 0; - var host = new ClientHost(invocation, operationOptions.Verbose, operationOptions.Debug); - var option = GetOption(host); - var requires = option.Requires.ToArray(); + var file = LockFile.Read(null); using var pwsh = PowerShell.Create(); - for (var i = 0; i < requires.Length; i++) + foreach (var kv in file.Modules) { - if (string.Equals(requires[i].Module, "PSRule", System.StringComparison.OrdinalIgnoreCase)) + var module = kv.Key; + var targetVersion = kv.Value.Version; + if (string.Equals(module, "PSRule", StringComparison.OrdinalIgnoreCase)) continue; - invocation.Console.WriteLine($"Getting {requires[i].Module}."); - if (IsInstalled(pwsh, requires[i], out var installedVersion) && !operationOptions.Force) + invocation.Log(Messages.UsingModule, module, targetVersion.ToString()); + if (IsInstalled(pwsh, module, targetVersion, out var installedVersion) && !operationOptions.Force) + { + if (operationOptions.Verbose) + { + invocation.Log($"The module {module} is already installed."); + } continue; + } - var idealVersion = FindVersion(pwsh, requires[i], installedVersion); + var idealVersion = FindVersion(pwsh, module, null, targetVersion, null); if (idealVersion != null) - { - var version = idealVersion.ToString(); - invocation.Console.WriteLine($"Installing {requires[i].Module} v{version}."); - InstallVersion(pwsh, requires[i].Module, version); - } + InstallVersion(invocation, pwsh, module, idealVersion.ToString()); if (pwsh.HadErrors || (idealVersion == null && installedVersion == null)) { exitCode = ERROR_MODUILE_FAILEDTOINSTALL; - invocation.Console.Error.Write($"Failed to install {requires[i].Module}."); + invocation.LogError(Messages.Error_501, module, targetVersion); foreach (var error in pwsh.Streams.Error) { - invocation.Console.Error.Write(error.Exception.Message); + invocation.LogError(error.Exception.Message); } } } return exitCode; } - private static bool IsInstalled(PowerShell pwsh, ModuleConstraint constraint, out SemanticVersion.Version installedVersion) + /// + /// Add a module to the lock file. + /// + public static int AddModule(ModuleOptions operationOptions, ClientContext clientContext, InvocationContext invocation) + { + var exitCode = 0; + var host = new ClientHost(invocation, operationOptions.Verbose, operationOptions.Debug); + var option = GetOption(host); + var requires = option.Requires.ToDictionary(); + var file = LockFile.Read(null); + + using var pwsh = PowerShell.Create(); + foreach (var module in operationOptions.Module) + { + if (!file.Modules.TryGetValue(module, out var item) || operationOptions.Force) + { + // Get a constraint if set from options. + var moduleConstraint = requires.TryGetValue(module, out var c) ? c : null; + + // Get target version if specified in command-line. + var targetVersion = !string.IsNullOrEmpty(operationOptions.Version) && SemanticVersion.TryParseVersion(operationOptions.Version, out var v) && v != null ? v : null; + + // Check if the target version is valid with the constraint if set. + if (targetVersion != null && moduleConstraint != null && !moduleConstraint.Constraint.Equals(targetVersion)) + { + invocation.LogError(Messages.Error_503, operationOptions.Version); + return ERROR_MODULE_ADD_VIOLATES_CONSTRAINT; + } + + // Find the ideal version. + var idealVersion = FindVersion(pwsh, module, moduleConstraint, targetVersion, null); + if (idealVersion == null && targetVersion != null && operationOptions.SkipVerification) + idealVersion = targetVersion; + + if (idealVersion == null) + { + invocation.LogError(Messages.Error_502, module); + return ERROR_MODULE_FAILEDTOFIND; + } + + invocation.Log(Messages.UsingModule, module, idealVersion.ToString()); + item = new LockEntry + { + Version = idealVersion + }; + file.Modules[module] = item; + } + else + { + + } + } + + file.Write(null); + return exitCode; + } + + /// + /// Remove a module from the lock file. + /// + public static int RemoveModule(ModuleOptions operationOptions, ClientContext clientContext, InvocationContext invocation) + { + var exitCode = 0; + var host = new ClientHost(invocation, operationOptions.Verbose, operationOptions.Debug); + var file = LockFile.Read(null); + + foreach (var module in operationOptions.Module) + { + if (file.Modules.TryGetValue(module, out var constraint)) + { + file.Modules.Remove(module); + } + else + { + + } + } + + file.Write(null); + return exitCode; + } + + /// + /// Upgrade a module within the lock file. + /// + public static int UpgradeModule(ModuleOptions operationOptions, ClientContext clientContext, InvocationContext invocation) + { + var exitCode = 0; + var host = new ClientHost(invocation, operationOptions.Verbose, operationOptions.Debug); + var option = GetOption(host); + var requires = option.Requires.ToDictionary(); + var file = LockFile.Read(null); + + using var pwsh = PowerShell.Create(); + foreach (var kv in file.Modules) + { + // Get a constraint if set from options. + var moduleConstraint = requires.TryGetValue(kv.Key, out var c) ? c : null; + + // Find the ideal version. + var idealVersion = FindVersion(pwsh, kv.Key, moduleConstraint, null, null); + if (idealVersion == null) + { + invocation.LogError(Messages.Error_502, kv.Key); + return ERROR_MODULE_FAILEDTOFIND; + } + + if (idealVersion == kv.Value.Version) + continue; + + invocation.Log(Messages.UsingModule, kv.Key, idealVersion.ToString()); + + kv.Value.Version = idealVersion; + file.Modules[kv.Key] = kv.Value; + } + + file.Write(null); + return exitCode; + } + + private static bool IsInstalled(PowerShell pwsh, string module, SemanticVersion.Version targetVersion, out SemanticVersion.Version installedVersion) { pwsh.Commands.Clear(); pwsh.Streams.ClearStreams(); pwsh.AddCommand("Get-Module") - .AddParameter(PARAM_NAME, constraint.Module) + .AddParameter(PARAM_NAME, module) .AddParameter("ListAvailable"); var versions = pwsh.Invoke(); @@ -119,7 +251,7 @@ private static bool IsInstalled(PowerShell pwsh, ModuleConstraint constraint, ou { if (TryModuleInfo(version, out var versionString) && SemanticVersion.TryParseVersion(versionString, out var v) && - constraint.Constraint.Equals(v) && + (targetVersion == null || targetVersion.Equals(v)) && v.CompareTo(installedVersion) > 0) installedVersion = v; } @@ -150,12 +282,12 @@ private static bool TryPrivateData(PSModuleInfo info, string propertyName, out H return false; } - private static SemanticVersion.Version FindVersion(PowerShell pwsh, ModuleConstraint constraint, SemanticVersion.Version installedVersion) + private static SemanticVersion.Version FindVersion(PowerShell pwsh, string module, ModuleConstraint constraint, SemanticVersion.Version targetVersion, SemanticVersion.Version installedVersion) { pwsh.Commands.Clear(); pwsh.Streams.ClearStreams(); pwsh.AddCommand("Find-Module") - .AddParameter(PARAM_NAME, constraint.Module) + .AddParameter(PARAM_NAME, module) .AddParameter("AllVersions"); var versions = pwsh.Invoke(); @@ -164,7 +296,8 @@ private static SemanticVersion.Version FindVersion(PowerShell pwsh, ModuleConstr { if (version.Properties[PARAM_VERSION].Value is string versionString && SemanticVersion.TryParseVersion(versionString, out var v) && - constraint.Constraint.Equals(v) && + (constraint == null || constraint.Constraint.Equals(v)) && + (targetVersion == null || targetVersion.Equals(v)) && v.CompareTo(result) > 0 && v.CompareTo(installedVersion) > 0) result = v; @@ -172,8 +305,10 @@ private static SemanticVersion.Version FindVersion(PowerShell pwsh, ModuleConstr return result; } - private static void InstallVersion(PowerShell pwsh, string name, string version) + private static void InstallVersion(InvocationContext invocation, PowerShell pwsh, string name, string version) { + invocation.Log(Messages.InstallingModule, name, version); + pwsh.Commands.Clear(); pwsh.Streams.ClearStreams(); pwsh.AddCommand("Install-Module") diff --git a/src/PSRule.Tool/InvocationExtensions.cs b/src/PSRule.Tool/InvocationExtensions.cs new file mode 100644 index 0000000000..2e47feaba7 --- /dev/null +++ b/src/PSRule.Tool/InvocationExtensions.cs @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.CommandLine; +using System.CommandLine.Invocation; + +namespace PSRule.Tool; + +internal static class InvocationExtensions +{ + public static void Log(this InvocationContext context, string message, params object[] args) + { + if (context == null || string.IsNullOrEmpty(message)) + return; + + var s = args != null && args.Length > 0 ? string.Format(Thread.CurrentThread.CurrentCulture, message, args) : message; + context.Console.WriteLine(s); + } + + public static void LogError(this InvocationContext context, string message, params object[] args) + { + if (context == null || string.IsNullOrEmpty(message)) + return; + + var s = args != null && args.Length > 0 ? string.Format(Thread.CurrentThread.CurrentCulture, message, args) : message; + context.Console.Error.Write(s); + } +} diff --git a/src/PSRule.Tool/ModuleOptions.cs b/src/PSRule.Tool/ModuleOptions.cs new file mode 100644 index 0000000000..4c823c7f7f --- /dev/null +++ b/src/PSRule.Tool/ModuleOptions.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace PSRule.Tool; + +internal sealed class ModuleOptions +{ + public string[] Path { get; set; } + + public string Option { get; set; } + + public bool Verbose { get; set; } + + public bool Debug { get; set; } + + public string[] Module { get; set; } + + public bool Force { get; set; } + + public string Version { get; set; } + + public bool SkipVerification { get; set; } +} diff --git a/src/PSRule.Tool/PSRule.Tool.csproj b/src/PSRule.Tool/PSRule.Tool.csproj index a6537fb8ee..10b7a6dd3e 100644 --- a/src/PSRule.Tool/PSRule.Tool.csproj +++ b/src/PSRule.Tool/PSRule.Tool.csproj @@ -36,7 +36,7 @@ - + True @@ -52,4 +52,19 @@ + + + True + True + Messages.resx + + + + + + ResXFileCodeGenerator + Messages.Designer.cs + + + diff --git a/src/PSRule.Tool/Properties/launchSettings.json b/src/PSRule.Tool/Properties/launchSettings.json index d3df27cbf5..4dd125d7f2 100644 --- a/src/PSRule.Tool/Properties/launchSettings.json +++ b/src/PSRule.Tool/Properties/launchSettings.json @@ -2,13 +2,18 @@ "profiles": { "ps-rule analyze": { "commandName": "Project", - "commandLineArgs": "analyze", + "commandLineArgs": "analyze -m PSRule.Rules.Azure", "workingDirectory": "../../" }, "ps-rule restore": { "commandName": "Project", "commandLineArgs": "restore", "workingDirectory": "../../" + }, + "ps-rule module add": { + "commandName": "Project", + "commandLineArgs": "module add abc --version 1.0.0", + "workingDirectory": "../../" } } -} +} \ No newline at end of file diff --git a/src/PSRule.Tool/Resources/CmdStrings.Designer.cs b/src/PSRule.Tool/Resources/CmdStrings.Designer.cs index 783e111b9b..27e34b315f 100644 --- a/src/PSRule.Tool/Resources/CmdStrings.Designer.cs +++ b/src/PSRule.Tool/Resources/CmdStrings.Designer.cs @@ -61,7 +61,7 @@ internal CmdStrings() { } /// - /// Looks up a localized string similar to Run rule analysis. + /// Looks up a localized string similar to Run rule analysis.. /// internal static string Analyze_Description { get { @@ -79,7 +79,79 @@ internal static string Cmd_Description { } /// - /// Looks up a localized string similar to Return debug output. + /// Looks up a localized string similar to Add one or more modules to the lock file.. + /// + internal static string Module_Add_Description { + get { + return ResourceManager.GetString("Module_Add_Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Override the module even if it already is added to the module list.. + /// + internal static string Module_Add_Force_Description { + get { + return ResourceManager.GetString("Module_Add_Force_Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Skip verification of the module.. + /// + internal static string Module_Add_SkipVerification_Description { + get { + return ResourceManager.GetString("Module_Add_SkipVerification_Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The target version of the module to add.. + /// + internal static string Module_Add_Version_Description { + get { + return ResourceManager.GetString("Module_Add_Version_Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Manage modules tracked by the PSRule lock file. To install modules within the lock file, use the `restore` command instead.. + /// + internal static string Module_Description { + get { + return ResourceManager.GetString("Module_Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The name of one or more modules.. + /// + internal static string Module_Module_Description { + get { + return ResourceManager.GetString("Module_Module_Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Remove one or more modules from the lock file.. + /// + internal static string Module_Remove_Description { + get { + return ResourceManager.GetString("Module_Remove_Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Upgrade to the latest versions of any modules within the lock file.. + /// + internal static string Module_Upgrade_Description { + get { + return ResourceManager.GetString("Module_Upgrade_Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Return debug output.. /// internal static string Options_Debug_Description { get { @@ -88,7 +160,16 @@ internal static string Options_Debug_Description { } /// - /// Looks up a localized string similar to An options file. + /// Looks up a localized string similar to The name of one or more modules to use during analysis.. + /// + internal static string Options_Module_Description { + get { + return ResourceManager.GetString("Options_Module_Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Specifies the path to an options file.. /// internal static string Options_Option_Description { get { @@ -97,7 +178,7 @@ internal static string Options_Option_Description { } /// - /// Looks up a localized string similar to . + /// Looks up a localized string similar to The path to run commands within. By default, this is the current working directory.. /// internal static string Options_Path_Description { get { @@ -106,7 +187,7 @@ internal static string Options_Path_Description { } /// - /// Looks up a localized string similar to Return verbose output. + /// Looks up a localized string similar to Return verbose output.. /// internal static string Options_Verbose_Description { get { @@ -115,7 +196,7 @@ internal static string Options_Verbose_Description { } /// - /// Looks up a localized string similar to Restore PSRule modules. + /// Looks up a localized string similar to Restore modules defined in configuration locally.. /// internal static string Restore_Description { get { @@ -124,7 +205,7 @@ internal static string Restore_Description { } /// - /// Looks up a localized string similar to Force restore of modules. + /// Looks up a localized string similar to Restore modules even when an existing version that meets constaints is already installed locally.. /// internal static string Restore_Force_Description { get { diff --git a/src/PSRule.Tool/Resources/CmdStrings.resx b/src/PSRule.Tool/Resources/CmdStrings.resx index d5dc1d6280..ed7d8183c1 100644 --- a/src/PSRule.Tool/Resources/CmdStrings.resx +++ b/src/PSRule.Tool/Resources/CmdStrings.resx @@ -118,27 +118,54 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Run rule analysis + Run rule analysis. PSRule CLI + + Add one or more modules to the lock file. + + + Override the module even if it already is added to the module list. + + + Skip verification of the module. + + + The target version of the module to add. + + + Manage modules tracked by the PSRule lock file. To install modules within the lock file, use the `restore` command instead. + + + The name of one or more modules. + + + Remove one or more modules from the lock file. + + + Upgrade to the latest versions of any modules within the lock file. + - Return debug output + Return debug output. + + + The name of one or more modules to use during analysis. - An options file + Specifies the path to an options file. - + The path to run commands within. By default, this is the current working directory. - Return verbose output + Return verbose output. - Restore PSRule modules + Restore modules defined in configuration locally. - Force restore of modules + Restore modules even when an existing version that meets constaints is already installed locally. \ No newline at end of file diff --git a/src/PSRule.Tool/Resources/Messages.Designer.cs b/src/PSRule.Tool/Resources/Messages.Designer.cs new file mode 100644 index 0000000000..f16662dcf8 --- /dev/null +++ b/src/PSRule.Tool/Resources/Messages.Designer.cs @@ -0,0 +1,108 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace PSRule.Tool.Resources { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Messages { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Messages() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("PSRule.Tool.Resources.Messages", typeof(Messages).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Failed to install module: {0} -- v{1}. + /// + internal static string Error_501 { + get { + return ResourceManager.GetString("Error_501", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Failed to find a valid version of the specified module '{0}'.. + /// + internal static string Error_502 { + get { + return ResourceManager.GetString("Error_502", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The specified verison v{0} does not meet the required constraint.. + /// + internal static string Error_503 { + get { + return ResourceManager.GetString("Error_503", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Installing module: {0} -- v{1}. + /// + internal static string InstallingModule { + get { + return ResourceManager.GetString("InstallingModule", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Using module: {0} -- v{1}. + /// + internal static string UsingModule { + get { + return ResourceManager.GetString("UsingModule", resourceCulture); + } + } + } +} diff --git a/src/PSRule.Tool/Resources/Messages.resx b/src/PSRule.Tool/Resources/Messages.resx new file mode 100644 index 0000000000..b1b9cb4624 --- /dev/null +++ b/src/PSRule.Tool/Resources/Messages.resx @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Failed to install module: {0} -- v{1} + + + Failed to find a valid version of the specified module '{0}'. + + + The specified verison v{0} does not meet the required constraint. + + + Installing module: {0} -- v{1} + + + Using module: {0} -- v{1} + + \ No newline at end of file diff --git a/src/PSRule.Types/Data/SemanticVersion.cs b/src/PSRule.Types/Data/SemanticVersion.cs index 93bc38e719..260be5f93b 100644 --- a/src/PSRule.Types/Data/SemanticVersion.cs +++ b/src/PSRule.Types/Data/SemanticVersion.cs @@ -1,7 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System; using System.Diagnostics; +using YamlDotNet.Core.Tokens; namespace PSRule.Data { @@ -88,6 +90,12 @@ internal enum ConstraintModifier public sealed class VersionConstraint : ISemanticVersionConstraint { private List? _Constraints; + private string _Value; + + internal VersionConstraint(string value) + { + _Value = value; + } /// public bool Equals(Version version) @@ -140,6 +148,18 @@ internal void Join(int major, int minor, int patch, PR prid, ComparisonOperator includePrerelease )); } + + /// + public override string ToString() + { + return _Value; + } + + /// + public override int GetHashCode() + { + return _Value.GetHashCode(); + } } [DebuggerDisplay("{_Major}.{_Minor}.{_Patch}")] @@ -393,23 +413,40 @@ public override int GetHashCode() } } + /// + public static bool operator ==(Version? a, Version? b) + { + return a is null && b is null || + a is not null && a.Equals(b) || + b is not null && b.Equals(a); + } + + /// + public static bool operator !=(Version? a, Version? b) + { + return !(a is null && b is null || + a is not null && a.Equals(b) || + b is not null && b.Equals(a)); + } + /// /// Compare the version against another version. /// - public bool Equals(Version other) + public bool Equals(Version? other) { return other != null && - Equals(other.Major, other.Minor, other.Patch); + Equals(other.Major, other.Minor, other.Patch, other.Prerelease?.Value); } /// /// Compare the version against another version based on major.minor.patch. /// - public bool Equals(int major, int minor, int patch) + public bool Equals(int major, int minor, int patch, string? prerelease = null) { return major == Major && minor == Minor && - patch == Patch; + patch == Patch && + new PR(prerelease).Equals(Prerelease); } /// @@ -447,10 +484,10 @@ private PR() _Identifiers = null; } - internal PR(string value) + internal PR(string? value) { - Value = string.IsNullOrEmpty(value) ? string.Empty : value; - _Identifiers = string.IsNullOrEmpty(value) ? null : value.Split(SEPARATORS, StringSplitOptions.RemoveEmptyEntries); + Value = value == null || string.IsNullOrEmpty(value) ? string.Empty : value; + _Identifiers = value == null || string.IsNullOrEmpty(value) ? null : value.Split(SEPARATORS, StringSplitOptions.RemoveEmptyEntries); } /// @@ -784,7 +821,7 @@ private static bool IsLetter(char c) /// public static bool TryParseConstraint(string value, out ISemanticVersionConstraint constraint, bool includePrerelease = false) { - var c = new VersionConstraint(); + var c = new VersionConstraint(value); constraint = c; if (string.IsNullOrEmpty(value)) return true; diff --git a/src/PSRule/Common/JsonConverters.cs b/src/PSRule/Common/JsonConverters.cs index 6b6dbd6e1b..2a6d4136be 100644 --- a/src/PSRule/Common/JsonConverters.cs +++ b/src/PSRule/Common/JsonConverters.cs @@ -956,4 +956,40 @@ public override void WriteJson(JsonWriter writer, ResourceId value, JsonSerializ writer.WriteValue(value.Value); } } + + /// + /// A converter for converting to/ from JSON. + /// + internal sealed class SemanticVersionConverter : JsonConverter + { + public override SemanticVersion.Version ReadJson(JsonReader reader, Type objectType, SemanticVersion.Version existingValue, bool hasExistingValue, JsonSerializer serializer) + { + return reader.TokenType == JsonToken.String && SemanticVersion.TryParseVersion(reader.Value as string, out var version) ? version : default; + } + + public override void WriteJson(JsonWriter writer, SemanticVersion.Version value, JsonSerializer serializer) + { + writer.WriteValue(value?.ToString()); + } + } + + internal sealed class CaseInsensitiveDictionaryConverter : JsonConverter + { + public override bool CanConvert(Type objectType) + { + return objectType == typeof(Dictionary) || objectType == typeof(IDictionary); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + existingValue ??= new Dictionary(StringComparer.OrdinalIgnoreCase); + serializer.Deserialize>(reader); + return existingValue; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + serializer.Serialize(writer, value); + } + } } diff --git a/src/PSRule/Configuration/RequiresOption.cs b/src/PSRule/Configuration/RequiresOption.cs index c88bdb3ba4..ac5dfb8de5 100644 --- a/src/PSRule/Configuration/RequiresOption.cs +++ b/src/PSRule/Configuration/RequiresOption.cs @@ -44,6 +44,20 @@ public ModuleConstraint[] ToArray() return result.ToArray(); } + /// + /// Return the module constaints as a dictionary indexed by module name. + /// + public IDictionary ToDictionary() + { + var result = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (var kv in this) + { + if (SemanticVersion.TryParseConstraint(kv.Value, out var constraint)) + result.Add(kv.Key, new ModuleConstraint(kv.Key, constraint)); + } + return result; + } + /// /// Merge two option instances by replacing any unset properties from with values. /// Values from that are set are not overridden. diff --git a/src/PSRule/Pipeline/CommandLineBuilder.cs b/src/PSRule/Pipeline/CommandLineBuilder.cs index 91cec22404..b05135c1ec 100644 --- a/src/PSRule/Pipeline/CommandLineBuilder.cs +++ b/src/PSRule/Pipeline/CommandLineBuilder.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using PSRule.Configuration; +using PSRule.Pipeline.Dependencies; namespace PSRule.Pipeline { @@ -42,17 +43,24 @@ public static IInvokePipelineBuilder Invoke(string[] module, PSRuleOption option /// Assert pipelines process objects with rules and produce text-based output suitable for output to a CI pipeline. /// /// The name of modules containing rules to process. + /// An optional lock file. /// Options that configure PSRule. /// An implementation of a host context that will recieve output and results. /// A builder object to configure the pipeline. - public static IInvokePipelineBuilder Assert(string[] module, PSRuleOption option, IHostContext hostContext) + public static IInvokePipelineBuilder Assert(string[] module, LockFile file, PSRuleOption option, IHostContext hostContext) { var sourcePipeline = new SourcePipelineBuilder(hostContext, option, GetLocalPath()); for (var i = 0; module != null && i < module.Length; i++) - sourcePipeline.ModuleByName(module[i]); + { + var version = file != null && file.Modules.TryGetValue(module[i], out var entry) ? entry.Version.ToString() : null; + sourcePipeline.ModuleByName(module[i], version); + } for (var i = 0; option.Include.Module != null && i < option.Include.Module.Length; i++) - sourcePipeline.ModuleByName(option.Include.Module[i]); + { + var version = file != null && file.Modules.TryGetValue(module[i], out var entry) ? entry.Version.ToString() : null; + sourcePipeline.ModuleByName(option.Include.Module[i], version); + } var source = sourcePipeline.Build(); var pipeline = new AssertPipelineBuilder(source, hostContext); diff --git a/src/PSRule/Pipeline/Dependencies/LockFile.cs b/src/PSRule/Pipeline/Dependencies/LockFile.cs new file mode 100644 index 0000000000..748366e9d8 --- /dev/null +++ b/src/PSRule/Pipeline/Dependencies/LockFile.cs @@ -0,0 +1,78 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Text; +using Newtonsoft.Json; +using PSRule.Data; + +namespace PSRule.Pipeline.Dependencies; + +/// +/// An entry within the lock file. +/// +public sealed class LockEntry +{ + /// + /// The version to use. + /// + [JsonProperty("version", NullValueHandling = NullValueHandling.Include)] + public SemanticVersion.Version Version { get; set; } +} + +/// +/// Define the structure for the PSRule lock file. +/// By default, this file is ps-rule.lock.json. +/// +public sealed class LockFile +{ + private const string DEFAULT_FILE = "ps-rule.lock.json"; + + /// + /// A mapping lock file entries for each module. + /// + [JsonProperty("modules")] + //[JsonConverter(typeof(CaseInsensitiveDictionaryConverter))] + public Dictionary Modules { get; set; } = new Dictionary(StringComparer.OrdinalIgnoreCase); + + /// + /// Read the lock file from disk. + /// + /// An alternative path to the lock file. + /// Returns an instance of the lock file or a default instance if the file does not exist. + public static LockFile Read(string path) + { + path = Environment.GetRootedPath(path); + path = Path.GetExtension(path) == ".json" ? path : Path.Combine(path, DEFAULT_FILE); + if (File.Exists(path)) + { + var json = File.ReadAllText(path, Encoding.UTF8); + return JsonConvert.DeserializeObject(json, new JsonSerializerSettings + { + Converters = new List + { + new SemanticVersionConverter() + }, + + }); + } + return new LockFile(); + } + + /// + /// Write the lock file to disk. + /// + /// An alternative path to the lock file. + public void Write(string path) + { + path = Environment.GetRootedPath(path); + path = Path.GetExtension(path) == "json" ? path : Path.Combine(path, DEFAULT_FILE); + var json = JsonConvert.SerializeObject(this, Formatting.Indented, new JsonSerializerSettings + { + Converters = new List + { + new SemanticVersionConverter() + } + }); + File.WriteAllText(path, json, Encoding.UTF8); + } +} diff --git a/src/PSRule/Pipeline/SourcePipeline.cs b/src/PSRule/Pipeline/SourcePipeline.cs index 5b8711c670..1c93d51712 100644 --- a/src/PSRule/Pipeline/SourcePipeline.cs +++ b/src/PSRule/Pipeline/SourcePipeline.cs @@ -85,7 +85,8 @@ public interface ISourceCommandlineBuilder /// Add a module source. /// /// The name of the module. - void ModuleByName(string name); + /// A specific version of the module. + void ModuleByName(string name, string version = null); /// /// Build a list of sources for executing within PSRule. @@ -205,9 +206,9 @@ public void Module(PSModuleInfo[] module) } /// - public void ModuleByName(string name) + public void ModuleByName(string name, string version = null) { - var basePath = FindModule(name); + var basePath = FindModule(name, version); if (basePath == null) throw new PipelineBuilderException(PSRuleResources.ModuleNotFound); @@ -227,10 +228,10 @@ public void ModuleByName(string name) // Module(module.RequiredModules[i], dependency: true); } - private string FindModule(string name) + private string FindModule(string name, string version) { return TryPackagedModule(name, out var path) || - TryInstalledModule(name, out path) ? path : null; + TryInstalledModule(name, version, out path) ? path : null; } /// @@ -250,7 +251,7 @@ private bool TryPackagedModule(string name, out string path) /// /// Try to find a module installed into PowerShell. /// - private bool TryInstalledModule(string name, out string path) + private bool TryInstalledModule(string name, string version, out string path) { path = null; if (!Environment.TryPathEnvironmentVariable("PSModulePath", out var searchPaths)) @@ -262,13 +263,31 @@ private bool TryInstalledModule(string name, out string path) { Debug($"Looking for modules search paths: {searchPaths[i]}"); var searchPath = Environment.GetRootedBasePath(Path.Combine(searchPaths[i], name)); + + // Try a specific version. + if (!string.IsNullOrEmpty(version)) + { + var versionPath = Path.Combine(searchPath, version); + var manifestPath = Path.Combine(versionPath, GetManifestName(name)); + if (File.Exists(manifestPath)) + { + Debug($"Found module manifest: {manifestPath}"); + unsorted.Add(versionPath); + } + continue; + } + + // Get other versions. if (System.IO.Directory.Exists(searchPath)) { foreach (var versionPath in System.IO.Directory.EnumerateDirectories(searchPath)) { var manifestPath = Path.Combine(versionPath, GetManifestName(name)); if (File.Exists(manifestPath)) + { + Debug($"Found module manifest: {manifestPath}"); unsorted.Add(versionPath); + } } } } diff --git a/tests/PSRule.Tests/InputPathBuilderTests.cs b/tests/PSRule.Tests/InputPathBuilderTests.cs index ec2f94c4f4..c65536e115 100644 --- a/tests/PSRule.Tests/InputPathBuilderTests.cs +++ b/tests/PSRule.Tests/InputPathBuilderTests.cs @@ -52,7 +52,7 @@ public void GetPath() builder.Add("./*.json"); actual = builder.Build(); - Assert.True(actual.Length == 2); + Assert.True(actual.Length == 3); builder.Add("src/"); actual = builder.Build(); diff --git a/tests/PSRule.Tests/LockFileTests.cs b/tests/PSRule.Tests/LockFileTests.cs new file mode 100644 index 0000000000..e1b3e8a4e8 --- /dev/null +++ b/tests/PSRule.Tests/LockFileTests.cs @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using PSRule.Pipeline.Dependencies; + +namespace PSRule; + +public sealed class LockFileTests +{ + [Fact] + public void ReadFile() + { + var lockFile = LockFile.Read(GetSourcePath("test.lock.json")); + Assert.True(lockFile.Modules.TryGetValue("PSRule.Rules.MSFT.OSS", out var item)); + Assert.Equal("1.1.0", item.Version.ToString()); + + Assert.True(lockFile.Modules.TryGetValue("psrule.rules.msft.oss", out item)); + Assert.Equal("1.1.0", item.Version.ToString()); + } + + #region Helper methods + + private static string GetSourcePath(string fileName) + { + return System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName); + } + + #endregion Helper methods +} diff --git a/tests/PSRule.Tests/PSRule.Tests.csproj b/tests/PSRule.Tests/PSRule.Tests.csproj index c84365d5f5..d7d3669667 100644 --- a/tests/PSRule.Tests/PSRule.Tests.csproj +++ b/tests/PSRule.Tests/PSRule.Tests.csproj @@ -152,6 +152,9 @@ PreserveNewest + + PreserveNewest + diff --git a/tests/PSRule.Tests/test.lock.json b/tests/PSRule.Tests/test.lock.json new file mode 100644 index 0000000000..9afd5428d1 --- /dev/null +++ b/tests/PSRule.Tests/test.lock.json @@ -0,0 +1,7 @@ +{ + "modules": { + "PSRule.Rules.MSFT.OSS": { + "version": "1.1.0" + } + } +} From 3db1546b2e1b097ab1c75d1ea768a13d4e21a5ed Mon Sep 17 00:00:00 2001 From: Bernie White Date: Sun, 12 Nov 2023 13:17:45 +1000 Subject: [PATCH 113/177] Pre-release v3.0.0-B0084 (#1662) --- docs/CHANGELOG-v3.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/CHANGELOG-v3.md b/docs/CHANGELOG-v3.md index fe6cd6e8ff..a6b0f33b33 100644 --- a/docs/CHANGELOG-v3.md +++ b/docs/CHANGELOG-v3.md @@ -27,6 +27,8 @@ See [upgrade notes][1] for helpful information when upgrading from previous vers ## Unreleased +## v3.0.0-B0084 (pre-release) + What's changed since release v2.9.0: - New features: From 1585349d623cf0ee3b1efcd458d83461ca3b9fc9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Nov 2023 08:55:48 +1000 Subject: [PATCH 114/177] Bump pymdown-extensions from 10.3.1 to 10.4 (#1663) Bumps [pymdown-extensions](https://github.com/facelessuser/pymdown-extensions) from 10.3.1 to 10.4. - [Release notes](https://github.com/facelessuser/pymdown-extensions/releases) - [Commits](https://github.com/facelessuser/pymdown-extensions/compare/10.3.1...10.4) --- updated-dependencies: - dependency-name: pymdown-extensions dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements-docs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-docs.txt b/requirements-docs.txt index 41387faa9a..fe9d87dee8 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -1,6 +1,6 @@ mkdocs==1.5.3 mkdocs-material==9.4.8 -pymdown-extensions==10.3.1 +pymdown-extensions==10.4 mike==2.0.0 mkdocs-simple-hooks==0.1.5 mkdocs-git-revision-date-plugin==0.3.2 From 11dc0b9c314689636771f151e2882763d0f586e8 Mon Sep 17 00:00:00 2001 From: Bernie White Date: Tue, 14 Nov 2023 12:24:24 +1000 Subject: [PATCH 115/177] Update docs deployment (#1664) --- .github/workflows/docs.yaml | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index f56c4bdd90..1b5eeae152 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -15,7 +15,7 @@ on: permissions: {} jobs: - deploy: + publish: name: Publish docs runs-on: ubuntu-latest permissions: @@ -43,5 +43,35 @@ jobs: python3 -m pip install wheel python3 -m pip install -r requirements-docs.txt - - name: Deploy site + - name: Generate site run: mike deploy --push --update-aliases v3 latest --title 'v3' + + deploy: + name: Deploy pages + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + needs: [publish] + concurrency: + group: pages + cancel-in-progress: false + permissions: + contents: read + pages: write + id-token: write + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: refs/heads/gh-pages + - name: Setup Pages + uses: actions/configure-pages@v3 + - name: Upload artifact + uses: actions/upload-pages-artifact@v2 + with: + # Upload entire repository + path: '.' + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v2 From de0128269c7266cd828c744f05592ce6f38b416b Mon Sep 17 00:00:00 2001 From: Bernie White Date: Tue, 14 Nov 2023 12:57:19 +1000 Subject: [PATCH 116/177] Minor fixes for documentation links (#1666) --- .github/workflows/docs.yaml | 4 +++- mkdocs.yml | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index 1b5eeae152..6ecb94ac99 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -65,13 +65,15 @@ jobs: uses: actions/checkout@v4 with: ref: refs/heads/gh-pages + - name: Setup Pages uses: actions/configure-pages@v3 + - name: Upload artifact uses: actions/upload-pages-artifact@v2 with: - # Upload entire repository path: '.' + - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v2 diff --git a/mkdocs.yml b/mkdocs.yml index e28e075eac..35dc5bb6d9 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -74,9 +74,9 @@ nav: - Releases: - Change log: - v3: 'CHANGELOG-v3.md' - - v2: 'CHANGELOG-v2.md' - - v1: 'CHANGELOG-v1.md' - - v0: 'CHANGELOG-v0.md' + - v2: https://microsoft.github.io/PSRule/stable/CHANGELOG-v2/ + - v1: https://microsoft.github.io/PSRule/stable/CHANGELOG-v1/ + - v0: https://microsoft.github.io/PSRule/stable/CHANGELOG-v0/ - Upgrade notes: upgrade-notes.md - Deprecations: deprecations.md - Changes and versioning: versioning.md From 70e3840d5f31f630549242d1c08250b8649014c3 Mon Sep 17 00:00:00 2001 From: Bernie White Date: Wed, 15 Nov 2023 00:08:11 +1000 Subject: [PATCH 117/177] Updates to started repo files (#1668) --- README.md | 8 +++++++- SECURITY.md | 30 +++++++++++++++--------------- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 59852effbf..781391b348 100644 --- a/README.md +++ b/README.md @@ -381,10 +381,16 @@ or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any addi ## Maintainers - [Bernie White](https://github.com/BernieWhite) -- [Sam Bell](https://github.com/ms-sambell) ## License This project is [licensed under the MIT License](LICENSE). +## Trademarks + +This project may contain trademarks or logos for projects, products, or services. +Authorized use of Microsoft trademarks or logos is subject to and must follow [Microsoft's Trademark & Brand Guidelines](https://www.microsoft.com/legal/intellectualproperty/trademarks). +Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship. +Any use of third-party trademarks or logos are subject to those third-party's policies. + > Back to the [summary](#summary) diff --git a/SECURITY.md b/SECURITY.md index 6e5b5fb271..643c09e320 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,36 +1,36 @@ # Security policy - + ## Security -Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). +Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet) and [Xamarin](https://github.com/xamarin). -If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below. +If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/security.md/definition), please report it to us as described below. ## Reporting Security Issues **Please do not report security vulnerabilities through public GitHub issues.** -Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report). +Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/security.md/msrc/create-report). -If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey). +If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/security.md/msrc/pgp). -You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc). +You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: -- Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) -- Full paths of source file(s) related to the manifestation of the issue -- The location of the affected source code (tag/branch/commit or direct URL) -- Any special configuration required to reproduce the issue -- Step-by-step instructions to reproduce the issue -- Proof-of-concept or exploit code (if possible) -- Impact of the issue, including how an attacker might exploit the issue + * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) + * Full paths of source file(s) related to the manifestation of the issue + * The location of the affected source code (tag/branch/commit or direct URL) + * Any special configuration required to reproduce the issue + * Step-by-step instructions to reproduce the issue + * Proof-of-concept or exploit code (if possible) + * Impact of the issue, including how an attacker might exploit the issue This information will help us triage your report more quickly. -If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs. +If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/security.md/msrc/bounty) page for more details about our active programs. ## Preferred Languages @@ -38,6 +38,6 @@ We prefer all communications to be in English. ## Policy -Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd). +Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/security.md/cvd). From 05512c77b2344e43a9b75ded1525afe67e5bce43 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Nov 2023 01:36:23 +1000 Subject: [PATCH 118/177] Bump xunit from 2.5.3 to 2.6.1 (#1656) * Bump xunit from 2.5.3 to 2.6.1 Bumps [xunit](https://github.com/xunit/xunit) from 2.5.3 to 2.6.1. - [Commits](https://github.com/xunit/xunit/compare/2.5.3...2.6.1) --- updated-dependencies: - dependency-name: xunit dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * Bump change log --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Bernie White --- docs/CHANGELOG-v3.md | 6 ++++++ tests/PSRule.Tests/PSRule.Tests.csproj | 2 +- tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/CHANGELOG-v3.md b/docs/CHANGELOG-v3.md index a6b0f33b33..a6766b1da2 100644 --- a/docs/CHANGELOG-v3.md +++ b/docs/CHANGELOG-v3.md @@ -27,6 +27,12 @@ See [upgrade notes][1] for helpful information when upgrading from previous vers ## Unreleased +What's changed since pre-release v3.0.0-B0084: + +- Engineering: + - Bump xunit to v2.6.1. + [#1656](https://github.com/microsoft/PSRule/pull/1656) + ## v3.0.0-B0084 (pre-release) What's changed since release v2.9.0: diff --git a/tests/PSRule.Tests/PSRule.Tests.csproj b/tests/PSRule.Tests/PSRule.Tests.csproj index d7d3669667..79ead9ac43 100644 --- a/tests/PSRule.Tests/PSRule.Tests.csproj +++ b/tests/PSRule.Tests/PSRule.Tests.csproj @@ -13,7 +13,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj b/tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj index 8cb8d777b1..380e2a876c 100644 --- a/tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj +++ b/tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj @@ -11,7 +11,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive From 928ddfa834853022f57008e6de93ea27d86cd59f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 19 Nov 2023 14:08:59 +1000 Subject: [PATCH 119/177] Bump mkdocs-material from 9.4.8 to 9.4.9 (#1672) Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.4.8 to 9.4.9. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.4.8...9.4.9) --- updated-dependencies: - dependency-name: mkdocs-material dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements-docs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-docs.txt b/requirements-docs.txt index fe9d87dee8..c30abff449 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -1,5 +1,5 @@ mkdocs==1.5.3 -mkdocs-material==9.4.8 +mkdocs-material==9.4.9 pymdown-extensions==10.4 mike==2.0.0 mkdocs-simple-hooks==0.1.5 From c16baa82e8e789df098b76622b3dfe61251bc16e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 Nov 2023 18:37:50 +1000 Subject: [PATCH 120/177] Bump mkdocs-material from 9.4.9 to 9.4.10 (#1675) Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.4.9 to 9.4.10. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.4.9...9.4.10) --- updated-dependencies: - dependency-name: mkdocs-material dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements-docs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-docs.txt b/requirements-docs.txt index c30abff449..a9d3815611 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -1,5 +1,5 @@ mkdocs==1.5.3 -mkdocs-material==9.4.9 +mkdocs-material==9.4.10 pymdown-extensions==10.4 mike==2.0.0 mkdocs-simple-hooks==0.1.5 From aa0eb944b6580fbff9144f6f3b92378974123390 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 Nov 2023 10:29:38 +1000 Subject: [PATCH 121/177] Bump System.Drawing.Common from 7.0.0 to 8.0.0 (#1669) * Bump System.Drawing.Common from 7.0.0 to 8.0.0 Bumps [System.Drawing.Common](https://github.com/dotnet/winforms) from 7.0.0 to 8.0.0. - [Release notes](https://github.com/dotnet/winforms/releases) - [Changelog](https://github.com/dotnet/winforms/blob/main/docs/release-activity.md) - [Commits](https://github.com/dotnet/winforms/commits/v8.0.0) --- updated-dependencies: - dependency-name: System.Drawing.Common dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] * Bump System.Drawing.Common to v8.0.0 #1669 --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Bernie White --- docs/CHANGELOG-v3.md | 2 ++ src/PSRule.BuildTool/PSRule.BuildTool.csproj | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/CHANGELOG-v3.md b/docs/CHANGELOG-v3.md index a6766b1da2..afbfbdd815 100644 --- a/docs/CHANGELOG-v3.md +++ b/docs/CHANGELOG-v3.md @@ -32,6 +32,8 @@ What's changed since pre-release v3.0.0-B0084: - Engineering: - Bump xunit to v2.6.1. [#1656](https://github.com/microsoft/PSRule/pull/1656) + - Bump System.Drawing.Common to v8.0.0. + [#1669](https://github.com/microsoft/PSRule/pull/1669) ## v3.0.0-B0084 (pre-release) diff --git a/src/PSRule.BuildTool/PSRule.BuildTool.csproj b/src/PSRule.BuildTool/PSRule.BuildTool.csproj index 4145f77d41..da2ed23880 100644 --- a/src/PSRule.BuildTool/PSRule.BuildTool.csproj +++ b/src/PSRule.BuildTool/PSRule.BuildTool.csproj @@ -11,7 +11,7 @@ - + From 07047926e5ca4d683e58a81040b193f62e9d63fb Mon Sep 17 00:00:00 2001 From: Bernie White Date: Wed, 22 Nov 2023 12:09:11 +1000 Subject: [PATCH 122/177] Fixed IndexOutOfRangeException on lock file #1676 (#1677) --- docs/CHANGELOG-v3.md | 3 ++ src/PSRule.Tool/ClientHelper.cs | 4 +- src/PSRule/Pipeline/CommandLineBuilder.cs | 46 +++++++++++++---------- 3 files changed, 31 insertions(+), 22 deletions(-) diff --git a/docs/CHANGELOG-v3.md b/docs/CHANGELOG-v3.md index afbfbdd815..c24ab172a3 100644 --- a/docs/CHANGELOG-v3.md +++ b/docs/CHANGELOG-v3.md @@ -34,6 +34,9 @@ What's changed since pre-release v3.0.0-B0084: [#1656](https://github.com/microsoft/PSRule/pull/1656) - Bump System.Drawing.Common to v8.0.0. [#1669](https://github.com/microsoft/PSRule/pull/1669) +- Bug fixes: + - Fixed CLI IndexOutOfRangeException with lock file by @BernieWhite. + [#1676](https://github.com/microsoft/PSRule/issues/1676) ## v3.0.0-B0084 (pre-release) diff --git a/src/PSRule.Tool/ClientHelper.cs b/src/PSRule.Tool/ClientHelper.cs index a3659d69eb..e669afe7ef 100644 --- a/src/PSRule.Tool/ClientHelper.cs +++ b/src/PSRule.Tool/ClientHelper.cs @@ -33,7 +33,7 @@ internal sealed class ClientHelper private const int ERROR_MODULE_FAILEDTOFIND = 502; private const int ERROR_MODULE_ADD_VIOLATES_CONSTRAINT = 503; - + /// /// One or more failures occurred. @@ -60,7 +60,7 @@ public static int RunAnalyze(AnalyzerOptions operationOptions, ClientContext cli option.Include.Path = operationOptions.Path; // Build command - var builder = CommandLineBuilder.Assert(operationOptions.Module, file, option, host); + var builder = CommandLineBuilder.Assert(operationOptions.Module, option, host, file); builder.Baseline(BaselineOption.FromString(operationOptions.Baseline)); builder.InputPath(inputPath); builder.UnblockPublisher(PUBLISHER); diff --git a/src/PSRule/Pipeline/CommandLineBuilder.cs b/src/PSRule/Pipeline/CommandLineBuilder.cs index b05135c1ec..5e5f200b50 100644 --- a/src/PSRule/Pipeline/CommandLineBuilder.cs +++ b/src/PSRule/Pipeline/CommandLineBuilder.cs @@ -4,6 +4,8 @@ using PSRule.Configuration; using PSRule.Pipeline.Dependencies; +#nullable enable + namespace PSRule.Pipeline { /// @@ -15,20 +17,17 @@ public static class CommandLineBuilder /// Create a builder for an Invoke pipeline. /// /// - /// Invoke piplines process objects and produce records indicating the outcome of each rule. + /// Invoke pipelines process objects and produce records indicating the outcome of each rule. /// /// The name of modules containing rules to process. /// Options that configure PSRule. - /// An implementation of a host context that will recieve output and results. + /// An implementation of a host context that will receive output and results. + /// An optional lock file. /// A builder object to configure the pipeline. - public static IInvokePipelineBuilder Invoke(string[] module, PSRuleOption option, IHostContext hostContext) + public static IInvokePipelineBuilder Invoke(string[] module, PSRuleOption option, IHostContext hostContext, LockFile? file = null) { var sourcePipeline = new SourcePipelineBuilder(hostContext, option, GetLocalPath()); - for (var i = 0; i < module.Length; i++) - sourcePipeline.ModuleByName(module[i]); - - for (var i = 0; option.Include.Module != null && i < option.Include.Module.Length; i++) - sourcePipeline.ModuleByName(option.Include.Module[i]); + LoadModules(sourcePipeline, module, option, file); var source = sourcePipeline.Build(); var pipeline = new InvokeRulePipelineBuilder(source, hostContext); @@ -43,34 +42,41 @@ public static IInvokePipelineBuilder Invoke(string[] module, PSRuleOption option /// Assert pipelines process objects with rules and produce text-based output suitable for output to a CI pipeline. /// /// The name of modules containing rules to process. - /// An optional lock file. /// Options that configure PSRule. - /// An implementation of a host context that will recieve output and results. + /// An implementation of a host context that will receive output and results. + /// An optional lock file. /// A builder object to configure the pipeline. - public static IInvokePipelineBuilder Assert(string[] module, LockFile file, PSRuleOption option, IHostContext hostContext) + public static IInvokePipelineBuilder Assert(string[] module, PSRuleOption option, IHostContext hostContext, LockFile? file = null) { var sourcePipeline = new SourcePipelineBuilder(hostContext, option, GetLocalPath()); + LoadModules(sourcePipeline, module, option, file); + + var source = sourcePipeline.Build(); + var pipeline = new AssertPipelineBuilder(source, hostContext); + pipeline.Configure(option); + return pipeline; + } + + private static void LoadModules(SourcePipelineBuilder builder, string[] module, PSRuleOption option, LockFile? file) + { for (var i = 0; module != null && i < module.Length; i++) { var version = file != null && file.Modules.TryGetValue(module[i], out var entry) ? entry.Version.ToString() : null; - sourcePipeline.ModuleByName(module[i], version); + builder.ModuleByName(module[i], version); } for (var i = 0; option.Include.Module != null && i < option.Include.Module.Length; i++) { - var version = file != null && file.Modules.TryGetValue(module[i], out var entry) ? entry.Version.ToString() : null; - sourcePipeline.ModuleByName(option.Include.Module[i], version); + var version = file != null && file.Modules.TryGetValue(option.Include.Module[i], out var entry) ? entry.Version.ToString() : null; + builder.ModuleByName(option.Include.Module[i], version); } - - var source = sourcePipeline.Build(); - var pipeline = new AssertPipelineBuilder(source, hostContext); - pipeline.Configure(option); - return pipeline; } - internal static string GetLocalPath() + private static string? GetLocalPath() { return string.IsNullOrEmpty(AppContext.BaseDirectory) ? null : Environment.GetRootedBasePath(AppContext.BaseDirectory); } } } + +#nullable restore From a3055ec8e028690228eb45c56472b3d505519c57 Mon Sep 17 00:00:00 2001 From: Bernie White Date: Wed, 22 Nov 2023 14:11:56 +1000 Subject: [PATCH 123/177] Pre-release v3.0.0-B0093 (#1678) --- docs/CHANGELOG-v3.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/CHANGELOG-v3.md b/docs/CHANGELOG-v3.md index c24ab172a3..c68f46eef5 100644 --- a/docs/CHANGELOG-v3.md +++ b/docs/CHANGELOG-v3.md @@ -27,6 +27,8 @@ See [upgrade notes][1] for helpful information when upgrading from previous vers ## Unreleased +## v3.0.0-B0093 (pre-release) + What's changed since pre-release v3.0.0-B0084: - Engineering: From 3d5df0547cf4b9c34cc87f89a788a62918526359 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 24 Nov 2023 09:32:23 +1000 Subject: [PATCH 124/177] Bump mkdocs-material from 9.4.10 to 9.4.11 (#1680) Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.4.10 to 9.4.11. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.4.10...9.4.11) --- updated-dependencies: - dependency-name: mkdocs-material dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements-docs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-docs.txt b/requirements-docs.txt index a9d3815611..26a7ac9b08 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -1,5 +1,5 @@ mkdocs==1.5.3 -mkdocs-material==9.4.10 +mkdocs-material==9.4.11 pymdown-extensions==10.4 mike==2.0.0 mkdocs-simple-hooks==0.1.5 From 06861dbfa15ae094d9dfd33407f2faa69ec196ad Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 25 Nov 2023 14:09:30 +1000 Subject: [PATCH 125/177] Bump mkdocs-material from 9.4.11 to 9.4.12 (#1681) Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.4.11 to 9.4.12. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.4.11...9.4.12) --- updated-dependencies: - dependency-name: mkdocs-material dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements-docs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-docs.txt b/requirements-docs.txt index 26a7ac9b08..ea2a74d61f 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -1,5 +1,5 @@ mkdocs==1.5.3 -mkdocs-material==9.4.11 +mkdocs-material==9.4.12 pymdown-extensions==10.4 mike==2.0.0 mkdocs-simple-hooks==0.1.5 From 74b10b77c1493925ba5e85064935ac4367d69c36 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Nov 2023 15:45:42 +1000 Subject: [PATCH 126/177] Bump xunit from 2.6.1 to 2.6.2 (#1679) * Bump xunit from 2.6.1 to 2.6.2 Bumps [xunit](https://github.com/xunit/xunit) from 2.6.1 to 2.6.2. - [Commits](https://github.com/xunit/xunit/compare/2.6.1...2.6.2) --- updated-dependencies: - dependency-name: xunit dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * Bump change log --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Bernie White --- docs/CHANGELOG-v3.md | 6 ++++++ tests/PSRule.Tests/PSRule.Tests.csproj | 2 +- tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/CHANGELOG-v3.md b/docs/CHANGELOG-v3.md index c68f46eef5..29653ddea6 100644 --- a/docs/CHANGELOG-v3.md +++ b/docs/CHANGELOG-v3.md @@ -27,6 +27,12 @@ See [upgrade notes][1] for helpful information when upgrading from previous vers ## Unreleased +What's changed since pre-release v3.0.0-B0093: + +- Engineering: + - Bump xunit to v2.6.2. + [#1679](https://github.com/microsoft/PSRule/pull/1679) + ## v3.0.0-B0093 (pre-release) What's changed since pre-release v3.0.0-B0084: diff --git a/tests/PSRule.Tests/PSRule.Tests.csproj b/tests/PSRule.Tests/PSRule.Tests.csproj index 79ead9ac43..f9054cd33c 100644 --- a/tests/PSRule.Tests/PSRule.Tests.csproj +++ b/tests/PSRule.Tests/PSRule.Tests.csproj @@ -13,7 +13,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj b/tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj index 380e2a876c..16b83a337a 100644 --- a/tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj +++ b/tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj @@ -11,7 +11,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive From c1feb7a2ceb316d1479c5eb8140c9b1f1a48869c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 28 Nov 2023 03:24:50 +1000 Subject: [PATCH 127/177] Bump mkdocs-material from 9.4.12 to 9.4.14 (#1683) Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.4.12 to 9.4.14. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.4.12...9.4.14) --- updated-dependencies: - dependency-name: mkdocs-material dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements-docs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-docs.txt b/requirements-docs.txt index ea2a74d61f..bb19d64eb2 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -1,5 +1,5 @@ mkdocs==1.5.3 -mkdocs-material==9.4.12 +mkdocs-material==9.4.14 pymdown-extensions==10.4 mike==2.0.0 mkdocs-simple-hooks==0.1.5 From fe2a59ad27575bd15641d77eb07e2911f8ac11c8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 28 Nov 2023 03:44:32 +1000 Subject: [PATCH 128/177] Bump Microsoft.NET.Test.Sdk from 17.7.2 to 17.8.0 (#1659) * Bump Microsoft.NET.Test.Sdk from 17.7.2 to 17.8.0 Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 17.7.2 to 17.8.0. - [Release notes](https://github.com/microsoft/vstest/releases) - [Changelog](https://github.com/microsoft/vstest/blob/main/docs/releases.md) - [Commits](https://github.com/microsoft/vstest/compare/v17.7.2...v17.8.0) --- updated-dependencies: - dependency-name: Microsoft.NET.Test.Sdk dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * Bump change log --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Bernie White --- docs/CHANGELOG-v3.md | 2 ++ tests/PSRule.Tests/PSRule.Tests.csproj | 2 +- tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/CHANGELOG-v3.md b/docs/CHANGELOG-v3.md index 29653ddea6..bbe9f23228 100644 --- a/docs/CHANGELOG-v3.md +++ b/docs/CHANGELOG-v3.md @@ -32,6 +32,8 @@ What's changed since pre-release v3.0.0-B0093: - Engineering: - Bump xunit to v2.6.2. [#1679](https://github.com/microsoft/PSRule/pull/1679) + - Bump Microsoft.NET.Test.Sdk to v17.8.0. + [#1659](https://github.com/microsoft/PSRule/pull/1659) ## v3.0.0-B0093 (pre-release) diff --git a/tests/PSRule.Tests/PSRule.Tests.csproj b/tests/PSRule.Tests/PSRule.Tests.csproj index f9054cd33c..c714273222 100644 --- a/tests/PSRule.Tests/PSRule.Tests.csproj +++ b/tests/PSRule.Tests/PSRule.Tests.csproj @@ -10,7 +10,7 @@ - + diff --git a/tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj b/tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj index 16b83a337a..bc17f2a9e3 100644 --- a/tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj +++ b/tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj @@ -10,7 +10,7 @@ - + all From 9329d287e5dd769266ecfbac2dc3070d974f8ef3 Mon Sep 17 00:00:00 2001 From: Bernie White Date: Tue, 28 Nov 2023 03:57:39 +1000 Subject: [PATCH 129/177] Improvements to options doco (#1685) --- docs/CHANGELOG-v2.md | 1 + .../PSRule/en-US/about_PSRule_Options.md | 24 +++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/docs/CHANGELOG-v2.md b/docs/CHANGELOG-v2.md index 70cff2ac4b..e0d6bbc51f 100644 --- a/docs/CHANGELOG-v2.md +++ b/docs/CHANGELOG-v2.md @@ -180,6 +180,7 @@ What's changed since release v2.7.0: - Deprecated `SuppressedRuleWarning` option, which will be removed in v3. - Added support for logging excluded rules by @BernieWhite. [#1432](https://github.com/microsoft/PSRule/issues/1432) + - Configure `Execution.RuleExcluded` to control output level of excluded rules as `Ignore`, `Warn`, `Error`, or `Debug`. - Added additional options to schema for PSRule for Azure by @BernieWhite. [#1446](https://github.com/microsoft/PSRule/issues/1446) - Improved error message for failing to read options file by @BernieWhite. diff --git a/docs/concepts/PSRule/en-US/about_PSRule_Options.md b/docs/concepts/PSRule/en-US/about_PSRule_Options.md index 94ce86b931..52896e2c65 100644 --- a/docs/concepts/PSRule/en-US/about_PSRule_Options.md +++ b/docs/concepts/PSRule/en-US/about_PSRule_Options.md @@ -732,6 +732,8 @@ variables: ### Execution.AliasReference +:octicons-milestone-24: v2.9.0 + Determines how to handle when an alias to a resource is used. By defaut, a warning is generated, however this behaviour can be modified by this option. @@ -786,6 +788,8 @@ variables: ### Execution.DuplicateResourceId +:octicons-milestone-24: v2.4.0 + Determines how to handle duplicate resources identifiers during execution. A duplicate resource identifier may exist if two resources are defined with the same name, ref, or alias. By defaut, an error is thrown, however this behaviour can be modified by this option. @@ -844,6 +848,8 @@ variables: ### Execution.HashAlgorithm +:octicons-milestone-24: v3.0.0 + Specifies the hashing algorithm used by the PSRule runtime. This hash algorithm is used when generating a resource identifier for an object that does not have a bound name. @@ -931,6 +937,8 @@ variables: ### Execution.InvariantCulture +:octicons-milestone-24: v2.9.0 + Determines how to report when an invariant culture is used. By defaut, a warning is generated, however this behaviour can be modified by this option. @@ -985,6 +993,8 @@ variables: ### Execution.InitialSessionState +:octicons-milestone-24: v2.5.0 + Determines how the initial session state for executing PowerShell code is created. The following preferences are available: @@ -1034,6 +1044,8 @@ variables: ### Execution.RuleInconclusive +:octicons-milestone-24: v2.9.0 + Determines how to handle rules that generate inconclusive results. By defaut, a warning is generated, however this behaviour can be modified by this option. @@ -1088,6 +1100,8 @@ variables: ### Execution.SuppressionGroupExpired +:octicons-milestone-24: v2.6.0 + Determines how to handle expired suppression groups. Regardless of the value, an expired suppression group will be ignored. By defaut, a warning is generated, however this behaviour can be modified by this option. @@ -1143,6 +1157,8 @@ variables: ### Execution.RuleExcluded +:octicons-milestone-24: v2.8.0 + Determines how to handle excluded rules. Regardless of the value, excluded rules are ignored. By defaut, a rule is excluded silently, however this behaviour can be modified by this option. @@ -1198,6 +1214,8 @@ variables: ### Execution.RuleSuppressed +:octicons-milestone-24: v2.8.0 + Determines how to handle suppressed rules. Regardless of the value, a suppressed rule is ignored. By defaut, a warning is generated, however this behaviour can be modified by this option. @@ -1253,6 +1271,8 @@ variables: ### Execution.UnprocessedObject +:octicons-milestone-24: v2.9.0 + Determines how to report objects that are not processed by any rule. By defaut, a warning is generated, however this behaviour can be modified by this option. @@ -1669,6 +1689,8 @@ variables: ### Input.IgnoreUnchangedPath +:octicons-milestone-24: v2.5.0 + By default, PSRule will process all files within an input path. For large repositories, this can result in a large number of files being processed. Additionally, for a pull request you may only be interested in files that have changed. @@ -2677,6 +2699,8 @@ variables: ### Output.JobSummaryPath +:octicons-milestone-24: v2.6.0 + Configures the file path a job summary will be written to when using `Assert-PSRule`. A job summary is a markdown file that summarizes the results of a job. When not specified, a job summary will not be generated. From 33f4f3cd22b47ea2d650fdc3df009f81df6598f7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Dec 2023 14:49:24 +1000 Subject: [PATCH 130/177] Bump xunit.runner.visualstudio from 2.5.3 to 2.5.4 (#1684) * Bump xunit.runner.visualstudio from 2.5.3 to 2.5.4 Bumps [xunit.runner.visualstudio](https://github.com/xunit/visualstudio.xunit) from 2.5.3 to 2.5.4. - [Release notes](https://github.com/xunit/visualstudio.xunit/releases) - [Commits](https://github.com/xunit/visualstudio.xunit/compare/2.5.3...2.5.4) --- updated-dependencies: - dependency-name: xunit.runner.visualstudio dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * Bump change log --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Bernie White --- docs/CHANGELOG-v3.md | 2 ++ tests/PSRule.Tests/PSRule.Tests.csproj | 2 +- tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/CHANGELOG-v3.md b/docs/CHANGELOG-v3.md index bbe9f23228..6d57a95569 100644 --- a/docs/CHANGELOG-v3.md +++ b/docs/CHANGELOG-v3.md @@ -32,6 +32,8 @@ What's changed since pre-release v3.0.0-B0093: - Engineering: - Bump xunit to v2.6.2. [#1679](https://github.com/microsoft/PSRule/pull/1679) + - Bump xunit.runner.visualstudio to v2.5.4. + [#1684](https://github.com/microsoft/PSRule/pull/1684) - Bump Microsoft.NET.Test.Sdk to v17.8.0. [#1659](https://github.com/microsoft/PSRule/pull/1659) diff --git a/tests/PSRule.Tests/PSRule.Tests.csproj b/tests/PSRule.Tests/PSRule.Tests.csproj index c714273222..65da853860 100644 --- a/tests/PSRule.Tests/PSRule.Tests.csproj +++ b/tests/PSRule.Tests/PSRule.Tests.csproj @@ -14,7 +14,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj b/tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj index bc17f2a9e3..81d4cdf190 100644 --- a/tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj +++ b/tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj @@ -12,7 +12,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive From c5fd71118cafcd0085de1f3857aaf37f105d9cc5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Dec 2023 15:05:47 +1000 Subject: [PATCH 131/177] Bump Microsoft.CodeAnalysis.NetAnalyzers from 7.0.4 to 8.0.0 (#1674) * Bump Microsoft.CodeAnalysis.NetAnalyzers from 7.0.4 to 8.0.0 Bumps [Microsoft.CodeAnalysis.NetAnalyzers](https://github.com/dotnet/roslyn-analyzers) from 7.0.4 to 8.0.0. - [Release notes](https://github.com/dotnet/roslyn-analyzers/releases) - [Changelog](https://github.com/dotnet/roslyn-analyzers/blob/main/PostReleaseActivities.md) - [Commits](https://github.com/dotnet/roslyn-analyzers/commits) --- updated-dependencies: - dependency-name: Microsoft.CodeAnalysis.NetAnalyzers dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] * Bump change log --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Bernie White --- docs/CHANGELOG-v3.md | 2 ++ src/PSRule.Tool/PSRule.Tool.csproj | 2 +- src/PSRule.Types/PSRule.Types.csproj | 2 +- src/PSRule/PSRule.csproj | 2 +- 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/CHANGELOG-v3.md b/docs/CHANGELOG-v3.md index 6d57a95569..846e22bad9 100644 --- a/docs/CHANGELOG-v3.md +++ b/docs/CHANGELOG-v3.md @@ -36,6 +36,8 @@ What's changed since pre-release v3.0.0-B0093: [#1684](https://github.com/microsoft/PSRule/pull/1684) - Bump Microsoft.NET.Test.Sdk to v17.8.0. [#1659](https://github.com/microsoft/PSRule/pull/1659) + - Bump Microsoft.CodeAnalysis.NetAnalyzers to v8.0.0. + [#1674](https://github.com/microsoft/PSRule/pull/1674) ## v3.0.0-B0093 (pre-release) diff --git a/src/PSRule.Tool/PSRule.Tool.csproj b/src/PSRule.Tool/PSRule.Tool.csproj index 10b7a6dd3e..38308aa267 100644 --- a/src/PSRule.Tool/PSRule.Tool.csproj +++ b/src/PSRule.Tool/PSRule.Tool.csproj @@ -19,7 +19,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/PSRule.Types/PSRule.Types.csproj b/src/PSRule.Types/PSRule.Types.csproj index c0a3eea6c8..7767750dfa 100644 --- a/src/PSRule.Types/PSRule.Types.csproj +++ b/src/PSRule.Types/PSRule.Types.csproj @@ -12,7 +12,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/PSRule/PSRule.csproj b/src/PSRule/PSRule.csproj index e7661fd2b0..a0f69f269f 100644 --- a/src/PSRule/PSRule.csproj +++ b/src/PSRule/PSRule.csproj @@ -13,7 +13,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive From 4367843f24dd1ea1a8614cf9d388c56fef5abcf0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Dec 2023 15:29:37 +1000 Subject: [PATCH 132/177] Bump pymdown-extensions from 10.4 to 10.5 (#1682) Bumps [pymdown-extensions](https://github.com/facelessuser/pymdown-extensions) from 10.4 to 10.5. - [Release notes](https://github.com/facelessuser/pymdown-extensions/releases) - [Commits](https://github.com/facelessuser/pymdown-extensions/compare/10.4...10.5) --- updated-dependencies: - dependency-name: pymdown-extensions dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements-docs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-docs.txt b/requirements-docs.txt index bb19d64eb2..f37c0c6769 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -1,6 +1,6 @@ mkdocs==1.5.3 mkdocs-material==9.4.14 -pymdown-extensions==10.4 +pymdown-extensions==10.5 mike==2.0.0 mkdocs-simple-hooks==0.1.5 mkdocs-git-revision-date-plugin==0.3.2 From 4d280ef2f33706718cb6137101d7560b3f5ee26f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 6 Dec 2023 18:09:05 +1000 Subject: [PATCH 133/177] Bump actions/setup-dotnet from 3 to 4 (#1690) Bumps [actions/setup-dotnet](https://github.com/actions/setup-dotnet) from 3 to 4. - [Release notes](https://github.com/actions/setup-dotnet/releases) - [Commits](https://github.com/actions/setup-dotnet/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/setup-dotnet dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/build.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 5925621d94..3243655742 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -31,7 +31,7 @@ jobs: uses: actions/checkout@v4 - name: Setup .NET - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: dotnet-version: 7.x @@ -108,7 +108,7 @@ jobs: uses: actions/checkout@v4 - name: Setup .NET - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: dotnet-version: 7.x From 3b6ea658c67238d29afc40d99f0f66cc69099759 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 7 Dec 2023 01:04:14 +1000 Subject: [PATCH 134/177] Bump actions/setup-python from 4 to 5 (#1692) Bumps [actions/setup-python](https://github.com/actions/setup-python) from 4 to 5. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/setup-python dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/docs.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index 6ecb94ac99..faafaa66b0 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -32,7 +32,7 @@ jobs: git config user.email '41898282+github-actions[bot]@users.noreply.github.com' - name: Setup Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.11' architecture: 'x64' From 279e30270fc2c42c2f5f3c07521bc24c83970cd3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 7 Dec 2023 15:58:20 +1000 Subject: [PATCH 135/177] Bump actions/deploy-pages from 2 to 3 (#1689) Bumps [actions/deploy-pages](https://github.com/actions/deploy-pages) from 2 to 3. - [Release notes](https://github.com/actions/deploy-pages/releases) - [Commits](https://github.com/actions/deploy-pages/compare/v2...v3) --- updated-dependencies: - dependency-name: actions/deploy-pages dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/docs.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index faafaa66b0..02f1570cfb 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -76,4 +76,4 @@ jobs: - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v2 + uses: actions/deploy-pages@v3 From 83c9ff1662b675f459ec3e73199e31149f9fb1b2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 7 Dec 2023 16:20:03 +1000 Subject: [PATCH 136/177] Bump actions/configure-pages from 3 to 4 (#1688) Bumps [actions/configure-pages](https://github.com/actions/configure-pages) from 3 to 4. - [Release notes](https://github.com/actions/configure-pages/releases) - [Commits](https://github.com/actions/configure-pages/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/configure-pages dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/docs.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index 02f1570cfb..bdd176a92e 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -67,7 +67,7 @@ jobs: ref: refs/heads/gh-pages - name: Setup Pages - uses: actions/configure-pages@v3 + uses: actions/configure-pages@v4 - name: Upload artifact uses: actions/upload-pages-artifact@v2 From 939350bfc1f546b42a6a96fdeff39c24d927f86d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 8 Dec 2023 07:35:19 +1000 Subject: [PATCH 137/177] Bump mkdocs-material from 9.4.14 to 9.5.0 (#1695) Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.4.14 to 9.5.0. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.4.14...9.5.0) --- updated-dependencies: - dependency-name: mkdocs-material dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements-docs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-docs.txt b/requirements-docs.txt index f37c0c6769..6c7a7ee8c2 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -1,5 +1,5 @@ mkdocs==1.5.3 -mkdocs-material==9.4.14 +mkdocs-material==9.5.0 pymdown-extensions==10.5 mike==2.0.0 mkdocs-simple-hooks==0.1.5 From 20bd04d3bbc472ff05a999bb3595624993f554ec Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 8 Dec 2023 11:29:38 +1000 Subject: [PATCH 138/177] Bump Microsoft.CodeAnalysis.Common from 4.7.0 to 4.8.0 (#1686) * Bump Microsoft.CodeAnalysis.Common from 4.7.0 to 4.8.0 Bumps [Microsoft.CodeAnalysis.Common](https://github.com/dotnet/roslyn) from 4.7.0 to 4.8.0. - [Release notes](https://github.com/dotnet/roslyn/releases) - [Changelog](https://github.com/dotnet/roslyn/blob/main/docs/Breaking%20API%20Changes.md) - [Commits](https://github.com/dotnet/roslyn/commits) --- updated-dependencies: - dependency-name: Microsoft.CodeAnalysis.Common dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * Bump change log --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Bernie White --- docs/CHANGELOG-v3.md | 2 ++ src/PSRule.BuildTask/PSRule.BuildTask.csproj | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/CHANGELOG-v3.md b/docs/CHANGELOG-v3.md index 846e22bad9..18e7d169ba 100644 --- a/docs/CHANGELOG-v3.md +++ b/docs/CHANGELOG-v3.md @@ -38,6 +38,8 @@ What's changed since pre-release v3.0.0-B0093: [#1659](https://github.com/microsoft/PSRule/pull/1659) - Bump Microsoft.CodeAnalysis.NetAnalyzers to v8.0.0. [#1674](https://github.com/microsoft/PSRule/pull/1674) + - Bump Microsoft.CodeAnalysis.Common to v4.8.0. + [#1686](https://github.com/microsoft/PSRule/pull/1686) ## v3.0.0-B0093 (pre-release) diff --git a/src/PSRule.BuildTask/PSRule.BuildTask.csproj b/src/PSRule.BuildTask/PSRule.BuildTask.csproj index d2209315e8..81d4af6921 100644 --- a/src/PSRule.BuildTask/PSRule.BuildTask.csproj +++ b/src/PSRule.BuildTask/PSRule.BuildTask.csproj @@ -8,7 +8,7 @@ - + diff --git a/src/PSRule.SDK/packages.lock.json b/src/PSRule.SDK/packages.lock.json new file mode 100644 index 0000000000..72daeae1cb --- /dev/null +++ b/src/PSRule.SDK/packages.lock.json @@ -0,0 +1,705 @@ +{ + "version": 1, + "dependencies": { + ".NETStandard,Version=v2.0": { + "Microsoft.SourceLink.GitHub": { + "type": "Direct", + "requested": "[8.0.0, )", + "resolved": "8.0.0", + "contentHash": "G5q7OqtwIyGTkeIOAc3u2ZuV/kicQaec5EaRnc0pIeSnh9LUjj+PYQrJYBURvDt7twGl2PKA7nSN0kz1Zw5bnQ==", + "dependencies": { + "Microsoft.Build.Tasks.Git": "8.0.0", + "Microsoft.SourceLink.Common": "8.0.0" + } + }, + "NETStandard.Library": { + "type": "Direct", + "requested": "[2.0.3, )", + "resolved": "2.0.3", + "contentHash": "st47PosZSHrjECdjeIzZQbzivYBJFv6P2nv4cj2ypdI204DO+vZ7l5raGMiX4eXMJ53RfOIg+/s4DHVZ54Nu2A==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0" + } + }, + "Manatee.Json": { + "type": "Transitive", + "resolved": "13.0.5", + "contentHash": "xCWPlnZbfXrOW28r9Cnw2U+kh1KUL5AkM9raGk/sO5nzIGkwAC04gCRI0S4Gqv63Sm2lPmhLvancxPzT6QE+Mw==", + "dependencies": { + "System.ComponentModel.Annotations": "4.4.0", + "System.Net.Http": "4.3.3", + "System.Reflection.Emit": "4.3.0", + "System.ValueTuple": "4.5.0" + } + }, + "Microsoft.Build.Tasks.Git": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ==" + }, + "Microsoft.NETCore.Platforms": { + "type": "Transitive", + "resolved": "1.1.0", + "contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==" + }, + "Microsoft.NETCore.Targets": { + "type": "Transitive", + "resolved": "1.1.0", + "contentHash": "aOZA3BWfz9RXjpzt0sRJJMjAscAUm3Hoa4UWAfceV9UTYxgwZ1lZt5nO2myFf+/jetYQo4uTP7zS8sJY67BBxg==" + }, + "Microsoft.SourceLink.Common": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw==" + }, + "Newtonsoft.Json": { + "type": "Transitive", + "resolved": "13.0.3", + "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==" + }, + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.2", + "contentHash": "7VSGO0URRKoMEAq0Sc9cRz8mb6zbyx/BZDEWhgPdzzpmFhkam3fJ1DAGWFXBI4nGlma+uPKpfuMQP5LXRnOH5g==" + }, + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.2", + "contentHash": "0oAaTAm6e2oVH+/Zttt0cuhGaePQYKII1dY8iaqP7CvOpVKgLybKRFvQjXR2LtxXOXTVPNv14j0ot8uV+HrUmw==" + }, + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.2", + "contentHash": "G24ibsCNi5Kbz0oXWynBoRgtGvsw5ZSVEWjv13/KiCAM8C6wz9zzcCniMeQFIkJ2tasjo2kXlvlBZhplL51kGg==" + }, + "runtime.native.System": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "c/qWt2LieNZIj1jGnVNsE2Kl23Ya2aSTBuXMD6V7k9KWr6l16Tqdwq+hJScEpWER9753NWC8h96PaVNY5Ld7Jw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0" + } + }, + "runtime.native.System.Net.Http": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "ZVuZJqnnegJhd2k/PtAbbIcZ3aZeITq3sj06oKfMBSfphW3HDmk/t4ObvbOk/JA/swGR0LNqMksAh/f7gpTROg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0" + } + }, + "runtime.native.System.Security.Cryptography.Apple": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "DloMk88juo0OuOWr56QG7MNchmafTLYWvABy36izkrLI5VledI0rq28KGs1i9wbpeT9NPQrx/wTf8U2vazqQ3Q==", + "dependencies": { + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "4.3.0" + } + }, + "runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.2", + "contentHash": "QR1OwtwehHxSeQvZKXe+iSd+d3XZNkEcuWMFYa2i0aG1l+lR739HPicKMlTbJst3spmeekDVBUS7SeS26s4U/g==", + "dependencies": { + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.2", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.2", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.2", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.2", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.2", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.2", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.2", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.2", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.2", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.2" + } + }, + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.2", + "contentHash": "I+GNKGg2xCHueRd1m9PzeEW7WLbNNLznmTuEi8/vZX71HudUbx1UTwlGkiwMri7JLl8hGaIAWnA/GONhu+LOyQ==" + }, + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.2", + "contentHash": "1Z3TAq1ytS1IBRtPXJvEUZdVsfWfeNEhBkbiOCGEl9wwAfsjP2lz3ZFDx5tq8p60/EqbS0HItG5piHuB71RjoA==" + }, + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "kVXCuMTrTlxq4XOOMAysuNwsXWpYeboGddNGpIgNSZmv1b6r/s/DPk0fYMB7Q5Qo4bY68o48jt4T4y5BVecbCQ==" + }, + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.2", + "contentHash": "6mU/cVmmHtQiDXhnzUImxIcDL48GbTk+TsptXyJA+MIOG9LRjPoAQC/qBFB7X+UNyK86bmvGwC8t+M66wsYC8w==" + }, + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.2", + "contentHash": "vjwG0GGcTW/PPg6KVud8F9GLWYuAV1rrw1BKAqY0oh4jcUqg15oYF1+qkGR2x2ZHM4DQnWKQ7cJgYbfncz/lYg==" + }, + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.2", + "contentHash": "7KMFpTkHC/zoExs+PwP8jDCWcrK9H6L7soowT80CUx3e+nxP/AFnq0AQAW5W76z2WYbLAYCRyPfwYFG6zkvQRw==" + }, + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.2", + "contentHash": "xrlmRCnKZJLHxyyLIqkZjNXqgxnKdZxfItrPkjI+6pkRo5lHX8YvSZlWrSI5AVwLMi4HbNWP7064hcAWeZKp5w==" + }, + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.2", + "contentHash": "leXiwfiIkW7Gmn7cgnNcdtNAU70SjmKW3jxGj1iKHOvdn0zRWsgv/l2OJUO5zdGdiv2VRFnAsxxhDgMzofPdWg==" + }, + "Sarif.Sdk": { + "type": "Transitive", + "resolved": "2.4.16", + "contentHash": "ikJcKaMnwEvlqmxD3rtMn4ksYr8tvWdid7uzq4JFPcASwz21ErE1wF2CHbY/orlxt+E7uhZ4zBblgk5rdflfpg==", + "dependencies": { + "Newtonsoft.Json": "13.0.1", + "System.Collections.Immutable": "5.0.0" + } + }, + "System.Buffers": { + "type": "Transitive", + "resolved": "4.5.1", + "contentHash": "Rw7ijyl1qqRS0YQD/WycNst8hUUMgrMH4FCn1nNm27M4VxchZ1js3fVjQaANHO5f3sN4isvP4a+Met9Y4YomAg==" + }, + "System.Collections": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "3Dcj85/TBdVpL5Zr+gEEBUuFe2icOnLalmEh9hfck1PTYbbyWuZgh4fmm2ysCLTrqLQw6t3TgTyJ+VLp+Qb+Lw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Collections.Concurrent": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "ztl69Xp0Y/UXCL+3v3tEU+lIy+bvjKNUmopn1wep/a291pVPK7dxBd6T7WnlQqRog+d1a/hSsgRsmFnIBKTPLQ==", + "dependencies": { + "System.Collections": "4.3.0", + "System.Diagnostics.Debug": "4.3.0", + "System.Diagnostics.Tracing": "4.3.0", + "System.Globalization": "4.3.0", + "System.Reflection": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Threading": "4.3.0", + "System.Threading.Tasks": "4.3.0" + } + }, + "System.Collections.Immutable": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "FXkLXiK0sVVewcso0imKQoOxjoPAj42R8HtjjbSjVPAzwDfzoyoznWxgA3c38LDbN9SJux1xXoXYAhz98j7r2g==", + "dependencies": { + "System.Memory": "4.5.4" + } + }, + "System.ComponentModel.Annotations": { + "type": "Transitive", + "resolved": "4.4.0", + "contentHash": "29K3DQ+IGU7LBaMjTo7SI7T7X/tsMtLvz1p56LJ556Iu0Dw3pKZw5g8yCYCWMRxrOF0Hr0FU0FwW0o42y2sb3A==" + }, + "System.Diagnostics.Debug": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "ZUhUOdqmaG5Jk3Xdb8xi5kIyQYAA4PnTNlHx1mu9ZY3qv4ELIdKbnL/akbGaKi2RnNUWaZsAs31rvzFdewTj2g==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Diagnostics.DiagnosticSource": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "tD6kosZnTAGdrEa0tZSuFyunMbt/5KYDnHdndJYGqZoNy00XVXyACd5d6KnE1YgYv3ne2CjtAfNXo/fwEhnKUA==", + "dependencies": { + "System.Collections": "4.3.0", + "System.Diagnostics.Tracing": "4.3.0", + "System.Reflection": "4.3.0", + "System.Runtime": "4.3.0", + "System.Threading": "4.3.0" + } + }, + "System.Diagnostics.Tracing": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "rswfv0f/Cqkh78rA5S8eN8Neocz234+emGCtTF3lxPY96F+mmmUen6tbn0glN6PMvlKQb9bPAY5e9u7fgPTkKw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Globalization": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "kYdVd2f2PAdFGblzFswE4hkNANJBKRmsfa2X5LG2AcWE1c7/4t0pYae1L8vfZ5xvE2nK/R9JprtToA61OSHWIg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Globalization.Calendars": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "GUlBtdOWT4LTV3I+9/PJW+56AnnChTaOqqTLFtdmype/L500M2LIyXgmtd9X2P2VOkmJd5c67H5SaC2QcL1bFA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Globalization": "4.3.0", + "System.Runtime": "4.3.0" + } + }, + "System.Globalization.Extensions": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "FhKmdR6MPG+pxow6wGtNAWdZh7noIOpdD5TwQ3CprzgIE1bBBoim0vbR1+AWsWjQmU7zXHgQo4TWSP6lCeiWcQ==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "System.Globalization": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.InteropServices": "4.3.0" + } + }, + "System.IO": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "3qjaHvxQPDpSOYICjUoTsmoq5u6QJAFRUITgeT/4gqkF1bajbSmb1kwSxEA8AHlofqgcKJcM8udgieRNhaJ5Cg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0", + "System.Text.Encoding": "4.3.0", + "System.Threading.Tasks": "4.3.0" + } + }, + "System.IO.FileSystem": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "3wEMARTnuio+ulnvi+hkRNROYwa1kylvYahhcLk4HSoVdl+xxTFVeVlYOfLwrDPImGls0mDqbMhrza8qnWPTdA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.IO": "4.3.0", + "System.IO.FileSystem.Primitives": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Text.Encoding": "4.3.0", + "System.Threading.Tasks": "4.3.0" + } + }, + "System.IO.FileSystem.Primitives": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "6QOb2XFLch7bEc4lIcJH49nJN2HV+OC3fHDgsLVsBVBk3Y4hFAnOBGzJ2lUu7CyDDFo9IBWkSsnbkT6IBwwiMw==", + "dependencies": { + "System.Runtime": "4.3.0" + } + }, + "System.Linq": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "5DbqIUpsDp0dFftytzuMmc0oeMdQwjcP/EWxsksIz/w1TcFRkZ3yKKz0PqiYFMmEwPSWw+qNVqD7PJ889JzHbw==", + "dependencies": { + "System.Collections": "4.3.0", + "System.Diagnostics.Debug": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0" + } + }, + "System.Memory": { + "type": "Transitive", + "resolved": "4.5.4", + "contentHash": "1MbJTHS1lZ4bS4FmsJjnuGJOu88ZzTT2rLvrhW7Ygic+pC0NWA+3hgAen0HRdsocuQXCkUTdFn9yHJJhsijDXw==", + "dependencies": { + "System.Buffers": "4.5.1", + "System.Numerics.Vectors": "4.4.0", + "System.Runtime.CompilerServices.Unsafe": "4.5.3" + } + }, + "System.Net.Http": { + "type": "Transitive", + "resolved": "4.3.3", + "contentHash": "7rCqIbkC/P2+A00NoDH5gnvFhADmX7Dc4INvsOajbU1MVhktE9vZNrjPtF82N6Uo7obK+yzlrPUv/M+snnN/9w==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "System.Collections": "4.3.0", + "System.Diagnostics.Debug": "4.3.0", + "System.Diagnostics.DiagnosticSource": "4.3.0", + "System.Diagnostics.Tracing": "4.3.0", + "System.Globalization": "4.3.0", + "System.Globalization.Extensions": "4.3.0", + "System.IO": "4.3.0", + "System.IO.FileSystem": "4.3.0", + "System.Net.Primitives": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Security.Cryptography.Algorithms": "4.3.0", + "System.Security.Cryptography.Encoding": "4.3.0", + "System.Security.Cryptography.OpenSsl": "4.3.0", + "System.Security.Cryptography.Primitives": "4.3.0", + "System.Security.Cryptography.X509Certificates": "4.3.0", + "System.Text.Encoding": "4.3.0", + "System.Threading": "4.3.0", + "System.Threading.Tasks": "4.3.0", + "runtime.native.System": "4.3.0", + "runtime.native.System.Net.Http": "4.3.0", + "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.2" + } + }, + "System.Net.Primitives": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "qOu+hDwFwoZPbzPvwut2qATe3ygjeQBDQj91xlsaqGFQUI5i4ZnZb8yyQuLGpDGivEPIt8EJkd1BVzVoP31FXA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0", + "System.Runtime.Handles": "4.3.0" + } + }, + "System.Numerics.Vectors": { + "type": "Transitive", + "resolved": "4.4.0", + "contentHash": "UiLzLW+Lw6HLed1Hcg+8jSRttrbuXv7DANVj0DkL9g6EnnzbL75EB7EWsw5uRbhxd/4YdG8li5XizGWepmG3PQ==" + }, + "System.Reflection": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "KMiAFoW7MfJGa9nDFNcfu+FpEdiHpWgTcS2HdMpDvt9saK3y/G4GwprPyzqjFH9NTaGPQeWNHU+iDlDILj96aQ==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.IO": "4.3.0", + "System.Reflection.Primitives": "4.3.0", + "System.Runtime": "4.3.0" + } + }, + "System.Reflection.Emit": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "228FG0jLcIwTVJyz8CLFKueVqQK36ANazUManGaJHkO0icjiIypKW7YLWLIWahyIkdh5M7mV2dJepllLyA1SKg==", + "dependencies": { + "System.IO": "4.3.0", + "System.Reflection": "4.3.0", + "System.Reflection.Emit.ILGeneration": "4.3.0", + "System.Reflection.Primitives": "4.3.0", + "System.Runtime": "4.3.0" + } + }, + "System.Reflection.Emit.ILGeneration": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "59tBslAk9733NXLrUJrwNZEzbMAcu8k344OYo+wfSVygcgZ9lgBdGIzH/nrg3LYhXceynyvTc8t5/GD4Ri0/ng==", + "dependencies": { + "System.Reflection": "4.3.0", + "System.Reflection.Primitives": "4.3.0", + "System.Runtime": "4.3.0" + } + }, + "System.Reflection.Primitives": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "5RXItQz5As4xN2/YUDxdpsEkMhvw3e6aNveFXUn4Hl/udNTCNhnKp8lT9fnc3MhvGKh1baak5CovpuQUXHAlIA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Resources.ResourceManager": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "/zrcPkkWdZmI4F92gL/TPumP98AVDu/Wxr3CSJGQQ+XN6wbRZcyfSKVoPo17ilb3iOr0cCRqJInGwNMolqhS8A==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Globalization": "4.3.0", + "System.Reflection": "4.3.0", + "System.Runtime": "4.3.0" + } + }, + "System.Runtime": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "JufQi0vPQ0xGnAczR13AUFglDyVYt4Kqnz1AZaiKZ5+GICq0/1MH/mO/eAJHt/mHW1zjKBJd7kV26SrxddAhiw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0" + } + }, + "System.Runtime.CompilerServices.Unsafe": { + "type": "Transitive", + "resolved": "4.5.3", + "contentHash": "3TIsJhD1EiiT0w2CcDMN/iSSwnNnsrnbzeVHSKkaEgV85txMprmuO+Yq2AdSbeVGcg28pdNDTPK87tJhX7VFHw==" + }, + "System.Runtime.Extensions": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "guW0uK0fn5fcJJ1tJVXYd7/1h5F+pea1r7FLSOz/f8vPEqbR2ZAknuRDvTQ8PzAilDveOxNjSfr0CHfIQfFk8g==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Runtime.Handles": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "OKiSUN7DmTWeYb3l51A7EYaeNMnvxwE249YtZz7yooT4gOZhmTjIn48KgSsw2k2lYdLgTKNJw/ZIfSElwDRVgg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Runtime.InteropServices": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "uv1ynXqiMK8mp1GM3jDqPCFN66eJ5w5XNomaK2XD+TuCroNTLFGeZ+WCmBMcBDyTFKou3P6cR6J/QsaqDp7fGQ==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Reflection": "4.3.0", + "System.Reflection.Primitives": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Handles": "4.3.0" + } + }, + "System.Runtime.Numerics": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "yMH+MfdzHjy17l2KESnPiF2dwq7T+xLnSJar7slyimAkUh/gTrS9/UQOtv7xarskJ2/XDSNvfLGOBQPjL7PaHQ==", + "dependencies": { + "System.Globalization": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0" + } + }, + "System.Security.Cryptography.Algorithms": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "W1kd2Y8mYSCgc3ULTAZ0hOP2dSdG5YauTb1089T0/kRcN2MpSAW1izOFROrJgxSlMn3ArsgHXagigyi+ibhevg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "System.Collections": "4.3.0", + "System.IO": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Runtime.Numerics": "4.3.0", + "System.Security.Cryptography.Encoding": "4.3.0", + "System.Security.Cryptography.Primitives": "4.3.0", + "System.Text.Encoding": "4.3.0", + "runtime.native.System.Security.Cryptography.Apple": "4.3.0", + "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" + } + }, + "System.Security.Cryptography.Cng": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "03idZOqFlsKRL4W+LuCpJ6dBYDUWReug6lZjBa3uJWnk5sPCUXckocevTaUA8iT/MFSrY/2HXkOt753xQ/cf8g==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "System.IO": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Security.Cryptography.Algorithms": "4.3.0", + "System.Security.Cryptography.Encoding": "4.3.0", + "System.Security.Cryptography.Primitives": "4.3.0", + "System.Text.Encoding": "4.3.0" + } + }, + "System.Security.Cryptography.Csp": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "X4s/FCkEUnRGnwR3aSfVIkldBmtURMhmexALNTwpjklzxWU7yjMk7GHLKOZTNkgnWnE0q7+BCf9N2LVRWxewaA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "System.IO": "4.3.0", + "System.Reflection": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Security.Cryptography.Algorithms": "4.3.0", + "System.Security.Cryptography.Encoding": "4.3.0", + "System.Security.Cryptography.Primitives": "4.3.0", + "System.Text.Encoding": "4.3.0", + "System.Threading": "4.3.0" + } + }, + "System.Security.Cryptography.Encoding": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "1DEWjZZly9ae9C79vFwqaO5kaOlI5q+3/55ohmq/7dpDyDfc8lYe7YVxJUZ5MF/NtbkRjwFRo14yM4OEo9EmDw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "System.Collections": "4.3.0", + "System.Collections.Concurrent": "4.3.0", + "System.Linq": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Security.Cryptography.Primitives": "4.3.0", + "System.Text.Encoding": "4.3.0", + "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" + } + }, + "System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "h4CEgOgv5PKVF/HwaHzJRiVboL2THYCou97zpmhjghx5frc7fIvlkY1jL+lnIQyChrJDMNEXS6r7byGif8Cy4w==", + "dependencies": { + "System.Collections": "4.3.0", + "System.IO": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Runtime.Numerics": "4.3.0", + "System.Security.Cryptography.Algorithms": "4.3.0", + "System.Security.Cryptography.Encoding": "4.3.0", + "System.Security.Cryptography.Primitives": "4.3.0", + "System.Text.Encoding": "4.3.0", + "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" + } + }, + "System.Security.Cryptography.Primitives": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "7bDIyVFNL/xKeFHjhobUAQqSpJq9YTOpbEs6mR233Et01STBMXNAc/V+BM6dwYGc95gVh/Zf+iVXWzj3mE8DWg==", + "dependencies": { + "System.Diagnostics.Debug": "4.3.0", + "System.Globalization": "4.3.0", + "System.IO": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Threading": "4.3.0", + "System.Threading.Tasks": "4.3.0" + } + }, + "System.Security.Cryptography.X509Certificates": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "t2Tmu6Y2NtJ2um0RtcuhP7ZdNNxXEgUm2JeoA/0NvlMjAhKCnM1NX07TDl3244mVp3QU6LPEhT3HTtH1uF7IYw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "System.Collections": "4.3.0", + "System.Diagnostics.Debug": "4.3.0", + "System.Globalization": "4.3.0", + "System.Globalization.Calendars": "4.3.0", + "System.IO": "4.3.0", + "System.IO.FileSystem": "4.3.0", + "System.IO.FileSystem.Primitives": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Runtime.Numerics": "4.3.0", + "System.Security.Cryptography.Algorithms": "4.3.0", + "System.Security.Cryptography.Cng": "4.3.0", + "System.Security.Cryptography.Csp": "4.3.0", + "System.Security.Cryptography.Encoding": "4.3.0", + "System.Security.Cryptography.OpenSsl": "4.3.0", + "System.Security.Cryptography.Primitives": "4.3.0", + "System.Text.Encoding": "4.3.0", + "System.Threading": "4.3.0", + "runtime.native.System": "4.3.0", + "runtime.native.System.Net.Http": "4.3.0", + "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" + } + }, + "System.Text.Encoding": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "BiIg+KWaSDOITze6jGQynxg64naAPtqGHBwDrLaCtixsa5bKiR8dpPOHA7ge3C0JJQizJE+sfkz1wV+BAKAYZw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Threading": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "VkUS0kOBcUf3Wwm0TSbrevDDZ6BlM+b/HRiapRFWjM5O0NS0LviG0glKmFK+hhPDd1XFeSdU1GmlLhb2CoVpIw==", + "dependencies": { + "System.Runtime": "4.3.0", + "System.Threading.Tasks": "4.3.0" + } + }, + "System.Threading.Tasks": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "LbSxKEdOUhVe8BezB/9uOGGppt+nZf6e1VFyw6v3DN6lqitm0OSn2uXMOdtP0M3W4iMcqcivm2J6UgqiwwnXiA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.ValueTuple": { + "type": "Transitive", + "resolved": "4.5.0", + "contentHash": "okurQJO6NRE/apDIP23ajJ0hpiNmJ+f0BwOlB/cSqTLQlw5upkf+5+96+iG2Jw40G1fCVCyPz/FhIABUjMR+RQ==" + }, + "YamlDotNet": { + "type": "Transitive", + "resolved": "13.7.1", + "contentHash": "X4m1PnFcJwvAj1sCDMntg/eZcX96CJLrWMiYnq41KqhFVZPuw63ZTSxIGqgdCwHWHvCAyTxheELC/VDf1HsU2A==" + }, + "Microsoft.PSRule.Badges": { + "type": "Project", + "dependencies": { + "Newtonsoft.Json": "[13.0.3, )" + } + }, + "Microsoft.PSRule.Core": { + "type": "Project", + "dependencies": { + "Manatee.Json": "[13.0.5, )", + "Microsoft.PSRule.Badges": "[0.0.1, )", + "Microsoft.PSRule.Types": "[0.0.1, )", + "Sarif.Sdk": "[2.4.16, )" + } + }, + "Microsoft.PSRule.Types": { + "type": "Project", + "dependencies": { + "Newtonsoft.Json": "[13.0.3, )", + "YamlDotNet": "[13.7.1, )" + } + } + } + } +} \ No newline at end of file diff --git a/src/PSRule.Tool/AnalyzerOptions.cs b/src/PSRule.Tool/AnalyzerOptions.cs index 39a1303030..ac361ba237 100644 --- a/src/PSRule.Tool/AnalyzerOptions.cs +++ b/src/PSRule.Tool/AnalyzerOptions.cs @@ -3,24 +3,23 @@ using PSRule.Rules; -namespace PSRule.Tool +namespace PSRule.Tool; + +internal sealed class AnalyzerOptions { - internal sealed class AnalyzerOptions - { - public string[] Path { get; set; } + public string[] Path { get; set; } - public string[] Module { get; set; } + public string[] Module { get; set; } - public string Option { get; set; } + public string Option { get; set; } - public string Baseline { get; set; } + public string Baseline { get; set; } - public RuleOutcome? Outcome { get; set; } + public RuleOutcome? Outcome { get; set; } - public string[] InputPath { get; set; } + public string[] InputPath { get; set; } - public bool Verbose { get; set; } + public bool Verbose { get; set; } - public bool Debug { get; set; } - } + public bool Debug { get; set; } } diff --git a/src/PSRule.Tool/ClientBuilder.cs b/src/PSRule.Tool/ClientBuilder.cs index 13d2d44fe6..c97fcc8a31 100644 --- a/src/PSRule.Tool/ClientBuilder.cs +++ b/src/PSRule.Tool/ClientBuilder.cs @@ -6,256 +6,255 @@ using PSRule.Rules; using PSRule.Tool.Resources; -namespace PSRule.Tool +namespace PSRule.Tool; + +internal sealed class ClientBuilder { - internal sealed class ClientBuilder - { - private static readonly string _Version = (Assembly.GetEntryAssembly() ?? Assembly.GetExecutingAssembly()).GetCustomAttribute().InformationalVersion; + private static readonly string _Version = (Assembly.GetEntryAssembly() ?? Assembly.GetExecutingAssembly()).GetCustomAttribute().InformationalVersion; - private readonly Option _Option; - private readonly Option _Verbose; - private readonly Option _Debug; - private readonly Option _RestoreForce; - private readonly Option _ModuleAddVersion; - private readonly Option _ModuleAddForce; - private readonly Option _ModuleAddSkipVerification; - private readonly Option _Path; - private readonly Option _OutputPath; - private readonly Option _OutputFormat; - private readonly Option _InputPath; - private readonly Option _Module; - private readonly Option _Baseline; - private readonly Option _Outcome; + private readonly Option _Option; + private readonly Option _Verbose; + private readonly Option _Debug; + private readonly Option _RestoreForce; + private readonly Option _ModuleAddVersion; + private readonly Option _ModuleAddForce; + private readonly Option _ModuleAddSkipVerification; + private readonly Option _Path; + private readonly Option _OutputPath; + private readonly Option _OutputFormat; + private readonly Option _InputPath; + private readonly Option _Module; + private readonly Option _Baseline; + private readonly Option _Outcome; - private ClientBuilder(RootCommand cmd) - { - Command = cmd; - _Option = new Option( - new string[] { "--option" }, - getDefaultValue: () => "ps-rule.yaml", - description: CmdStrings.Options_Option_Description - ); - _Verbose = new Option( - new string[] { "--verbose" }, - CmdStrings.Options_Verbose_Description - ); - _Debug = new Option( - new string[] { "--debug" }, - CmdStrings.Options_Debug_Description - ); - _Path = new Option( - new string[] { "-p", "--path" }, - CmdStrings.Options_Path_Description - ); - _OutputPath = new Option( - new string[] { "--output-path" } - ); - _OutputFormat = new Option( - new string[] { "-o", "--output" } - ); - _InputPath = new Option( - new string[] { "-f", "--input-path" } - ); - _Module = new Option( - new string[] { "-m", "--module" }, - CmdStrings.Options_Module_Description - ); - _Baseline = new Option( - new string[] { "--baseline" }, - CmdStrings.Analyze_Baseline_Description - ); - _Outcome = new Option( - new string[] { "--outcome" }, - description: CmdStrings.Analyze_Outcome_Description - ).FromAmong("Pass", "Fail", "Error", "Processed", "Problem"); - _Outcome.Arity = ArgumentArity.ZeroOrMore; + private ClientBuilder(RootCommand cmd) + { + Command = cmd; + _Option = new Option( + new string[] { "--option" }, + getDefaultValue: () => "ps-rule.yaml", + description: CmdStrings.Options_Option_Description + ); + _Verbose = new Option( + new string[] { "--verbose" }, + CmdStrings.Options_Verbose_Description + ); + _Debug = new Option( + new string[] { "--debug" }, + CmdStrings.Options_Debug_Description + ); + _Path = new Option( + new string[] { "-p", "--path" }, + CmdStrings.Options_Path_Description + ); + _OutputPath = new Option( + new string[] { "--output-path" } + ); + _OutputFormat = new Option( + new string[] { "-o", "--output" } + ); + _InputPath = new Option( + new string[] { "-f", "--input-path" } + ); + _Module = new Option( + new string[] { "-m", "--module" }, + CmdStrings.Options_Module_Description + ); + _Baseline = new Option( + new string[] { "--baseline" }, + CmdStrings.Analyze_Baseline_Description + ); + _Outcome = new Option( + new string[] { "--outcome" }, + description: CmdStrings.Analyze_Outcome_Description + ).FromAmong("Pass", "Fail", "Error", "Processed", "Problem"); + _Outcome.Arity = ArgumentArity.ZeroOrMore; - _RestoreForce = new Option( - new string[] { "--force" }, - CmdStrings.Restore_Force_Description - ); - _ModuleAddVersion = new Option - ( - new string[] { "--version" }, - CmdStrings.Module_Add_Version_Description - ); - _ModuleAddForce = new Option( - new string[] { "--force" }, - CmdStrings.Module_Add_Force_Description - ); - _ModuleAddSkipVerification = new Option( - new string[] { "--skip-verification" }, - CmdStrings.Module_Add_SkipVerification_Description - ); + _RestoreForce = new Option( + new string[] { "--force" }, + CmdStrings.Restore_Force_Description + ); + _ModuleAddVersion = new Option + ( + new string[] { "--version" }, + CmdStrings.Module_Add_Version_Description + ); + _ModuleAddForce = new Option( + new string[] { "--force" }, + CmdStrings.Module_Add_Force_Description + ); + _ModuleAddSkipVerification = new Option( + new string[] { "--skip-verification" }, + CmdStrings.Module_Add_SkipVerification_Description + ); - cmd.AddGlobalOption(_Option); - cmd.AddGlobalOption(_Verbose); - cmd.AddGlobalOption(_Debug); - } + cmd.AddGlobalOption(_Option); + cmd.AddGlobalOption(_Verbose); + cmd.AddGlobalOption(_Debug); + } - public RootCommand Command { get; } + public RootCommand Command { get; } - public static Command New() + public static Command New() + { + var cmd = new RootCommand(string.Concat(CmdStrings.Cmd_Description, " v", _Version)) { - var cmd = new RootCommand(string.Concat(CmdStrings.Cmd_Description, " v", _Version)) - { - Name = "ps-rule" - }; - var builder = new ClientBuilder(cmd); - builder.AddAnalyze(); - builder.AddModule(); - builder.AddRestore(); - return builder.Command; - } + Name = "ps-rule" + }; + var builder = new ClientBuilder(cmd); + builder.AddAnalyze(); + builder.AddModule(); + builder.AddRestore(); + return builder.Command; + } - private void AddAnalyze() + private void AddAnalyze() + { + var cmd = new Command("analyze", CmdStrings.Analyze_Description); + cmd.AddOption(_Path); + cmd.AddOption(_OutputPath); + cmd.AddOption(_OutputFormat); + cmd.AddOption(_InputPath); + cmd.AddOption(_Module); + cmd.AddOption(_Baseline); + cmd.AddOption(_Outcome); + cmd.SetHandler((invocation) => { - var cmd = new Command("analyze", CmdStrings.Analyze_Description); - cmd.AddOption(_Path); - cmd.AddOption(_OutputPath); - cmd.AddOption(_OutputFormat); - cmd.AddOption(_InputPath); - cmd.AddOption(_Module); - cmd.AddOption(_Baseline); - cmd.AddOption(_Outcome); - cmd.SetHandler((invocation) => + var option = new AnalyzerOptions { - var option = new AnalyzerOptions - { - Path = invocation.ParseResult.GetValueForOption(_Path), - InputPath = invocation.ParseResult.GetValueForOption(_InputPath), - Module = invocation.ParseResult.GetValueForOption(_Module), - Option = invocation.ParseResult.GetValueForOption(_Option), - Baseline = invocation.ParseResult.GetValueForOption(_Baseline), - Outcome = ParseOutcome(invocation.ParseResult.GetValueForOption(_Outcome)), - Verbose = invocation.ParseResult.GetValueForOption(_Verbose), - Debug = invocation.ParseResult.GetValueForOption(_Debug), - }; - var client = new ClientContext(); - invocation.ExitCode = ClientHelper.RunAnalyze(option, client, invocation); - }); - Command.AddCommand(cmd); - } + Path = invocation.ParseResult.GetValueForOption(_Path), + InputPath = invocation.ParseResult.GetValueForOption(_InputPath), + Module = invocation.ParseResult.GetValueForOption(_Module), + Option = invocation.ParseResult.GetValueForOption(_Option), + Baseline = invocation.ParseResult.GetValueForOption(_Baseline), + Outcome = ParseOutcome(invocation.ParseResult.GetValueForOption(_Outcome)), + Verbose = invocation.ParseResult.GetValueForOption(_Verbose), + Debug = invocation.ParseResult.GetValueForOption(_Debug), + }; + var client = new ClientContext(); + invocation.ExitCode = ClientHelper.RunAnalyze(option, client, invocation); + }); + Command.AddCommand(cmd); + } - private void AddRestore() + private void AddRestore() + { + var cmd = new Command("restore", CmdStrings.Restore_Description); + cmd.AddOption(_Path); + cmd.AddOption(_RestoreForce); + cmd.SetHandler((invocation) => { - var cmd = new Command("restore", CmdStrings.Restore_Description); - cmd.AddOption(_Path); - cmd.AddOption(_RestoreForce); - cmd.SetHandler((invocation) => + var option = new RestoreOptions { - var option = new RestoreOptions - { - Path = invocation.ParseResult.GetValueForOption(_Path), - Option = invocation.ParseResult.GetValueForOption(_Option), - Verbose = invocation.ParseResult.GetValueForOption(_Verbose), - Debug = invocation.ParseResult.GetValueForOption(_Debug), - Force = invocation.ParseResult.GetValueForOption(_RestoreForce), - }; - var client = new ClientContext(); - invocation.ExitCode = ClientHelper.RunRestore(option, client, invocation); - }); - Command.AddCommand(cmd); - } + Path = invocation.ParseResult.GetValueForOption(_Path), + Option = invocation.ParseResult.GetValueForOption(_Option), + Verbose = invocation.ParseResult.GetValueForOption(_Verbose), + Debug = invocation.ParseResult.GetValueForOption(_Debug), + Force = invocation.ParseResult.GetValueForOption(_RestoreForce), + }; + var client = new ClientContext(); + invocation.ExitCode = ClientHelper.RunRestore(option, client, invocation); + }); + Command.AddCommand(cmd); + } - private void AddModule() - { - var cmd = new Command("module", CmdStrings.Module_Description); + private void AddModule() + { + var cmd = new Command("module", CmdStrings.Module_Description); - var moduleArg = new Argument - ( - "module", - CmdStrings.Module_Module_Description - ); - moduleArg.Arity = ArgumentArity.OneOrMore; + var moduleArg = new Argument + ( + "module", + CmdStrings.Module_Module_Description + ); + moduleArg.Arity = ArgumentArity.OneOrMore; - // Add - var add = new Command( - "add", - CmdStrings.Module_Add_Description - ); - add.AddArgument(moduleArg); - add.AddOption(_ModuleAddVersion); - add.AddOption(_ModuleAddForce); - add.AddOption(_ModuleAddSkipVerification); - add.SetHandler((invocation) => + // Add + var add = new Command( + "add", + CmdStrings.Module_Add_Description + ); + add.AddArgument(moduleArg); + add.AddOption(_ModuleAddVersion); + add.AddOption(_ModuleAddForce); + add.AddOption(_ModuleAddSkipVerification); + add.SetHandler((invocation) => + { + var option = new ModuleOptions { - var option = new ModuleOptions - { - Path = invocation.ParseResult.GetValueForOption(_Path), - Option = invocation.ParseResult.GetValueForOption(_Option), - Verbose = invocation.ParseResult.GetValueForOption(_Verbose), - Debug = invocation.ParseResult.GetValueForOption(_Debug), - Module = invocation.ParseResult.GetValueForArgument(moduleArg), - Version = invocation.ParseResult.GetValueForOption(_ModuleAddVersion), - Force = invocation.ParseResult.GetValueForOption(_ModuleAddForce), - SkipVerification = invocation.ParseResult.GetValueForOption(_ModuleAddSkipVerification), - }; + Path = invocation.ParseResult.GetValueForOption(_Path), + Option = invocation.ParseResult.GetValueForOption(_Option), + Verbose = invocation.ParseResult.GetValueForOption(_Verbose), + Debug = invocation.ParseResult.GetValueForOption(_Debug), + Module = invocation.ParseResult.GetValueForArgument(moduleArg), + Version = invocation.ParseResult.GetValueForOption(_ModuleAddVersion), + Force = invocation.ParseResult.GetValueForOption(_ModuleAddForce), + SkipVerification = invocation.ParseResult.GetValueForOption(_ModuleAddSkipVerification), + }; - var client = new ClientContext(); - invocation.ExitCode = ClientHelper.AddModule(option, client, invocation); - }); + var client = new ClientContext(); + invocation.ExitCode = ClientHelper.AddModule(option, client, invocation); + }); - // Remove - var remove = new Command( - "remove", - CmdStrings.Module_Remove_Description - ); - remove.AddArgument(moduleArg); - remove.SetHandler((invocation) => + // Remove + var remove = new Command( + "remove", + CmdStrings.Module_Remove_Description + ); + remove.AddArgument(moduleArg); + remove.SetHandler((invocation) => + { + var option = new ModuleOptions { - var option = new ModuleOptions - { - Path = invocation.ParseResult.GetValueForOption(_Path), - Option = invocation.ParseResult.GetValueForOption(_Option), - Verbose = invocation.ParseResult.GetValueForOption(_Verbose), - Debug = invocation.ParseResult.GetValueForOption(_Debug), - Module = invocation.ParseResult.GetValueForArgument(moduleArg) - }; + Path = invocation.ParseResult.GetValueForOption(_Path), + Option = invocation.ParseResult.GetValueForOption(_Option), + Verbose = invocation.ParseResult.GetValueForOption(_Verbose), + Debug = invocation.ParseResult.GetValueForOption(_Debug), + Module = invocation.ParseResult.GetValueForArgument(moduleArg) + }; - var client = new ClientContext(); - invocation.ExitCode = ClientHelper.RemoveModule(option, client, invocation); - }); + var client = new ClientContext(); + invocation.ExitCode = ClientHelper.RemoveModule(option, client, invocation); + }); - // Upgrade - var upgrade = new Command( - "upgrade", - CmdStrings.Module_Upgrade_Description - ); - upgrade.SetHandler((invocation) => + // Upgrade + var upgrade = new Command( + "upgrade", + CmdStrings.Module_Upgrade_Description + ); + upgrade.SetHandler((invocation) => + { + var option = new ModuleOptions { - var option = new ModuleOptions - { - Path = invocation.ParseResult.GetValueForOption(_Path), - Option = invocation.ParseResult.GetValueForOption(_Option), - Verbose = invocation.ParseResult.GetValueForOption(_Verbose), - Debug = invocation.ParseResult.GetValueForOption(_Debug) - }; + Path = invocation.ParseResult.GetValueForOption(_Path), + Option = invocation.ParseResult.GetValueForOption(_Option), + Verbose = invocation.ParseResult.GetValueForOption(_Verbose), + Debug = invocation.ParseResult.GetValueForOption(_Debug) + }; - var client = new ClientContext(); - invocation.ExitCode = ClientHelper.UpgradeModule(option, client, invocation); - }); + var client = new ClientContext(); + invocation.ExitCode = ClientHelper.UpgradeModule(option, client, invocation); + }); - cmd.AddCommand(add); - cmd.AddCommand(remove); - cmd.AddCommand(upgrade); + cmd.AddCommand(add); + cmd.AddCommand(remove); + cmd.AddCommand(upgrade); - cmd.AddOption(_Path); - Command.AddCommand(cmd); - } + cmd.AddOption(_Path); + Command.AddCommand(cmd); + } - /// - /// Convert string arguments to flags of . - /// - private static RuleOutcome? ParseOutcome(string[] s) + /// + /// Convert string arguments to flags of . + /// + private static RuleOutcome? ParseOutcome(string[] s) + { + var result = RuleOutcome.None; + for (var i = 0; s != null && i < s.Length; i++) { - var result = RuleOutcome.None; - for (var i = 0; s != null && i < s.Length; i++) - { - if (Enum.TryParse(s[i], ignoreCase: true, result: out RuleOutcome flag)) - result |= flag; - } - return result == RuleOutcome.None ? null : result; + if (Enum.TryParse(s[i], ignoreCase: true, result: out RuleOutcome flag)) + result |= flag; } + return result == RuleOutcome.None ? null : result; } } diff --git a/src/PSRule.Tool/ClientContext.cs b/src/PSRule.Tool/ClientContext.cs index 0d2ab8675f..0254f742e9 100644 --- a/src/PSRule.Tool/ClientContext.cs +++ b/src/PSRule.Tool/ClientContext.cs @@ -1,15 +1,14 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -namespace PSRule.Tool +namespace PSRule.Tool; + +internal sealed class ClientContext { - internal sealed class ClientContext + public ClientContext() { - public ClientContext() - { - Path = AppDomain.CurrentDomain.BaseDirectory; - } - - public string Path { get; } + Path = AppDomain.CurrentDomain.BaseDirectory; } + + public string Path { get; } } diff --git a/src/PSRule.Tool/ClientHelper.cs b/src/PSRule.Tool/ClientHelper.cs index f702be3ffb..3b99d53648 100644 --- a/src/PSRule.Tool/ClientHelper.cs +++ b/src/PSRule.Tool/ClientHelper.cs @@ -11,324 +11,323 @@ using PSRule.Tool.Resources; using SemanticVersion = PSRule.Data.SemanticVersion; -namespace PSRule.Tool +namespace PSRule.Tool; + +internal sealed class ClientHelper { - internal sealed class ClientHelper - { - private const string PUBLISHER = "CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US"; + private const string PUBLISHER = "CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US"; - /// - /// A generic error. - /// - private const int ERROR_GENERIC = 1; + /// + /// A generic error. + /// + private const int ERROR_GENERIC = 1; - /// - /// Failed to install a module. - /// - private const int ERROR_MODUILE_FAILEDTOINSTALL = 501; + /// + /// Failed to install a module. + /// + private const int ERROR_MODUILE_FAILEDTOINSTALL = 501; - private const int ERROR_MODULE_FAILEDTOFIND = 502; + private const int ERROR_MODULE_FAILEDTOFIND = 502; - private const int ERROR_MODULE_ADD_VIOLATES_CONSTRAINT = 503; + private const int ERROR_MODULE_ADD_VIOLATES_CONSTRAINT = 503; - /// - /// One or more failures occurred. - /// - private const int ERROR_BREAK_ON_FAILURE = 100; + /// + /// One or more failures occurred. + /// + private const int ERROR_BREAK_ON_FAILURE = 100; - private const string PARAM_NAME = "Name"; - private const string PARAM_VERSION = "Version"; + private const string PARAM_NAME = "Name"; + private const string PARAM_VERSION = "Version"; - private const string FIELD_PRERELEASE = "Prerelease"; - private const string FIELD_PSDATA = "PSData"; - private const string PRERELEASE_SEPARATOR = "-"; + private const string FIELD_PRERELEASE = "Prerelease"; + private const string FIELD_PSDATA = "PSData"; + private const string PRERELEASE_SEPARATOR = "-"; - public static int RunAnalyze(AnalyzerOptions operationOptions, ClientContext clientContext, InvocationContext invocation) + public static int RunAnalyze(AnalyzerOptions operationOptions, ClientContext clientContext, InvocationContext invocation) + { + var exitCode = 0; + var host = new ClientHost(invocation, operationOptions.Verbose, operationOptions.Debug); + var option = GetOption(host); + var file = LockFile.Read(null); + var inputPath = operationOptions.InputPath == null || operationOptions.InputPath.Length == 0 ? + new string[] { Environment.GetWorkingPath() } : operationOptions.InputPath; + + if (operationOptions.Path != null) + option.Include.Path = operationOptions.Path; + + if (operationOptions.Outcome != null && operationOptions.Outcome.Value != Rules.RuleOutcome.None) + option.Output.Outcome = operationOptions.Outcome; + + // Build command + var builder = CommandLineBuilder.Assert(operationOptions.Module, option, host, file); + builder.Baseline(BaselineOption.FromString(operationOptions.Baseline)); + builder.InputPath(inputPath); + builder.UnblockPublisher(PUBLISHER); + + using var pipeline = builder.Build(); + if (pipeline != null) { - var exitCode = 0; - var host = new ClientHost(invocation, operationOptions.Verbose, operationOptions.Debug); - var option = GetOption(host); - var file = LockFile.Read(null); - var inputPath = operationOptions.InputPath == null || operationOptions.InputPath.Length == 0 ? - new string[] { Environment.GetWorkingPath() } : operationOptions.InputPath; - - if (operationOptions.Path != null) - option.Include.Path = operationOptions.Path; - - if (operationOptions.Outcome != null && operationOptions.Outcome.Value != Rules.RuleOutcome.None) - option.Output.Outcome = operationOptions.Outcome; - - // Build command - var builder = CommandLineBuilder.Assert(operationOptions.Module, option, host, file); - builder.Baseline(BaselineOption.FromString(operationOptions.Baseline)); - builder.InputPath(inputPath); - builder.UnblockPublisher(PUBLISHER); - - using var pipeline = builder.Build(); - if (pipeline != null) - { - pipeline.Begin(); - pipeline.Process(null); - pipeline.End(); - if (pipeline.Result.HadFailures) - exitCode = ERROR_BREAK_ON_FAILURE; - } - return host.HadErrors || pipeline == null ? ERROR_GENERIC : exitCode; + pipeline.Begin(); + pipeline.Process(null); + pipeline.End(); + if (pipeline.Result.HadFailures) + exitCode = ERROR_BREAK_ON_FAILURE; } + return host.HadErrors || pipeline == null ? ERROR_GENERIC : exitCode; + } + + public static int RunRestore(RestoreOptions operationOptions, ClientContext clientContext, InvocationContext invocation) + { + var exitCode = 0; + var file = LockFile.Read(null); - public static int RunRestore(RestoreOptions operationOptions, ClientContext clientContext, InvocationContext invocation) + using var pwsh = PowerShell.Create(); + foreach (var kv in file.Modules) { - var exitCode = 0; - var file = LockFile.Read(null); + var module = kv.Key; + var targetVersion = kv.Value.Version; + if (string.Equals(module, "PSRule", StringComparison.OrdinalIgnoreCase)) + continue; - using var pwsh = PowerShell.Create(); - foreach (var kv in file.Modules) + invocation.Log(Messages.UsingModule, module, targetVersion.ToString()); + if (IsInstalled(pwsh, module, targetVersion, out var installedVersion) && !operationOptions.Force) { - var module = kv.Key; - var targetVersion = kv.Value.Version; - if (string.Equals(module, "PSRule", StringComparison.OrdinalIgnoreCase)) - continue; - - invocation.Log(Messages.UsingModule, module, targetVersion.ToString()); - if (IsInstalled(pwsh, module, targetVersion, out var installedVersion) && !operationOptions.Force) + if (operationOptions.Verbose) { - if (operationOptions.Verbose) - { - invocation.Log($"The module {module} is already installed."); - } - continue; + invocation.Log($"The module {module} is already installed."); } + continue; + } - var idealVersion = FindVersion(pwsh, module, null, targetVersion, null); - if (idealVersion != null) - InstallVersion(invocation, pwsh, module, idealVersion.ToString()); + var idealVersion = FindVersion(pwsh, module, null, targetVersion, null); + if (idealVersion != null) + InstallVersion(invocation, pwsh, module, idealVersion.ToString()); - if (pwsh.HadErrors || (idealVersion == null && installedVersion == null)) + if (pwsh.HadErrors || (idealVersion == null && installedVersion == null)) + { + exitCode = ERROR_MODUILE_FAILEDTOINSTALL; + invocation.LogError(Messages.Error_501, module, targetVersion); + foreach (var error in pwsh.Streams.Error) { - exitCode = ERROR_MODUILE_FAILEDTOINSTALL; - invocation.LogError(Messages.Error_501, module, targetVersion); - foreach (var error in pwsh.Streams.Error) - { - invocation.LogError(error.Exception.Message); - } + invocation.LogError(error.Exception.Message); } } - return exitCode; } + return exitCode; + } - /// - /// Add a module to the lock file. - /// - public static int AddModule(ModuleOptions operationOptions, ClientContext clientContext, InvocationContext invocation) + /// + /// Add a module to the lock file. + /// + public static int AddModule(ModuleOptions operationOptions, ClientContext clientContext, InvocationContext invocation) + { + var exitCode = 0; + var host = new ClientHost(invocation, operationOptions.Verbose, operationOptions.Debug); + var option = GetOption(host); + var requires = option.Requires.ToDictionary(); + var file = LockFile.Read(null); + + using var pwsh = PowerShell.Create(); + foreach (var module in operationOptions.Module) { - var exitCode = 0; - var host = new ClientHost(invocation, operationOptions.Verbose, operationOptions.Debug); - var option = GetOption(host); - var requires = option.Requires.ToDictionary(); - var file = LockFile.Read(null); - - using var pwsh = PowerShell.Create(); - foreach (var module in operationOptions.Module) + if (!file.Modules.TryGetValue(module, out var item) || operationOptions.Force) { - if (!file.Modules.TryGetValue(module, out var item) || operationOptions.Force) - { - // Get a constraint if set from options. - var moduleConstraint = requires.TryGetValue(module, out var c) ? c : null; - - // Get target version if specified in command-line. - var targetVersion = !string.IsNullOrEmpty(operationOptions.Version) && SemanticVersion.TryParseVersion(operationOptions.Version, out var v) && v != null ? v : null; - - // Check if the target version is valid with the constraint if set. - if (targetVersion != null && moduleConstraint != null && !moduleConstraint.Constraint.Equals(targetVersion)) - { - invocation.LogError(Messages.Error_503, operationOptions.Version); - return ERROR_MODULE_ADD_VIOLATES_CONSTRAINT; - } - - // Find the ideal version. - var idealVersion = FindVersion(pwsh, module, moduleConstraint, targetVersion, null); - if (idealVersion == null && targetVersion != null && operationOptions.SkipVerification) - idealVersion = targetVersion; - - if (idealVersion == null) - { - invocation.LogError(Messages.Error_502, module); - return ERROR_MODULE_FAILEDTOFIND; - } - - invocation.Log(Messages.UsingModule, module, idealVersion.ToString()); - item = new LockEntry - { - Version = idealVersion - }; - file.Modules[module] = item; - } - else - { - - } - } - - file.Write(null); - return exitCode; - } + // Get a constraint if set from options. + var moduleConstraint = requires.TryGetValue(module, out var c) ? c : null; - /// - /// Remove a module from the lock file. - /// - public static int RemoveModule(ModuleOptions operationOptions, ClientContext clientContext, InvocationContext invocation) - { - var exitCode = 0; - var host = new ClientHost(invocation, operationOptions.Verbose, operationOptions.Debug); - var file = LockFile.Read(null); + // Get target version if specified in command-line. + var targetVersion = !string.IsNullOrEmpty(operationOptions.Version) && SemanticVersion.TryParseVersion(operationOptions.Version, out var v) && v != null ? v : null; - foreach (var module in operationOptions.Module) - { - if (file.Modules.TryGetValue(module, out var constraint)) - { - file.Modules.Remove(module); - } - else + // Check if the target version is valid with the constraint if set. + if (targetVersion != null && moduleConstraint != null && !moduleConstraint.Constraint.Equals(targetVersion)) { - + invocation.LogError(Messages.Error_503, operationOptions.Version); + return ERROR_MODULE_ADD_VIOLATES_CONSTRAINT; } - } - - file.Write(null); - return exitCode; - } - - /// - /// Upgrade a module within the lock file. - /// - public static int UpgradeModule(ModuleOptions operationOptions, ClientContext clientContext, InvocationContext invocation) - { - var exitCode = 0; - var host = new ClientHost(invocation, operationOptions.Verbose, operationOptions.Debug); - var option = GetOption(host); - var requires = option.Requires.ToDictionary(); - var file = LockFile.Read(null); - - using var pwsh = PowerShell.Create(); - foreach (var kv in file.Modules) - { - // Get a constraint if set from options. - var moduleConstraint = requires.TryGetValue(kv.Key, out var c) ? c : null; // Find the ideal version. - var idealVersion = FindVersion(pwsh, kv.Key, moduleConstraint, null, null); + var idealVersion = FindVersion(pwsh, module, moduleConstraint, targetVersion, null); + if (idealVersion == null && targetVersion != null && operationOptions.SkipVerification) + idealVersion = targetVersion; + if (idealVersion == null) { - invocation.LogError(Messages.Error_502, kv.Key); + invocation.LogError(Messages.Error_502, module); return ERROR_MODULE_FAILEDTOFIND; } - if (idealVersion == kv.Value.Version) - continue; - - invocation.Log(Messages.UsingModule, kv.Key, idealVersion.ToString()); - - kv.Value.Version = idealVersion; - file.Modules[kv.Key] = kv.Value; + invocation.Log(Messages.UsingModule, module, idealVersion.ToString()); + item = new LockEntry + { + Version = idealVersion + }; + file.Modules[module] = item; } + else + { - file.Write(null); - return exitCode; + } } - private static bool IsInstalled(PowerShell pwsh, string module, SemanticVersion.Version targetVersion, out SemanticVersion.Version installedVersion) + file.Write(null); + return exitCode; + } + + /// + /// Remove a module from the lock file. + /// + public static int RemoveModule(ModuleOptions operationOptions, ClientContext clientContext, InvocationContext invocation) + { + var exitCode = 0; + var host = new ClientHost(invocation, operationOptions.Verbose, operationOptions.Debug); + var file = LockFile.Read(null); + + foreach (var module in operationOptions.Module) { - pwsh.Commands.Clear(); - pwsh.Streams.ClearStreams(); - pwsh.AddCommand("Get-Module") - .AddParameter(PARAM_NAME, module) - .AddParameter("ListAvailable"); - - var versions = pwsh.Invoke(); - installedVersion = null; - foreach (var version in versions) + if (file.Modules.TryGetValue(module, out var constraint)) { - if (TryModuleInfo(version, out var versionString) && - SemanticVersion.TryParseVersion(versionString, out var v) && - (targetVersion == null || targetVersion.Equals(v)) && - v.CompareTo(installedVersion) > 0) - installedVersion = v; + file.Modules.Remove(module); } - return installedVersion != null; - } - - private static bool TryModuleInfo(PSObject value, out string version) - { - version = null; - if (value?.BaseObject is not PSModuleInfo info) - return false; - - version = info.Version?.ToString(); - if (TryPrivateData(info, FIELD_PSDATA, out var psData) && psData.ContainsKey(FIELD_PRERELEASE)) - version = string.Concat(version, PRERELEASE_SEPARATOR, psData[FIELD_PRERELEASE].ToString()); + else + { - return version != null; + } } - private static bool TryPrivateData(PSModuleInfo info, string propertyName, out Hashtable value) + file.Write(null); + return exitCode; + } + + /// + /// Upgrade a module within the lock file. + /// + public static int UpgradeModule(ModuleOptions operationOptions, ClientContext clientContext, InvocationContext invocation) + { + var exitCode = 0; + var host = new ClientHost(invocation, operationOptions.Verbose, operationOptions.Debug); + var option = GetOption(host); + var requires = option.Requires.ToDictionary(); + var file = LockFile.Read(null); + + using var pwsh = PowerShell.Create(); + foreach (var kv in file.Modules) { - value = null; - if (info.PrivateData is Hashtable privateData && privateData.ContainsKey(propertyName) && privateData[propertyName] is Hashtable data) + // Get a constraint if set from options. + var moduleConstraint = requires.TryGetValue(kv.Key, out var c) ? c : null; + + // Find the ideal version. + var idealVersion = FindVersion(pwsh, kv.Key, moduleConstraint, null, null); + if (idealVersion == null) { - value = data; - return true; + invocation.LogError(Messages.Error_502, kv.Key); + return ERROR_MODULE_FAILEDTOFIND; } - return false; + + if (idealVersion == kv.Value.Version) + continue; + + invocation.Log(Messages.UsingModule, kv.Key, idealVersion.ToString()); + + kv.Value.Version = idealVersion; + file.Modules[kv.Key] = kv.Value; } - private static SemanticVersion.Version FindVersion(PowerShell pwsh, string module, ModuleConstraint constraint, SemanticVersion.Version targetVersion, SemanticVersion.Version installedVersion) + file.Write(null); + return exitCode; + } + + private static bool IsInstalled(PowerShell pwsh, string module, SemanticVersion.Version targetVersion, out SemanticVersion.Version installedVersion) + { + pwsh.Commands.Clear(); + pwsh.Streams.ClearStreams(); + pwsh.AddCommand("Get-Module") + .AddParameter(PARAM_NAME, module) + .AddParameter("ListAvailable"); + + var versions = pwsh.Invoke(); + installedVersion = null; + foreach (var version in versions) { - pwsh.Commands.Clear(); - pwsh.Streams.ClearStreams(); - pwsh.AddCommand("Find-Module") - .AddParameter(PARAM_NAME, module) - .AddParameter("AllVersions"); - - var versions = pwsh.Invoke(); - SemanticVersion.Version result = null; - foreach (var version in versions) - { - if (version.Properties[PARAM_VERSION].Value is string versionString && - SemanticVersion.TryParseVersion(versionString, out var v) && - (constraint == null || constraint.Constraint.Equals(v)) && - (targetVersion == null || targetVersion.Equals(v)) && - v.CompareTo(result) > 0 && - v.CompareTo(installedVersion) > 0) - result = v; - } - return result; + if (TryModuleInfo(version, out var versionString) && + SemanticVersion.TryParseVersion(versionString, out var v) && + (targetVersion == null || targetVersion.Equals(v)) && + v.CompareTo(installedVersion) > 0) + installedVersion = v; } + return installedVersion != null; + } + + private static bool TryModuleInfo(PSObject value, out string version) + { + version = null; + if (value?.BaseObject is not PSModuleInfo info) + return false; + + version = info.Version?.ToString(); + if (TryPrivateData(info, FIELD_PSDATA, out var psData) && psData.ContainsKey(FIELD_PRERELEASE)) + version = string.Concat(version, PRERELEASE_SEPARATOR, psData[FIELD_PRERELEASE].ToString()); + + return version != null; + } - private static void InstallVersion(InvocationContext invocation, PowerShell pwsh, string name, string version) + private static bool TryPrivateData(PSModuleInfo info, string propertyName, out Hashtable value) + { + value = null; + if (info.PrivateData is Hashtable privateData && privateData.ContainsKey(propertyName) && privateData[propertyName] is Hashtable data) { - invocation.Log(Messages.InstallingModule, name, version); - - pwsh.Commands.Clear(); - pwsh.Streams.ClearStreams(); - pwsh.AddCommand("Install-Module") - .AddParameter(PARAM_NAME, name) - .AddParameter("RequiredVersion", version) - .AddParameter("Scope", "CurrentUser") - .AddParameter("AllowPrerelease") - .AddParameter("Force"); - - pwsh.Invoke(); + value = data; + return true; } + return false; + } - private static PSRuleOption GetOption(ClientHost host) + private static SemanticVersion.Version FindVersion(PowerShell pwsh, string module, ModuleConstraint constraint, SemanticVersion.Version targetVersion, SemanticVersion.Version installedVersion) + { + pwsh.Commands.Clear(); + pwsh.Streams.ClearStreams(); + pwsh.AddCommand("Find-Module") + .AddParameter(PARAM_NAME, module) + .AddParameter("AllVersions"); + + var versions = pwsh.Invoke(); + SemanticVersion.Version result = null; + foreach (var version in versions) { - PSRuleOption.UseHostContext(host); - var option = PSRuleOption.FromFileOrEmpty(); - option.Execution.InitialSessionState = Options.SessionState.Minimal; - option.Input.Format = InputFormat.File; - option.Output.Style ??= OutputStyle.Client; - return option; + if (version.Properties[PARAM_VERSION].Value is string versionString && + SemanticVersion.TryParseVersion(versionString, out var v) && + (constraint == null || constraint.Constraint.Equals(v)) && + (targetVersion == null || targetVersion.Equals(v)) && + v.CompareTo(result) > 0 && + v.CompareTo(installedVersion) > 0) + result = v; } + return result; + } + + private static void InstallVersion(InvocationContext invocation, PowerShell pwsh, string name, string version) + { + invocation.Log(Messages.InstallingModule, name, version); + + pwsh.Commands.Clear(); + pwsh.Streams.ClearStreams(); + pwsh.AddCommand("Install-Module") + .AddParameter(PARAM_NAME, name) + .AddParameter("RequiredVersion", version) + .AddParameter("Scope", "CurrentUser") + .AddParameter("AllowPrerelease") + .AddParameter("Force"); + + pwsh.Invoke(); + } + + private static PSRuleOption GetOption(ClientHost host) + { + PSRuleOption.UseHostContext(host); + var option = PSRuleOption.FromFileOrEmpty(); + option.Execution.InitialSessionState = Options.SessionState.Minimal; + option.Input.Format = InputFormat.File; + option.Output.Style ??= OutputStyle.Client; + return option; } } diff --git a/src/PSRule.Tool/ClientHost.cs b/src/PSRule.Tool/ClientHost.cs index 21ac7620b6..ef2957b2fc 100644 --- a/src/PSRule.Tool/ClientHost.cs +++ b/src/PSRule.Tool/ClientHost.cs @@ -7,94 +7,93 @@ using System.Management.Automation; using PSRule.Pipeline; -namespace PSRule.Tool +namespace PSRule.Tool; + +internal sealed class ClientHost : HostContext { - internal sealed class ClientHost : HostContext + private readonly InvocationContext _Invocation; + private readonly bool _Verbose; + private readonly bool _Debug; + private readonly ConsoleColor _BackgroundColor; + private readonly ConsoleColor _ForegroundColor; + + public ClientHost(InvocationContext invocation, bool verbose, bool debug) { - private readonly InvocationContext _Invocation; - private readonly bool _Verbose; - private readonly bool _Debug; - private readonly ConsoleColor _BackgroundColor; - private readonly ConsoleColor _ForegroundColor; + _Invocation = invocation; + _Verbose = verbose; + _Debug = debug; + _BackgroundColor = Console.BackgroundColor; + _ForegroundColor = Console.ForegroundColor; - public ClientHost(InvocationContext invocation, bool verbose, bool debug) - { - _Invocation = invocation; - _Verbose = verbose; - _Debug = debug; - _BackgroundColor = Console.BackgroundColor; - _ForegroundColor = Console.ForegroundColor; + Verbose($"Using working path: {Directory.GetCurrentDirectory()}"); + } - Verbose($"Using working path: {Directory.GetCurrentDirectory()}"); - } + public override ActionPreference GetPreferenceVariable(string variableName) + { + if (variableName == "VerbosePreference") + return _Verbose ? ActionPreference.Continue : ActionPreference.SilentlyContinue; - public override ActionPreference GetPreferenceVariable(string variableName) - { - if (variableName == "VerbosePreference") - return _Verbose ? ActionPreference.Continue : ActionPreference.SilentlyContinue; + if (variableName == "DebugPreference") + return _Debug ? ActionPreference.Continue : ActionPreference.SilentlyContinue; - if (variableName == "DebugPreference") - return _Debug ? ActionPreference.Continue : ActionPreference.SilentlyContinue; + return base.GetPreferenceVariable(variableName); + } - return base.GetPreferenceVariable(variableName); - } + public override void Error(ErrorRecord errorRecord) + { + _Invocation.Console.Error.WriteLine(errorRecord.Exception.Message); + base.Error(errorRecord); + } - public override void Error(ErrorRecord errorRecord) - { - _Invocation.Console.Error.WriteLine(errorRecord.Exception.Message); - base.Error(errorRecord); - } + public override void Warning(string text) + { + _Invocation.Console.WriteLine(text); + } - public override void Warning(string text) - { - _Invocation.Console.WriteLine(text); - } + public override bool ShouldProcess(string target, string action) + { + return true; + } - public override bool ShouldProcess(string target, string action) + public override void Information(InformationRecord informationRecord) + { + if (informationRecord?.MessageData is HostInformationMessage info) { - return true; - } + SetConsole(info); + if (info.NoNewLine.GetValueOrDefault(false)) + _Invocation.Console.Write(info.Message); + else + _Invocation.Console.WriteLine(info.Message); - public override void Information(InformationRecord informationRecord) - { - if (informationRecord?.MessageData is HostInformationMessage info) - { - SetConsole(info); - if (info.NoNewLine.GetValueOrDefault(false)) - _Invocation.Console.Write(info.Message); - else - _Invocation.Console.WriteLine(info.Message); - - RevertConsole(); - } + RevertConsole(); } + } - private void SetConsole(HostInformationMessage info) - { - Console.BackgroundColor = info.BackgroundColor.GetValueOrDefault(_BackgroundColor); - Console.ForegroundColor = info.ForegroundColor.GetValueOrDefault(_ForegroundColor); - } + private void SetConsole(HostInformationMessage info) + { + Console.BackgroundColor = info.BackgroundColor.GetValueOrDefault(_BackgroundColor); + Console.ForegroundColor = info.ForegroundColor.GetValueOrDefault(_ForegroundColor); + } - private void RevertConsole() - { - Console.BackgroundColor = _BackgroundColor; - Console.ForegroundColor = _ForegroundColor; - } + private void RevertConsole() + { + Console.BackgroundColor = _BackgroundColor; + Console.ForegroundColor = _ForegroundColor; + } - public override void Verbose(string text) - { - if (!_Verbose) - return; + public override void Verbose(string text) + { + if (!_Verbose) + return; - _Invocation.Console.WriteLine(text); - } + _Invocation.Console.WriteLine(text); + } - public override void Debug(string text) - { - if (!_Debug) - return; + public override void Debug(string text) + { + if (!_Debug) + return; - _Invocation.Console.WriteLine(text); - } + _Invocation.Console.WriteLine(text); } } diff --git a/src/PSRule.Tool/Program.cs b/src/PSRule.Tool/Program.cs index 6bdbef79fa..d1db4c11a9 100644 --- a/src/PSRule.Tool/Program.cs +++ b/src/PSRule.Tool/Program.cs @@ -4,16 +4,15 @@ using System.CommandLine; using System.CommandLine.Parsing; -namespace PSRule.Tool +namespace PSRule.Tool; + +static class Program { - static class Program + /// + /// Entry point for CLI tool. + /// + static async Task Main(string[] args) { - /// - /// Entry point for CLI tool. - /// - static async Task Main(string[] args) - { - return await ClientBuilder.New().InvokeAsync(args); - } + return await ClientBuilder.New().InvokeAsync(args); } } diff --git a/src/PSRule.Tool/RestoreOptions.cs b/src/PSRule.Tool/RestoreOptions.cs index 5730e20c42..f38d2c87ff 100644 --- a/src/PSRule.Tool/RestoreOptions.cs +++ b/src/PSRule.Tool/RestoreOptions.cs @@ -1,18 +1,17 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -namespace PSRule.Tool +namespace PSRule.Tool; + +internal sealed class RestoreOptions { - internal sealed class RestoreOptions - { - public string[] Path { get; set; } + public string[] Path { get; set; } - public string Option { get; set; } + public string Option { get; set; } - public bool Verbose { get; set; } + public bool Verbose { get; set; } - public bool Debug { get; set; } + public bool Debug { get; set; } - public bool Force { get; set; } - } + public bool Force { get; set; } } diff --git a/src/PSRule.Tool/packages.lock.json b/src/PSRule.Tool/packages.lock.json new file mode 100644 index 0000000000..23de967d47 --- /dev/null +++ b/src/PSRule.Tool/packages.lock.json @@ -0,0 +1,1367 @@ +{ + "version": 1, + "dependencies": { + "net7.0": { + "Microsoft.CodeAnalysis.NetAnalyzers": { + "type": "Direct", + "requested": "[8.0.0, )", + "resolved": "8.0.0", + "contentHash": "DxiTgkCl3CGq1rYmBX2wjY7XGbxiBdL4J+/AJIAFLKy5z70NxhnVRnPghnicXZ8oF6JKVXlW3xwznRbI3ioEKg==" + }, + "Microsoft.PowerShell.SDK": { + "type": "Direct", + "requested": "[7.3.7, )", + "resolved": "7.3.7", + "contentHash": "5SJlr0NojZTlYmBBSHAvAQP8k6nI/GcFCds4LOj0hH4h24EHW9gYXOeFMM4LUvmg2SuBe0F4fdv4ZFHTaTpZJA==", + "dependencies": { + "Microsoft.Bcl.AsyncInterfaces": "7.0.0", + "Microsoft.Extensions.ObjectPool": "7.0.11", + "Microsoft.Management.Infrastructure.CimCmdlets": "7.3.7", + "Microsoft.PowerShell.Commands.Diagnostics": "7.3.7", + "Microsoft.PowerShell.Commands.Management": "7.3.7", + "Microsoft.PowerShell.Commands.Utility": "7.3.7", + "Microsoft.PowerShell.ConsoleHost": "7.3.7", + "Microsoft.PowerShell.Security": "7.3.7", + "Microsoft.WSMan.Management": "7.3.7", + "Microsoft.Win32.Registry": "5.0.0", + "Microsoft.Windows.Compatibility": "7.0.5", + "System.Data.SqlClient": "4.8.5", + "System.IO.Packaging": "7.0.0", + "System.Management.Automation": "7.3.7", + "System.Net.Http.WinHttpHandler": "7.0.0", + "System.Private.ServiceModel": "4.10.2", + "System.Security.Cryptography.ProtectedData": "7.0.1", + "System.Security.Cryptography.Xml": "7.0.1", + "System.ServiceModel.Duplex": "4.10.2", + "System.ServiceModel.Http": "4.10.2", + "System.ServiceModel.NetTcp": "4.10.2", + "System.ServiceModel.Primitives": "4.10.2", + "System.ServiceModel.Security": "4.10.2", + "System.Text.Encodings.Web": "7.0.0", + "System.Web.Services.Description": "4.10.2" + } + }, + "Microsoft.SourceLink.GitHub": { + "type": "Direct", + "requested": "[8.0.0, )", + "resolved": "8.0.0", + "contentHash": "G5q7OqtwIyGTkeIOAc3u2ZuV/kicQaec5EaRnc0pIeSnh9LUjj+PYQrJYBURvDt7twGl2PKA7nSN0kz1Zw5bnQ==", + "dependencies": { + "Microsoft.Build.Tasks.Git": "8.0.0", + "Microsoft.SourceLink.Common": "8.0.0" + } + }, + "System.CommandLine": { + "type": "Direct", + "requested": "[2.0.0-beta4.22272.1, )", + "resolved": "2.0.0-beta4.22272.1", + "contentHash": "1uqED/q2H0kKoLJ4+hI2iPSBSEdTuhfCYADeJrAqERmiGQ2NNacYKRNEQ+gFbU4glgVyK8rxI+ZOe1onEtr/Pg==" + }, + "System.Management.Automation": { + "type": "Direct", + "requested": "[7.3.7, )", + "resolved": "7.3.7", + "contentHash": "2RXHukEXtvizHACZPsLHIpWU1IZ6dasuQ5JyAIr69saqRSqmWNFUj6Qj8dgWlC89WoWNVra0n9Ty4xjg6xVOfg==", + "dependencies": { + "Microsoft.ApplicationInsights": "2.21.0", + "Microsoft.CSharp": "4.7.0", + "Microsoft.Management.Infrastructure": "2.0.0", + "Microsoft.PowerShell.CoreCLR.Eventing": "7.3.7", + "Microsoft.PowerShell.Native": "7.3.2", + "Microsoft.Security.Extensions": "1.2.0", + "Microsoft.Win32.Registry.AccessControl": "7.0.0", + "Newtonsoft.Json": "13.0.3", + "System.Configuration.ConfigurationManager": "7.0.0", + "System.Diagnostics.DiagnosticSource": "7.0.2", + "System.DirectoryServices": "7.0.1", + "System.Management": "7.0.2", + "System.Security.AccessControl": "6.0.0", + "System.Security.Cryptography.Pkcs": "7.0.3", + "System.Security.Cryptography.ProtectedData": "7.0.1", + "System.Security.Permissions": "7.0.0", + "System.Text.Encoding.CodePages": "7.0.0" + } + }, + "Manatee.Json": { + "type": "Transitive", + "resolved": "13.0.5", + "contentHash": "xCWPlnZbfXrOW28r9Cnw2U+kh1KUL5AkM9raGk/sO5nzIGkwAC04gCRI0S4Gqv63Sm2lPmhLvancxPzT6QE+Mw==", + "dependencies": { + "System.ComponentModel.Annotations": "4.4.0", + "System.Net.Http": "4.3.3", + "System.Reflection.Emit": "4.3.0", + "System.ValueTuple": "4.5.0" + } + }, + "Markdig.Signed": { + "type": "Transitive", + "resolved": "0.31.0", + "contentHash": "u05eQvNRunYLR+J0SZAgt8ia+qCF3cMfyYh7LR4jSjG5Tg+0HuRrv7u/Gox9kOItWlSacMITcHBio7jas/zaEQ==" + }, + "Microsoft.ApplicationInsights": { + "type": "Transitive", + "resolved": "2.21.0", + "contentHash": "btZEDWAFNo9CoYliMCriSMTX3ruRGZTtYw4mo2XyyfLlowFicYVM2Xszi5evDG95QRYV7MbbH3D2RqVwfZlJHw==", + "dependencies": { + "System.Diagnostics.DiagnosticSource": "5.0.0" + } + }, + "Microsoft.Bcl.AsyncInterfaces": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "3aeMZ1N0lJoSyzqiP03hqemtb1BijhsJADdobn/4nsMJ8V1H+CrpuduUe4hlRdx+ikBQju1VGjMD1GJ3Sk05Eg==" + }, + "Microsoft.Build.Tasks.Git": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ==" + }, + "Microsoft.CodeAnalysis.Analyzers": { + "type": "Transitive", + "resolved": "3.3.3", + "contentHash": "j/rOZtLMVJjrfLRlAMckJLPW/1rze9MT1yfWqSIbUPGRu1m1P0fuo9PmqapwsmePfGB5PJrudQLvmUOAMF0DqQ==" + }, + "Microsoft.CodeAnalysis.Common": { + "type": "Transitive", + "resolved": "4.4.0", + "contentHash": "JfHupS/B7Jb5MZoYkFFABn3mux0wQgxi2D8F/rJYZeRBK2ZOyk7TjQ2Kq9rh6W/DCh0KNbbSbn5qoFar+ueHqw==", + "dependencies": { + "Microsoft.CodeAnalysis.Analyzers": "3.3.3", + "System.Collections.Immutable": "6.0.0", + "System.Memory": "4.5.5", + "System.Reflection.Metadata": "5.0.0", + "System.Runtime.CompilerServices.Unsafe": "6.0.0", + "System.Text.Encoding.CodePages": "6.0.0", + "System.Threading.Tasks.Extensions": "4.5.4" + } + }, + "Microsoft.CodeAnalysis.CSharp": { + "type": "Transitive", + "resolved": "4.4.0", + "contentHash": "eD2w0xHRoaqK07hjlOKGR9eLNy3nimiGNeCClNax1NDgS/+DBtBqCjXelOa+TNy99kIB3nHhUqDmr46nDXy/RQ==", + "dependencies": { + "Microsoft.CodeAnalysis.Common": "[4.4.0]" + } + }, + "Microsoft.CSharp": { + "type": "Transitive", + "resolved": "4.7.0", + "contentHash": "pTj+D3uJWyN3My70i2Hqo+OXixq3Os2D1nJ2x92FFo6sk8fYS1m1WLNTs0Dc1uPaViH0YvEEwvzddQ7y4rhXmA==" + }, + "Microsoft.Extensions.ObjectPool": { + "type": "Transitive", + "resolved": "7.0.11", + "contentHash": "DfzSvWdWl4b5VYGQI6tzxJWXSoyLE88C0W+whseE8RuYvC5wEGQLuIPiMXQtqxCBGN4rt0PTuDhdGAjtz4JZzw==" + }, + "Microsoft.Management.Infrastructure": { + "type": "Transitive", + "resolved": "2.0.0", + "contentHash": "IaKZRNBBv3sdrmBWd+aqwHq8cVHk/3WgWFAN/dt40MRY9rbtHiDfTTmaEN0tGTmQqGCGDo/ncntA8MvFMvcsRw==", + "dependencies": { + "Microsoft.Management.Infrastructure.Runtime.Unix": "2.0.0", + "Microsoft.Management.Infrastructure.Runtime.Win": "2.0.0" + } + }, + "Microsoft.Management.Infrastructure.CimCmdlets": { + "type": "Transitive", + "resolved": "7.3.7", + "contentHash": "Gf5kH5gBwq41jB6VMBwfjJSJLbc0A+B+f+6buH5TmJ4+M62/Ye44mbUKMMPWAd4tSlsK8cfhUrPp4u6Br/wDJw==", + "dependencies": { + "System.Management.Automation": "7.3.7" + } + }, + "Microsoft.Management.Infrastructure.Runtime.Unix": { + "type": "Transitive", + "resolved": "2.0.0", + "contentHash": "p0lslMX5bdWLxO2P7ao+rjAMOB0LEwPYpzvdCQ2OEYgX2NxFpQ8ILvqPGnYlTAb53rT8gu5DyIol1HboHFYfxQ==" + }, + "Microsoft.Management.Infrastructure.Runtime.Win": { + "type": "Transitive", + "resolved": "2.0.0", + "contentHash": "vjBWQeDOjgernkrOdbEgn7M70SF7hof7ORdKPSlL06Uc15+oYdth5dZju9KsgUoti/cwnkZTiwtDx/lRtay0sA==" + }, + "Microsoft.NETCore.Platforms": { + "type": "Transitive", + "resolved": "1.1.0", + "contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==" + }, + "Microsoft.NETCore.Targets": { + "type": "Transitive", + "resolved": "1.1.0", + "contentHash": "aOZA3BWfz9RXjpzt0sRJJMjAscAUm3Hoa4UWAfceV9UTYxgwZ1lZt5nO2myFf+/jetYQo4uTP7zS8sJY67BBxg==" + }, + "Microsoft.PowerShell.Commands.Diagnostics": { + "type": "Transitive", + "resolved": "7.3.7", + "contentHash": "/luilQ1m5xwwfmw0P/d+7VCdfVsqFwGFXo6rfk6EePzpveY+NMrKteWMFK8Z/ofzpEqCfDKQoQ02PnPjLHl8ZA==", + "dependencies": { + "System.Management.Automation": "7.3.7" + } + }, + "Microsoft.PowerShell.Commands.Management": { + "type": "Transitive", + "resolved": "7.3.7", + "contentHash": "lPJVT77kxKpCQmSNRDjjUrL/RuNw6hm3CpJZ533uPxALhkoemXgCGtukOqYh0mCL7CuDWEcRs6HGOLh0AdEpaA==", + "dependencies": { + "Microsoft.PowerShell.Security": "7.3.7", + "System.ServiceProcess.ServiceController": "7.0.1" + } + }, + "Microsoft.PowerShell.Commands.Utility": { + "type": "Transitive", + "resolved": "7.3.7", + "contentHash": "y0Hqr61WeXL1mQdYXilPBFUGLOFesKCXNpf/qScvkMpA7HVZywsIpe/JnSnEXcAVFOqqCKfqWBGhoHDxo/XU1A==", + "dependencies": { + "Markdig.Signed": "0.31.0", + "Microsoft.CodeAnalysis.CSharp": "4.4.0", + "Microsoft.PowerShell.MarkdownRender": "7.2.1", + "NJsonSchema": "10.8.0", + "Namotion.Reflection": "2.1.2", + "System.Drawing.Common": "7.0.0", + "System.Management.Automation": "7.3.7", + "System.Threading.AccessControl": "7.0.1" + } + }, + "Microsoft.PowerShell.ConsoleHost": { + "type": "Transitive", + "resolved": "7.3.7", + "contentHash": "fRk4MFn5kv8RkKmQ+FhA7CPmKWxgYwa6y6VpyrUyIdnWDJYuzWocV9CnHJcjZ1GKpapa+g0CMVv1ZZSfMuOC0Q==", + "dependencies": { + "System.Management.Automation": "7.3.7" + } + }, + "Microsoft.PowerShell.CoreCLR.Eventing": { + "type": "Transitive", + "resolved": "7.3.7", + "contentHash": "NQ/c1Ar+HezhDP95DJBX4xk1/EfvQ+J2I6scjqd7zKW8r5ZoQnDAtCCPPmXriHXjbYLvsQcVvRfVEc1UcELvug==", + "dependencies": { + "System.Diagnostics.EventLog": "7.0.0" + } + }, + "Microsoft.PowerShell.MarkdownRender": { + "type": "Transitive", + "resolved": "7.2.1", + "contentHash": "o5oUwL23R/KnjQPD2Oi49WAG5j4O4VLo1fPRSyM/aq0HuTrY2RnF4B3MCGk13BfcmK51p9kPlHZ1+8a/ZjO4Jg==", + "dependencies": { + "Markdig.Signed": "0.31.0" + } + }, + "Microsoft.PowerShell.Native": { + "type": "Transitive", + "resolved": "7.3.2", + "contentHash": "MlLhJgzrUlxijTKJ19Eht++iGTUdg/F1jSbqwzjnc2Q8XStkUYNh8/81aUcNxWcg+0z1Yj/iUjW7czgWUYdV6Q==" + }, + "Microsoft.PowerShell.Security": { + "type": "Transitive", + "resolved": "7.3.7", + "contentHash": "5HiwXbj6ZXUGVBu3vMmorNJxoDEM5UYGFBRqrQ9ZiEryz3fmnAgJ2/KN2H9pvHWS/5o9PQLctjlPRCcDduX2qQ==", + "dependencies": { + "System.Management.Automation": "7.3.7" + } + }, + "Microsoft.Security.Extensions": { + "type": "Transitive", + "resolved": "1.2.0", + "contentHash": "GjHZBE5PHKrxPRyGujWQKwbKNjPQYds6HcAWKeV49X3KPgBfF2B1vV5uJey5UluyGQlvAO/DezL7WzEx9HlPQA==" + }, + "Microsoft.SourceLink.Common": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw==" + }, + "Microsoft.Win32.Registry": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "dependencies": { + "System.Security.AccessControl": "5.0.0", + "System.Security.Principal.Windows": "5.0.0" + } + }, + "Microsoft.Win32.Registry.AccessControl": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "JwM65WXVca58WzqY/Rpz7FGyHbN/SMdyr/3EI2CwPIYkB55EIRJUdPQJwO64x3ntOwPQoqCATKuDYA9K7Np5Ww==" + }, + "Microsoft.Win32.SystemEvents": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "2nXPrhdAyAzir0gLl8Yy8S5Mnm/uBSQQA7jEsILOS1MTyS7DbmV1NgViMtvV1sfCD1ebITpNwb1NIinKeJgUVQ==" + }, + "Microsoft.Windows.Compatibility": { + "type": "Transitive", + "resolved": "7.0.5", + "contentHash": "N4aTGZVV1PYPLVLtNn6jsh2b20oS87jegwkB1yDbV4Fy8bs2FZvsjEjjQg1wc7E29JKuwdNXOUYd9ww9LKuLtA==", + "dependencies": { + "Microsoft.Win32.Registry.AccessControl": "7.0.0", + "Microsoft.Win32.SystemEvents": "7.0.0", + "System.CodeDom": "7.0.0", + "System.ComponentModel.Composition": "7.0.0", + "System.ComponentModel.Composition.Registration": "7.0.0", + "System.Configuration.ConfigurationManager": "7.0.0", + "System.Data.Odbc": "7.0.0", + "System.Data.OleDb": "7.0.0", + "System.Data.SqlClient": "4.8.5", + "System.Diagnostics.EventLog": "7.0.0", + "System.Diagnostics.PerformanceCounter": "7.0.0", + "System.DirectoryServices": "7.0.1", + "System.DirectoryServices.AccountManagement": "7.0.1", + "System.DirectoryServices.Protocols": "7.0.1", + "System.Drawing.Common": "7.0.0", + "System.IO.Packaging": "7.0.0", + "System.IO.Ports": "7.0.0", + "System.Management": "7.0.2", + "System.Reflection.Context": "7.0.0", + "System.Runtime.Caching": "7.0.0", + "System.Security.Cryptography.Pkcs": "7.0.2", + "System.Security.Cryptography.ProtectedData": "7.0.1", + "System.Security.Cryptography.Xml": "7.0.1", + "System.Security.Permissions": "7.0.0", + "System.ServiceModel.Duplex": "4.9.0", + "System.ServiceModel.Http": "4.9.0", + "System.ServiceModel.NetTcp": "4.9.0", + "System.ServiceModel.Primitives": "4.9.0", + "System.ServiceModel.Security": "4.9.0", + "System.ServiceModel.Syndication": "7.0.0", + "System.ServiceProcess.ServiceController": "7.0.1", + "System.Speech": "7.0.0", + "System.Text.Encoding.CodePages": "7.0.0", + "System.Threading.AccessControl": "7.0.1", + "System.Web.Services.Description": "4.9.0" + } + }, + "Microsoft.WSMan.Management": { + "type": "Transitive", + "resolved": "7.3.7", + "contentHash": "PAIPSykF8HrRhw8OvfWrKPvfKDVHH5geenSjpcoE2UpDHt2EpQsoS05ufc8drNTSzltr7qGYY3yue+Y09eoJAA==", + "dependencies": { + "Microsoft.WSMan.Runtime": "7.3.7", + "System.Management.Automation": "7.3.7", + "System.ServiceProcess.ServiceController": "7.0.1" + } + }, + "Microsoft.WSMan.Runtime": { + "type": "Transitive", + "resolved": "7.3.7", + "contentHash": "auQMUGq8oVuqYj6O7zJX0Q22Kjdw54gQIeo88tjsADkdtgyfSe1XFFONX/JHPCn3KbYjJrIXI1kYPCwr0wGftQ==" + }, + "Namotion.Reflection": { + "type": "Transitive", + "resolved": "2.1.2", + "contentHash": "7tSHAzX8GWKy0qrW6OgQWD7kAZiqzhq+m1503qczuwuK6ZYhOGCQUxw+F3F4KkRM70aB6RMslsRVSCFeouIehw==", + "dependencies": { + "Microsoft.CSharp": "4.3.0" + } + }, + "Newtonsoft.Json": { + "type": "Transitive", + "resolved": "13.0.3", + "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==" + }, + "NJsonSchema": { + "type": "Transitive", + "resolved": "10.8.0", + "contentHash": "lChjsLWaxyvElh4WJjVhdIiCtx7rimYGFTxtSi2pAkZf0ZnKaXYIX484HCVyzbDDHejDZPgOrcfAJ3kqNSTONw==", + "dependencies": { + "Namotion.Reflection": "2.1.0", + "Newtonsoft.Json": "9.0.1" + } + }, + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.2", + "contentHash": "7VSGO0URRKoMEAq0Sc9cRz8mb6zbyx/BZDEWhgPdzzpmFhkam3fJ1DAGWFXBI4nGlma+uPKpfuMQP5LXRnOH5g==" + }, + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.2", + "contentHash": "0oAaTAm6e2oVH+/Zttt0cuhGaePQYKII1dY8iaqP7CvOpVKgLybKRFvQjXR2LtxXOXTVPNv14j0ot8uV+HrUmw==" + }, + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.2", + "contentHash": "G24ibsCNi5Kbz0oXWynBoRgtGvsw5ZSVEWjv13/KiCAM8C6wz9zzcCniMeQFIkJ2tasjo2kXlvlBZhplL51kGg==" + }, + "runtime.linux-arm.runtime.native.System.IO.Ports": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "CBvgRaF+M0xGLDv2Geb/0v0LEADheH8aK72GRAUJdnqnJVsQO60ki1XO8M3keEhnjm+T5NvLm41pNXAVYAPiSg==" + }, + "runtime.linux-arm64.runtime.native.System.IO.Ports": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "5VCyRCtCIYU8FR/W8oo7ouFuJ8tmAg9ddsuXhfCKZfZrbaVZSKxkmNBa6fxkfYPueD0jQfOvwFBmE5c6zalCSw==" + }, + "runtime.linux-x64.runtime.native.System.IO.Ports": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "DV9dWDUs23OoZqMWl5IhLr3D+b9koDiSHQxFKdYgWnQbnthv8c/yDjrlrI8nMrDc71RAKCO8jlUojzuPMX04gg==" + }, + "runtime.native.System": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "c/qWt2LieNZIj1jGnVNsE2Kl23Ya2aSTBuXMD6V7k9KWr6l16Tqdwq+hJScEpWER9753NWC8h96PaVNY5Ld7Jw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0" + } + }, + "runtime.native.System.Data.SqlClient.sni": { + "type": "Transitive", + "resolved": "4.7.0", + "contentHash": "9kyFSIdN3T0qjDQ2R0HRXYIhS3l5psBzQi6qqhdLz+SzFyEy4sVxNOke+yyYv8Cu8rPER12c3RDjLT8wF3WBYQ==", + "dependencies": { + "runtime.win-arm64.runtime.native.System.Data.SqlClient.sni": "4.4.0", + "runtime.win-x64.runtime.native.System.Data.SqlClient.sni": "4.4.0", + "runtime.win-x86.runtime.native.System.Data.SqlClient.sni": "4.4.0" + } + }, + "runtime.native.System.IO.Ports": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "L4Ivegqc3B0Fee7VifFy2JST9nndm+uvJ0viLIZUaImDfnr+JmRin9Tbqd56KuMtm0eVxHpNOWZBPtKrA/1h5Q==", + "dependencies": { + "runtime.linux-arm.runtime.native.System.IO.Ports": "7.0.0", + "runtime.linux-arm64.runtime.native.System.IO.Ports": "7.0.0", + "runtime.linux-x64.runtime.native.System.IO.Ports": "7.0.0", + "runtime.osx-arm64.runtime.native.System.IO.Ports": "7.0.0", + "runtime.osx-x64.runtime.native.System.IO.Ports": "7.0.0" + } + }, + "runtime.native.System.Net.Http": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "ZVuZJqnnegJhd2k/PtAbbIcZ3aZeITq3sj06oKfMBSfphW3HDmk/t4ObvbOk/JA/swGR0LNqMksAh/f7gpTROg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0" + } + }, + "runtime.native.System.Security.Cryptography.Apple": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "DloMk88juo0OuOWr56QG7MNchmafTLYWvABy36izkrLI5VledI0rq28KGs1i9wbpeT9NPQrx/wTf8U2vazqQ3Q==", + "dependencies": { + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "4.3.0" + } + }, + "runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.2", + "contentHash": "QR1OwtwehHxSeQvZKXe+iSd+d3XZNkEcuWMFYa2i0aG1l+lR739HPicKMlTbJst3spmeekDVBUS7SeS26s4U/g==", + "dependencies": { + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.2", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.2", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.2", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.2", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.2", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.2", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.2", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.2", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.2", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.2" + } + }, + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.2", + "contentHash": "I+GNKGg2xCHueRd1m9PzeEW7WLbNNLznmTuEi8/vZX71HudUbx1UTwlGkiwMri7JLl8hGaIAWnA/GONhu+LOyQ==" + }, + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.2", + "contentHash": "1Z3TAq1ytS1IBRtPXJvEUZdVsfWfeNEhBkbiOCGEl9wwAfsjP2lz3ZFDx5tq8p60/EqbS0HItG5piHuB71RjoA==" + }, + "runtime.osx-arm64.runtime.native.System.IO.Ports": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "jFwh4sKSXZ7al5XrItEO4GdGWa6XNxvNx+LhEHjrSzOwawO1znwJ+Dy+VjnrkySX9Qi4bnHNLoiqOXbqMuka4g==" + }, + "runtime.osx-x64.runtime.native.System.IO.Ports": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "X4LrHEfke/z9+z+iuVr35NlkhdZldY8JGNMYUN+sfPK/U/6TcE+vP44I0Yv0ir1v0bqIzq3v6Qdv1c1vmp8s4g==" + }, + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "kVXCuMTrTlxq4XOOMAysuNwsXWpYeboGddNGpIgNSZmv1b6r/s/DPk0fYMB7Q5Qo4bY68o48jt4T4y5BVecbCQ==" + }, + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.2", + "contentHash": "6mU/cVmmHtQiDXhnzUImxIcDL48GbTk+TsptXyJA+MIOG9LRjPoAQC/qBFB7X+UNyK86bmvGwC8t+M66wsYC8w==" + }, + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.2", + "contentHash": "vjwG0GGcTW/PPg6KVud8F9GLWYuAV1rrw1BKAqY0oh4jcUqg15oYF1+qkGR2x2ZHM4DQnWKQ7cJgYbfncz/lYg==" + }, + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.2", + "contentHash": "7KMFpTkHC/zoExs+PwP8jDCWcrK9H6L7soowT80CUx3e+nxP/AFnq0AQAW5W76z2WYbLAYCRyPfwYFG6zkvQRw==" + }, + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.2", + "contentHash": "xrlmRCnKZJLHxyyLIqkZjNXqgxnKdZxfItrPkjI+6pkRo5lHX8YvSZlWrSI5AVwLMi4HbNWP7064hcAWeZKp5w==" + }, + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.2", + "contentHash": "leXiwfiIkW7Gmn7cgnNcdtNAU70SjmKW3jxGj1iKHOvdn0zRWsgv/l2OJUO5zdGdiv2VRFnAsxxhDgMzofPdWg==" + }, + "runtime.win-arm64.runtime.native.System.Data.SqlClient.sni": { + "type": "Transitive", + "resolved": "4.4.0", + "contentHash": "LbrynESTp3bm5O/+jGL8v0Qg5SJlTV08lpIpFesXjF6uGNMWqFnUQbYBJwZTeua6E/Y7FIM1C54Ey1btLWupdg==" + }, + "runtime.win-x64.runtime.native.System.Data.SqlClient.sni": { + "type": "Transitive", + "resolved": "4.4.0", + "contentHash": "38ugOfkYJqJoX9g6EYRlZB5U2ZJH51UP8ptxZgdpS07FgOEToV+lS11ouNK2PM12Pr6X/PpT5jK82G3DwH/SxQ==" + }, + "runtime.win-x86.runtime.native.System.Data.SqlClient.sni": { + "type": "Transitive", + "resolved": "4.4.0", + "contentHash": "YhEdSQUsTx+C8m8Bw7ar5/VesXvCFMItyZF7G1AUY+OM0VPZUOeAVpJ4Wl6fydBGUYZxojTDR3I6Bj/+BPkJNA==" + }, + "Sarif.Sdk": { + "type": "Transitive", + "resolved": "2.4.16", + "contentHash": "ikJcKaMnwEvlqmxD3rtMn4ksYr8tvWdid7uzq4JFPcASwz21ErE1wF2CHbY/orlxt+E7uhZ4zBblgk5rdflfpg==", + "dependencies": { + "Newtonsoft.Json": "13.0.1", + "System.Collections.Immutable": "5.0.0" + } + }, + "System.CodeDom": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "GLltyqEsE5/3IE+zYRP5sNa1l44qKl9v+bfdMcwg+M9qnQf47wK3H0SUR/T+3N4JEQXF3vV4CSuuo0rsg+nq2A==" + }, + "System.Collections": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "3Dcj85/TBdVpL5Zr+gEEBUuFe2icOnLalmEh9hfck1PTYbbyWuZgh4fmm2ysCLTrqLQw6t3TgTyJ+VLp+Qb+Lw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Collections.Concurrent": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "ztl69Xp0Y/UXCL+3v3tEU+lIy+bvjKNUmopn1wep/a291pVPK7dxBd6T7WnlQqRog+d1a/hSsgRsmFnIBKTPLQ==", + "dependencies": { + "System.Collections": "4.3.0", + "System.Diagnostics.Debug": "4.3.0", + "System.Diagnostics.Tracing": "4.3.0", + "System.Globalization": "4.3.0", + "System.Reflection": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Threading": "4.3.0", + "System.Threading.Tasks": "4.3.0" + } + }, + "System.Collections.Immutable": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "l4zZJ1WU2hqpQQHXz1rvC3etVZN+2DLmQMO79FhOTZHMn8tDRr+WU287sbomD0BETlmKDn0ygUgVy9k5xkkJdA==", + "dependencies": { + "System.Runtime.CompilerServices.Unsafe": "6.0.0" + } + }, + "System.ComponentModel.Annotations": { + "type": "Transitive", + "resolved": "4.4.0", + "contentHash": "29K3DQ+IGU7LBaMjTo7SI7T7X/tsMtLvz1p56LJ556Iu0Dw3pKZw5g8yCYCWMRxrOF0Hr0FU0FwW0o42y2sb3A==" + }, + "System.ComponentModel.Composition": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "orv0h38ZVPCPo/FW0LGv8/TigXwX8cIwXeQcaNYhikkqELDm8sUFLMcof/Sjcq5EvYCm5NA7MV3hG4u75H44UQ==" + }, + "System.ComponentModel.Composition.Registration": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "yy/xYOznnc7Hfg2/LeVqAMlJGv1v7b1ILxFShzx5PWUv53PwU0MaKPG8Dh9DC3gxayzw44UVuQJImhw7LtMKlw==", + "dependencies": { + "System.ComponentModel.Composition": "7.0.0", + "System.Reflection.Context": "7.0.0" + } + }, + "System.Configuration.ConfigurationManager": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "WvRUdlL1lB0dTRZSs5XcQOd5q9MYNk90GkbmRmiCvRHThWiojkpGqWdmEDJdXyHbxG/BhE5hmVbMfRLXW9FJVA==", + "dependencies": { + "System.Diagnostics.EventLog": "7.0.0", + "System.Security.Cryptography.ProtectedData": "7.0.0", + "System.Security.Permissions": "7.0.0" + } + }, + "System.Data.Odbc": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "siwu7NoCsfHa9bfw2a2wSeTt2c/rhk3X8I28nJln1dlxdW3KqhRp0aW87yH1XkCo9h8zO1qcIfdTHO7YvvWLEA==", + "dependencies": { + "System.Text.Encoding.CodePages": "7.0.0" + } + }, + "System.Data.OleDb": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "bhAs+5X5acgg3zQ6N4HqxqfwwmqWJzgt54BC8iwygcqa2jktxDFzxwN83GNvqgoTcTs2tenDS/jmhC+AQsmcyg==", + "dependencies": { + "System.Configuration.ConfigurationManager": "7.0.0", + "System.Diagnostics.PerformanceCounter": "7.0.0" + } + }, + "System.Data.SqlClient": { + "type": "Transitive", + "resolved": "4.8.5", + "contentHash": "fRqxut4lrndPHrXD+ht1XRmCL3obuKldm4XjCRYS9p5f7FSR7shBxAwTkDrpFMsHC9BhNgjjmUtiIjvehn5zkg==", + "dependencies": { + "Microsoft.Win32.Registry": "4.7.0", + "System.Security.Principal.Windows": "4.7.0", + "runtime.native.System.Data.SqlClient.sni": "4.7.0" + } + }, + "System.Diagnostics.Debug": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "ZUhUOdqmaG5Jk3Xdb8xi5kIyQYAA4PnTNlHx1mu9ZY3qv4ELIdKbnL/akbGaKi2RnNUWaZsAs31rvzFdewTj2g==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Diagnostics.DiagnosticSource": { + "type": "Transitive", + "resolved": "7.0.2", + "contentHash": "hYr3I9N9811e0Bjf2WNwAGGyTuAFbbTgX1RPLt/3Wbm68x3IGcX5Cl75CMmgT6WlNwLQ2tCCWfqYPpypjaf2xA==" + }, + "System.Diagnostics.EventLog": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "eUDP47obqQm3SFJfP6z+Fx2nJ4KKTQbXB4Q9Uesnzw9SbYdhjyoGXuvDn/gEmFY6N5Z3bFFbpAQGA7m6hrYJCw==" + }, + "System.Diagnostics.PerformanceCounter": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "L+zIMEaXp1vA4wZk1KLMpk6tvU0xy94R0IfmhkmTWeC4KwShsmAfbg5I19LgjsCTYp6GVdXZ2aHluVWL0QqBdA==", + "dependencies": { + "System.Configuration.ConfigurationManager": "7.0.0" + } + }, + "System.Diagnostics.Tracing": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "rswfv0f/Cqkh78rA5S8eN8Neocz234+emGCtTF3lxPY96F+mmmUen6tbn0glN6PMvlKQb9bPAY5e9u7fgPTkKw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.DirectoryServices": { + "type": "Transitive", + "resolved": "7.0.1", + "contentHash": "Z4FVdUJEVXbf7/f/hU6cFZDtxN5ozUVKJMzXoHmC+GCeTcqzlxqmWtxurejxG3K+kZ6H0UKwNshoK1CYnmJ1sg==", + "dependencies": { + "System.Security.Permissions": "7.0.0" + } + }, + "System.DirectoryServices.AccountManagement": { + "type": "Transitive", + "resolved": "7.0.1", + "contentHash": "UNytHYwA5IF55WQhashsMG57ize83JUGJxD8YJlOyO9ZlMTOD4Nt7y+A6mvmrU/swDoYWaVL+TNwE6hk9lyvbA==", + "dependencies": { + "System.Configuration.ConfigurationManager": "7.0.0", + "System.DirectoryServices": "7.0.1", + "System.DirectoryServices.Protocols": "7.0.1" + } + }, + "System.DirectoryServices.Protocols": { + "type": "Transitive", + "resolved": "7.0.1", + "contentHash": "t9hsL+UYRzNs30pnT2Tdx6ngX8McFUjru0a0ekNgu/YXfkXN+dx5OvSEv0/p7H2q3pdJLH7TJPWX7e55J8QB9A==" + }, + "System.Drawing.Common": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "KIX+oBU38pxkKPxvLcLfIkOV5Ien8ReN78wro7OF5/erwcmortzeFx+iBswlh2Vz6gVne0khocQudGwaO1Ey6A==", + "dependencies": { + "Microsoft.Win32.SystemEvents": "7.0.0" + } + }, + "System.Formats.Asn1": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "+nfpV0afLmvJW8+pLlHxRjz3oZJw4fkyU9MMEaMhCsHi/SN9bGF9q79ROubDiwTiCHezmK0uCWkPP7tGFP/4yg==" + }, + "System.Globalization": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "kYdVd2f2PAdFGblzFswE4hkNANJBKRmsfa2X5LG2AcWE1c7/4t0pYae1L8vfZ5xvE2nK/R9JprtToA61OSHWIg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Globalization.Calendars": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "GUlBtdOWT4LTV3I+9/PJW+56AnnChTaOqqTLFtdmype/L500M2LIyXgmtd9X2P2VOkmJd5c67H5SaC2QcL1bFA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Globalization": "4.3.0", + "System.Runtime": "4.3.0" + } + }, + "System.Globalization.Extensions": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "FhKmdR6MPG+pxow6wGtNAWdZh7noIOpdD5TwQ3CprzgIE1bBBoim0vbR1+AWsWjQmU7zXHgQo4TWSP6lCeiWcQ==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "System.Globalization": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.InteropServices": "4.3.0" + } + }, + "System.IO": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "3qjaHvxQPDpSOYICjUoTsmoq5u6QJAFRUITgeT/4gqkF1bajbSmb1kwSxEA8AHlofqgcKJcM8udgieRNhaJ5Cg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0", + "System.Text.Encoding": "4.3.0", + "System.Threading.Tasks": "4.3.0" + } + }, + "System.IO.FileSystem": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "3wEMARTnuio+ulnvi+hkRNROYwa1kylvYahhcLk4HSoVdl+xxTFVeVlYOfLwrDPImGls0mDqbMhrza8qnWPTdA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.IO": "4.3.0", + "System.IO.FileSystem.Primitives": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Text.Encoding": "4.3.0", + "System.Threading.Tasks": "4.3.0" + } + }, + "System.IO.FileSystem.Primitives": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "6QOb2XFLch7bEc4lIcJH49nJN2HV+OC3fHDgsLVsBVBk3Y4hFAnOBGzJ2lUu7CyDDFo9IBWkSsnbkT6IBwwiMw==", + "dependencies": { + "System.Runtime": "4.3.0" + } + }, + "System.IO.Packaging": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "+j5ezLP7785/pd4taKQhXAWsymsIW2nTnE/U3/jpGZzcJx5lip6qkj6UrxSE7ZYZfL0GaLuymwGLqwJV/c7O7Q==" + }, + "System.IO.Ports": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "0nWQjM5IofaIGpvkifN+LLuYwBG6BHlpmphLhhOJepcW12G8qToGuNDRgBzeTVBZzp33wVsESSZ8hUOCfq+8QA==", + "dependencies": { + "runtime.native.System.IO.Ports": "7.0.0" + } + }, + "System.Linq": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "5DbqIUpsDp0dFftytzuMmc0oeMdQwjcP/EWxsksIz/w1TcFRkZ3yKKz0PqiYFMmEwPSWw+qNVqD7PJ889JzHbw==", + "dependencies": { + "System.Collections": "4.3.0", + "System.Diagnostics.Debug": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0" + } + }, + "System.Management": { + "type": "Transitive", + "resolved": "7.0.2", + "contentHash": "/qEUN91mP/MUQmJnM5y5BdT7ZoPuVrtxnFlbJ8a3kBJGhe2wCzBfnPFtK2wTtEEcf3DMGR9J00GZZfg6HRI6yA==", + "dependencies": { + "System.CodeDom": "7.0.0" + } + }, + "System.Memory": { + "type": "Transitive", + "resolved": "4.5.5", + "contentHash": "XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw==" + }, + "System.Net.Http": { + "type": "Transitive", + "resolved": "4.3.3", + "contentHash": "7rCqIbkC/P2+A00NoDH5gnvFhADmX7Dc4INvsOajbU1MVhktE9vZNrjPtF82N6Uo7obK+yzlrPUv/M+snnN/9w==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "System.Collections": "4.3.0", + "System.Diagnostics.Debug": "4.3.0", + "System.Diagnostics.DiagnosticSource": "4.3.0", + "System.Diagnostics.Tracing": "4.3.0", + "System.Globalization": "4.3.0", + "System.Globalization.Extensions": "4.3.0", + "System.IO": "4.3.0", + "System.IO.FileSystem": "4.3.0", + "System.Net.Primitives": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Security.Cryptography.Algorithms": "4.3.0", + "System.Security.Cryptography.Encoding": "4.3.0", + "System.Security.Cryptography.OpenSsl": "4.3.0", + "System.Security.Cryptography.Primitives": "4.3.0", + "System.Security.Cryptography.X509Certificates": "4.3.0", + "System.Text.Encoding": "4.3.0", + "System.Threading": "4.3.0", + "System.Threading.Tasks": "4.3.0", + "runtime.native.System": "4.3.0", + "runtime.native.System.Net.Http": "4.3.0", + "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.2" + } + }, + "System.Net.Http.WinHttpHandler": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "DJjlxpGJMHd7WxTo/WRb9q1tahjZvjer7Xo4rsOKAocrwewpA9L0YLxcKEx0nHQnruiWGgbngSzYt3YUwxc+2A==" + }, + "System.Net.Primitives": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "qOu+hDwFwoZPbzPvwut2qATe3ygjeQBDQj91xlsaqGFQUI5i4ZnZb8yyQuLGpDGivEPIt8EJkd1BVzVoP31FXA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0", + "System.Runtime.Handles": "4.3.0" + } + }, + "System.Numerics.Vectors": { + "type": "Transitive", + "resolved": "4.5.0", + "contentHash": "QQTlPTl06J/iiDbJCiepZ4H//BVraReU4O4EoRw1U02H5TLUIT7xn3GnDp9AXPSlJUDyFs4uWjWafNX6WrAojQ==" + }, + "System.Private.ServiceModel": { + "type": "Transitive", + "resolved": "4.10.2", + "contentHash": "bi2/w2EDXqxno8zfbt6vHcrpGw0Pav8tEMzmJraHwJvWYJd45wcqr7gNa2IUs91j4z+BNGMooStaWS6pm2Lq0A==", + "dependencies": { + "Microsoft.Bcl.AsyncInterfaces": "5.0.0", + "Microsoft.Extensions.ObjectPool": "5.0.10", + "System.Numerics.Vectors": "4.5.0", + "System.Reflection.DispatchProxy": "4.7.1", + "System.Security.Cryptography.Xml": "6.0.1", + "System.Security.Principal.Windows": "5.0.0" + } + }, + "System.Reflection": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "KMiAFoW7MfJGa9nDFNcfu+FpEdiHpWgTcS2HdMpDvt9saK3y/G4GwprPyzqjFH9NTaGPQeWNHU+iDlDILj96aQ==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.IO": "4.3.0", + "System.Reflection.Primitives": "4.3.0", + "System.Runtime": "4.3.0" + } + }, + "System.Reflection.Context": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "rVf4vEyGQphXTITF39uXlgTcp8Ekcu2aNwxyVLU7fDyNOk0W+/PPpj9PoC2cFL4wgJZJltiss5eQptE2C4f1Sw==" + }, + "System.Reflection.DispatchProxy": { + "type": "Transitive", + "resolved": "4.7.1", + "contentHash": "C1sMLwIG6ILQ2bmOT4gh62V6oJlyF4BlHcVMrOoor49p0Ji2tA8QAoqyMcIhAdH6OHKJ8m7BU+r4LK2CUEOKqw==" + }, + "System.Reflection.Emit": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "228FG0jLcIwTVJyz8CLFKueVqQK36ANazUManGaJHkO0icjiIypKW7YLWLIWahyIkdh5M7mV2dJepllLyA1SKg==", + "dependencies": { + "System.IO": "4.3.0", + "System.Reflection": "4.3.0", + "System.Reflection.Emit.ILGeneration": "4.3.0", + "System.Reflection.Primitives": "4.3.0", + "System.Runtime": "4.3.0" + } + }, + "System.Reflection.Emit.ILGeneration": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "59tBslAk9733NXLrUJrwNZEzbMAcu8k344OYo+wfSVygcgZ9lgBdGIzH/nrg3LYhXceynyvTc8t5/GD4Ri0/ng==", + "dependencies": { + "System.Reflection": "4.3.0", + "System.Reflection.Primitives": "4.3.0", + "System.Runtime": "4.3.0" + } + }, + "System.Reflection.Metadata": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "5NecZgXktdGg34rh1OenY1rFNDCI8xSjFr+Z4OU4cU06AQHUdRnIIEeWENu3Wl4YowbzkymAIMvi3WyK9U53pQ==" + }, + "System.Reflection.Primitives": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "5RXItQz5As4xN2/YUDxdpsEkMhvw3e6aNveFXUn4Hl/udNTCNhnKp8lT9fnc3MhvGKh1baak5CovpuQUXHAlIA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Resources.ResourceManager": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "/zrcPkkWdZmI4F92gL/TPumP98AVDu/Wxr3CSJGQQ+XN6wbRZcyfSKVoPo17ilb3iOr0cCRqJInGwNMolqhS8A==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Globalization": "4.3.0", + "System.Reflection": "4.3.0", + "System.Runtime": "4.3.0" + } + }, + "System.Runtime": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "JufQi0vPQ0xGnAczR13AUFglDyVYt4Kqnz1AZaiKZ5+GICq0/1MH/mO/eAJHt/mHW1zjKBJd7kV26SrxddAhiw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0" + } + }, + "System.Runtime.Caching": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "M0riW7Zgxca3Elp1iZVhzH7PWWT5bPSrdMFGCAGoH1n9YLuXOYE78ryui051Icf3swWWa8feBRoSxOCYwgMy8w==", + "dependencies": { + "System.Configuration.ConfigurationManager": "7.0.0" + } + }, + "System.Runtime.CompilerServices.Unsafe": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg==" + }, + "System.Runtime.Extensions": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "guW0uK0fn5fcJJ1tJVXYd7/1h5F+pea1r7FLSOz/f8vPEqbR2ZAknuRDvTQ8PzAilDveOxNjSfr0CHfIQfFk8g==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Runtime.Handles": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "OKiSUN7DmTWeYb3l51A7EYaeNMnvxwE249YtZz7yooT4gOZhmTjIn48KgSsw2k2lYdLgTKNJw/ZIfSElwDRVgg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Runtime.InteropServices": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "uv1ynXqiMK8mp1GM3jDqPCFN66eJ5w5XNomaK2XD+TuCroNTLFGeZ+WCmBMcBDyTFKou3P6cR6J/QsaqDp7fGQ==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Reflection": "4.3.0", + "System.Reflection.Primitives": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Handles": "4.3.0" + } + }, + "System.Runtime.Numerics": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "yMH+MfdzHjy17l2KESnPiF2dwq7T+xLnSJar7slyimAkUh/gTrS9/UQOtv7xarskJ2/XDSNvfLGOBQPjL7PaHQ==", + "dependencies": { + "System.Globalization": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0" + } + }, + "System.Security.AccessControl": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "AUADIc0LIEQe7MzC+I0cl0rAT8RrTAKFHl53yHjEUzNVIaUlhFY11vc2ebiVJzVBuOzun6F7FBA+8KAbGTTedQ==" + }, + "System.Security.Cryptography.Algorithms": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "W1kd2Y8mYSCgc3ULTAZ0hOP2dSdG5YauTb1089T0/kRcN2MpSAW1izOFROrJgxSlMn3ArsgHXagigyi+ibhevg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "System.Collections": "4.3.0", + "System.IO": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Runtime.Numerics": "4.3.0", + "System.Security.Cryptography.Encoding": "4.3.0", + "System.Security.Cryptography.Primitives": "4.3.0", + "System.Text.Encoding": "4.3.0", + "runtime.native.System.Security.Cryptography.Apple": "4.3.0", + "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" + } + }, + "System.Security.Cryptography.Cng": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "03idZOqFlsKRL4W+LuCpJ6dBYDUWReug6lZjBa3uJWnk5sPCUXckocevTaUA8iT/MFSrY/2HXkOt753xQ/cf8g==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "System.IO": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Security.Cryptography.Algorithms": "4.3.0", + "System.Security.Cryptography.Encoding": "4.3.0", + "System.Security.Cryptography.Primitives": "4.3.0", + "System.Text.Encoding": "4.3.0" + } + }, + "System.Security.Cryptography.Csp": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "X4s/FCkEUnRGnwR3aSfVIkldBmtURMhmexALNTwpjklzxWU7yjMk7GHLKOZTNkgnWnE0q7+BCf9N2LVRWxewaA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "System.IO": "4.3.0", + "System.Reflection": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Security.Cryptography.Algorithms": "4.3.0", + "System.Security.Cryptography.Encoding": "4.3.0", + "System.Security.Cryptography.Primitives": "4.3.0", + "System.Text.Encoding": "4.3.0", + "System.Threading": "4.3.0" + } + }, + "System.Security.Cryptography.Encoding": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "1DEWjZZly9ae9C79vFwqaO5kaOlI5q+3/55ohmq/7dpDyDfc8lYe7YVxJUZ5MF/NtbkRjwFRo14yM4OEo9EmDw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "System.Collections": "4.3.0", + "System.Collections.Concurrent": "4.3.0", + "System.Linq": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Security.Cryptography.Primitives": "4.3.0", + "System.Text.Encoding": "4.3.0", + "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" + } + }, + "System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "h4CEgOgv5PKVF/HwaHzJRiVboL2THYCou97zpmhjghx5frc7fIvlkY1jL+lnIQyChrJDMNEXS6r7byGif8Cy4w==", + "dependencies": { + "System.Collections": "4.3.0", + "System.IO": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Runtime.Numerics": "4.3.0", + "System.Security.Cryptography.Algorithms": "4.3.0", + "System.Security.Cryptography.Encoding": "4.3.0", + "System.Security.Cryptography.Primitives": "4.3.0", + "System.Text.Encoding": "4.3.0", + "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" + } + }, + "System.Security.Cryptography.Pkcs": { + "type": "Transitive", + "resolved": "7.0.3", + "contentHash": "yhwEHH5Gzl/VoADrXtt5XC95OFoSjNSWLHNutE7GwdOgefZVRvEXRSooSpL8HHm3qmdd9epqzsWg28UJemt22w==", + "dependencies": { + "System.Formats.Asn1": "7.0.0" + } + }, + "System.Security.Cryptography.Primitives": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "7bDIyVFNL/xKeFHjhobUAQqSpJq9YTOpbEs6mR233Et01STBMXNAc/V+BM6dwYGc95gVh/Zf+iVXWzj3mE8DWg==", + "dependencies": { + "System.Diagnostics.Debug": "4.3.0", + "System.Globalization": "4.3.0", + "System.IO": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Threading": "4.3.0", + "System.Threading.Tasks": "4.3.0" + } + }, + "System.Security.Cryptography.ProtectedData": { + "type": "Transitive", + "resolved": "7.0.1", + "contentHash": "3evI3sBfKqwYSwuBcYgShbmEgtXcg8N5Qu+jExLdkBXPty2yGDXq5m1/4sx9Exb8dqdeMPUs/d9DQ0wy/9Adwg==" + }, + "System.Security.Cryptography.X509Certificates": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "t2Tmu6Y2NtJ2um0RtcuhP7ZdNNxXEgUm2JeoA/0NvlMjAhKCnM1NX07TDl3244mVp3QU6LPEhT3HTtH1uF7IYw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "System.Collections": "4.3.0", + "System.Diagnostics.Debug": "4.3.0", + "System.Globalization": "4.3.0", + "System.Globalization.Calendars": "4.3.0", + "System.IO": "4.3.0", + "System.IO.FileSystem": "4.3.0", + "System.IO.FileSystem.Primitives": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Runtime.Numerics": "4.3.0", + "System.Security.Cryptography.Algorithms": "4.3.0", + "System.Security.Cryptography.Cng": "4.3.0", + "System.Security.Cryptography.Csp": "4.3.0", + "System.Security.Cryptography.Encoding": "4.3.0", + "System.Security.Cryptography.OpenSsl": "4.3.0", + "System.Security.Cryptography.Primitives": "4.3.0", + "System.Text.Encoding": "4.3.0", + "System.Threading": "4.3.0", + "runtime.native.System": "4.3.0", + "runtime.native.System.Net.Http": "4.3.0", + "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" + } + }, + "System.Security.Cryptography.Xml": { + "type": "Transitive", + "resolved": "7.0.1", + "contentHash": "MCxBCtH0GrDuvU63ZODwQHQZPchb24pUAX3MfZ6b13qg246ZD10PRdOvay8C9HBPfCXkymUNwFPEegud7ax2zg==", + "dependencies": { + "System.Security.Cryptography.Pkcs": "7.0.0" + } + }, + "System.Security.Permissions": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "Vmp0iRmCEno9BWiskOW5pxJ3d9n+jUqKxvX4GhLwFhnQaySZmBN2FuC0N5gjFHgyFMUjC5sfIJ8KZfoJwkcMmA==", + "dependencies": { + "System.Windows.Extensions": "7.0.0" + } + }, + "System.Security.Principal.Windows": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "t0MGLukB5WAVU9bO3MGzvlGnyJPgUlcwerXn1kzBRjwLKixT96XV0Uza41W49gVd8zEMFu9vQEFlv0IOrytICA==" + }, + "System.ServiceModel.Duplex": { + "type": "Transitive", + "resolved": "4.10.2", + "contentHash": "FjFGC7DOTNrkItAYQZVyrDTqXezqFIzmdKL1YaN1ZHdbq5oeyo//iyR+OGXLAfbBxyHultFgV1DetarYBSvIxw==", + "dependencies": { + "System.Private.ServiceModel": "4.10.2", + "System.ServiceModel.Primitives": "4.10.2" + } + }, + "System.ServiceModel.Http": { + "type": "Transitive", + "resolved": "4.10.2", + "contentHash": "1AhiJwPc+90GjBd/sDkT93RVuRj688ZQInXzWfd56AEjDzWieUcAUh9ppXhRuEVpT+x48D5GBYJc1VxDP4IT+Q==", + "dependencies": { + "System.Private.ServiceModel": "4.10.2", + "System.ServiceModel.Primitives": "4.10.2" + } + }, + "System.ServiceModel.NetTcp": { + "type": "Transitive", + "resolved": "4.10.2", + "contentHash": "jWNKeXfccRqkbjTkVyKD0QBBO+FnYWQrNrJByT+oOab17UBLfvUdt1kLhPe3qZalIlBoqvRFtw6yR/oRs5r3Vw==", + "dependencies": { + "System.Private.ServiceModel": "4.10.2", + "System.ServiceModel.Primitives": "4.10.2" + } + }, + "System.ServiceModel.Primitives": { + "type": "Transitive", + "resolved": "4.10.2", + "contentHash": "8QOguIqHtWYywBt7SubPhdICE2LClHzqOMDy0LQIui4T3QJOae7g6UR+alCW61nEufYNtO8Uss41EbXqD8hdww==", + "dependencies": { + "System.Private.ServiceModel": "4.10.2" + } + }, + "System.ServiceModel.Security": { + "type": "Transitive", + "resolved": "4.10.2", + "contentHash": "BdYdPHRJLqwFAJYbmdl9M5qqjJ6O7TVw9AMcW0h7+oc0ah0ufYuSIrN75BhNNAIcaYcviWzC3PDT5H0QkNX7ug==", + "dependencies": { + "System.Private.ServiceModel": "4.10.2", + "System.ServiceModel.Primitives": "4.10.2" + } + }, + "System.ServiceModel.Syndication": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "V3q1Jr3KWo+i201/vUUPfg83rjJLhL5+ROh16PtPhaUJRHwoEBoGWtg0r6pFBRPaDqNY6hXvNgHktDj0gvMEpA==" + }, + "System.ServiceProcess.ServiceController": { + "type": "Transitive", + "resolved": "7.0.1", + "contentHash": "rPfXTJzYU46AmWYXRATQzQQ01hICrkl3GuUHgpAr9mnUwAVSsga5x3mBxanFPlJBV9ilzqMXbQyDLJQAbyTnSw==", + "dependencies": { + "System.Diagnostics.EventLog": "7.0.0" + } + }, + "System.Speech": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "7E0uB92Cx2sXR67HW9rMKJqDACdLuz9t3I3OwZUFDzAgwKXWuY6CYeRT/NiypHcyZO2be9+0H0w0M6fn7HQtgQ==" + }, + "System.Text.Encoding": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "BiIg+KWaSDOITze6jGQynxg64naAPtqGHBwDrLaCtixsa5bKiR8dpPOHA7ge3C0JJQizJE+sfkz1wV+BAKAYZw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Text.Encoding.CodePages": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "LSyCblMpvOe0N3E+8e0skHcrIhgV2huaNcjUUEa8hRtgEAm36aGkRoC8Jxlb6Ra6GSfF29ftduPNywin8XolzQ==" + }, + "System.Text.Encodings.Web": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "OP6umVGxc0Z0MvZQBVigj4/U31Pw72ITihDWP9WiWDm+q5aoe0GaJivsfYGq53o6dxH7DcXWiCTl7+0o2CGdmg==" + }, + "System.Threading": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "VkUS0kOBcUf3Wwm0TSbrevDDZ6BlM+b/HRiapRFWjM5O0NS0LviG0glKmFK+hhPDd1XFeSdU1GmlLhb2CoVpIw==", + "dependencies": { + "System.Runtime": "4.3.0", + "System.Threading.Tasks": "4.3.0" + } + }, + "System.Threading.AccessControl": { + "type": "Transitive", + "resolved": "7.0.1", + "contentHash": "uh6LWSk8Dlp1cavk4XQYtDHOMZpSa5KiqM0VBiflhXWGT63RGV+NhNsVxiEykL4S/0LVcgy+/AxC5ITQ9QLo8w==" + }, + "System.Threading.Tasks": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "LbSxKEdOUhVe8BezB/9uOGGppt+nZf6e1VFyw6v3DN6lqitm0OSn2uXMOdtP0M3W4iMcqcivm2J6UgqiwwnXiA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Threading.Tasks.Extensions": { + "type": "Transitive", + "resolved": "4.5.4", + "contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==" + }, + "System.ValueTuple": { + "type": "Transitive", + "resolved": "4.5.0", + "contentHash": "okurQJO6NRE/apDIP23ajJ0hpiNmJ+f0BwOlB/cSqTLQlw5upkf+5+96+iG2Jw40G1fCVCyPz/FhIABUjMR+RQ==" + }, + "System.Web.Services.Description": { + "type": "Transitive", + "resolved": "4.10.2", + "contentHash": "AuovAoDhSaXAn7zhOboWW9G5P7LfTEEMOLzeZS87QuGZ5r4kFmJ3+NrzxeS6tumE+KQCQ/K5tNeTXCiVddErxQ==" + }, + "System.Windows.Extensions": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "bR4qdCmssMMbo9Fatci49An5B1UaVJZHKNq70PRgzoLYIlitb8Tj7ns/Xt5Pz1CkERiTjcVBDU2y1AVrPBYkaw==", + "dependencies": { + "System.Drawing.Common": "7.0.0" + } + }, + "YamlDotNet": { + "type": "Transitive", + "resolved": "13.7.1", + "contentHash": "X4m1PnFcJwvAj1sCDMntg/eZcX96CJLrWMiYnq41KqhFVZPuw63ZTSxIGqgdCwHWHvCAyTxheELC/VDf1HsU2A==" + }, + "Microsoft.PSRule.Badges": { + "type": "Project", + "dependencies": { + "Newtonsoft.Json": "[13.0.3, )" + } + }, + "Microsoft.PSRule.Core": { + "type": "Project", + "dependencies": { + "Manatee.Json": "[13.0.5, )", + "Microsoft.PSRule.Badges": "[0.0.1, )", + "Microsoft.PSRule.Types": "[0.0.1, )", + "Sarif.Sdk": "[2.4.16, )" + } + }, + "Microsoft.PSRule.SDK": { + "type": "Project", + "dependencies": { + "Microsoft.PSRule.Core": "[0.0.1, )" + } + }, + "Microsoft.PSRule.Types": { + "type": "Project", + "dependencies": { + "Newtonsoft.Json": "[13.0.3, )", + "YamlDotNet": "[13.7.1, )" + } + } + } + } +} \ No newline at end of file diff --git a/src/PSRule.Types/Converters/TypeConverter.cs b/src/PSRule.Types/Converters/TypeConverter.cs index 224a60b7ae..f4319b9329 100644 --- a/src/PSRule.Types/Converters/TypeConverter.cs +++ b/src/PSRule.Types/Converters/TypeConverter.cs @@ -4,279 +4,278 @@ using System.Collections; using Newtonsoft.Json.Linq; -namespace PSRule.Converters +namespace PSRule.Converters; + +internal static class TypeConverter { - internal static class TypeConverter - { - private const string PROPERTY_BASEOBJECT = "BaseObject"; + private const string PROPERTY_BASEOBJECT = "BaseObject"; - public static bool TryString(object o, out string? value) + public static bool TryString(object o, out string? value) + { + value = null; + if (o == null) return false; + if (o is string s) { - value = null; - if (o == null) return false; - if (o is string s) - { - value = s; - return true; - } - else if (o is JToken token && token.Type == JTokenType.String) - { - value = token.Value(); - return true; - } - else if (TryGetValue(o, PROPERTY_BASEOBJECT, out var baseValue) && baseValue is string s_baseValue) - { - value = s_baseValue; - return true; - } - return false; + value = s; + return true; } - - public static bool TryString(object o, bool convert, out string? value) + else if (o is JToken token && token.Type == JTokenType.String) { - if (TryString(o, out value) && value != null) - return true; - - if (convert && o is Enum evalue) - { - value = evalue.ToString(); - return true; - } - else if (convert && TryLong(o, false, out var l_value)) - { - value = l_value.ToString(Thread.CurrentThread.CurrentCulture); - return true; - } - else if (convert && TryBool(o, false, out var b_value)) - { - value = b_value.ToString(Thread.CurrentThread.CurrentCulture); - return true; - } - else if (convert && TryInt(o, false, out var i_value)) - { - value = i_value.ToString(Thread.CurrentThread.CurrentCulture); - return true; - } - return false; + value = token.Value(); + return true; + } + else if (TryGetValue(o, PROPERTY_BASEOBJECT, out var baseValue) && baseValue is string s_baseValue) + { + value = s_baseValue; + return true; } + return false; + } - public static bool TryArray(object o, out Array? value) + public static bool TryString(object o, bool convert, out string? value) + { + if (TryString(o, out value) && value != null) + return true; + + if (convert && o is Enum evalue) + { + value = evalue.ToString(); + return true; + } + else if (convert && TryLong(o, false, out var l_value)) + { + value = l_value.ToString(Thread.CurrentThread.CurrentCulture); + return true; + } + else if (convert && TryBool(o, false, out var b_value)) + { + value = b_value.ToString(Thread.CurrentThread.CurrentCulture); + return true; + } + else if (convert && TryInt(o, false, out var i_value)) { - value = null; - if (o is string) return false; - if (o is Array a) - value = a; + value = i_value.ToString(Thread.CurrentThread.CurrentCulture); + return true; + } + return false; + } - else if (o is JArray jArray) - value = jArray.Values().ToArray(); + public static bool TryArray(object o, out Array? value) + { + value = null; + if (o is string) return false; + if (o is Array a) + value = a; - else if (o is IEnumerable e) - value = e.OfType().ToArray(); + else if (o is JArray jArray) + value = jArray.Values().ToArray(); - return value != null; - } + else if (o is IEnumerable e) + value = e.OfType().ToArray(); - public static bool TryStringOrArray(object o, bool convert, out string[]? value) - { - // Handle single string - if (TryString(o, convert, value: out var s) && s != null) - { - value = new string[] { s }; - return true; - } + return value != null; + } - // Handle multiple strings - return TryStringArray(o, convert, out value); + public static bool TryStringOrArray(object o, bool convert, out string[]? value) + { + // Handle single string + if (TryString(o, convert, value: out var s) && s != null) + { + value = new string[] { s }; + return true; } - public static bool TryStringArray(object o, bool convert, out string[]? value) + // Handle multiple strings + return TryStringArray(o, convert, out value); + } + + public static bool TryStringArray(object o, bool convert, out string[]? value) + { + value = null; + if (o is Array array) { - value = null; - if (o is Array array) - { - value = new string[array.Length]; - for (var i = 0; i < array.Length; i++) - { - if (TryString(array.GetValue(i), convert, value: out var s) && s != null) - value[i] = s; - } - } - else if (o is JArray jArray) + value = new string[array.Length]; + for (var i = 0; i < array.Length; i++) { - value = new string[jArray.Count]; - for (var i = 0; i < jArray.Count; i++) - { - if (TryString(jArray[i], convert, out var s) && s != null) - value[i] = s; - } + if (TryString(array.GetValue(i), convert, value: out var s) && s != null) + value[i] = s; } - else if (o is IEnumerable enumerable) - { - value = enumerable.ToArray(); - } - else if (o is IEnumerable e) - { - value = e.OfType().ToArray(); - } - return value != null; } - - /// - /// Try to get an int from the existing object. - /// - public static bool TryInt(object o, bool convert, out int value) + else if (o is JArray jArray) { - if (o is int ivalue) - { - value = ivalue; - return true; - } - if (o is long lvalue && lvalue <= int.MaxValue && lvalue >= int.MinValue) - { - value = (int)lvalue; - return true; - } - else if (o is JToken token && token.Type == JTokenType.Integer) + value = new string[jArray.Count]; + for (var i = 0; i < jArray.Count; i++) { - value = token.Value(); - return true; + if (TryString(jArray[i], convert, out var s) && s != null) + value[i] = s; } - else if (convert && TryString(o, out var s) && int.TryParse(s, out ivalue)) - { - value = ivalue; - return true; - } - value = default; - return false; } + else if (o is IEnumerable enumerable) + { + value = enumerable.ToArray(); + } + else if (o is IEnumerable e) + { + value = e.OfType().ToArray(); + } + return value != null; + } - public static bool TryBool(object o, bool convert, out bool value) + /// + /// Try to get an int from the existing object. + /// + public static bool TryInt(object o, bool convert, out int value) + { + if (o is int ivalue) { - if (o is bool bvalue) - { - value = bvalue; - return true; - } - else if (o is JToken token && token.Type == JTokenType.Boolean) - { - value = token.Value(); - return true; - } - else if (convert && TryString(o, out var s) && bool.TryParse(s, out bvalue)) - { - value = bvalue; - return true; - } - else if (convert && TryLong(o, convert: false, out var lvalue)) - { - value = lvalue > 0; - return true; - } - value = default; - return false; + value = ivalue; + return true; + } + if (o is long lvalue && lvalue <= int.MaxValue && lvalue >= int.MinValue) + { + value = (int)lvalue; + return true; } + else if (o is JToken token && token.Type == JTokenType.Integer) + { + value = token.Value(); + return true; + } + else if (convert && TryString(o, out var s) && int.TryParse(s, out ivalue)) + { + value = ivalue; + return true; + } + value = default; + return false; + } - public static bool TryByte(object o, bool convert, out byte value) + public static bool TryBool(object o, bool convert, out bool value) + { + if (o is bool bvalue) { - if (o is byte bvalue) - { - value = bvalue; - return true; - } - else if (o is JToken token && token.Type == JTokenType.Integer) - { - value = token.Value(); - return true; - } - else if (convert && TryString(o, out var s) && byte.TryParse(s, out bvalue)) - { - value = bvalue; - return true; - } - value = default; - return false; + value = bvalue; + return true; + } + else if (o is JToken token && token.Type == JTokenType.Boolean) + { + value = token.Value(); + return true; + } + else if (convert && TryString(o, out var s) && bool.TryParse(s, out bvalue)) + { + value = bvalue; + return true; + } + else if (convert && TryLong(o, convert: false, out var lvalue)) + { + value = lvalue > 0; + return true; } + value = default; + return false; + } - public static bool TryLong(object o, bool convert, out long value) + public static bool TryByte(object o, bool convert, out byte value) + { + if (o is byte bvalue) { - if (o is byte b) - { - value = b; - return true; - } - else if (o is int i) - { - value = i; - return true; - } - else if (o is uint ui) - { - value = (long)ui; - return true; - } - else if (o is long l) - { - value = l; - return true; - } - else if (o is ulong ul && ul <= long.MaxValue) - { - value = (long)ul; - return true; - } - else if (o is JToken token && token.Type == JTokenType.Integer) - { - value = token.Value(); - return true; - } - else if (convert && TryString(o, out var s) && long.TryParse(s, out l)) - { - value = l; - return true; - } - value = default; - return false; + value = bvalue; + return true; + } + else if (o is JToken token && token.Type == JTokenType.Integer) + { + value = token.Value(); + return true; } + else if (convert && TryString(o, out var s) && byte.TryParse(s, out bvalue)) + { + value = bvalue; + return true; + } + value = default; + return false; + } - public static bool TryFloat(object o, bool convert, out float value) + public static bool TryLong(object o, bool convert, out long value) + { + if (o is byte b) { - if (o is float fvalue || (convert && o is string s && float.TryParse(s, out fvalue))) - { - value = fvalue; - return true; - } - else if (convert && o is int ivalue) - { - value = ivalue; - return true; - } - value = default; - return false; + value = b; + return true; + } + else if (o is int i) + { + value = i; + return true; + } + else if (o is uint ui) + { + value = (long)ui; + return true; + } + else if (o is long l) + { + value = l; + return true; + } + else if (o is ulong ul && ul <= long.MaxValue) + { + value = (long)ul; + return true; + } + else if (o is JToken token && token.Type == JTokenType.Integer) + { + value = token.Value(); + return true; } + else if (convert && TryString(o, out var s) && long.TryParse(s, out l)) + { + value = l; + return true; + } + value = default; + return false; + } - public static bool TryDouble(object o, bool convert, out double value) + public static bool TryFloat(object o, bool convert, out float value) + { + if (o is float fvalue || (convert && o is string s && float.TryParse(s, out fvalue))) { - if (o is double dvalue || (convert && o is string s && double.TryParse(s, out dvalue))) - { - value = dvalue; - return true; - } - value = default; - return false; + value = fvalue; + return true; + } + else if (convert && o is int ivalue) + { + value = ivalue; + return true; } + value = default; + return false; + } - private static bool TryGetValue(object o, string propertyName, out object? value) + public static bool TryDouble(object o, bool convert, out double value) + { + if (o is double dvalue || (convert && o is string s && double.TryParse(s, out dvalue))) { - value = null; - if (o == null) return false; + value = dvalue; + return true; + } + value = default; + return false; + } - var type = o.GetType(); - if (type.TryGetPropertyInfo(propertyName, out var propertyInfo) && propertyInfo != null) - { - value = propertyInfo.GetValue(o); - return true; - } - 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/Converters/Yaml/StringArrayConverter.cs b/src/PSRule.Types/Converters/Yaml/StringArrayConverter.cs index aebc495a9d..bd098a3cb5 100644 --- a/src/PSRule.Types/Converters/Yaml/StringArrayConverter.cs +++ b/src/PSRule.Types/Converters/Yaml/StringArrayConverter.cs @@ -5,42 +5,41 @@ using YamlDotNet.Core.Events; using YamlDotNet.Serialization; -namespace PSRule.Converters.Yaml +namespace PSRule.Converters.Yaml; + +/// +/// A YAML converter for deserializing a string array. +/// +public sealed class StringArrayConverter : IYamlTypeConverter { - /// - /// A YAML converter for deserializing a string array. - /// - public sealed class StringArrayConverter : IYamlTypeConverter + /// + bool IYamlTypeConverter.Accepts(Type type) { - /// - bool IYamlTypeConverter.Accepts(Type type) - { - return type == typeof(string[]); - } + return type == typeof(string[]); + } - /// - object? IYamlTypeConverter.ReadYaml(IParser parser, Type type) + /// + object? IYamlTypeConverter.ReadYaml(IParser parser, Type type) + { + if (parser.TryConsume(out _)) { - if (parser.TryConsume(out _)) - { - var result = new List(); - while (parser.TryConsume(out var scalar)) - result.Add(scalar.Value); + var result = new List(); + while (parser.TryConsume(out var scalar)) + result.Add(scalar.Value); - parser.Consume(); - return result.ToArray(); - } - else if (parser.TryConsume(out var scalar)) - { - return new string[] { scalar.Value }; - } - return null; + parser.Consume(); + return result.ToArray(); } - - /// - void IYamlTypeConverter.WriteYaml(IEmitter emitter, object? value, Type type) + else if (parser.TryConsume(out var scalar)) { - throw new NotImplementedException(); + return new string[] { scalar.Value }; } + return null; + } + + /// + void IYamlTypeConverter.WriteYaml(IEmitter emitter, object? value, Type type) + { + throw new NotImplementedException(); } } diff --git a/src/PSRule.Types/Converters/Yaml/StringArrayMapConverter.cs b/src/PSRule.Types/Converters/Yaml/StringArrayMapConverter.cs index 1b86c9ecb5..8a323a573a 100644 --- a/src/PSRule.Types/Converters/Yaml/StringArrayMapConverter.cs +++ b/src/PSRule.Types/Converters/Yaml/StringArrayMapConverter.cs @@ -6,78 +6,77 @@ using YamlDotNet.Core.Events; using YamlDotNet.Serialization; -namespace PSRule.Converters.Yaml +namespace PSRule.Converters.Yaml; + +/// +/// A YAML converter for de/serializing . +/// +public sealed class StringArrayMapConverter : IYamlTypeConverter { - /// - /// A YAML converter for de/serializing . - /// - public sealed class StringArrayMapConverter : IYamlTypeConverter + /// + bool IYamlTypeConverter.Accepts(Type type) { - /// - bool IYamlTypeConverter.Accepts(Type type) - { - return type == typeof(StringArrayMap); - } + return type == typeof(StringArrayMap); + } - /// - object? IYamlTypeConverter.ReadYaml(IParser parser, Type type) + /// + object? IYamlTypeConverter.ReadYaml(IParser parser, Type type) + { + var result = new StringArrayMap(); + if (parser.TryConsume(out _)) { - var result = new StringArrayMap(); - if (parser.TryConsume(out _)) + while (parser.TryConsume(out var scalar)) { - while (parser.TryConsume(out var scalar)) - { #pragma warning disable IDE0001 - var key = scalar.Value; - if (parser.TryConsume(out _)) + var key = scalar.Value; + if (parser.TryConsume(out _)) + { + var values = new List(); + while (!parser.Accept(out _)) { - var values = new List(); - while (!parser.Accept(out _)) - { - if (parser.TryConsume(out scalar)) - values.Add(scalar.Value); + if (parser.TryConsume(out scalar)) + values.Add(scalar.Value); - } - result[key] = values.ToArray(); - parser.Require(); - parser.MoveNext(); } - else if (parser.TryConsume(out scalar)) - { - result[key] = new string[] { scalar.Value }; - } -#pragma warning restore IDE0001 + result[key] = values.ToArray(); + parser.Require(); + parser.MoveNext(); } - parser.Require(); - parser.MoveNext(); + else if (parser.TryConsume(out scalar)) + { + result[key] = new string[] { scalar.Value }; + } +#pragma warning restore IDE0001 } - return result; + parser.Require(); + parser.MoveNext(); } + return result; + } - /// - void IYamlTypeConverter.WriteYaml(IEmitter emitter, object? value, Type type) + /// + void IYamlTypeConverter.WriteYaml(IEmitter emitter, object? value, Type type) + { + if (type == typeof(StringArrayMap) && value == null) { - if (type == typeof(StringArrayMap) && value == null) - { - emitter.Emit(new MappingStart()); - emitter.Emit(new MappingEnd()); - } - if (value is not StringArrayMap map) - return; - emitter.Emit(new MappingStart()); - foreach (var field in map) + emitter.Emit(new MappingEnd()); + } + if (value is not StringArrayMap map) + return; + + emitter.Emit(new MappingStart()); + foreach (var field in map) + { + emitter.Emit(new Scalar(field.Key)); + emitter.Emit(new SequenceStart(null, null, false, SequenceStyle.Block)); + for (var i = 0; i < field.Value.Length; i++) { - emitter.Emit(new Scalar(field.Key)); - emitter.Emit(new SequenceStart(null, null, false, SequenceStyle.Block)); - for (var i = 0; i < field.Value.Length; i++) - { - emitter.Emit(new Scalar(field.Value[i])); - } - emitter.Emit(new SequenceEnd()); + emitter.Emit(new Scalar(field.Value[i])); } - emitter.Emit(new MappingEnd()); + emitter.Emit(new SequenceEnd()); } + emitter.Emit(new MappingEnd()); } } diff --git a/src/PSRule.Types/Data/DateVersion.cs b/src/PSRule.Types/Data/DateVersion.cs index 2863885b94..92a32d9b71 100644 --- a/src/PSRule.Types/Data/DateVersion.cs +++ b/src/PSRule.Types/Data/DateVersion.cs @@ -3,773 +3,772 @@ using System.Diagnostics; -namespace PSRule.Data +namespace PSRule.Data; + +/// +/// An date version constraint. +/// +public interface IDateVersionConstraint { /// - /// An date version constraint. + /// Determines if the date version meets the requirments of the constraint. /// - public interface IDateVersionConstraint - { - /// - /// Determines if the date version meets the requirments of the constraint. - /// - bool Equals(DateVersion.Version version); - } + bool Equals(DateVersion.Version version); +} + +/// +/// A helper for comparing date version strings. +/// An date version is represented as YYYY-MM-DD-prerelease. +/// +public static class DateVersion +{ + private const char EQUAL = '='; + private const char GREATER = '>'; + private const char LESS = '<'; + private const char DOT = '.'; + private const char DASH = '-'; + private const char PLUS = '+'; + private const char ZERO = '0'; + private const char PIPE = '|'; + private const char SPACE = ' '; + private const char AT = '@'; + + private const string FLAG_PRERELEASE = "@prerelease "; + private const string FLAG_PRE = "@pre "; /// - /// A helper for comparing date version strings. - /// An date version is represented as YYYY-MM-DD-prerelease. + /// A comparison operation for a version constraint. /// - public static class DateVersion + [Flags] + internal enum ComparisonOperator { - private const char EQUAL = '='; - private const char GREATER = '>'; - private const char LESS = '<'; - private const char DOT = '.'; - private const char DASH = '-'; - private const char PLUS = '+'; - private const char ZERO = '0'; - private const char PIPE = '|'; - private const char SPACE = ' '; - private const char AT = '@'; - - private const string FLAG_PRERELEASE = "@prerelease "; - private const string FLAG_PRE = "@pre "; + None = 0, /// - /// A comparison operation for a version constraint. + /// YYYY-MM-DD bits must match. /// - [Flags] - internal enum ComparisonOperator - { - None = 0, + Equals = 1, - /// - /// YYYY-MM-DD bits must match. - /// - Equals = 1, + GreaterThan = 8, - GreaterThan = 8, + LessThan = 16 + } - LessThan = 16 - } + internal enum JoinOperator + { + None = 0, + And = 1, + Or = 2 + } - internal enum JoinOperator - { - None = 0, - And = 1, - Or = 2 - } + [Flags] + internal enum ConstraintModifier + { + None = 0, - [Flags] - internal enum ConstraintModifier - { - None = 0, + Prerelease = 1 + } - Prerelease = 1 - } + /// + /// An date version constraint. + /// + public sealed class VersionConstraint : IDateVersionConstraint + { + private List? _Constraints; - /// - /// An date version constraint. - /// - public sealed class VersionConstraint : IDateVersionConstraint + /// + public bool Equals(Version version) { - private List? _Constraints; + if (_Constraints == null || _Constraints.Count == 0) + return true; - /// - public bool Equals(Version version) + var match = false; + var i = 0; + while (!match && i < _Constraints.Count) { - if (_Constraints == null || _Constraints.Count == 0) - return true; - - var match = false; - var i = 0; - while (!match && i < _Constraints.Count) - { - var result = _Constraints[i].Equals(version); + var result = _Constraints[i].Equals(version); - // True OR - if (result && _Constraints[i].Join == JoinOperator.Or) - return true; + // True OR + if (result && _Constraints[i].Join == JoinOperator.Or) + return true; - // True AND - if (result && _Constraints[i].Join == JoinOperator.And && ++i < _Constraints.Count) - continue; + // True AND + if (result && _Constraints[i].Join == JoinOperator.And && ++i < _Constraints.Count) + continue; - // False OR - if (_Constraints[i].Join == JoinOperator.Or && ++i < _Constraints.Count) - continue; + // False OR + if (_Constraints[i].Join == JoinOperator.Or && ++i < _Constraints.Count) + continue; - // False AND - while (++i < _Constraints.Count) + // False AND + while (++i < _Constraints.Count) + { + // Move to after the next OR. + if (_Constraints[i].Join == JoinOperator.Or) { - // Move to after the next OR. - if (_Constraints[i].Join == JoinOperator.Or) - { - i++; - continue; - } + i++; + continue; } } - return false; - } - - internal void Join(int year, int month, int day, PR prid, ComparisonOperator flag, JoinOperator join, bool includePrerelease) - { - _Constraints ??= new List(); - _Constraints.Add(new ConstraintExpression( - year, - month, - day, - prid, - flag, - join == JoinOperator.None ? JoinOperator.Or : join, - includePrerelease - )); } + return false; } - [DebuggerDisplay("{_Year}.{_Month}.{_Day}")] - internal sealed class ConstraintExpression : IDateVersionConstraint + internal void Join(int year, int month, int day, PR prid, ComparisonOperator flag, JoinOperator join, bool includePrerelease) { - private readonly ComparisonOperator _Flag; - private readonly int _Year; - private readonly int _Month; - private readonly int _Day; - private readonly PR _PRID; - private readonly bool _IncludePrerelease; + _Constraints ??= new List(); + _Constraints.Add(new ConstraintExpression( + year, + month, + day, + prid, + flag, + join == JoinOperator.None ? JoinOperator.Or : join, + includePrerelease + )); + } + } - internal ConstraintExpression(int year, int month, int day, PR prid, ComparisonOperator flag, JoinOperator join, bool includePrerelease) - { - _Flag = flag == ComparisonOperator.None ? ComparisonOperator.Equals : flag; - _Year = year; - _Month = month; - _Day = day; - _PRID = prid; - Join = join; - _IncludePrerelease = includePrerelease; - } + [DebuggerDisplay("{_Year}.{_Month}.{_Day}")] + internal sealed class ConstraintExpression : IDateVersionConstraint + { + private readonly ComparisonOperator _Flag; + private readonly int _Year; + private readonly int _Month; + private readonly int _Day; + private readonly PR _PRID; + private readonly bool _IncludePrerelease; + + internal ConstraintExpression(int year, int month, int day, PR prid, ComparisonOperator flag, JoinOperator join, bool includePrerelease) + { + _Flag = flag == ComparisonOperator.None ? ComparisonOperator.Equals : flag; + _Year = year; + _Month = month; + _Day = day; + _PRID = prid; + Join = join; + _IncludePrerelease = includePrerelease; + } - public bool Stable => IsStable(_PRID); + public bool Stable => IsStable(_PRID); - public JoinOperator Join { get; } + public JoinOperator Join { get; } - public static bool TryParse(string value, out IDateVersionConstraint constraint) - { - return TryParseConstraint(value, out constraint); - } + public static bool TryParse(string value, out IDateVersionConstraint constraint) + { + return TryParseConstraint(value, out constraint); + } - public bool Equals(Version version) - { - return Equals(version.Year, version.Month, version.Day, version.Prerelease); - } + public bool Equals(Version version) + { + return Equals(version.Year, version.Month, version.Day, version.Prerelease); + } - public bool Equals(int year, int month, int day, PR prid) - { - if (_Flag == ComparisonOperator.Equals) - return EQ(year, month, day, prid); + public bool Equals(int year, int month, int day, PR prid) + { + if (_Flag == ComparisonOperator.Equals) + return EQ(year, month, day, prid); - // Fail when pre-release should not be included - if (GuardPRID(prid)) - return false; + // Fail when pre-release should not be included + if (GuardPRID(prid)) + return false; - // Fail when not greater - if (GuardGreater(year, month, day, prid)) - return false; + // Fail when not greater + if (GuardGreater(year, month, day, prid)) + return false; - // Fail when not greater or equal to - if (GuardGreaterOrEqual(year, month, day, prid)) - return false; + // Fail when not greater or equal to + if (GuardGreaterOrEqual(year, month, day, prid)) + return false; - // Fail when not less - if (GaurdLess(year, month, day, prid)) - return false; + // Fail when not less + if (GaurdLess(year, month, day, prid)) + return false; - // Fail with not less or equal to - return !GuardLessOrEqual(year, month, day, prid); - } + // Fail with not less or equal to + return !GuardLessOrEqual(year, month, day, prid); + } - private bool GuardLessOrEqual(int year, int month, int day, PR prid) - { - return _Flag == (ComparisonOperator.LessThan | ComparisonOperator.Equals) && !(LT(year, month, day, prid) || EQ(year, month, day, prid)); - } + private bool GuardLessOrEqual(int year, int month, int day, PR prid) + { + return _Flag == (ComparisonOperator.LessThan | ComparisonOperator.Equals) && !(LT(year, month, day, prid) || EQ(year, month, day, prid)); + } - private bool GaurdLess(int year, int month, int day, PR prid) - { - return _Flag == ComparisonOperator.LessThan && !LT(year, month, day, prid); - } + private bool GaurdLess(int year, int month, int day, PR prid) + { + return _Flag == ComparisonOperator.LessThan && !LT(year, month, day, prid); + } - private bool GuardGreaterOrEqual(int year, int month, int day, PR prid) - { - return _Flag == (ComparisonOperator.GreaterThan | ComparisonOperator.Equals) && !(GT(year, month, day, prid) || EQ(year, month, day, prid)); - } + private bool GuardGreaterOrEqual(int year, int month, int day, PR prid) + { + return _Flag == (ComparisonOperator.GreaterThan | ComparisonOperator.Equals) && !(GT(year, month, day, prid) || EQ(year, month, day, prid)); + } - private bool GuardGreater(int year, int month, int day, PR prid) - { - return _Flag == ComparisonOperator.GreaterThan && !GT(year, month, day, prid); - } + private bool GuardGreater(int year, int month, int day, PR prid) + { + return _Flag == ComparisonOperator.GreaterThan && !GT(year, month, day, prid); + } - private bool GuardPRID(PR prid) - { - return !_IncludePrerelease && Stable && !IsStable(prid); - } + private bool GuardPRID(PR prid) + { + return !_IncludePrerelease && Stable && !IsStable(prid); + } - private bool EQ(int year, int month, int day, PR prid) - { - return EQCore(year, month, day) && PR(prid) == 0; - } + private bool EQ(int year, int month, int day, PR prid) + { + return EQCore(year, month, day) && PR(prid) == 0; + } - private bool EQCore(int year, int month, int day) - { - return (_Year == -1 || _Year == year) && - (_Month == -1 || _Month == month) && - (_Day == -1 || _Day == day); - } + private bool EQCore(int year, int month, int day) + { + return (_Year == -1 || _Year == year) && + (_Month == -1 || _Month == month) && + (_Day == -1 || _Day == day); + } - private bool GTCore(int year, int month, int day) - { - return (year > _Year) || - (year == _Year && month > _Month) || - (year == _Year && month == _Month && day > _Day); - } + private bool GTCore(int year, int month, int day) + { + return (year > _Year) || + (year == _Year && month > _Month) || + (year == _Year && month == _Month && day > _Day); + } - private bool LTCore(int year, int month, int day) - { - return (year < _Year) || - (year == _Year && month < _Month) || - (year == _Year && month == _Month && day < _Day); - } + private bool LTCore(int year, int month, int day) + { + return (year < _Year) || + (year == _Year && month < _Month) || + (year == _Year && month == _Month && day < _Day); + } - /// - /// Greater Than. - /// - private bool GT(int year, int month, int day, PR prid) - { - return !IsStable(prid) && !_IncludePrerelease - ? EQCore(year, month, day) && PR(prid) < 0 - : GTCore(year, month, day) || (EQCore(year, month, day) && PR(prid) < 0); - } + /// + /// Greater Than. + /// + private bool GT(int year, int month, int day, PR prid) + { + return !IsStable(prid) && !_IncludePrerelease + ? EQCore(year, month, day) && PR(prid) < 0 + : GTCore(year, month, day) || (EQCore(year, month, day) && PR(prid) < 0); + } - /// - /// Less Than. - /// - private bool LT(int year, int month, int day, PR prid) - { - return !IsStable(prid) && !_IncludePrerelease - ? EQCore(year, month, day) && PR(prid) > 0 - : LTCore(year, month, day) || (EQCore(year, month, day) && PR(prid) > 0); - } + /// + /// Less Than. + /// + private bool LT(int year, int month, int day, PR prid) + { + return !IsStable(prid) && !_IncludePrerelease + ? EQCore(year, month, day) && PR(prid) > 0 + : LTCore(year, month, day) || (EQCore(year, month, day) && PR(prid) > 0); + } - /// - /// Compare pre-release. - /// - private int PR(PR prid) - { - return _PRID.CompareTo(prid); - } + /// + /// Compare pre-release. + /// + private int PR(PR prid) + { + return _PRID.CompareTo(prid); + } - private static bool IsStable(PR prid) - { - return prid == null || prid.Stable; - } + private static bool IsStable(PR prid) + { + return prid == null || prid.Stable; } + } + /// + /// An date version. + /// + public sealed class Version : IComparable, IEquatable + { /// - /// An date version. + /// The year part of the version. /// - public sealed class Version : IComparable, IEquatable - { - /// - /// The year part of the version. - /// - public readonly int Year; + public readonly int Year; - /// - /// The month part of the version. - /// - public readonly int Month; + /// + /// The month part of the version. + /// + public readonly int Month; - /// - /// The day part of the version. - /// - public readonly int Day; + /// + /// The day part of the version. + /// + public readonly int Day; - /// - /// The pre-release part of the version. - /// - public readonly PR Prerelease; + /// + /// The pre-release part of the version. + /// + public readonly PR Prerelease; - internal Version(int year, int month, int day, PR prerelease) - { - Year = year; - Month = month; - Day = day; - Prerelease = prerelease; - } + internal Version(int year, int month, int day, PR prerelease) + { + Year = year; + Month = month; + Day = day; + Prerelease = prerelease; + } - /// - public override string ToString() - { - return string.Concat(Year, DASH, Month, DASH, Day); - } + /// + public override string ToString() + { + return string.Concat(Year, DASH, Month, DASH, Day); + } - /// - public override bool Equals(object obj) - { - return obj is Version version && Equals(version); - } + /// + public override bool Equals(object obj) + { + return obj is Version version && Equals(version); + } - /// - public override int GetHashCode() + /// + public override int GetHashCode() + { + unchecked // Overflow is fine { - unchecked // Overflow is fine - { - var hash = 17; - hash = hash * 23 + Year.GetHashCode(); - hash = hash * 23 + Month.GetHashCode(); - hash = hash * 23 + Day.GetHashCode(); - hash = hash * 23 + (Prerelease != null ? Prerelease.GetHashCode() : 0); - return hash; - } + var hash = 17; + hash = hash * 23 + Year.GetHashCode(); + hash = hash * 23 + Month.GetHashCode(); + hash = hash * 23 + Day.GetHashCode(); + hash = hash * 23 + (Prerelease != null ? Prerelease.GetHashCode() : 0); + return hash; } + } - /// - /// Compare the version against another version. - /// - public bool Equals(Version other) - { - return other != null && - Equals(other.Year, other.Month, other.Day); - } + /// + /// Compare the version against another version. + /// + public bool Equals(Version other) + { + return other != null && + Equals(other.Year, other.Month, other.Day); + } - /// - /// Compare the version against another version based on YYYY-MM-DD. - /// - public bool Equals(int year, int month, int day) - { - return year == Year && - month == Month && - day == Day; - } + /// + /// Compare the version against another version based on YYYY-MM-DD. + /// + public bool Equals(int year, int month, int day) + { + return year == Year && + month == Month && + day == Day; + } - /// - /// Compare the version against another version. - /// - public int CompareTo(Version other) - { - if (other == null) - return 1; + /// + /// Compare the version against another version. + /// + public int CompareTo(Version other) + { + if (other == null) + return 1; - if (Year != other.Year) - return Year > other.Year ? 32 : -32; + if (Year != other.Year) + return Year > other.Year ? 32 : -32; - if (Month != other.Month) - return Month > other.Month ? 16 : -16; + if (Month != other.Month) + return Month > other.Month ? 16 : -16; - if (Day != other.Day) - return Day > other.Day ? 8 : -8; + if (Day != other.Day) + return Day > other.Day ? 8 : -8; - if ((Prerelease == null || Prerelease.Stable) && (other.Prerelease == null || other.Prerelease.Stable)) - return 0; + if ((Prerelease == null || Prerelease.Stable) && (other.Prerelease == null || other.Prerelease.Stable)) + return 0; - if (Prerelease != null && !Prerelease.Stable && other.Prerelease != null && !other.Prerelease.Stable) - return Prerelease.CompareTo(other.Prerelease); + if (Prerelease != null && !Prerelease.Stable && other.Prerelease != null && !other.Prerelease.Stable) + return Prerelease.CompareTo(other.Prerelease); - return Prerelease == null || Prerelease.Stable ? 1 : -1; - } + return Prerelease == null || Prerelease.Stable ? 1 : -1; + } + } + + /// + /// An date version pre-release identifier. + /// + [DebuggerDisplay("{Value}")] + public sealed class PR + { + internal static readonly PR Empty = new(); + private static readonly char[] SEPARATORS = new char[] { DOT }; + + private readonly string[]? _Identifiers; + + private PR() + { + Value = string.Empty; + _Identifiers = null; + } + + internal PR(string value) + { + Value = string.IsNullOrEmpty(value) ? string.Empty : value; + _Identifiers = string.IsNullOrEmpty(value) ? null : value.Split(SEPARATORS, StringSplitOptions.RemoveEmptyEntries); } /// - /// An date version pre-release identifier. + /// The string value of a pre-release identifier. /// - [DebuggerDisplay("{Value}")] - public sealed class PR - { - internal static readonly PR Empty = new(); - private static readonly char[] SEPARATORS = new char[] { DOT }; + public string Value { get; } - private readonly string[]? _Identifiers; + /// + /// Is the pre-release identifier empty, indicating a stable release. + /// + public bool Stable => _Identifiers == null; - private PR() + /// + /// Compare the pre-release identifer to another pre-release identifier. + /// + public int CompareTo(PR pr) + { + if (pr == null || pr.Stable || pr._Identifiers == null) { - Value = string.Empty; - _Identifiers = null; + return Stable ? 0 : -1; } - - internal PR(string value) + else if (Stable || _Identifiers == null) { - Value = string.IsNullOrEmpty(value) ? string.Empty : value; - _Identifiers = string.IsNullOrEmpty(value) ? null : value.Split(SEPARATORS, StringSplitOptions.RemoveEmptyEntries); + return 1; } - /// - /// The string value of a pre-release identifier. - /// - public string Value { get; } - - /// - /// Is the pre-release identifier empty, indicating a stable release. - /// - public bool Stable => _Identifiers == null; + var i = -1; + var left = _Identifiers; + var right = pr._Identifiers; - /// - /// Compare the pre-release identifer to another pre-release identifier. - /// - public int CompareTo(PR pr) + while (++i < left.Length && i < right.Length) { - if (pr == null || pr.Stable || pr._Identifiers == null) - { - return Stable ? 0 : -1; - } - else if (Stable || _Identifiers == null) - { - return 1; - } + var leftNumeric = false; + var rightNumeric = false; + if (long.TryParse(left[i], out var l)) + leftNumeric = true; - var i = -1; - var left = _Identifiers; - var right = pr._Identifiers; + if (long.TryParse(right[i], out var r)) + rightNumeric = true; - while (++i < left.Length && i < right.Length) - { - var leftNumeric = false; - var rightNumeric = false; - if (long.TryParse(left[i], out var l)) - leftNumeric = true; + if (leftNumeric != rightNumeric) + return leftNumeric ? -1 : 1; - if (long.TryParse(right[i], out var r)) - rightNumeric = true; + if (leftNumeric && rightNumeric && l == r) + continue; - if (leftNumeric != rightNumeric) - return leftNumeric ? -1 : 1; + if (leftNumeric && rightNumeric) + return l.CompareTo(r); - if (leftNumeric && rightNumeric && l == r) - continue; + var result = string.Compare(left[i], right[i], StringComparison.Ordinal); + if (result == 0) + continue; - if (leftNumeric && rightNumeric) - return l.CompareTo(r); - - var result = string.Compare(left[i], right[i], StringComparison.Ordinal); - if (result == 0) - continue; - - return result; - } - if (left.Length == right.Length) - return 0; - - return left.Length > right.Length ? 1 : -1; + return result; } + if (left.Length == right.Length) + return 0; - /// - public override bool Equals(object obj) - { - return obj is PR prerelease && Value.Equals(prerelease.Value); - } + return left.Length > right.Length ? 1 : -1; + } - /// - public override int GetHashCode() - { - return Value.GetHashCode(); - } + /// + public override bool Equals(object obj) + { + return obj is PR prerelease && Value.Equals(prerelease.Value); + } - /// - public override string ToString() - { - return Value.ToString(); - } + /// + public override int GetHashCode() + { + return Value.GetHashCode(); } - [DebuggerDisplay("Current = {_Current}, Position = {_Position}, Value = {_Value}")] - private sealed class VersionStream + /// + public override string ToString() { - private readonly string _Value; - private int _Position; - private char _Current; + return Value.ToString(); + } + } - internal VersionStream(string value) - { - _Value = value; - _Position = 0; - _Current = _Value.Length > 0 ? _Value[0] : char.MinValue; - } + [DebuggerDisplay("Current = {_Current}, Position = {_Position}, Value = {_Value}")] + private sealed class VersionStream + { + private readonly string _Value; + private int _Position; + private char _Current; - internal bool EOF => _Position >= _Value.Length; + internal VersionStream(string value) + { + _Value = value; + _Position = 0; + _Current = _Value.Length > 0 ? _Value[0] : char.MinValue; + } - internal void Next() - { - Next(1); - } + internal bool EOF => _Position >= _Value.Length; - private void Next(int count) - { - _Position += count; - if (EOF || _Position >= _Value.Length) - return; + internal void Next() + { + Next(1); + } - _Current = _Value[_Position]; - } + private void Next(int count) + { + _Position += count; + if (EOF || _Position >= _Value.Length) + return; - internal void Operator(out ComparisonOperator comparison) + _Current = _Value[_Position]; + } + + internal void Operator(out ComparisonOperator comparison) + { + comparison = ComparisonOperator.None; + while (!EOF && IsConstraint(_Current)) { - comparison = ComparisonOperator.None; - while (!EOF && IsConstraint(_Current)) - { - if (_Current == EQUAL) - comparison |= ComparisonOperator.Equals; - else if (_Current == GREATER) - comparison |= ComparisonOperator.GreaterThan; - else if (_Current == LESS) - comparison |= ComparisonOperator.LessThan; + if (_Current == EQUAL) + comparison |= ComparisonOperator.Equals; + else if (_Current == GREATER) + comparison |= ComparisonOperator.GreaterThan; + else if (_Current == LESS) + comparison |= ComparisonOperator.LessThan; - Next(); - } + Next(); } + } - internal void Flags(out ConstraintModifier flag) - { - flag = ConstraintModifier.None; - if (EOF || _Current != AT) - return; + internal void Flags(out ConstraintModifier flag) + { + flag = ConstraintModifier.None; + if (EOF || _Current != AT) + return; - if (HasFlag(FLAG_PRE) || HasFlag(FLAG_PRERELEASE)) - flag |= ConstraintModifier.Prerelease; - } + if (HasFlag(FLAG_PRE) || HasFlag(FLAG_PRERELEASE)) + flag |= ConstraintModifier.Prerelease; + } - private bool HasFlag(string value) - { - if (string.Compare(_Value, _Position, value, 0, value.Length, false) != 0) - return false; + private bool HasFlag(string value) + { + if (string.Compare(_Value, _Position, value, 0, value.Length, false) != 0) + return false; - Next(value.Length); - return true; - } + Next(value.Length); + return true; + } - internal bool TryDigit(out int digit) + internal bool TryDigit(out int digit) + { + var pos = _Position; + var count = 0; + while (!EOF && IsVersionDigit(_Current)) { - var pos = _Position; - var count = 0; - while (!EOF && IsVersionDigit(_Current)) - { - count++; - Next(); - } - digit = count > 0 ? int.Parse(_Value.Substring(pos, count), Thread.CurrentThread.CurrentCulture) : 0; - return count > 0; + count++; + Next(); } + digit = count > 0 ? int.Parse(_Value.Substring(pos, count), Thread.CurrentThread.CurrentCulture) : 0; + return count > 0; + } - internal bool TrySegments(out int[] segments) + internal bool TrySegments(out int[] segments) + { + segments = new int[] { -1, -1, -1 }; + var segmentIndex = 0; + while (!EOF) { - segments = new int[] { -1, -1, -1 }; - var segmentIndex = 0; - while (!EOF) - { - if (!IsAllowedChar(_Current)) - return false; - - if (TryDigit(out var digit)) - { - segments[segmentIndex++] = digit; - if (segments.Length <= segmentIndex) - return true; - } - - if (IsSeparator(_Current)) - Next(); - - if (IsWildcard(_Current)) - { - segments[segmentIndex++] = -1; - Next(); - } - - if (IsIdentifier(_Current)) - return true; + if (!IsAllowedChar(_Current)) + return false; - if (IsJoin(_Current)) + if (TryDigit(out var digit)) + { + segments[segmentIndex++] = digit; + if (segments.Length <= segmentIndex) return true; } - return segmentIndex > 0; - } - - internal bool Prerelease(out PR identifier) - { - identifier = PR.Empty; - if (EOF || _Current != DASH) - return true; - Next(); - var start = _Position; - if (EOF) - return false; + if (IsSeparator(_Current)) + Next(); - var numeric = true; - while (!EOF && IsPrereleaseChar(_Current, ref numeric)) + if (IsWildcard(_Current)) + { + segments[segmentIndex++] = -1; Next(); + } - if (_Position - start == 0) - return false; + if (IsIdentifier(_Current)) + return true; - // No leading 0 if numeric - var id = _Value.Substring(start, _Position - start); - if (numeric && id.Length > 1 && id[0] == ZERO) - return false; + if (IsJoin(_Current)) + return true; + } + return segmentIndex > 0; + } - identifier = new PR(id); + internal bool Prerelease(out PR identifier) + { + identifier = PR.Empty; + if (EOF || _Current != DASH) return true; - } - internal void Build(out string label) - { - label = string.Empty; - if (EOF || _Current != PLUS) - return; + Next(); + var start = _Position; + if (EOF) + return false; + var numeric = true; + while (!EOF && IsPrereleaseChar(_Current, ref numeric)) Next(); - var start = _Position; - if (_Current == ZERO) - return; - while (!EOF && IsBuildChar(_Current)) - Next(); + if (_Position - start == 0) + return false; - label = _Value.Substring(start, _Position - start); - } + // No leading 0 if numeric + var id = _Value.Substring(start, _Position - start); + if (numeric && id.Length > 1 && id[0] == ZERO) + return false; - /// - /// 1.2.3 || 3.4.5 - /// >=1.2.3 <3.4.5 - /// - internal JoinOperator GetJoin() - { - var result = JoinOperator.None; - while ((_Current == SPACE || _Current == PIPE) && !EOF) - { - if (result == JoinOperator.None && _Current == SPACE) - result = JoinOperator.And; + identifier = new PR(id); + return true; + } - if (_Current == PIPE) - result = JoinOperator.Or; + internal void Build(out string label) + { + label = string.Empty; + if (EOF || _Current != PLUS) + return; - Next(); - } - return _Current != SPACE || _Current != PIPE ? result : JoinOperator.Or; - } + Next(); + var start = _Position; + if (_Current == ZERO) + return; - [DebuggerStepThrough()] - private static bool IsConstraint(char c) - { - return c == EQUAL || c == GREATER || c == LESS; - } + while (!EOF && IsBuildChar(_Current)) + Next(); - [DebuggerStepThrough()] - private static bool IsVersionDigit(char c) - { - return char.IsDigit(c); - } + label = _Value.Substring(start, _Position - start); + } - [DebuggerStepThrough()] - private static bool IsWildcard(char c) + /// + /// 1.2.3 || 3.4.5 + /// >=1.2.3 <3.4.5 + /// + internal JoinOperator GetJoin() + { + var result = JoinOperator.None; + while ((_Current == SPACE || _Current == PIPE) && !EOF) { - return c == '*' || c == 'X' || c == 'x'; - } + if (result == JoinOperator.None && _Current == SPACE) + result = JoinOperator.And; - [DebuggerStepThrough()] - private static bool IsSeparator(char c) - { - return c == DASH; - } + if (_Current == PIPE) + result = JoinOperator.Or; - [DebuggerStepThrough()] - private static bool IsAllowedChar(char c) - { - return IsVersionDigit(c) || IsSeparator(c) || IsWildcard(c) || IsIdentifier(c); + Next(); } + return _Current != SPACE || _Current != PIPE ? result : JoinOperator.Or; + } - [DebuggerStepThrough()] - private static bool IsPrereleaseChar(char c, ref bool numeric) - { - if (numeric && char.IsDigit(c)) - return true; - - numeric = false; - return char.IsDigit(c) || IsLetter(c) || c == DASH || c == DOT; - } + [DebuggerStepThrough()] + private static bool IsConstraint(char c) + { + return c == EQUAL || c == GREATER || c == LESS; + } - [DebuggerStepThrough()] - private static bool IsBuildChar(char c) - { - return char.IsDigit(c) || IsLetter(c); - } + [DebuggerStepThrough()] + private static bool IsVersionDigit(char c) + { + return char.IsDigit(c); + } - [DebuggerStepThrough()] - private static bool IsIdentifier(char c) - { - return c == DASH || c == PLUS; - } + [DebuggerStepThrough()] + private static bool IsWildcard(char c) + { + return c == '*' || c == 'X' || c == 'x'; + } - [DebuggerStepThrough()] - private static bool IsJoin(char c) - { - return c == SPACE || c == PIPE; - } + [DebuggerStepThrough()] + private static bool IsSeparator(char c) + { + return c == DASH; + } - /// - /// Is the character within the reduced set of allowed characters. a-z or A-Z. - /// - [DebuggerStepThrough()] - private static bool IsLetter(char c) - { - var nc = (int)c; - return (nc >= 0x41 && nc <= 0x5a) || (nc >= 0x61 && nc <= 0x7a); - } + [DebuggerStepThrough()] + private static bool IsAllowedChar(char c) + { + return IsVersionDigit(c) || IsSeparator(c) || IsWildcard(c) || IsIdentifier(c); } - /// - /// Try to parse a version constraint from the provided string. - /// - public static bool TryParseConstraint(string value, out IDateVersionConstraint constraint, bool includePrerelease = false) + [DebuggerStepThrough()] + private static bool IsPrereleaseChar(char c, ref bool numeric) { - var c = new VersionConstraint(); - constraint = c; - if (string.IsNullOrEmpty(value)) + if (numeric && char.IsDigit(c)) return true; - var stream = new VersionStream(value); - stream.Flags(out var flags); - if (flags.HasFlag(ConstraintModifier.Prerelease)) - includePrerelease = true; + numeric = false; + return char.IsDigit(c) || IsLetter(c) || c == DASH || c == DOT; + } - while (!stream.EOF) - { - stream.Operator(out var comparison); - if (!stream.TrySegments(out var segments)) - return false; + [DebuggerStepThrough()] + private static bool IsBuildChar(char c) + { + return char.IsDigit(c) || IsLetter(c); + } - stream.Prerelease(out var prerelease); - c.Join(segments[0], segments[1], segments[2], prerelease, comparison, stream.GetJoin(), includePrerelease); - } - return true; + [DebuggerStepThrough()] + private static bool IsIdentifier(char c) + { + return c == DASH || c == PLUS; + } + + [DebuggerStepThrough()] + private static bool IsJoin(char c) + { + return c == SPACE || c == PIPE; } /// - /// Try to parse a version from the provided string. + /// Is the character within the reduced set of allowed characters. a-z or A-Z. /// - public static bool TryParseVersion(string value, out Version? version) + [DebuggerStepThrough()] + private static bool IsLetter(char c) { - version = null; - if (string.IsNullOrEmpty(value)) - return false; + var nc = (int)c; + return (nc >= 0x41 && nc <= 0x5a) || (nc >= 0x61 && nc <= 0x7a); + } + } - var stream = new VersionStream(value); - if (!stream.TrySegments(out var segments)) - return false; + /// + /// Try to parse a version constraint from the provided string. + /// + public static bool TryParseConstraint(string value, out IDateVersionConstraint constraint, bool includePrerelease = false) + { + var c = new VersionConstraint(); + constraint = c; + if (string.IsNullOrEmpty(value)) + return true; + + var stream = new VersionStream(value); + stream.Flags(out var flags); + if (flags.HasFlag(ConstraintModifier.Prerelease)) + includePrerelease = true; - if (!stream.Prerelease(out var prerelease)) + while (!stream.EOF) + { + stream.Operator(out var comparison); + if (!stream.TrySegments(out var segments)) return false; - version = new Version(segments[0], segments[1], segments[2], prerelease); - return true; + stream.Prerelease(out var prerelease); + c.Join(segments[0], segments[1], segments[2], prerelease, comparison, stream.GetJoin(), includePrerelease); } + return true; + } + + /// + /// Try to parse a version from the provided string. + /// + public static bool TryParseVersion(string value, out Version? version) + { + version = null; + if (string.IsNullOrEmpty(value)) + return false; + + var stream = new VersionStream(value); + if (!stream.TrySegments(out var segments)) + return false; + + if (!stream.Prerelease(out var prerelease)) + return false; + + version = new Version(segments[0], segments[1], segments[2], prerelease); + return true; } } diff --git a/src/PSRule.Types/Data/IFileInfo.cs b/src/PSRule.Types/Data/IFileInfo.cs index e46f4c15fe..e30e070747 100644 --- a/src/PSRule.Types/Data/IFileInfo.cs +++ b/src/PSRule.Types/Data/IFileInfo.cs @@ -1,21 +1,20 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -namespace PSRule.Data +namespace PSRule.Data; + +/// +/// An object with information about an input file. +/// +public interface IFileInfo { /// - /// An object with information about an input file. + /// The full path to the file. /// - public interface IFileInfo - { - /// - /// The full path to the file. - /// - string? Path { get; } + string? Path { get; } - /// - /// The extension for the file. - /// - string? Extension { get; } - } + /// + /// The extension for the file. + /// + string? Extension { get; } } diff --git a/src/PSRule.Types/Data/ISourceInfo.cs b/src/PSRule.Types/Data/ISourceInfo.cs index 5fe3eaa594..7d8c9b5fbe 100644 --- a/src/PSRule.Types/Data/ISourceInfo.cs +++ b/src/PSRule.Types/Data/ISourceInfo.cs @@ -1,12 +1,11 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -namespace PSRule.Data +namespace PSRule.Data; + +/// +/// Information about the source of an object. +/// +public interface ISourceInfo { - /// - /// Information about the source of an object. - /// - public interface ISourceInfo - { - } } diff --git a/src/PSRule.Types/Data/ITargetInfo.cs b/src/PSRule.Types/Data/ITargetInfo.cs index cfa3fa0202..31f7f4021e 100644 --- a/src/PSRule.Types/Data/ITargetInfo.cs +++ b/src/PSRule.Types/Data/ITargetInfo.cs @@ -1,26 +1,25 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -namespace PSRule.Data +namespace PSRule.Data; + +/// +/// An interface implemented by objects that automatically provide binding and source information. +/// +internal interface ITargetInfo { /// - /// An interface implemented by objects that automatically provide binding and source information. + /// The target name provided by the object. /// - internal interface ITargetInfo - { - /// - /// The target name provided by the object. - /// - string? TargetName { get; } + string? TargetName { get; } - /// - /// The target type provided by the object. - /// - string? TargetType { get; } + /// + /// The target type provided by the object. + /// + string? TargetType { get; } - /// - /// The source information provided by the object. - /// - TargetSourceInfo Source { get; } - } + /// + /// The source information provided by the object. + /// + TargetSourceInfo Source { get; } } diff --git a/src/PSRule.Types/Data/ITargetObject.cs b/src/PSRule.Types/Data/ITargetObject.cs index 232e095303..4b604ecb5d 100644 --- a/src/PSRule.Types/Data/ITargetObject.cs +++ b/src/PSRule.Types/Data/ITargetObject.cs @@ -1,12 +1,11 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -namespace PSRule.Data +namespace PSRule.Data; + +/// +/// An instance of a target object. +/// +public interface ITargetObject { - /// - /// An instance of a target object. - /// - public interface ITargetObject - { - } } diff --git a/src/PSRule.Types/Data/InputFileInfo.cs b/src/PSRule.Types/Data/InputFileInfo.cs index 7975775383..de6c62083f 100644 --- a/src/PSRule.Types/Data/InputFileInfo.cs +++ b/src/PSRule.Types/Data/InputFileInfo.cs @@ -3,91 +3,90 @@ using PSRule.Expressions; -namespace PSRule.Data +namespace PSRule.Data; + +/// +/// An input file information structure. +/// +public sealed class InputFileInfo : ITargetInfo { - /// - /// An input file information structure. - /// - public sealed class InputFileInfo : ITargetInfo - { - private readonly string? _TargetType; - private readonly TargetSourceInfo _Source; + private readonly string? _TargetType; + private readonly TargetSourceInfo _Source; - internal readonly bool IsUrl; + internal readonly bool IsUrl; - internal InputFileInfo(string basePath, string path) + internal InputFileInfo(string basePath, string path) + { + FullName = path; + _Source = new TargetSourceInfo(this); + if (path.IsURL()) { - FullName = path; - _Source = new TargetSourceInfo(this); - if (path.IsURL()) - { - IsUrl = true; - return; - } - BasePath = basePath; - Name = System.IO.Path.GetFileName(path); - Extension = System.IO.Path.GetExtension(path); - DirectoryName = System.IO.Path.GetDirectoryName(path); - DisplayName = Helpers.NormalizePath(basePath, FullName); - Path = Helpers.NormalizePath(basePath, FullName); - _TargetType = string.IsNullOrEmpty(Extension) ? System.IO.Path.GetFileNameWithoutExtension(path) : Extension; + IsUrl = true; + return; } + BasePath = basePath; + Name = System.IO.Path.GetFileName(path); + Extension = System.IO.Path.GetExtension(path); + DirectoryName = System.IO.Path.GetDirectoryName(path); + DisplayName = Helpers.NormalizePath(basePath, FullName); + Path = Helpers.NormalizePath(basePath, FullName); + _TargetType = string.IsNullOrEmpty(Extension) ? System.IO.Path.GetFileNameWithoutExtension(path) : Extension; + } - /// - /// The fully qualified path to the file. - /// - public string? FullName { get; } + /// + /// The fully qualified path to the file. + /// + public string? FullName { get; } - /// - /// The path to the parent directory containing the file. - /// - public string? BasePath { get; } + /// + /// The path to the parent directory containing the file. + /// + public string? BasePath { get; } - /// - /// The file name. - /// - public string? Name { get; } + /// + /// The file name. + /// + public string? Name { get; } - /// - /// The file extension. - /// - public string? Extension { get; } + /// + /// The file extension. + /// + public string? Extension { get; } - /// - /// The name of the directory containing the file. - /// - public string? DirectoryName { get; } + /// + /// The name of the directory containing the file. + /// + public string? DirectoryName { get; } - /// - /// The normalized path to the file. - /// - public string? Path { get; } + /// + /// The normalized path to the file. + /// + public string? Path { get; } - /// - /// The friendly display name for the file. - /// - public string? DisplayName { get; } + /// + /// The friendly display name for the file. + /// + public string? DisplayName { get; } - string? ITargetInfo.TargetName => DisplayName; + string? ITargetInfo.TargetName => DisplayName; - string? ITargetInfo.TargetType => _TargetType; + string? ITargetInfo.TargetType => _TargetType; - TargetSourceInfo ITargetInfo.Source => _Source; + TargetSourceInfo ITargetInfo.Source => _Source; - /// - /// Convert to string. - /// - public override string? ToString() - { - return FullName; - } + /// + /// Convert to string. + /// + public override string? ToString() + { + return FullName; + } - /// - /// Convert to FileInfo. - /// - public FileInfo AsFileInfo() - { - return new FileInfo(FullName); - } + /// + /// Convert to FileInfo. + /// + public FileInfo AsFileInfo() + { + return new FileInfo(FullName); } } diff --git a/src/PSRule.Types/Data/ModuleConstraint.cs b/src/PSRule.Types/Data/ModuleConstraint.cs index b0c27f1396..e163ec9b5a 100644 --- a/src/PSRule.Types/Data/ModuleConstraint.cs +++ b/src/PSRule.Types/Data/ModuleConstraint.cs @@ -1,32 +1,31 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -namespace PSRule.Data +namespace PSRule.Data; + +/// +/// A version constraint for a PSRule module. +/// +public sealed class ModuleConstraint { /// - /// A version constraint for a PSRule module. + /// Create a version constraint for a PSRule module. /// - public sealed class ModuleConstraint + /// The name of the module. + /// The version constraint of the module. + public ModuleConstraint(string module, ISemanticVersionConstraint constraint) { - /// - /// Create a version constraint for a PSRule module. - /// - /// The name of the module. - /// The version constraint of the module. - public ModuleConstraint(string module, ISemanticVersionConstraint constraint) - { - Module = module; - Constraint = constraint; - } + Module = module; + Constraint = constraint; + } - /// - /// The name of the module. - /// - public string Module { get; } + /// + /// The name of the module. + /// + public string Module { get; } - /// - /// The version constraint of the module. - /// - public ISemanticVersionConstraint Constraint { get; } - } + /// + /// The version constraint of the module. + /// + public ISemanticVersionConstraint Constraint { get; } } diff --git a/src/PSRule.Types/Data/SemanticVersion.cs b/src/PSRule.Types/Data/SemanticVersion.cs index bf27056b5e..ca8e71475a 100644 --- a/src/PSRule.Types/Data/SemanticVersion.cs +++ b/src/PSRule.Types/Data/SemanticVersion.cs @@ -3,865 +3,864 @@ using System.Diagnostics; -namespace PSRule.Data +namespace PSRule.Data; + +/// +/// A semantic version constraint. +/// +public interface ISemanticVersionConstraint { /// - /// A semantic version constraint. + /// Determines if the semantic version meets the requirments of the constraint. /// - public interface ISemanticVersionConstraint - { - /// - /// Determines if the semantic version meets the requirments of the constraint. - /// - bool Equals(SemanticVersion.Version version); - } + bool Equals(SemanticVersion.Version version); +} + +/// +/// A helper for comparing semantic version strings. +/// +public static class SemanticVersion +{ + private const char MINOR = '^'; + private const char PATCH = '~'; + private const char EQUAL = '='; + private const char VUPPER = 'V'; + private const char VLOWER = 'v'; + private const char GREATER = '>'; + private const char LESS = '<'; + private const char SEPARATOR = '.'; + private const char DASH = '-'; + private const char PLUS = '+'; + private const char ZERO = '0'; + private const char PIPE = '|'; + private const char SPACE = ' '; + private const char AT = '@'; + + private const string FLAG_PRERELEASE = "@prerelease "; + private const string FLAG_PRE = "@pre "; /// - /// A helper for comparing semantic version strings. + /// A comparison operation for a version constraint. /// - public static class SemanticVersion + [Flags] + internal enum ComparisonOperator { - private const char MINOR = '^'; - private const char PATCH = '~'; - private const char EQUAL = '='; - private const char VUPPER = 'V'; - private const char VLOWER = 'v'; - private const char GREATER = '>'; - private const char LESS = '<'; - private const char SEPARATOR = '.'; - private const char DASH = '-'; - private const char PLUS = '+'; - private const char ZERO = '0'; - private const char PIPE = '|'; - private const char SPACE = ' '; - private const char AT = '@'; - - private const string FLAG_PRERELEASE = "@prerelease "; - private const string FLAG_PRE = "@pre "; + None = 0, /// - /// A comparison operation for a version constraint. + /// Major.Minor.Patch bits must match. /// - [Flags] - internal enum ComparisonOperator - { - None = 0, + Equals = 1, - /// - /// Major.Minor.Patch bits must match. - /// - Equals = 1, + /// + /// Major.Minor bits must match, Patch can equal to or greater. + /// + PatchUplift = 2, - /// - /// Major.Minor bits must match, Patch can equal to or greater. - /// - PatchUplift = 2, + /// + /// Major bit must match, Minor.Patch can be equal to or greater. + /// + MinorUplift = 4, - /// - /// Major bit must match, Minor.Patch can be equal to or greater. - /// - MinorUplift = 4, + GreaterThan = 8, - GreaterThan = 8, + LessThan = 16 + } - LessThan = 16 - } + internal enum JoinOperator + { + None = 0, + And = 1, + Or = 2 + } - internal enum JoinOperator - { - None = 0, - And = 1, - Or = 2 - } + [Flags] + internal enum ConstraintModifier + { + None = 0, - [Flags] - internal enum ConstraintModifier - { - None = 0, + Prerelease = 1 + } - Prerelease = 1 + /// + /// A semantic version constraint. + /// + public sealed class VersionConstraint : ISemanticVersionConstraint + { + private List? _Constraints; + private readonly string _Value; + + internal VersionConstraint(string value) + { + _Value = value; } - /// - /// A semantic version constraint. - /// - public sealed class VersionConstraint : ISemanticVersionConstraint + /// + public bool Equals(Version version) { - private List? _Constraints; - private string _Value; + if (_Constraints == null || _Constraints.Count == 0) + return true; - internal VersionConstraint(string value) + var match = false; + var i = 0; + while (!match && i < _Constraints.Count) { - _Value = value; - } + var result = _Constraints[i].Equals(version); - /// - public bool Equals(Version version) - { - if (_Constraints == null || _Constraints.Count == 0) + // True OR + if (result && _Constraints[i].Join == JoinOperator.Or) return true; - var match = false; - var i = 0; - while (!match && i < _Constraints.Count) - { - var result = _Constraints[i].Equals(version); - - // True OR - if (result && _Constraints[i].Join == JoinOperator.Or) - return true; + // True AND + if (result && _Constraints[i].Join == JoinOperator.And && ++i < _Constraints.Count) + continue; - // True AND - if (result && _Constraints[i].Join == JoinOperator.And && ++i < _Constraints.Count) - continue; - - // False OR - if (_Constraints[i].Join == JoinOperator.Or && ++i < _Constraints.Count) - continue; + // False OR + if (_Constraints[i].Join == JoinOperator.Or && ++i < _Constraints.Count) + continue; - // False AND - while (++i < _Constraints.Count) + // False AND + while (++i < _Constraints.Count) + { + // Move to after the next OR. + if (_Constraints[i].Join == JoinOperator.Or) { - // Move to after the next OR. - if (_Constraints[i].Join == JoinOperator.Or) - { - i++; - continue; - } + i++; + continue; } } - return false; - } - - internal void Join(int major, int minor, int patch, PR prid, ComparisonOperator flag, JoinOperator join, bool includePrerelease) - { - _Constraints ??= new List(); - _Constraints.Add(new ConstraintExpression( - major, - minor, - patch, - prid, - flag, - join == JoinOperator.None ? JoinOperator.Or : join, - includePrerelease - )); } + return false; + } - /// - public override string ToString() - { - return _Value; - } + internal void Join(int major, int minor, int patch, PR prid, ComparisonOperator flag, JoinOperator join, bool includePrerelease) + { + _Constraints ??= new List(); + _Constraints.Add(new ConstraintExpression( + major, + minor, + patch, + prid, + flag, + join == JoinOperator.None ? JoinOperator.Or : join, + includePrerelease + )); + } - /// - public override int GetHashCode() - { - return _Value.GetHashCode(); - } + /// + public override string ToString() + { + return _Value; } - [DebuggerDisplay("{_Major}.{_Minor}.{_Patch}")] - internal sealed class ConstraintExpression : ISemanticVersionConstraint + /// + public override int GetHashCode() { - private readonly ComparisonOperator _Flag; - private readonly int _Major; - private readonly int _Minor; - private readonly int _Patch; - private readonly PR _PRID; - private readonly bool _IncludePrerelease; + return _Value.GetHashCode(); + } + } - internal ConstraintExpression(int major, int minor, int patch, PR prid, ComparisonOperator flag, JoinOperator join, bool includePrerelease) - { - _Flag = flag == ComparisonOperator.None ? ComparisonOperator.Equals : flag; - _Major = major; - _Minor = minor; - _Patch = patch; - _PRID = prid; - Join = join; - _IncludePrerelease = includePrerelease; - } + [DebuggerDisplay("{_Major}.{_Minor}.{_Patch}")] + internal sealed class ConstraintExpression : ISemanticVersionConstraint + { + private readonly ComparisonOperator _Flag; + private readonly int _Major; + private readonly int _Minor; + private readonly int _Patch; + private readonly PR _PRID; + private readonly bool _IncludePrerelease; + + internal ConstraintExpression(int major, int minor, int patch, PR prid, ComparisonOperator flag, JoinOperator join, bool includePrerelease) + { + _Flag = flag == ComparisonOperator.None ? ComparisonOperator.Equals : flag; + _Major = major; + _Minor = minor; + _Patch = patch; + _PRID = prid; + Join = join; + _IncludePrerelease = includePrerelease; + } - public bool Stable => IsStable(_PRID); + public bool Stable => IsStable(_PRID); - public JoinOperator Join { get; } + public JoinOperator Join { get; } - public static bool TryParse(string value, out ISemanticVersionConstraint constraint) - { - return TryParseConstraint(value, out constraint); - } + public static bool TryParse(string value, out ISemanticVersionConstraint constraint) + { + return TryParseConstraint(value, out constraint); + } - public bool Equals(System.Version version) - { - return Equals(version.Major, version.Minor, version.Build, null); - } + public bool Equals(System.Version version) + { + return Equals(version.Major, version.Minor, version.Build, null); + } - public bool Equals(Version version) - { - return Equals(version.Major, version.Minor, version.Patch, version.Prerelease); - } + public bool Equals(Version version) + { + return Equals(version.Major, version.Minor, version.Patch, version.Prerelease); + } - public bool Equals(int major, int minor, int patch, PR? prid) - { - if (_Flag == ComparisonOperator.Equals) - return EQ(major, minor, patch, prid); + public bool Equals(int major, int minor, int patch, PR? prid) + { + if (_Flag == ComparisonOperator.Equals) + return EQ(major, minor, patch, prid); - // Fail when pre-release should not be included - if (GuardPRID(prid)) - return false; + // Fail when pre-release should not be included + if (GuardPRID(prid)) + return false; - // Fail when major is less - if (GuardMajor(major)) - return false; + // Fail when major is less + if (GuardMajor(major)) + return false; - // Fail when patch is less - if (GuardPatch(minor, patch)) - return false; + // Fail when patch is less + if (GuardPatch(minor, patch)) + return false; - // Fail when minor is less - if (GuardMinor(minor, patch)) - return false; + // Fail when minor is less + if (GuardMinor(minor, patch)) + return false; - // Fail when not greater - if (GuardGreater(major, minor, patch, prid)) - return false; + // Fail when not greater + if (GuardGreater(major, minor, patch, prid)) + return false; - // Fail when not greater or equal to - if (GuardGreaterOrEqual(major, minor, patch, prid)) - return false; + // Fail when not greater or equal to + if (GuardGreaterOrEqual(major, minor, patch, prid)) + return false; - // Fail when not less - if (GaurdLess(major, minor, patch, prid)) - return false; + // Fail when not less + if (GaurdLess(major, minor, patch, prid)) + return false; - // Fail with not less or equal to - return !GuardLessOrEqual(major, minor, patch, prid); - } + // Fail with not less or equal to + return !GuardLessOrEqual(major, minor, patch, prid); + } - private bool GuardLessOrEqual(int major, int minor, int patch, PR? prid) - { - return _Flag == (ComparisonOperator.LessThan | ComparisonOperator.Equals) && !(LT(major, minor, patch, prid) || EQ(major, minor, patch, prid)); - } + private bool GuardLessOrEqual(int major, int minor, int patch, PR? prid) + { + return _Flag == (ComparisonOperator.LessThan | ComparisonOperator.Equals) && !(LT(major, minor, patch, prid) || EQ(major, minor, patch, prid)); + } - private bool GaurdLess(int major, int minor, int patch, PR? prid) - { - return _Flag == ComparisonOperator.LessThan && !LT(major, minor, patch, prid); - } + private bool GaurdLess(int major, int minor, int patch, PR? prid) + { + return _Flag == ComparisonOperator.LessThan && !LT(major, minor, patch, prid); + } - private bool GuardGreaterOrEqual(int major, int minor, int patch, PR? prid) - { - return _Flag == (ComparisonOperator.GreaterThan | ComparisonOperator.Equals) && !(GT(major, minor, patch, prid) || EQ(major, minor, patch, prid)); - } + private bool GuardGreaterOrEqual(int major, int minor, int patch, PR? prid) + { + return _Flag == (ComparisonOperator.GreaterThan | ComparisonOperator.Equals) && !(GT(major, minor, patch, prid) || EQ(major, minor, patch, prid)); + } - private bool GuardGreater(int major, int minor, int patch, PR? prid) - { - return _Flag == ComparisonOperator.GreaterThan && !GT(major, minor, patch, prid); - } + private bool GuardGreater(int major, int minor, int patch, PR? prid) + { + return _Flag == ComparisonOperator.GreaterThan && !GT(major, minor, patch, prid); + } - private bool GuardMinor(int minor, int patch) - { - return _Flag == ComparisonOperator.MinorUplift && (minor < _Minor || (minor == _Minor && patch < _Patch)); - } + private bool GuardMinor(int minor, int patch) + { + return _Flag == ComparisonOperator.MinorUplift && (minor < _Minor || (minor == _Minor && patch < _Patch)); + } - private bool GuardPatch(int minor, int patch) - { - return _Flag == ComparisonOperator.PatchUplift && (minor != _Minor || patch < _Patch); - } + private bool GuardPatch(int minor, int patch) + { + return _Flag == ComparisonOperator.PatchUplift && (minor != _Minor || patch < _Patch); + } - private bool GuardMajor(int major) - { - return (_Flag == ComparisonOperator.MinorUplift || _Flag == ComparisonOperator.PatchUplift) && major != _Major; - } + private bool GuardMajor(int major) + { + return (_Flag == ComparisonOperator.MinorUplift || _Flag == ComparisonOperator.PatchUplift) && major != _Major; + } - private bool GuardPRID(PR? prid) - { - return !_IncludePrerelease && Stable && !IsStable(prid); - } + private bool GuardPRID(PR? prid) + { + return !_IncludePrerelease && Stable && !IsStable(prid); + } - private bool EQ(int major, int minor, int patch, PR? prid) - { - return EQCore(major, minor, patch) && PR(prid) == 0; - } + private bool EQ(int major, int minor, int patch, PR? prid) + { + return EQCore(major, minor, patch) && PR(prid) == 0; + } - private bool EQCore(int major, int minor, int patch) - { - return (_Major == -1 || _Major == major) && - (_Minor == -1 || _Minor == minor) && - (_Patch == -1 || _Patch == patch); - } + private bool EQCore(int major, int minor, int patch) + { + return (_Major == -1 || _Major == major) && + (_Minor == -1 || _Minor == minor) && + (_Patch == -1 || _Patch == patch); + } - private bool GTCore(int major, int minor, int patch) - { - return (major > _Major) || - (major == _Major && minor > _Minor) || - (major == _Major && minor == _Minor && patch > _Patch); - } + private bool GTCore(int major, int minor, int patch) + { + return (major > _Major) || + (major == _Major && minor > _Minor) || + (major == _Major && minor == _Minor && patch > _Patch); + } - private bool LTCore(int major, int minor, int patch) - { - return (major < _Major) || - (major == _Major && minor < _Minor) || - (major == _Major && minor == _Minor && patch < _Patch); - } + private bool LTCore(int major, int minor, int patch) + { + return (major < _Major) || + (major == _Major && minor < _Minor) || + (major == _Major && minor == _Minor && patch < _Patch); + } - /// - /// Greater Than. - /// - private bool GT(int major, int minor, int patch, PR? prid) - { - return !IsStable(prid) && !_IncludePrerelease - ? EQCore(major, minor, patch) && PR(prid) < 0 - : GTCore(major, minor, patch) || (EQCore(major, minor, patch) && PR(prid) < 0); - } + /// + /// Greater Than. + /// + private bool GT(int major, int minor, int patch, PR? prid) + { + return !IsStable(prid) && !_IncludePrerelease + ? EQCore(major, minor, patch) && PR(prid) < 0 + : GTCore(major, minor, patch) || (EQCore(major, minor, patch) && PR(prid) < 0); + } - /// - /// Less Than. - /// - private bool LT(int major, int minor, int patch, PR? prid) - { - return !IsStable(prid) && !_IncludePrerelease - ? EQCore(major, minor, patch) && PR(prid) > 0 - : LTCore(major, minor, patch) || (EQCore(major, minor, patch) && PR(prid) > 0); - } + /// + /// Less Than. + /// + private bool LT(int major, int minor, int patch, PR? prid) + { + return !IsStable(prid) && !_IncludePrerelease + ? EQCore(major, minor, patch) && PR(prid) > 0 + : LTCore(major, minor, patch) || (EQCore(major, minor, patch) && PR(prid) > 0); + } - /// - /// Compare pre-release. - /// - private int PR(PR? prid) - { - return _PRID.CompareTo(prid); - } + /// + /// Compare pre-release. + /// + private int PR(PR? prid) + { + return _PRID.CompareTo(prid); + } - private static bool IsStable(PR? prid) - { - return prid == null || prid.Stable; - } + private static bool IsStable(PR? prid) + { + return prid == null || prid.Stable; } + } + /// + /// A semantic version. + /// + public sealed class Version : IComparable, IEquatable + { /// - /// A semantic version. + /// The major part of the version. /// - public sealed class Version : IComparable, IEquatable - { - /// - /// The major part of the version. - /// - public readonly int Major; - - /// - /// The minor part of the version. - /// - public readonly int Minor; - - /// - /// The patch part of the version. - /// - public readonly int Patch; - - /// - /// The pre-release part of the version. - /// - public readonly PR Prerelease; - - /// - /// The build part of the version. - /// - public readonly string Build; - - internal Version(int major, int minor, int patch, PR prerelease, string build) - { - Major = major; - Minor = minor; - Patch = patch; - Prerelease = prerelease; - Build = build; - } + public readonly int Major; - /// - /// Try to parse a semantic version from a string. - /// - public static bool TryParse(string value, out Version? version) - { - return TryParseVersion(value, out version); - } + /// + /// The minor part of the version. + /// + public readonly int Minor; - /// - public override string ToString() - { - return string.Concat(Major, '.', Minor, '.', Patch); - } + /// + /// The patch part of the version. + /// + public readonly int Patch; - /// - public override bool Equals(object obj) - { - return obj is Version version && Equals(version); - } + /// + /// The pre-release part of the version. + /// + public readonly PR Prerelease; - /// - public override int GetHashCode() - { - unchecked // Overflow is fine - { - var hash = 17; - hash = hash * 23 + Major.GetHashCode(); - hash = hash * 23 + Minor.GetHashCode(); - hash = hash * 23 + Patch.GetHashCode(); - hash = hash * 23 + (Prerelease != null ? Prerelease.GetHashCode() : 0); - hash = hash * 23 + (Build != null ? Build.GetHashCode() : 0); - return hash; - } - } + /// + /// The build part of the version. + /// + public readonly string Build; - /// - public static bool operator ==(Version? a, Version? b) - { - return a is null && b is null || - a is not null && a.Equals(b) || - b is not null && b.Equals(a); - } + internal Version(int major, int minor, int patch, PR prerelease, string build) + { + Major = major; + Minor = minor; + Patch = patch; + Prerelease = prerelease; + Build = build; + } - /// - public static bool operator !=(Version? a, Version? b) - { - return !(a is null && b is null || - a is not null && a.Equals(b) || - b is not null && b.Equals(a)); - } + /// + /// Try to parse a semantic version from a string. + /// + public static bool TryParse(string value, out Version? version) + { + return TryParseVersion(value, out version); + } - /// - /// Compare the version against another version. - /// - public bool Equals(Version? other) - { - return other != null && - Equals(other.Major, other.Minor, other.Patch, other.Prerelease?.Value); - } + /// + public override string ToString() + { + return string.Concat(Major, '.', Minor, '.', Patch); + } + + /// + public override bool Equals(object obj) + { + return obj is Version version && Equals(version); + } - /// - /// Compare the version against another version based on major.minor.patch. - /// - public bool Equals(int major, int minor, int patch, string? prerelease = null) + /// + public override int GetHashCode() + { + unchecked // Overflow is fine { - return major == Major && - minor == Minor && - patch == Patch && - new PR(prerelease).Equals(Prerelease); + var hash = 17; + hash = hash * 23 + Major.GetHashCode(); + hash = hash * 23 + Minor.GetHashCode(); + hash = hash * 23 + Patch.GetHashCode(); + hash = hash * 23 + (Prerelease != null ? Prerelease.GetHashCode() : 0); + hash = hash * 23 + (Build != null ? Build.GetHashCode() : 0); + return hash; } + } - /// - /// Compare the version against another version. - /// - public int CompareTo(Version other) - { - if (other == null) - return 1; + /// + public static bool operator ==(Version? a, Version? b) + { + return a is null && b is null || + a is not null && a.Equals(b) || + b is not null && b.Equals(a); + } - if (Major != other.Major) - return Major > other.Major ? 32 : -32; + /// + public static bool operator !=(Version? a, Version? b) + { + return !(a is null && b is null || + a is not null && a.Equals(b) || + b is not null && b.Equals(a)); + } - if (Minor != other.Minor) - return Minor > other.Minor ? 16 : -16; + /// + /// Compare the version against another version. + /// + public bool Equals(Version? other) + { + return other != null && + Equals(other.Major, other.Minor, other.Patch, other.Prerelease?.Value); + } - return Patch != other.Patch ? Patch > other.Patch ? 8 : -8 : 0; - } + /// + /// Compare the version against another version based on major.minor.patch. + /// + public bool Equals(int major, int minor, int patch, string? prerelease = null) + { + return major == Major && + minor == Minor && + patch == Patch && + new PR(prerelease).Equals(Prerelease); } /// - /// A semantic version pre-release identifier. + /// Compare the version against another version. /// - [DebuggerDisplay("{Value}")] - public sealed class PR + public int CompareTo(Version other) { - internal static readonly PR Empty = new(); - private static readonly char[] SEPARATORS = new char[] { SEPARATOR }; + if (other == null) + return 1; - private readonly string[]? _Identifiers; + if (Major != other.Major) + return Major > other.Major ? 32 : -32; - private PR() - { - Value = string.Empty; - _Identifiers = null; - } + if (Minor != other.Minor) + return Minor > other.Minor ? 16 : -16; - internal PR(string? value) - { - Value = value == null || string.IsNullOrEmpty(value) ? string.Empty : value; - _Identifiers = value == null || string.IsNullOrEmpty(value) ? null : value.Split(SEPARATORS, StringSplitOptions.RemoveEmptyEntries); - } + return Patch != other.Patch ? Patch > other.Patch ? 8 : -8 : 0; + } + } - /// - /// The string value of a pre-release identifier. - /// - public string Value { get; } + /// + /// A semantic version pre-release identifier. + /// + [DebuggerDisplay("{Value}")] + public sealed class PR + { + internal static readonly PR Empty = new(); + private static readonly char[] SEPARATORS = new char[] { SEPARATOR }; - /// - /// Is the pre-release identifier empty, indicating a stable release. - /// - public bool Stable => _Identifiers == null; + private readonly string[]? _Identifiers; - /// - /// Compare the pre-release identifer to another pre-release identifier. - /// - public int CompareTo(PR? pr) - { - if (pr == null || pr.Stable || pr._Identifiers == null) - return Stable ? 0 : -1; - else if (Stable || _Identifiers == null) - return 1; + private PR() + { + Value = string.Empty; + _Identifiers = null; + } - var i = -1; - var left = _Identifiers; - var right = pr._Identifiers; + internal PR(string? value) + { + Value = value == null || string.IsNullOrEmpty(value) ? string.Empty : value; + _Identifiers = value == null || string.IsNullOrEmpty(value) ? null : value.Split(SEPARATORS, StringSplitOptions.RemoveEmptyEntries); + } - while (++i < left.Length && i < right.Length) - { - var leftNumeric = false; - var rightNumeric = false; - if (long.TryParse(left[i], out var l)) - leftNumeric = true; + /// + /// The string value of a pre-release identifier. + /// + public string Value { get; } - if (long.TryParse(right[i], out var r)) - rightNumeric = true; + /// + /// Is the pre-release identifier empty, indicating a stable release. + /// + public bool Stable => _Identifiers == null; - if (leftNumeric != rightNumeric) - return leftNumeric ? -1 : 1; + /// + /// Compare the pre-release identifer to another pre-release identifier. + /// + public int CompareTo(PR? pr) + { + if (pr == null || pr.Stable || pr._Identifiers == null) + return Stable ? 0 : -1; + else if (Stable || _Identifiers == null) + return 1; - if (leftNumeric && rightNumeric && l == r) - continue; + var i = -1; + var left = _Identifiers; + var right = pr._Identifiers; - if (leftNumeric && rightNumeric) - return l.CompareTo(r); + while (++i < left.Length && i < right.Length) + { + var leftNumeric = false; + var rightNumeric = false; + if (long.TryParse(left[i], out var l)) + leftNumeric = true; - var result = string.Compare(left[i], right[i], StringComparison.Ordinal); - if (result == 0) - continue; + if (long.TryParse(right[i], out var r)) + rightNumeric = true; - return result; - } - if (left.Length == right.Length) - return 0; + if (leftNumeric != rightNumeric) + return leftNumeric ? -1 : 1; - return left.Length > right.Length ? 1 : -1; - } + if (leftNumeric && rightNumeric && l == r) + continue; - /// - public override bool Equals(object obj) - { - return obj is PR prerelease && Value.Equals(prerelease.Value); - } + if (leftNumeric && rightNumeric) + return l.CompareTo(r); - /// - public override int GetHashCode() - { - return Value.GetHashCode(); - } + var result = string.Compare(left[i], right[i], StringComparison.Ordinal); + if (result == 0) + continue; - /// - public override string ToString() - { - return Value.ToString(); + return result; } + if (left.Length == right.Length) + return 0; + + return left.Length > right.Length ? 1 : -1; } - [DebuggerDisplay("Current = {_Current}, Position = {_Position}, Value = {_Value}")] - private sealed class VersionStream + /// + public override bool Equals(object obj) { - private readonly string _Value; - private int _Position; - private char _Current; - - internal VersionStream(string value) - { - _Value = value; - _Position = 0; - _Current = _Value.Length > 0 ? _Value[0] : char.MinValue; - } - - internal bool EOF => _Position >= _Value.Length; - - internal void Next() - { - Next(1); - } + return obj is PR prerelease && Value.Equals(prerelease.Value); + } - private void Next(int count) - { - _Position += count; - if (EOF || _Position >= _Value.Length) - return; + /// + public override int GetHashCode() + { + return Value.GetHashCode(); + } - _Current = _Value[_Position]; - } + /// + public override string ToString() + { + return Value.ToString(); + } + } - internal void Operator(out ComparisonOperator comparison) - { - comparison = ComparisonOperator.None; - while (!EOF && IsConstraint(_Current)) - { - if (_Current == MINOR) - comparison = ComparisonOperator.MinorUplift; - else if (_Current == PATCH) - comparison = ComparisonOperator.PatchUplift; - else if (_Current == EQUAL) - comparison |= ComparisonOperator.Equals; - else if (_Current == GREATER) - comparison |= ComparisonOperator.GreaterThan; - else if (_Current == LESS) - comparison |= ComparisonOperator.LessThan; + [DebuggerDisplay("Current = {_Current}, Position = {_Position}, Value = {_Value}")] + private sealed class VersionStream + { + private readonly string _Value; + private int _Position; + private char _Current; - Next(); - } - } + internal VersionStream(string value) + { + _Value = value; + _Position = 0; + _Current = _Value.Length > 0 ? _Value[0] : char.MinValue; + } - internal void Flags(out ConstraintModifier flag) - { - flag = ConstraintModifier.None; - if (EOF || _Current != AT) - return; + internal bool EOF => _Position >= _Value.Length; - if (HasFlag(FLAG_PRE) || HasFlag(FLAG_PRERELEASE)) - flag |= ConstraintModifier.Prerelease; - } + internal void Next() + { + Next(1); + } - private bool HasFlag(string value) - { - if (string.Compare(_Value, _Position, value, 0, value.Length, false) != 0) - return false; + private void Next(int count) + { + _Position += count; + if (EOF || _Position >= _Value.Length) + return; - Next(value.Length); - return true; - } + _Current = _Value[_Position]; + } - private void SkipLeading() - { - if (!EOF && (_Current == VUPPER || _Current == VLOWER)) - Next(); - } + internal void Operator(out ComparisonOperator comparison) + { + comparison = ComparisonOperator.None; + while (!EOF && IsConstraint(_Current)) + { + if (_Current == MINOR) + comparison = ComparisonOperator.MinorUplift; + else if (_Current == PATCH) + comparison = ComparisonOperator.PatchUplift; + else if (_Current == EQUAL) + comparison |= ComparisonOperator.Equals; + else if (_Current == GREATER) + comparison |= ComparisonOperator.GreaterThan; + else if (_Current == LESS) + comparison |= ComparisonOperator.LessThan; - internal bool TryDigit(out int digit) - { - var pos = _Position; - var count = 0; - while (!EOF && IsVersionDigit(_Current)) - { - count++; - Next(); - } - digit = count > 0 ? int.Parse(_Value.Substring(pos, count), Thread.CurrentThread.CurrentCulture) : 0; - return count > 0; + Next(); } + } - internal bool TrySegments(out int[] segments) - { - segments = new int[] { -1, -1, -1, -1 }; - var segmentIndex = 0; - SkipLeading(); - while (!EOF) - { - if (!IsAllowedChar(_Current)) - return false; + internal void Flags(out ConstraintModifier flag) + { + flag = ConstraintModifier.None; + if (EOF || _Current != AT) + return; - if (TryDigit(out var digit)) - segments[segmentIndex++] = digit; + if (HasFlag(FLAG_PRE) || HasFlag(FLAG_PRERELEASE)) + flag |= ConstraintModifier.Prerelease; + } - if (IsSeparator(_Current)) - Next(); + private bool HasFlag(string value) + { + if (string.Compare(_Value, _Position, value, 0, value.Length, false) != 0) + return false; - if (IsWildcard(_Current)) - { - segments[segmentIndex++] = -1; - Next(); - } + Next(value.Length); + return true; + } - if (IsIdentifier(_Current)) - return true; + private void SkipLeading() + { + if (!EOF && (_Current == VUPPER || _Current == VLOWER)) + Next(); + } - if (IsJoin(_Current)) - return true; - } - return segmentIndex > 0; + internal bool TryDigit(out int digit) + { + var pos = _Position; + var count = 0; + while (!EOF && IsVersionDigit(_Current)) + { + count++; + Next(); } + digit = count > 0 ? int.Parse(_Value.Substring(pos, count), Thread.CurrentThread.CurrentCulture) : 0; + return count > 0; + } - internal bool Prerelease(out PR identifier) + internal bool TrySegments(out int[] segments) + { + segments = new int[] { -1, -1, -1, -1 }; + var segmentIndex = 0; + SkipLeading(); + while (!EOF) { - identifier = PR.Empty; - if (EOF || _Current != DASH) - return true; - - Next(); - var start = _Position; - if (EOF) + if (!IsAllowedChar(_Current)) return false; - var numeric = true; - while (!EOF && IsPrereleaseChar(_Current, ref numeric)) + if (TryDigit(out var digit)) + segments[segmentIndex++] = digit; + + if (IsSeparator(_Current)) Next(); - if (_Position - start == 0) - return false; + if (IsWildcard(_Current)) + { + segments[segmentIndex++] = -1; + Next(); + } - // No leading 0 if numeric - var id = _Value.Substring(start, _Position - start); - if (numeric && id.Length > 1 && id[0] == ZERO) - return false; + if (IsIdentifier(_Current)) + return true; - identifier = new PR(id); - return true; + if (IsJoin(_Current)) + return true; } + return segmentIndex > 0; + } - internal void Build(out string label) - { - label = string.Empty; - if (EOF || _Current != PLUS) - return; + internal bool Prerelease(out PR identifier) + { + identifier = PR.Empty; + if (EOF || _Current != DASH) + return true; + Next(); + var start = _Position; + if (EOF) + return false; + + var numeric = true; + while (!EOF && IsPrereleaseChar(_Current, ref numeric)) Next(); - var start = _Position; - if (_Current == ZERO) - return; - while (!EOF && IsBuildChar(_Current)) - Next(); + if (_Position - start == 0) + return false; - label = _Value.Substring(start, _Position - start); - } + // No leading 0 if numeric + var id = _Value.Substring(start, _Position - start); + if (numeric && id.Length > 1 && id[0] == ZERO) + return false; - /// - /// 1.2.3 || 3.4.5 - /// >=1.2.3 <3.4.5 - /// - internal JoinOperator GetJoin() - { - var result = JoinOperator.None; - while ((_Current == SPACE || _Current == PIPE) && !EOF) - { - if (result == JoinOperator.None && _Current == SPACE) - result = JoinOperator.And; + identifier = new PR(id); + return true; + } - if (_Current == PIPE) - result = JoinOperator.Or; + internal void Build(out string label) + { + label = string.Empty; + if (EOF || _Current != PLUS) + return; - Next(); - } - return _Current != SPACE || _Current != PIPE ? result : JoinOperator.Or; - } + Next(); + var start = _Position; + if (_Current == ZERO) + return; - [DebuggerStepThrough()] - private static bool IsConstraint(char c) - { - return c == MINOR || c == PATCH || c == EQUAL || c == GREATER || c == LESS; - } + while (!EOF && IsBuildChar(_Current)) + Next(); - [DebuggerStepThrough()] - private static bool IsVersionDigit(char c) - { - return char.IsDigit(c); - } + label = _Value.Substring(start, _Position - start); + } - [DebuggerStepThrough()] - private static bool IsWildcard(char c) + /// + /// 1.2.3 || 3.4.5 + /// >=1.2.3 <3.4.5 + /// + internal JoinOperator GetJoin() + { + var result = JoinOperator.None; + while ((_Current == SPACE || _Current == PIPE) && !EOF) { - return c == '*' || c == 'X' || c == 'x'; - } + if (result == JoinOperator.None && _Current == SPACE) + result = JoinOperator.And; - [DebuggerStepThrough()] - private static bool IsSeparator(char c) - { - return c == SEPARATOR; - } + if (_Current == PIPE) + result = JoinOperator.Or; - [DebuggerStepThrough()] - private static bool IsAllowedChar(char c) - { - return IsVersionDigit(c) || IsSeparator(c) || IsWildcard(c) || IsIdentifier(c); + Next(); } + return _Current != SPACE || _Current != PIPE ? result : JoinOperator.Or; + } - [DebuggerStepThrough()] - private static bool IsPrereleaseChar(char c, ref bool numeric) - { - if (numeric && char.IsDigit(c)) - return true; - - numeric = false; - return char.IsDigit(c) || IsLetter(c) || c == DASH || c == SEPARATOR; - } + [DebuggerStepThrough()] + private static bool IsConstraint(char c) + { + return c == MINOR || c == PATCH || c == EQUAL || c == GREATER || c == LESS; + } - [DebuggerStepThrough()] - private static bool IsBuildChar(char c) - { - return char.IsDigit(c) || IsLetter(c); - } + [DebuggerStepThrough()] + private static bool IsVersionDigit(char c) + { + return char.IsDigit(c); + } - [DebuggerStepThrough()] - private static bool IsIdentifier(char c) - { - return c == DASH || c == PLUS; - } + [DebuggerStepThrough()] + private static bool IsWildcard(char c) + { + return c == '*' || c == 'X' || c == 'x'; + } - [DebuggerStepThrough()] - private static bool IsJoin(char c) - { - return c == SPACE || c == PIPE; - } + [DebuggerStepThrough()] + private static bool IsSeparator(char c) + { + return c == SEPARATOR; + } - /// - /// Is the character within the reduced set of allowed characters. a-z or A-Z. - /// - [DebuggerStepThrough()] - private static bool IsLetter(char c) - { - var nc = (int)c; - return (nc >= 0x41 && nc <= 0x5a) || (nc >= 0x61 && nc <= 0x7a); - } + [DebuggerStepThrough()] + private static bool IsAllowedChar(char c) + { + return IsVersionDigit(c) || IsSeparator(c) || IsWildcard(c) || IsIdentifier(c); } - /// - /// Try to parse a version constraint from the provided string. - /// - public static bool TryParseConstraint(string value, out ISemanticVersionConstraint constraint, bool includePrerelease = false) + [DebuggerStepThrough()] + private static bool IsPrereleaseChar(char c, ref bool numeric) { - var c = new VersionConstraint(value); - constraint = c; - if (string.IsNullOrEmpty(value)) + if (numeric && char.IsDigit(c)) return true; - var stream = new VersionStream(value); - stream.Flags(out var flags); - if (flags.HasFlag(ConstraintModifier.Prerelease)) - includePrerelease = true; + numeric = false; + return char.IsDigit(c) || IsLetter(c) || c == DASH || c == SEPARATOR; + } - while (!stream.EOF) - { - stream.Operator(out var comparison); - if (!stream.TrySegments(out var segments)) - return false; + [DebuggerStepThrough()] + private static bool IsBuildChar(char c) + { + return char.IsDigit(c) || IsLetter(c); + } - stream.Prerelease(out var prerelease); - stream.Build(out _); + [DebuggerStepThrough()] + private static bool IsIdentifier(char c) + { + return c == DASH || c == PLUS; + } - c.Join(segments[0], segments[1], segments[2], prerelease, comparison, stream.GetJoin(), includePrerelease); - } - return true; + [DebuggerStepThrough()] + private static bool IsJoin(char c) + { + return c == SPACE || c == PIPE; } /// - /// Try to parse a version from the provided string. + /// Is the character within the reduced set of allowed characters. a-z or A-Z. /// - public static bool TryParseVersion(string value, out Version? version) + [DebuggerStepThrough()] + private static bool IsLetter(char c) { - version = null; - if (string.IsNullOrEmpty(value)) - return false; + var nc = (int)c; + return (nc >= 0x41 && nc <= 0x5a) || (nc >= 0x61 && nc <= 0x7a); + } + } + + /// + /// Try to parse a version constraint from the provided string. + /// + public static bool TryParseConstraint(string value, out ISemanticVersionConstraint constraint, bool includePrerelease = false) + { + var c = new VersionConstraint(value); + constraint = c; + if (string.IsNullOrEmpty(value)) + return true; - var stream = new VersionStream(value); + var stream = new VersionStream(value); + stream.Flags(out var flags); + if (flags.HasFlag(ConstraintModifier.Prerelease)) + includePrerelease = true; + + while (!stream.EOF) + { + stream.Operator(out var comparison); if (!stream.TrySegments(out var segments)) return false; - if (!stream.Prerelease(out var prerelease)) - return false; + stream.Prerelease(out var prerelease); + stream.Build(out _); - stream.Build(out var build); - version = new Version(segments[0], segments[1], segments[2], prerelease, build); - return true; + c.Join(segments[0], segments[1], segments[2], prerelease, comparison, stream.GetJoin(), includePrerelease); } + return true; + } + + /// + /// Try to parse a version from the provided string. + /// + public static bool TryParseVersion(string value, out Version? version) + { + version = null; + if (string.IsNullOrEmpty(value)) + return false; + + var stream = new VersionStream(value); + if (!stream.TrySegments(out var segments)) + return false; + + if (!stream.Prerelease(out var prerelease)) + return false; + + stream.Build(out var build); + version = new Version(segments[0], segments[1], segments[2], prerelease, build); + return true; } } diff --git a/src/PSRule.Types/Data/StringArrayMap.cs b/src/PSRule.Types/Data/StringArrayMap.cs index e803dec584..7a60de4d53 100644 --- a/src/PSRule.Types/Data/StringArrayMap.cs +++ b/src/PSRule.Types/Data/StringArrayMap.cs @@ -4,129 +4,128 @@ using System.Collections; using PSRule.Converters; -namespace PSRule.Data +namespace PSRule.Data; + +/// +/// A mapping of string to string arrays. +/// +public sealed class StringArrayMap : IEnumerable> { + private readonly Dictionary _Map; + /// - /// A mapping of string to string arrays. + /// Create an empty instance. /// - public sealed class StringArrayMap : IEnumerable> + public StringArrayMap() { - private readonly Dictionary _Map; - - /// - /// Create an empty instance. - /// - public StringArrayMap() - { - _Map = new Dictionary(StringComparer.OrdinalIgnoreCase); - } + _Map = new Dictionary(StringComparer.OrdinalIgnoreCase); + } - /// - /// Create an instance by copying an existing . - /// - internal StringArrayMap(StringArrayMap map) - { - _Map = new Dictionary(map._Map, StringComparer.OrdinalIgnoreCase); - } + /// + /// Create an instance by copying an existing . + /// + internal StringArrayMap(StringArrayMap map) + { + _Map = new Dictionary(map._Map, StringComparer.OrdinalIgnoreCase); + } - /// - /// Create an instance by copying mapped keys from a string dictionary. - /// - internal StringArrayMap(Dictionary map) - { - _Map = new Dictionary(map, StringComparer.OrdinalIgnoreCase); - } + /// + /// Create an instance by copying mapped keys from a string dictionary. + /// + internal StringArrayMap(Dictionary map) + { + _Map = new Dictionary(map, StringComparer.OrdinalIgnoreCase); + } - /// - /// Create an instance by copying mapped keys from a . - /// - /// - internal StringArrayMap(Hashtable map) - : this() - { - if (map != null) Load(this, map.IndexByString()); - } + /// + /// Create an instance by copying mapped keys from a . + /// + /// + internal StringArrayMap(Hashtable map) + : this() + { + if (map != null) Load(this, map.IndexByString()); + } - /// - /// The number of mapped keys. - /// - public int Count => _Map.Count; + /// + /// The number of mapped keys. + /// + public int Count => _Map.Count; - /// - /// Get or set mapping for a specified key. - /// - public string[] this[string key] + /// + /// Get or set mapping for a specified key. + /// + public string[] this[string key] + { + get { - get - { - return !string.IsNullOrEmpty(key) && _Map.TryGetValue(key, out var value) ? value : Array.Empty(); - } - set - { - if (!string.IsNullOrEmpty(key)) - _Map[key] = value; - } + return !string.IsNullOrEmpty(key) && _Map.TryGetValue(key, out var value) ? value : Array.Empty(); } - - /// - /// - /// - /// - public static implicit operator StringArrayMap(Hashtable hashtable) + set { - return new StringArrayMap(hashtable); + if (!string.IsNullOrEmpty(key)) + _Map[key] = value; } + } - /// - /// Try to get a mapping by key. - /// - /// The key. - /// Returns an array of mapped keys. - /// Returns true if the key was found. Otherwise false is returned. - public bool TryGetValue(string key, out string[] value) - { - return _Map.TryGetValue(key, out value); - } + /// + /// + /// + /// + public static implicit operator StringArrayMap(Hashtable hashtable) + { + return new StringArrayMap(hashtable); + } - /// - /// Load a key map from an existing dictionary. - /// - internal static void Load(StringArrayMap map, IDictionary properties) - { - foreach (var property in properties) - { - if (TypeConverter.TryStringOrArray(property.Value, convert: true, out var values) && values != null) - map[property.Key] = values; - } - } + /// + /// Try to get a mapping by key. + /// + /// The key. + /// Returns an array of mapped keys. + /// Returns true if the key was found. Otherwise false is returned. + public bool TryGetValue(string key, out string[] value) + { + return _Map.TryGetValue(key, out value); + } - /// - public IEnumerator> GetEnumerator() + /// + /// Load a key map from an existing dictionary. + /// + internal static void Load(StringArrayMap map, IDictionary properties) + { + foreach (var property in properties) { - return ((IEnumerable>)_Map).GetEnumerator(); + if (TypeConverter.TryStringOrArray(property.Value, convert: true, out var values) && values != null) + map[property.Key] = values; } + } - /// - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } + /// + public IEnumerator> GetEnumerator() + { + return ((IEnumerable>)_Map).GetEnumerator(); + } - /// - /// Convert the instance into a dictionary. - /// - /// - public IDictionary ToDictionary() - { - return _Map; - } + /// + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } - /// - /// Convert a hashtable into a instance. - /// - public static StringArrayMap FromHashtable(Hashtable hashtable) - { - return new StringArrayMap(hashtable); - } + /// + /// Convert the instance into a dictionary. + /// + /// + public IDictionary ToDictionary() + { + return _Map; + } + + /// + /// Convert a hashtable into a instance. + /// + public static StringArrayMap FromHashtable(Hashtable hashtable) + { + return new StringArrayMap(hashtable); } } diff --git a/src/PSRule.Types/Data/TargetIssueInfo.cs b/src/PSRule.Types/Data/TargetIssueInfo.cs index 4b18ace25a..3940a87baa 100644 --- a/src/PSRule.Types/Data/TargetIssueInfo.cs +++ b/src/PSRule.Types/Data/TargetIssueInfo.cs @@ -3,78 +3,77 @@ using Newtonsoft.Json; -namespace PSRule.Data +namespace PSRule.Data; + +/// +/// An issue reported by a downstream tool. +/// +public sealed class TargetIssueInfo : IEquatable { + private const string PROPERTY_TYPE = "type"; + private const string PROPERTY_NAME = "name"; + private const string PROPERTY_PATH = "path"; + private const string PROPERTY_MESSAGE = "message"; + /// - /// An issue reported by a downstream tool. + /// Create an empty issue. /// - public sealed class TargetIssueInfo : IEquatable + public TargetIssueInfo() { - private const string PROPERTY_TYPE = "type"; - private const string PROPERTY_NAME = "name"; - private const string PROPERTY_PATH = "path"; - private const string PROPERTY_MESSAGE = "message"; - - /// - /// Create an empty issue. - /// - public TargetIssueInfo() - { - // Do nothing - } + // Do nothing + } - /// - /// The type of issue. - /// - [JsonProperty(PropertyName = PROPERTY_TYPE)] - public string? Type { get; internal set; } + /// + /// The type of issue. + /// + [JsonProperty(PropertyName = PROPERTY_TYPE)] + public string? Type { get; internal set; } - /// - /// The name of the issue. - /// - [JsonProperty(PropertyName = PROPERTY_NAME)] - public string? Name { get; internal set; } + /// + /// The name of the issue. + /// + [JsonProperty(PropertyName = PROPERTY_NAME)] + public string? Name { get; internal set; } - /// - /// The object path reported by the issue. - /// - [JsonProperty(PropertyName = PROPERTY_PATH)] - public string? Path { get; internal set; } + /// + /// The object path reported by the issue. + /// + [JsonProperty(PropertyName = PROPERTY_PATH)] + public string? Path { get; internal set; } - /// - /// A localized message describing the issue. - /// - [JsonProperty(PropertyName = PROPERTY_MESSAGE)] - public string? Message { get; internal set; } + /// + /// A localized message describing the issue. + /// + [JsonProperty(PropertyName = PROPERTY_MESSAGE)] + public string? Message { get; internal set; } - /// - public bool Equals(TargetIssueInfo other) - { - return other != null && - Type == other.Type && - Name == other.Name && - Path == other.Path && - Message == other.Message; - } + /// + public bool Equals(TargetIssueInfo other) + { + return other != null && + Type == other.Type && + Name == other.Name && + Path == other.Path && + Message == other.Message; + } - /// - public override bool Equals(object obj) - { - return obj is TargetIssueInfo info && Equals(info); - } + /// + public override bool Equals(object obj) + { + return obj is TargetIssueInfo info && Equals(info); + } - /// - public override int GetHashCode() + /// + public override int GetHashCode() + { + unchecked // Overflow is fine { - unchecked // Overflow is fine - { - var hash = 17; - hash = hash * 23 + (Type != null ? Type.GetHashCode() : 0); - hash = hash * 23 + (Name != null ? Name.GetHashCode() : 0); - hash = hash * 23 + (Path != null ? Path.GetHashCode() : 0); - hash = hash * 23 + (Message != null ? Message.GetHashCode() : 0); - return hash; - } + var hash = 17; + hash = hash * 23 + (Type != null ? Type.GetHashCode() : 0); + hash = hash * 23 + (Name != null ? Name.GetHashCode() : 0); + hash = hash * 23 + (Path != null ? Path.GetHashCode() : 0); + hash = hash * 23 + (Message != null ? Message.GetHashCode() : 0); + return hash; } } } diff --git a/src/PSRule.Types/Data/TargetSourceInfo.cs b/src/PSRule.Types/Data/TargetSourceInfo.cs index 6a421184a6..f68ea06654 100644 --- a/src/PSRule.Types/Data/TargetSourceInfo.cs +++ b/src/PSRule.Types/Data/TargetSourceInfo.cs @@ -4,129 +4,128 @@ using Newtonsoft.Json; using PSRule.Expressions; -namespace PSRule.Data +namespace PSRule.Data; + +/// +/// An object source location reported by a downstream tool. +/// +public sealed class TargetSourceInfo : IEquatable, IFileInfo { + private const string PROPERTY_FILE = "file"; + private const string PROPERTY_LINE = "line"; + private const string PROPERTY_POSITION = "position"; + private const string PROPERTY_TYPE = "type"; + + private const string COLON = ":"; + private const string COLONSPACE = ": "; + /// - /// An object source location reported by a downstream tool. + /// Creates an empty source information structure. /// - public sealed class TargetSourceInfo : IEquatable, IFileInfo + public TargetSourceInfo() { - private const string PROPERTY_FILE = "file"; - private const string PROPERTY_LINE = "line"; - private const string PROPERTY_POSITION = "position"; - private const string PROPERTY_TYPE = "type"; - - private const string COLON = ":"; - private const string COLONSPACE = ": "; - - /// - /// Creates an empty source information structure. - /// - public TargetSourceInfo() - { - // Do nothing - } + // Do nothing + } - internal TargetSourceInfo(InputFileInfo info) - { - File = info.FullName; - Type = "File"; - } + internal TargetSourceInfo(InputFileInfo info) + { + File = info.FullName; + Type = "File"; + } - internal TargetSourceInfo(FileInfo info) - { - File = info.FullName; - Type = "File"; - } + internal TargetSourceInfo(FileInfo info) + { + File = info.FullName; + Type = "File"; + } - internal TargetSourceInfo(Uri uri) - { - File = uri.AbsoluteUri; - Type = "File"; - } + internal TargetSourceInfo(Uri uri) + { + File = uri.AbsoluteUri; + Type = "File"; + } - /// - /// The file path of the source file. - /// - [JsonProperty(PropertyName = PROPERTY_FILE)] - public string? File { get; internal set; } + /// + /// The file path of the source file. + /// + [JsonProperty(PropertyName = PROPERTY_FILE)] + public string? File { get; internal set; } - /// - /// The first line of the object. - /// - [JsonProperty(PropertyName = PROPERTY_LINE)] - public int? Line { get; internal set; } + /// + /// The first line of the object. + /// + [JsonProperty(PropertyName = PROPERTY_LINE)] + public int? Line { get; internal set; } - /// - /// The first position of the object. - /// - [JsonProperty(PropertyName = PROPERTY_POSITION)] - public int? Position { get; internal set; } + /// + /// The first position of the object. + /// + [JsonProperty(PropertyName = PROPERTY_POSITION)] + public int? Position { get; internal set; } - /// - /// The type of source. - /// - [JsonProperty(PropertyName = PROPERTY_TYPE)] - public string? Type { get; internal set; } + /// + /// The type of source. + /// + [JsonProperty(PropertyName = PROPERTY_TYPE)] + public string? Type { get; internal set; } - string? IFileInfo.Path => File; + string? IFileInfo.Path => File; - string? IFileInfo.Extension => Path.GetExtension(File); + string? IFileInfo.Extension => Path.GetExtension(File); - /// - public bool Equals(TargetSourceInfo other) - { - return other != null && - File == other.File && - Line == other.Line && - Position == other.Position && - Type == other.Type; - } + /// + public bool Equals(TargetSourceInfo other) + { + return other != null && + File == other.File && + Line == other.Line && + Position == other.Position && + Type == other.Type; + } - /// - public override bool Equals(object obj) - { - return obj is TargetSourceInfo info && Equals(info); - } + /// + public override bool Equals(object obj) + { + return obj is TargetSourceInfo info && Equals(info); + } - /// - public override int GetHashCode() + /// + public override int GetHashCode() + { + unchecked // Overflow is fine { - unchecked // Overflow is fine - { - var hash = 17; - hash = hash * 23 + (File != null ? File.GetHashCode() : 0); - hash = hash * 23 + (Line.HasValue ? Line.Value.GetHashCode() : 0); - hash = hash * 23 + (Position.HasValue ? Position.Value.GetHashCode() : 0); - hash = hash * 23 + (Type != null ? Type.GetHashCode() : 0); - return hash; - } + var hash = 17; + hash = hash * 23 + (File != null ? File.GetHashCode() : 0); + hash = hash * 23 + (Line.HasValue ? Line.Value.GetHashCode() : 0); + hash = hash * 23 + (Position.HasValue ? Position.Value.GetHashCode() : 0); + hash = hash * 23 + (Type != null ? Type.GetHashCode() : 0); + return hash; } + } - /// - public override string ToString() - { - return ToString(null, false); - } + /// + public override string ToString() + { + return ToString(null, false); + } - /// - /// Converts the souce information into a formatted string for display. - /// - /// The default type to use if the type was not specified. - /// Determine if a relative path is returned. - /// A formatted source string. - public string ToString(string? defaultType, bool useRelativePath) - { - var type = Type ?? defaultType; - var file = GetPath(useRelativePath); - return string.IsNullOrEmpty(type) - ? string.Concat(file, COLON, Line, COLON, Position) - : string.Concat(type, COLONSPACE, file, COLON, Line, COLON, Position); - } + /// + /// Converts the souce information into a formatted string for display. + /// + /// The default type to use if the type was not specified. + /// Determine if a relative path is returned. + /// A formatted source string. + public string ToString(string? defaultType, bool useRelativePath) + { + var type = Type ?? defaultType; + var file = GetPath(useRelativePath); + return string.IsNullOrEmpty(type) + ? string.Concat(file, COLON, Line, COLON, Position) + : string.Concat(type, COLONSPACE, file, COLON, Line, COLON, Position); + } - internal string? GetPath(bool useRelativePath) - { - return useRelativePath ? Helpers.NormalizePath(Environment.GetWorkingPath(), File) : File; - } + internal string? GetPath(bool useRelativePath) + { + return useRelativePath ? Helpers.NormalizePath(Environment.GetWorkingPath(), File) : File; } } diff --git a/src/PSRule.Types/DictionaryExtensions.cs b/src/PSRule.Types/DictionaryExtensions.cs index be66a996f0..cf9b1f3d8f 100644 --- a/src/PSRule.Types/DictionaryExtensions.cs +++ b/src/PSRule.Types/DictionaryExtensions.cs @@ -6,265 +6,264 @@ using PSRule.Converters; using PSRule.Data; -namespace PSRule +namespace PSRule; + +/// +/// Extension methods for . +/// +public static class DictionaryExtensions { /// - /// Extension methods for . + /// Try to get a value and remove it from the dictionary. /// - public static class DictionaryExtensions + [DebuggerStepThrough] + public static bool TryPopValue(this IDictionary dictionary, string key, out object value) { - /// - /// Try to get a value and remove it from the dictionary. - /// - [DebuggerStepThrough] - public static bool TryPopValue(this IDictionary dictionary, string key, out object value) - { - return dictionary.TryGetValue(key, out value) && dictionary.Remove(key); - } + return dictionary.TryGetValue(key, out value) && dictionary.Remove(key); + } - /// - /// Try to get a value and remove it from the dictionary. - /// - [DebuggerStepThrough] - public static bool TryPopValue(this IDictionary dictionary, string key, out T? value) + /// + /// Try to get a value and remove it from the dictionary. + /// + [DebuggerStepThrough] + public static bool TryPopValue(this IDictionary dictionary, string key, out T? value) + { + value = default; + if (dictionary.TryGetValue(key, out var v) && dictionary.Remove(key) && v is T result) { - value = default; - if (dictionary.TryGetValue(key, out var v) && dictionary.Remove(key) && v is T result) - { - value = result; - return true; - } - return false; + value = result; + return true; } + return false; + } - /// - /// Try to get a and remove it from the dictionary. - /// - [DebuggerStepThrough] - public static bool TryPopBool(this IDictionary dictionary, string key, out bool value) - { - value = default; - return TryPopValue(dictionary, key, out var v) && bool.TryParse(v.ToString(), out value); - } + /// + /// Try to get a and remove it from the dictionary. + /// + [DebuggerStepThrough] + public static bool TryPopBool(this IDictionary dictionary, string key, out bool value) + { + value = default; + return TryPopValue(dictionary, key, out var v) && bool.TryParse(v.ToString(), out value); + } + + /// + /// Try to get a and remove it from the dictionary. + /// + [DebuggerStepThrough] + public static bool TryPopEnum(this IDictionary dictionary, string key, out TEnum value) where TEnum : struct + { + value = default; + return TryPopValue(dictionary, key, out var v) && Enum.TryParse(v.ToString(), ignoreCase: true, result: out value); + } - /// - /// Try to get a and remove it from the dictionary. - /// - [DebuggerStepThrough] - public static bool TryPopEnum(this IDictionary dictionary, string key, out TEnum value) where TEnum : struct + /// + /// Try to get a and remove it from the dictionary. + /// + [DebuggerStepThrough] + public static bool TryPopString(this IDictionary dictionary, string key, out string? value) + { + value = default; + if (TryPopValue(dictionary, key, out var v) && v is string svalue) { - value = default; - return TryPopValue(dictionary, key, out var v) && Enum.TryParse(v.ToString(), ignoreCase: true, result: out value); + value = svalue; + return true; } + return false; + } - /// - /// Try to get a and remove it from the dictionary. - /// - [DebuggerStepThrough] - public static bool TryPopString(this IDictionary dictionary, string key, out string? value) + /// + /// Try to get an array of strings and remove it from the dictionary. + /// + [DebuggerStepThrough] + public static bool TryPopStringArray(this IDictionary dictionary, string key, out string[]? value) + { + value = default; + return TryPopValue(dictionary, key, out var v) && TypeConverter.TryStringOrArray(v, convert: true, value: out value); + } + + /// + /// Try to get a and remove it from the dictionary. + /// + [DebuggerStepThrough] + public static bool TryPopStringArrayMap(this IDictionary dictionary, string key, out StringArrayMap? value) + { + value = default; + if (TryPopValue(dictionary, key, out var v) && v is StringArrayMap svalue) { - value = default; - if (TryPopValue(dictionary, key, out var v) && v is string svalue) - { - value = svalue; - return true; - } - return false; + value = svalue; + return true; } - - /// - /// Try to get an array of strings and remove it from the dictionary. - /// - [DebuggerStepThrough] - public static bool TryPopStringArray(this IDictionary dictionary, string key, out string[]? value) + if (v is Hashtable hashtable) { - value = default; - return TryPopValue(dictionary, key, out var v) && TypeConverter.TryStringOrArray(v, convert: true, value: out value); + value = StringArrayMap.FromHashtable(hashtable); + return true; } + return false; + } - /// - /// Try to get a and remove it from the dictionary. - /// - [DebuggerStepThrough] - public static bool TryPopStringArrayMap(this IDictionary dictionary, string key, out StringArrayMap? value) - { - value = default; - if (TryPopValue(dictionary, key, out var v) && v is StringArrayMap svalue) - { - value = svalue; - return true; - } - if (v is Hashtable hashtable) - { - value = StringArrayMap.FromHashtable(hashtable); - return true; - } + /// + /// Try to get the value as a . + /// + [DebuggerStepThrough] + public static bool TryGetBool(this IDictionary dictionary, string key, out bool? value) + { + value = null; + if (!dictionary.TryGetValue(key, out var o)) return false; - } - /// - /// Try to get the value as a . - /// - [DebuggerStepThrough] - public static bool TryGetBool(this IDictionary dictionary, string key, out bool? value) + if (o is bool bvalue || (o is string svalue && bool.TryParse(svalue, out bvalue))) { - value = null; - if (!dictionary.TryGetValue(key, out var o)) - return false; + value = bvalue; + return true; + } + return false; + } - if (o is bool bvalue || (o is string svalue && bool.TryParse(svalue, out bvalue))) - { - value = bvalue; - return true; - } + /// + /// Try to get the value as a . + /// + [DebuggerStepThrough] + public static bool TryGetLong(this IDictionary dictionary, string key, out long? value) + { + value = null; + if (!dictionary.TryGetValue(key, out var o)) return false; - } - /// - /// Try to get the value as a . - /// - [DebuggerStepThrough] - public static bool TryGetLong(this IDictionary dictionary, string key, out long? value) + if (TypeConverter.TryLong(o, convert: true, value: out var i_value)) { - value = null; - if (!dictionary.TryGetValue(key, out var o)) - return false; + value = i_value; + return true; + } + return false; + } - if (TypeConverter.TryLong(o, convert: true, value: out var i_value)) - { - value = i_value; - return true; - } + /// + /// Try to get the value as a . + /// + [DebuggerStepThrough] + public static bool TryGetInt(this IDictionary dictionary, string key, out int? value) + { + value = null; + if (!dictionary.TryGetValue(key, out var o)) return false; - } - /// - /// Try to get the value as a . - /// - [DebuggerStepThrough] - public static bool TryGetInt(this IDictionary dictionary, string key, out int? value) + if (TypeConverter.TryInt(o, convert: true, value: out var i_value)) { - value = null; - if (!dictionary.TryGetValue(key, out var o)) - return false; + value = i_value; + return true; + } + return false; + } - if (TypeConverter.TryInt(o, convert: true, value: out var i_value)) - { - value = i_value; - return true; - } + /// + /// Try to get the value as a . + /// + [DebuggerStepThrough] + public static bool TryGetChar(this IDictionary dictionary, string key, out char? value) + { + value = null; + if (!dictionary.TryGetValue(key, out var o)) return false; - } - /// - /// Try to get the value as a . - /// - [DebuggerStepThrough] - public static bool TryGetChar(this IDictionary dictionary, string key, out char? value) + if (o is string svalue && svalue.Length == 1) { - value = null; - if (!dictionary.TryGetValue(key, out var o)) - return false; - - if (o is string svalue && svalue.Length == 1) - { - value = svalue[0]; - return true; - } - if (o is char cvalue) - { - value = cvalue; - return true; - } - return false; + value = svalue[0]; + return true; } - - /// - /// Try to get the value as a . - /// - [DebuggerStepThrough] - public static bool TryGetString(this IDictionary dictionary, string key, out string? value) + if (o is char cvalue) { - value = null; - if (!dictionary.TryGetValue(key, out var o)) - return false; + value = cvalue; + return true; + } + return false; + } - if (o is string svalue) - { - value = svalue; - return true; - } + /// + /// Try to get the value as a . + /// + [DebuggerStepThrough] + public static bool TryGetString(this IDictionary dictionary, string key, out string? value) + { + value = null; + if (!dictionary.TryGetValue(key, out var o)) return false; - } - /// - /// Try to get the value as an . - /// - [DebuggerStepThrough] - public static bool TryGetEnumerable(this IDictionary dictionary, string key, out IEnumerable? value) + if (o is string svalue) { - value = null; - if (!dictionary.TryGetValue(key, out var o)) - return false; + value = svalue; + return true; + } + return false; + } - if (o is IEnumerable evalue) - { - value = evalue; - return true; - } + /// + /// Try to get the value as an . + /// + [DebuggerStepThrough] + public static bool TryGetEnumerable(this IDictionary dictionary, string key, out IEnumerable? value) + { + value = null; + if (!dictionary.TryGetValue(key, out var o)) return false; - } - /// - /// Try to get the value as an array of strings. - /// - [DebuggerStepThrough] - public static bool TryGetStringArray(this IDictionary dictionary, string key, out string[]? value) + if (o is IEnumerable evalue) { - value = null; - return dictionary.TryGetValue(key, out var o) && TypeConverter.TryStringOrArray(o, convert: true, value: out value); + value = evalue; + return true; } + return false; + } - /// - /// Add unique keys to the dictionary. - /// Duplicate keys are ignored. - /// - [DebuggerStepThrough] - public static void AddUnique(this IDictionary dictionary, IEnumerable> values) where T : class - { - if (values == null) return; + /// + /// Try to get the value as an array of strings. + /// + [DebuggerStepThrough] + public static bool TryGetStringArray(this IDictionary dictionary, string key, out string[]? value) + { + value = null; + return dictionary.TryGetValue(key, out var o) && TypeConverter.TryStringOrArray(o, convert: true, value: out value); + } - 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) where T : class + { + if (values == null) return; - /// - /// Add unique keys to the dictionary. - /// Duplicate keys are ignored. - /// - [DebuggerStepThrough] - public static void AddUnique(this IDictionary dictionary, IEnumerable> values) + foreach (var kv in values) { - if (values == null) return; - - foreach (var kv in values) - { - if (!dictionary.ContainsKey(kv.Key)) - dictionary.Add(kv.Key, kv.Value); - } + if (!dictionary.ContainsKey(kv.Key)) + dictionary.Add(kv.Key, kv.Value); } + } - internal static SortedDictionary ToSortedDictionary(this IDictionary dictionary) - { - return new SortedDictionary(dictionary); - } + /// + /// Add unique keys to the dictionary. + /// Duplicate keys are ignored. + /// + [DebuggerStepThrough] + public static void AddUnique(this IDictionary dictionary, IEnumerable> values) + { + if (values == null) return; - internal static bool NullOrEmpty(this IDictionary dictionary) + foreach (var kv in values) { - return dictionary == null || dictionary.Count == 0; + if (!dictionary.ContainsKey(kv.Key)) + dictionary.Add(kv.Key, kv.Value); } } + + internal static SortedDictionary ToSortedDictionary(this IDictionary dictionary) + { + return new SortedDictionary(dictionary); + } + + internal static bool NullOrEmpty(this IDictionary dictionary) + { + return dictionary == null || dictionary.Count == 0; + } } diff --git a/src/PSRule.Types/Environment.cs b/src/PSRule.Types/Environment.cs index ce0b908f23..179d1781d8 100644 --- a/src/PSRule.Types/Environment.cs +++ b/src/PSRule.Types/Environment.cs @@ -7,333 +7,332 @@ using System.Security; using PSRule.Data; -namespace PSRule +namespace PSRule; + +/// +/// A helper for accessing environment and runtime variables. +/// +public static class Environment { + private static readonly char[] STRINGARRAYMAP_ITEMSEPARATOR = new char[] { ',' }; + private static readonly char[] STRINGARRAY_SEPARATOR = new char[] { ';' }; + private static readonly char[] LINUX_PATH_ENV_SEPARATOR = new char[] { ':' }; + private static readonly char[] WINDOWS_PATH_ENV_SEPARATOR = new char[] { ';' }; + + private const char BACKSLASH = '\\'; + private const char SLASH = '/'; + + private const char STRINGARRYAMAP_PAIRSEPARATOR = '='; + private const string PATH_ENV = "PATH"; + private const string DEFAULT_CREDENTIAL_USERNAME = "na"; + private const string TF_BUILD = "TF_BUILD"; + private const string GITHUB_ACTIONS = "GITHUB_ACTIONS"; + /// - /// A helper for accessing environment and runtime variables. + /// A callback that is overridden by PowerShell so that the current working path can be retrieved. /// - public static class Environment + private static WorkingPathResolver _GetWorkingPath = () => Directory.GetCurrentDirectory(); + + /// + /// Sets the current culture to use when processing rules unless otherwise specified. + /// + private static CultureInfo _CurrentCulture = Thread.CurrentThread.CurrentCulture; + + /// + /// A delgate to allow callback get current working path. + /// + public delegate string WorkingPathResolver(); + + /// + /// Configures PSRule to use the culture of the current thread at runtime. + /// + [DebuggerStepThrough] + public static void UseCurrentCulture() { - private static readonly char[] STRINGARRAYMAP_ITEMSEPARATOR = new char[] { ',' }; - private static readonly char[] STRINGARRAY_SEPARATOR = new char[] { ';' }; - private static readonly char[] LINUX_PATH_ENV_SEPARATOR = new char[] { ':' }; - private static readonly char[] WINDOWS_PATH_ENV_SEPARATOR = new char[] { ';' }; - - private const char BACKSLASH = '\\'; - private const char SLASH = '/'; - - private const char STRINGARRYAMAP_PAIRSEPARATOR = '='; - private const string PATH_ENV = "PATH"; - private const string DEFAULT_CREDENTIAL_USERNAME = "na"; - private const string TF_BUILD = "TF_BUILD"; - private const string GITHUB_ACTIONS = "GITHUB_ACTIONS"; - - /// - /// A callback that is overridden by PowerShell so that the current working path can be retrieved. - /// - private static WorkingPathResolver _GetWorkingPath = () => Directory.GetCurrentDirectory(); - - /// - /// Sets the current culture to use when processing rules unless otherwise specified. - /// - private static CultureInfo _CurrentCulture = Thread.CurrentThread.CurrentCulture; - - /// - /// A delgate to allow callback get current working path. - /// - public delegate string WorkingPathResolver(); - - /// - /// Configures PSRule to use the culture of the current thread at runtime. - /// - [DebuggerStepThrough] - public static void UseCurrentCulture() - { - UseCurrentCulture(Thread.CurrentThread.CurrentCulture); - } + UseCurrentCulture(Thread.CurrentThread.CurrentCulture); + } - /// - /// Configures PSRule to use the specified culture at runtime. - /// - /// A valid culture. - [DebuggerStepThrough] - public static void UseCurrentCulture(string culture) - { - UseCurrentCulture(CultureInfo.CreateSpecificCulture(culture)); - } + /// + /// Configures PSRule to use the specified culture at runtime. + /// + /// A valid culture. + [DebuggerStepThrough] + public static void UseCurrentCulture(string culture) + { + UseCurrentCulture(CultureInfo.CreateSpecificCulture(culture)); + } - /// - /// Configures PSRule to use the specified culture at runtime. - /// - /// A valid culture. - public static void UseCurrentCulture(CultureInfo culture) - { - _CurrentCulture = culture; - } + /// + /// Configures PSRule to use the specified culture at runtime. + /// + /// A valid culture. + public static void UseCurrentCulture(CultureInfo culture) + { + _CurrentCulture = culture; + } - /// - /// Configures PSRule to use the specified resolver to determine the current working path. - /// - /// A method that can be used to resolve the current working path. - internal static void UseWorkingPathResolver(WorkingPathResolver resolver) - { - _GetWorkingPath = resolver; - } + /// + /// Configures PSRule to use the specified resolver to determine the current working path. + /// + /// A method that can be used to resolve the current working path. + internal static void UseWorkingPathResolver(WorkingPathResolver resolver) + { + _GetWorkingPath = resolver; + } - /// - /// Gets the current working path being used by PSRule. - /// - /// The current working path. - public static string GetWorkingPath() - { - return _GetWorkingPath(); - } + /// + /// Gets the current working path being used by PSRule. + /// + /// The current working path. + public static string GetWorkingPath() + { + return _GetWorkingPath(); + } - /// - /// Get the current culture being used by PSRule. - /// - /// The current culture. - public static CultureInfo GetCurrentCulture() - { - return _CurrentCulture; - } + /// + /// Get the current culture being used by PSRule. + /// + /// The current culture. + public static CultureInfo GetCurrentCulture() + { + return _CurrentCulture; + } - /// - /// Get a full path instead of a relative path that may be passed from PowerShell. - /// - /// A full or relative path. - /// When set to true the returned path uses forward slashes instead of backslashes. - /// The base path to use. When null of unspecified, the current working path will be used. - /// A absolute path. - internal static string GetRootedPath(string? path, bool normalize = false, string? basePath = null) - { - if (string.IsNullOrEmpty(path)) - path = string.Empty; + /// + /// Get a full path instead of a relative path that may be passed from PowerShell. + /// + /// A full or relative path. + /// When set to true the returned path uses forward slashes instead of backslashes. + /// The base path to use. When null of unspecified, the current working path will be used. + /// A absolute path. + internal static string GetRootedPath(string? path, bool normalize = false, string? basePath = null) + { + if (string.IsNullOrEmpty(path)) + path = string.Empty; - basePath ??= GetWorkingPath(); - var rootedPath = Path.IsPathRooted(path) ? Path.GetFullPath(path) : Path.GetFullPath(Path.Combine(basePath, path)); - return normalize ? rootedPath.Replace(BACKSLASH, SLASH) : rootedPath; - } + basePath ??= GetWorkingPath(); + var rootedPath = Path.IsPathRooted(path) ? Path.GetFullPath(path) : Path.GetFullPath(Path.Combine(basePath, path)); + return normalize ? rootedPath.Replace(BACKSLASH, SLASH) : rootedPath; + } - /// - /// Get a full base path instead of a relative path that may be passed from PowerShell. - /// - /// A full or relative path. - /// When set to true the returned path uses forward slashes instead of backslashes. - /// A absolute base path. - /// - /// A base path always includes a trailing /. - /// - internal static string GetRootedBasePath(string path, bool normalize = false) - { - if (string.IsNullOrEmpty(path)) - path = string.Empty; - - var rootedPath = GetRootedPath(path); - var basePath = rootedPath.Length > 0 && IsPathSeparator(rootedPath[rootedPath.Length - 1]) - ? rootedPath - : string.Concat(rootedPath, Path.DirectorySeparatorChar); - return normalize ? basePath.Replace(BACKSLASH, SLASH) : basePath; - } + /// + /// Get a full base path instead of a relative path that may be passed from PowerShell. + /// + /// A full or relative path. + /// When set to true the returned path uses forward slashes instead of backslashes. + /// A absolute base path. + /// + /// A base path always includes a trailing /. + /// + internal static string GetRootedBasePath(string path, bool normalize = false) + { + if (string.IsNullOrEmpty(path)) + path = string.Empty; + + var rootedPath = GetRootedPath(path); + var basePath = rootedPath.Length > 0 && IsPathSeparator(rootedPath[rootedPath.Length - 1]) + ? rootedPath + : string.Concat(rootedPath, Path.DirectorySeparatorChar); + return normalize ? basePath.Replace(BACKSLASH, SLASH) : basePath; + } - /// - /// Determine if the environment is running within Azure Pipelines. - /// - public static bool IsAzurePipelines() - { - return TryBool(TF_BUILD, out var azp) && azp.HasValue && azp.Value; - } + /// + /// Determine if the environment is running within Azure Pipelines. + /// + public static bool IsAzurePipelines() + { + return TryBool(TF_BUILD, out var azp) && azp.HasValue && azp.Value; + } - /// - /// Determines if the environment is running within GitHub Actions. - /// - public static bool IsGitHubActions() - { - return TryBool(GITHUB_ACTIONS, out var gh) && gh.HasValue && gh.Value; - } + /// + /// Determines if the environment is running within GitHub Actions. + /// + public static bool IsGitHubActions() + { + return TryBool(GITHUB_ACTIONS, out var gh) && gh.HasValue && gh.Value; + } - /// - /// Determine if the environment is running within Visual Studio Code. - /// - public static bool IsVisualStudioCode() - { - return TryString("TERM_PROGRAM", out var term) && term == "vscode"; - } + /// + /// Determine if the environment is running within Visual Studio Code. + /// + public static bool IsVisualStudioCode() + { + return TryString("TERM_PROGRAM", out var term) && term == "vscode"; + } - /// - /// Get the run identifier for the current environment. - /// - public static string? GetRunId() - { - if (TryString("PSRULE_RUN_ID", out var runId) && runId != null) - return runId; + /// + /// Get the run identifier for the current environment. + /// + public static string? GetRunId() + { + if (TryString("PSRULE_RUN_ID", out var runId) && runId != null) + return runId; - return TryString("BUILD_REPOSITORY_NAME", out var prefix) && TryString("BUILD_BUILDID", out var suffix) || - TryString("GITHUB_REPOSITORY", out prefix) && TryString("GITHUB_RUN_ID", out suffix) - ? string.Concat(prefix, "/", suffix) - : null; - } + return TryString("BUILD_REPOSITORY_NAME", out var prefix) && TryString("BUILD_BUILDID", out var suffix) || + TryString("GITHUB_REPOSITORY", out prefix) && TryString("GITHUB_RUN_ID", out suffix) + ? string.Concat(prefix, "/", suffix) + : null; + } - /// - /// Try to get the environment variable as a . - /// - public static bool TryString(string key, out string? value) - { - return TryVariable(key, out value) && !string.IsNullOrEmpty(value); - } + /// + /// Try to get the environment variable as a . + /// + public static bool TryString(string key, out string? value) + { + return TryVariable(key, out value) && !string.IsNullOrEmpty(value); + } - /// - /// Try to get the environment variable as a . - /// - public static bool TrySecureString(string key, out SecureString? value) - { - value = null; - if (!TryString(key, out var variable)) - return false; + /// + /// Try to get the environment variable as a . + /// + public static bool TrySecureString(string key, out SecureString? value) + { + value = null; + if (!TryString(key, out var variable)) + return false; - value = new NetworkCredential(DEFAULT_CREDENTIAL_USERNAME, variable).SecurePassword; - return true; - } + value = new NetworkCredential(DEFAULT_CREDENTIAL_USERNAME, variable).SecurePassword; + return true; + } - /// - /// Try to get the environment variable as an . - /// - public static bool TryInt(string key, out int value) - { - value = default; - return TryVariable(key, out var variable) && int.TryParse(variable, out value); - } + /// + /// Try to get the environment variable as an . + /// + public static bool TryInt(string key, out int value) + { + value = default; + return TryVariable(key, out var variable) && int.TryParse(variable, out value); + } - /// - /// Try to get the environment variable as a . - /// - public static bool TryBool(string key, out bool? value) - { - value = default; - return TryVariable(key, out var variable) && TryParseBool(variable, out value); - } + /// + /// Try to get the environment variable as a . + /// + public static bool TryBool(string key, out bool? value) + { + value = default; + return TryVariable(key, out var variable) && TryParseBool(variable, out value); + } - /// - /// Try to get the environment variable as a enum of type . - /// - public static bool TryEnum(string key, out TEnum value) where TEnum : struct - { - value = default; - return TryVariable(key, out var variable) && Enum.TryParse(variable, ignoreCase: true, out value); - } + /// + /// Try to get the environment variable as a enum of type . + /// + public static bool TryEnum(string key, out TEnum value) where TEnum : struct + { + value = default; + return TryVariable(key, out var variable) && Enum.TryParse(variable, ignoreCase: true, out value); + } - /// - /// Try to get the environment variable as an array of strings. - /// - public static bool TryStringArray(string key, out string[]? value) - { - value = default; - if (!TryVariable(key, out var variable) || variable == null) - return false; + /// + /// Try to get the environment variable as an array of strings. + /// + public static bool TryStringArray(string key, out string[]? value) + { + value = default; + if (!TryVariable(key, out var variable) || variable == null) + return false; - value = variable.Split(STRINGARRAY_SEPARATOR, options: StringSplitOptions.RemoveEmptyEntries); - return value != null; - } + value = variable.Split(STRINGARRAY_SEPARATOR, options: StringSplitOptions.RemoveEmptyEntries); + return value != null; + } - /// - /// Try to get the environment variable as a . - /// - public static bool TryStringArrayMap(string key, out StringArrayMap? value) - { - value = default; - if (!TryVariable(key, out var variable) || variable == null) - return false; - - var pairs = variable.Split(STRINGARRAY_SEPARATOR, options: StringSplitOptions.RemoveEmptyEntries); - if (pairs == null) - return false; - - var map = new StringArrayMap(); - for (var i = 0; i < pairs.Length; i++) - { - var index = pairs[i].IndexOf(STRINGARRYAMAP_PAIRSEPARATOR); - if (index < 1 || index + 1 >= pairs[i].Length) continue; - - var left = pairs[i].Substring(0, index); - var right = pairs[i].Substring(index + 1); - var pair = right.Split(STRINGARRAYMAP_ITEMSEPARATOR, StringSplitOptions.RemoveEmptyEntries); - map[left] = pair; - } - value = map; - return true; - } + /// + /// Try to get the environment variable as a . + /// + public static bool TryStringArrayMap(string key, out StringArrayMap? value) + { + value = default; + if (!TryVariable(key, out var variable) || variable == null) + return false; - /// - /// Try to get the PATH environment variable. - /// - public static bool TryPathEnvironmentVariable(out string[]? value) - { - return TryPathEnvironmentVariable(PATH_ENV, out value); - } + var pairs = variable.Split(STRINGARRAY_SEPARATOR, options: StringSplitOptions.RemoveEmptyEntries); + if (pairs == null) + return false; - /// - /// Try to get a PATH environment variable with a specific name. - /// - public static bool TryPathEnvironmentVariable(string key, out string[]? value) + var map = new StringArrayMap(); + for (var i = 0; i < pairs.Length; i++) { - value = default; - if (!TryVariable(key, out var variable) || variable == null) - return false; + var index = pairs[i].IndexOf(STRINGARRYAMAP_PAIRSEPARATOR); + if (index < 1 || index + 1 >= pairs[i].Length) continue; - var separator = System.Environment.OSVersion.Platform == PlatformID.Win32NT ? WINDOWS_PATH_ENV_SEPARATOR : LINUX_PATH_ENV_SEPARATOR; - value = variable.Split(separator, options: StringSplitOptions.RemoveEmptyEntries); - return value != null; + var left = pairs[i].Substring(0, index); + var right = pairs[i].Substring(index + 1); + var pair = right.Split(STRINGARRAYMAP_ITEMSEPARATOR, StringSplitOptions.RemoveEmptyEntries); + map[left] = pair; } + value = map; + return true; + } - /// - /// Try to get any environment variable with a specific prefix. - /// - public static IEnumerable> GetByPrefix(string prefix) - { - var env = System.Environment.GetEnvironmentVariables(); - var enumerator = env.GetEnumerator(); - while (enumerator.MoveNext()) - { - var key = enumerator.Key.ToString(); - if (key.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) - yield return new KeyValuePair(key, enumerator.Value); - } - } + /// + /// Try to get the PATH environment variable. + /// + public static bool TryPathEnvironmentVariable(out string[]? value) + { + return TryPathEnvironmentVariable(PATH_ENV, out value); + } + + /// + /// Try to get a PATH environment variable with a specific name. + /// + public static bool TryPathEnvironmentVariable(string key, out string[]? value) + { + value = default; + if (!TryVariable(key, out var variable) || variable == null) + return false; - private static bool TryVariable(string key, out string? variable) + var separator = System.Environment.OSVersion.Platform == PlatformID.Win32NT ? WINDOWS_PATH_ENV_SEPARATOR : LINUX_PATH_ENV_SEPARATOR; + value = variable.Split(separator, options: StringSplitOptions.RemoveEmptyEntries); + return value != null; + } + + /// + /// Try to get any environment variable with a specific prefix. + /// + public static IEnumerable> GetByPrefix(string prefix) + { + var env = System.Environment.GetEnvironmentVariables(); + var enumerator = env.GetEnumerator(); + while (enumerator.MoveNext()) { - variable = System.Environment.GetEnvironmentVariable(key); - return variable != null; + var key = enumerator.Key.ToString(); + if (key.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) + yield return new KeyValuePair(key, enumerator.Value); } + } - private static bool TryParseBool(string? variable, out bool? value) - { - value = default; - if (variable == null) - return false; - - if (bool.TryParse(variable, out var b)) - { - value = b; - return true; - } - if (int.TryParse(variable, out var i)) - { - value = i > 0; - return true; - } + private static bool TryVariable(string key, out string? variable) + { + variable = System.Environment.GetEnvironmentVariable(key); + return variable != null; + } + + private static bool TryParseBool(string? variable, out bool? value) + { + value = default; + if (variable == null) return false; - } - /// - /// Determine if the is a path separator character. - /// - /// The character to check. - /// Returns true if the charater is a path separator. Otherwise false is returned. - [DebuggerStepThrough] - private static bool IsPathSeparator(char c) + if (bool.TryParse(variable, out var b)) { - return c == Path.DirectorySeparatorChar || c == Path.AltDirectorySeparatorChar || c == SLASH || c == BACKSLASH; + value = b; + return true; + } + if (int.TryParse(variable, out var i)) + { + value = i > 0; + return true; } + return false; + } + + /// + /// Determine if the is a path separator character. + /// + /// The character to check. + /// Returns true if the charater is a path separator. Otherwise false is returned. + [DebuggerStepThrough] + private static bool IsPathSeparator(char c) + { + return c == Path.DirectorySeparatorChar || c == Path.AltDirectorySeparatorChar || c == SLASH || c == BACKSLASH; } } diff --git a/src/PSRule.Types/Expressions/Helpers.cs b/src/PSRule.Types/Expressions/Helpers.cs index 33be56f3b3..43c750de52 100644 --- a/src/PSRule.Types/Expressions/Helpers.cs +++ b/src/PSRule.Types/Expressions/Helpers.cs @@ -1,26 +1,25 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -namespace PSRule.Expressions +namespace PSRule.Expressions; + +internal static class Helpers { - internal static class Helpers - { - private const char Backslash = '\\'; - private const char Slash = '/'; + private const char Backslash = '\\'; + private const char Slash = '/'; - internal static bool WithinPath(string actualPath, string expectedPath, bool caseSensitive) - { - var expected = Environment.GetRootedBasePath(expectedPath, normalize: true); - var actual = Environment.GetRootedPath(actualPath, normalize: true); - return actual.StartsWith(expected, ignoreCase: !caseSensitive, Thread.CurrentThread.CurrentCulture); - } + internal static bool WithinPath(string actualPath, string expectedPath, bool caseSensitive) + { + var expected = Environment.GetRootedBasePath(expectedPath, normalize: true); + var actual = Environment.GetRootedPath(actualPath, normalize: true); + return actual.StartsWith(expected, ignoreCase: !caseSensitive, Thread.CurrentThread.CurrentCulture); + } - internal static string NormalizePath(string basePath, string? path, bool caseSensitive = true) - { - path = Environment.GetRootedPath(path, normalize: true, basePath: basePath); - basePath = Environment.GetRootedBasePath(basePath, normalize: true); - return path.Length >= basePath.Length && - path.StartsWith(basePath, caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase) ? path.Substring(basePath.Length).Replace(Backslash, Slash) : path; - } + internal static string NormalizePath(string basePath, string? path, bool caseSensitive = true) + { + path = Environment.GetRootedPath(path, normalize: true, basePath: basePath); + basePath = Environment.GetRootedBasePath(basePath, normalize: true); + return path.Length >= basePath.Length && + path.StartsWith(basePath, caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase) ? path.Substring(basePath.Length).Replace(Backslash, Slash) : path; } } diff --git a/src/PSRule.Types/HashtableExtensions.cs b/src/PSRule.Types/HashtableExtensions.cs index a0eaf3af87..53bafa4593 100644 --- a/src/PSRule.Types/HashtableExtensions.cs +++ b/src/PSRule.Types/HashtableExtensions.cs @@ -4,25 +4,24 @@ using System.Collections; using System.Diagnostics; -namespace PSRule +namespace PSRule; + +/// +/// Extension methods for . +/// +public static class HashtableExtensions { /// - /// Extension methods for . + /// Map the hashtable into a dictionary string a string key. /// - public static class HashtableExtensions + [DebuggerStepThrough] + public static IDictionary IndexByString(this Hashtable hashtable, bool ignoreCase = true) { - /// - /// Map the hashtable into a dictionary string a string key. - /// - [DebuggerStepThrough] - public static IDictionary IndexByString(this Hashtable hashtable, bool ignoreCase = true) - { - var comparer = ignoreCase ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal; - var index = new Dictionary(comparer); - foreach (DictionaryEntry entry in hashtable) - index.Add(entry.Key.ToString(), entry.Value); + var comparer = ignoreCase ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal; + var index = new Dictionary(comparer); + foreach (DictionaryEntry entry in hashtable) + index.Add(entry.Key.ToString(), entry.Value); - return index; - } + return index; } } diff --git a/src/PSRule.Types/Options/BaselineOption.cs b/src/PSRule.Types/Options/BaselineOption.cs index cb6a2021e5..3444dd5f9d 100644 --- a/src/PSRule.Types/Options/BaselineOption.cs +++ b/src/PSRule.Types/Options/BaselineOption.cs @@ -4,115 +4,114 @@ using System.ComponentModel; using PSRule.Data; -namespace PSRule.Options +namespace PSRule.Options; + +/// +/// Options that configure baselines. +/// +/// +/// See . +/// +public interface IBaselineOption : IOption { /// - /// Options that configure baselines. + /// A mapping of baseline group names to baselines. /// /// - /// See . + /// See . /// - public interface IBaselineOption : IOption + StringArrayMap? Group { get; } +} + +/// +/// Options that configure baselines. +/// +/// +/// See . +/// +public sealed class BaselineOption : IEquatable, IBaselineOption +{ + internal static readonly BaselineOption Default = new() { - /// - /// A mapping of baseline group names to baselines. - /// - /// - /// See . - /// - StringArrayMap? Group { get; } - } + }; /// - /// Options that configure baselines. + /// Create an option instance. /// - /// - /// See . - /// - public sealed class BaselineOption : IEquatable, IBaselineOption + public BaselineOption() { - internal static readonly BaselineOption Default = new() - { - }; - - /// - /// Create an option instance. - /// - public BaselineOption() - { - Group = null; - } + Group = null; + } - /// - /// Create an option instance based on an existing object. - /// - /// The existing object to copy. - public BaselineOption(BaselineOption option) - { - if (option == null) - return; + /// + /// Create an option instance based on an existing object. + /// + /// The existing object to copy. + public BaselineOption(BaselineOption option) + { + if (option == null) + return; - Group = option.Group; - } + Group = option.Group; + } - /// - public override bool Equals(object obj) - { - return obj is BaselineOption option && Equals(option); - } + /// + public override bool Equals(object obj) + { + return obj is BaselineOption option && Equals(option); + } - /// - public bool Equals(BaselineOption other) - { - return other != null && - Group == other.Group; - } + /// + public bool Equals(BaselineOption other) + { + return other != null && + Group == other.Group; + } - /// - public override int GetHashCode() + /// + public override int GetHashCode() + { + unchecked // Overflow is fine { - unchecked // Overflow is fine - { - var hash = 17; - hash = hash * 23 + (Group != null ? Group.GetHashCode() : 0); - return hash; - } + var hash = 17; + hash = hash * 23 + (Group != null ? Group.GetHashCode() : 0); + return hash; } + } - /// - /// Combines two option instances into a new merged instance. - /// The new instance uses any non-null values from . - /// Any null values from are replaced with . - /// - public static BaselineOption Combine(BaselineOption o1, BaselineOption o2) + /// + /// Combines two option instances into a new merged instance. + /// The new instance uses any non-null values from . + /// Any null values from are replaced with . + /// + public static BaselineOption Combine(BaselineOption o1, BaselineOption o2) + { + var result = new BaselineOption(o1) { - var result = new BaselineOption(o1) - { - Group = o1?.Group ?? o2?.Group, - }; - return result; - } + Group = o1?.Group ?? o2?.Group, + }; + return result; + } - /// - [DefaultValue(null)] - public StringArrayMap? Group { get; set; } + /// + [DefaultValue(null)] + public StringArrayMap? Group { get; set; } - /// - /// Load from environment variables. - /// - internal void Load() - { - if (Environment.TryStringArrayMap("PSRULE_BASELINE_GROUP", out var group)) - Group = group; - } + /// + /// Load from environment variables. + /// + internal void Load() + { + if (Environment.TryStringArrayMap("PSRULE_BASELINE_GROUP", out var group)) + Group = group; + } - /// - /// Load from a dictionary. - /// - internal void Load(Dictionary index) - { - if (index.TryPopStringArrayMap("Baseline.Group", out var group)) - Group = group; - } + /// + /// Load from a dictionary. + /// + internal void Load(Dictionary index) + { + if (index.TryPopStringArrayMap("Baseline.Group", out var group)) + Group = group; } } diff --git a/src/PSRule.Types/Options/ExecutionActionPreference.cs b/src/PSRule.Types/Options/ExecutionActionPreference.cs index 13e57af5e2..d2e6cd0df8 100644 --- a/src/PSRule.Types/Options/ExecutionActionPreference.cs +++ b/src/PSRule.Types/Options/ExecutionActionPreference.cs @@ -1,38 +1,37 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -namespace PSRule.Options +namespace PSRule.Options; + +/// +/// Determines the action to take for execution behaviors. +/// See for the specific behaviors that are configurable. +/// +public enum ExecutionActionPreference { /// - /// Determines the action to take for execution behaviors. - /// See for the specific behaviors that are configurable. + /// No preference. + /// This will inherit from the default. /// - public enum ExecutionActionPreference - { - /// - /// No preference. - /// This will inherit from the default. - /// - None = 0, + None = 0, - /// - /// Continue to execute silently. - /// - Ignore = 1, + /// + /// Continue to execute silently. + /// + Ignore = 1, - /// - /// Continue to execute but log a warning. - /// - Warn = 2, + /// + /// Continue to execute but log a warning. + /// + Warn = 2, - /// - /// Generate an error. - /// - Error = 3, + /// + /// Generate an error. + /// + Error = 3, - /// - /// Continue to execute but write a debug log. - /// - Debug = 4 - } + /// + /// Continue to execute but write a debug log. + /// + Debug = 4 } diff --git a/src/PSRule.Types/Options/ExecutionOption.cs b/src/PSRule.Types/Options/ExecutionOption.cs index 7787ccb70e..e1c17e8a18 100644 --- a/src/PSRule.Types/Options/ExecutionOption.cs +++ b/src/PSRule.Types/Options/ExecutionOption.cs @@ -3,452 +3,451 @@ using System.ComponentModel; -namespace PSRule.Options +namespace PSRule.Options; + +/// +/// Options that configure the execution sandbox. +/// +/// +/// See . +/// +public interface IExecutionOption : IOption { /// - /// Options that configure the execution sandbox. + /// 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. + /// 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. /// - /// - /// See . - /// - public interface IExecutionOption : IOption + ExecutionActionPreference DuplicateResourceId { get; } + + /// + /// Configures the hashing algorithm used by the PSRule runtime. + /// + HashAlgorithm HashAlgorithm { get; } + + /// + /// The langauge mode to execute PowerShell code with. + /// + LanguageMode LanguageMode { get; } + + /// + /// Determines how the initial session state for executing PowerShell code is created. + /// The default is . + /// + SessionState InitialSessionState { get; } + + /// + /// Determines how to handle expired suppression groups. + /// Regardless of the value, an expired suppression group will be ignored. + /// By default, a warning is generated. + /// When set to Error, an error is thrown. + /// When set to Debug, a message is written to the debug log. + /// When set to Ignore, no output will be displayed. + /// + ExecutionActionPreference SuppressionGroupExpired { get; } + + /// + /// Determines how to handle rules that are excluded. + /// By default, excluded rules do not generated any output. + /// When set to Error, an error is thrown. + /// When set to Warn, a warning is generated. + /// When set to Debug, a message is written to the debug log. + /// + ExecutionActionPreference RuleExcluded { get; } + + /// + /// Determines how to handle rules that are suppressed. + /// By default, a warning is generated. + /// When set to Error, an error is thrown. + /// When set to Debug, a message is written to the debug log. + /// When set to Ignore, no output will be displayed. + /// + ExecutionActionPreference RuleSuppressed { get; } + + /// + /// Determines how to handle when an alias to a resource is used. + /// By default, a warning is generated. + /// When set to Error, an error is thrown. + /// When set to Debug, a message is written to the debug log. + /// When set to Ignore, no output will be displayed. + /// + ExecutionActionPreference AliasReference { get; } + + /// + /// Determines how to handle rules that generate inconclusive results. + /// By default, a warning is generated. + /// When set to Error, an error is thrown. + /// When set to Debug, a message is written to the debug log. + /// When set to Ignore, no output will be displayed. + /// + ExecutionActionPreference RuleInconclusive { get; } + + /// + /// Determines how to report when an invariant culture is used. + /// By default, a warning is generated. + /// When set to Error, an error is thrown. + /// When set to Debug, a message is written to the debug log. + /// When set to Ignore, no output will be displayed. + /// + ExecutionActionPreference InvariantCulture { get; } + + /// + /// Determines how to report objects that are not processed by any rule. + /// By default, a warning is generated. + /// When set to Error, an error is thrown. + /// When set to Debug, a message is written to the debug log. + /// When set to Ignore, no output will be displayed. + /// + ExecutionActionPreference UnprocessedObject { get; } +} + +/// +/// Options that configure the execution sandbox. +/// +/// +/// See . +/// +public sealed class ExecutionOption : IEquatable, IExecutionOption +{ + private const LanguageMode DEFAULT_LANGUAGEMODE = Options.LanguageMode.FullLanguage; + private const ExecutionActionPreference DEFAULT_DUPLICATERESOURCEID = ExecutionActionPreference.Error; + private const SessionState DEFAULT_INITIALSESSIONSTATE = SessionState.BuiltIn; + private const ExecutionActionPreference DEFAULT_SUPPRESSIONGROUPEXPIRED = ExecutionActionPreference.Warn; + private const ExecutionActionPreference DEFAULT_RULEEXCLUDED = ExecutionActionPreference.Ignore; + private const ExecutionActionPreference DEFAULT_RULESUPPRESSED = ExecutionActionPreference.Warn; + private const ExecutionActionPreference DEFAULT_ALIASREFERENCE = ExecutionActionPreference.Warn; + private const ExecutionActionPreference DEFAULT_RULEINCONCLUSIVE = ExecutionActionPreference.Warn; + private const ExecutionActionPreference DEFAULT_INVARIANTCULTURE = ExecutionActionPreference.Warn; + private const ExecutionActionPreference DEFAULT_UNPROCESSEDOBJECT = ExecutionActionPreference.Warn; + private const HashAlgorithm DEFAULT_HASHALGORITHM = Options.HashAlgorithm.SHA512; + + internal static readonly ExecutionOption Default = new() + { + DuplicateResourceId = DEFAULT_DUPLICATERESOURCEID, + HashAlgorithm = DEFAULT_HASHALGORITHM, + LanguageMode = DEFAULT_LANGUAGEMODE, + InitialSessionState = DEFAULT_INITIALSESSIONSTATE, + SuppressionGroupExpired = DEFAULT_SUPPRESSIONGROUPEXPIRED, + RuleExcluded = DEFAULT_RULEEXCLUDED, + RuleSuppressed = DEFAULT_RULESUPPRESSED, + AliasReference = DEFAULT_ALIASREFERENCE, + RuleInconclusive = DEFAULT_RULEINCONCLUSIVE, + InvariantCulture = DEFAULT_INVARIANTCULTURE, + UnprocessedObject = DEFAULT_UNPROCESSEDOBJECT, + }; + + /// + /// Creates an empty execution option. + /// + public ExecutionOption() { - /// - /// 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. - /// 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. - /// - ExecutionActionPreference DuplicateResourceId { get; } - - /// - /// Configures the hashing algorithm used by the PSRule runtime. - /// - HashAlgorithm HashAlgorithm { get; } - - /// - /// The langauge mode to execute PowerShell code with. - /// - LanguageMode LanguageMode { get; } - - /// - /// Determines how the initial session state for executing PowerShell code is created. - /// The default is . - /// - SessionState InitialSessionState { get; } - - /// - /// Determines how to handle expired suppression groups. - /// Regardless of the value, an expired suppression group will be ignored. - /// By default, a warning is generated. - /// When set to Error, an error is thrown. - /// When set to Debug, a message is written to the debug log. - /// When set to Ignore, no output will be displayed. - /// - ExecutionActionPreference SuppressionGroupExpired { get; } - - /// - /// Determines how to handle rules that are excluded. - /// By default, excluded rules do not generated any output. - /// When set to Error, an error is thrown. - /// When set to Warn, a warning is generated. - /// When set to Debug, a message is written to the debug log. - /// - ExecutionActionPreference RuleExcluded { get; } - - /// - /// Determines how to handle rules that are suppressed. - /// By default, a warning is generated. - /// When set to Error, an error is thrown. - /// When set to Debug, a message is written to the debug log. - /// When set to Ignore, no output will be displayed. - /// - ExecutionActionPreference RuleSuppressed { get; } - - /// - /// Determines how to handle when an alias to a resource is used. - /// By default, a warning is generated. - /// When set to Error, an error is thrown. - /// When set to Debug, a message is written to the debug log. - /// When set to Ignore, no output will be displayed. - /// - ExecutionActionPreference AliasReference { get; } - - /// - /// Determines how to handle rules that generate inconclusive results. - /// By default, a warning is generated. - /// When set to Error, an error is thrown. - /// When set to Debug, a message is written to the debug log. - /// When set to Ignore, no output will be displayed. - /// - ExecutionActionPreference RuleInconclusive { get; } - - /// - /// Determines how to report when an invariant culture is used. - /// By default, a warning is generated. - /// When set to Error, an error is thrown. - /// When set to Debug, a message is written to the debug log. - /// When set to Ignore, no output will be displayed. - /// - ExecutionActionPreference InvariantCulture { get; } - - /// - /// Determines how to report objects that are not processed by any rule. - /// By default, a warning is generated. - /// When set to Error, an error is thrown. - /// When set to Debug, a message is written to the debug log. - /// When set to Ignore, no output will be displayed. - /// - ExecutionActionPreference UnprocessedObject { get; } + DuplicateResourceId = null; + HashAlgorithm = null; + LanguageMode = null; + InitialSessionState = null; + SuppressionGroupExpired = null; + RuleExcluded = null; + RuleSuppressed = null; + AliasReference = null; + RuleInconclusive = null; + InvariantCulture = null; + UnprocessedObject = null; } /// - /// Options that configure the execution sandbox. + /// Creates a execution option by copying an existing instance. /// - /// - /// See . - /// - public sealed class ExecutionOption : IEquatable, IExecutionOption + /// The option instance to copy. + public ExecutionOption(ExecutionOption option) { - private const LanguageMode DEFAULT_LANGUAGEMODE = Options.LanguageMode.FullLanguage; - private const ExecutionActionPreference DEFAULT_DUPLICATERESOURCEID = ExecutionActionPreference.Error; - private const SessionState DEFAULT_INITIALSESSIONSTATE = SessionState.BuiltIn; - private const ExecutionActionPreference DEFAULT_SUPPRESSIONGROUPEXPIRED = ExecutionActionPreference.Warn; - private const ExecutionActionPreference DEFAULT_RULEEXCLUDED = ExecutionActionPreference.Ignore; - private const ExecutionActionPreference DEFAULT_RULESUPPRESSED = ExecutionActionPreference.Warn; - private const ExecutionActionPreference DEFAULT_ALIASREFERENCE = ExecutionActionPreference.Warn; - private const ExecutionActionPreference DEFAULT_RULEINCONCLUSIVE = ExecutionActionPreference.Warn; - private const ExecutionActionPreference DEFAULT_INVARIANTCULTURE = ExecutionActionPreference.Warn; - private const ExecutionActionPreference DEFAULT_UNPROCESSEDOBJECT = ExecutionActionPreference.Warn; - private const HashAlgorithm DEFAULT_HASHALGORITHM = Options.HashAlgorithm.SHA512; - - internal static readonly ExecutionOption Default = new() - { - DuplicateResourceId = DEFAULT_DUPLICATERESOURCEID, - HashAlgorithm = DEFAULT_HASHALGORITHM, - LanguageMode = DEFAULT_LANGUAGEMODE, - InitialSessionState = DEFAULT_INITIALSESSIONSTATE, - SuppressionGroupExpired = DEFAULT_SUPPRESSIONGROUPEXPIRED, - RuleExcluded = DEFAULT_RULEEXCLUDED, - RuleSuppressed = DEFAULT_RULESUPPRESSED, - AliasReference = DEFAULT_ALIASREFERENCE, - RuleInconclusive = DEFAULT_RULEINCONCLUSIVE, - InvariantCulture = DEFAULT_INVARIANTCULTURE, - UnprocessedObject = DEFAULT_UNPROCESSEDOBJECT, - }; + if (option == null) + return; + + DuplicateResourceId = option.DuplicateResourceId; + HashAlgorithm = option.HashAlgorithm; + LanguageMode = option.LanguageMode; + InitialSessionState = option.InitialSessionState; + SuppressionGroupExpired = option.SuppressionGroupExpired; + RuleExcluded = option.RuleExcluded; + RuleSuppressed = option.RuleSuppressed; + AliasReference = option.AliasReference; + RuleInconclusive = option.RuleInconclusive; + InvariantCulture = option.InvariantCulture; + UnprocessedObject = option.UnprocessedObject; + } - /// - /// Creates an empty execution option. - /// - public ExecutionOption() - { - DuplicateResourceId = null; - HashAlgorithm = null; - LanguageMode = null; - InitialSessionState = null; - SuppressionGroupExpired = null; - RuleExcluded = null; - RuleSuppressed = null; - AliasReference = null; - RuleInconclusive = null; - InvariantCulture = null; - UnprocessedObject = null; - } + /// + public override bool Equals(object obj) + { + return obj is ExecutionOption option && Equals(option); + } - /// - /// Creates a execution option by copying an existing instance. - /// - /// The option instance to copy. - public ExecutionOption(ExecutionOption option) - { - if (option == null) - return; - - DuplicateResourceId = option.DuplicateResourceId; - HashAlgorithm = option.HashAlgorithm; - LanguageMode = option.LanguageMode; - InitialSessionState = option.InitialSessionState; - SuppressionGroupExpired = option.SuppressionGroupExpired; - RuleExcluded = option.RuleExcluded; - RuleSuppressed = option.RuleSuppressed; - AliasReference = option.AliasReference; - RuleInconclusive = option.RuleInconclusive; - InvariantCulture = option.InvariantCulture; - UnprocessedObject = option.UnprocessedObject; - } + /// + public bool Equals(ExecutionOption other) + { + return other != null && + DuplicateResourceId == other.DuplicateResourceId && + HashAlgorithm == other.HashAlgorithm && + LanguageMode == other.LanguageMode && + InitialSessionState == other.InitialSessionState && + SuppressionGroupExpired == other.SuppressionGroupExpired && + RuleExcluded == other.RuleExcluded && + RuleSuppressed == other.RuleSuppressed && + AliasReference == other.AliasReference && + RuleInconclusive == other.RuleInconclusive && + InvariantCulture == other.InvariantCulture && + UnprocessedObject == other.UnprocessedObject; + } - /// - public override bool Equals(object obj) + /// + public override int GetHashCode() + { + unchecked // Overflow is fine { - return obj is ExecutionOption option && Equals(option); + var hash = 17; + hash = hash * 23 + (DuplicateResourceId.HasValue ? DuplicateResourceId.Value.GetHashCode() : 0); + hash = hash * 23 + (HashAlgorithm.HasValue ? HashAlgorithm.Value.GetHashCode() : 0); + hash = hash * 23 + (LanguageMode.HasValue ? LanguageMode.Value.GetHashCode() : 0); + hash = hash * 23 + (InitialSessionState.HasValue ? InitialSessionState.Value.GetHashCode() : 0); + hash = hash * 23 + (SuppressionGroupExpired.HasValue ? SuppressionGroupExpired.Value.GetHashCode() : 0); + hash = hash * 23 + (RuleExcluded.HasValue ? RuleExcluded.Value.GetHashCode() : 0); + hash = hash * 23 + (RuleSuppressed.HasValue ? RuleSuppressed.Value.GetHashCode() : 0); + hash = hash * 23 + (AliasReference.HasValue ? AliasReference.Value.GetHashCode() : 0); + hash = hash * 23 + (RuleInconclusive.HasValue ? RuleInconclusive.Value.GetHashCode() : 0); + hash = hash * 23 + (InvariantCulture.HasValue ? InvariantCulture.Value.GetHashCode() : 0); + hash = hash * 23 + (UnprocessedObject.HasValue ? UnprocessedObject.Value.GetHashCode() : 0); + return hash; } + } - /// - public bool Equals(ExecutionOption other) + /// + /// 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) { - return other != null && - DuplicateResourceId == other.DuplicateResourceId && - HashAlgorithm == other.HashAlgorithm && - LanguageMode == other.LanguageMode && - InitialSessionState == other.InitialSessionState && - SuppressionGroupExpired == other.SuppressionGroupExpired && - RuleExcluded == other.RuleExcluded && - RuleSuppressed == other.RuleSuppressed && - AliasReference == other.AliasReference && - RuleInconclusive == other.RuleInconclusive && - InvariantCulture == other.InvariantCulture && - UnprocessedObject == other.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; + } - /// - public override int GetHashCode() - { - unchecked // Overflow is fine - { - var hash = 17; - hash = hash * 23 + (DuplicateResourceId.HasValue ? DuplicateResourceId.Value.GetHashCode() : 0); - hash = hash * 23 + (HashAlgorithm.HasValue ? HashAlgorithm.Value.GetHashCode() : 0); - hash = hash * 23 + (LanguageMode.HasValue ? LanguageMode.Value.GetHashCode() : 0); - hash = hash * 23 + (InitialSessionState.HasValue ? InitialSessionState.Value.GetHashCode() : 0); - hash = hash * 23 + (SuppressionGroupExpired.HasValue ? SuppressionGroupExpired.Value.GetHashCode() : 0); - hash = hash * 23 + (RuleExcluded.HasValue ? RuleExcluded.Value.GetHashCode() : 0); - hash = hash * 23 + (RuleSuppressed.HasValue ? RuleSuppressed.Value.GetHashCode() : 0); - hash = hash * 23 + (AliasReference.HasValue ? AliasReference.Value.GetHashCode() : 0); - hash = hash * 23 + (RuleInconclusive.HasValue ? RuleInconclusive.Value.GetHashCode() : 0); - hash = hash * 23 + (InvariantCulture.HasValue ? InvariantCulture.Value.GetHashCode() : 0); - hash = hash * 23 + (UnprocessedObject.HasValue ? UnprocessedObject.Value.GetHashCode() : 0); - return hash; - } - } + /// + /// Determines how to handle duplicate resources identifiers during execution. + /// Regardless of the value, only the first resource will be used. + /// 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. + /// + [DefaultValue(null)] + public ExecutionActionPreference? DuplicateResourceId { get; set; } - /// - /// 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, - }; - return result; - } + /// + /// Configures the hashing algorithm used by the PSRule runtime. + /// + [DefaultValue(null)] + public HashAlgorithm? HashAlgorithm { get; set; } - /// - /// Determines how to handle duplicate resources identifiers during execution. - /// Regardless of the value, only the first resource will be used. - /// 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. - /// - [DefaultValue(null)] - public ExecutionActionPreference? DuplicateResourceId { get; set; } - - /// - /// Configures the hashing algorithm used by the PSRule runtime. - /// - [DefaultValue(null)] - public HashAlgorithm? HashAlgorithm { get; set; } - - /// - /// The langauge mode to execute PowerShell code with. - /// - [DefaultValue(null)] - public LanguageMode? LanguageMode { get; set; } - - /// - /// Determines how the initial session state for executing PowerShell code is created. - /// The default is . - /// - [DefaultValue(null)] - public SessionState? InitialSessionState { get; set; } - - /// - /// Determines how to handle expired suppression groups. - /// Regardless of the value, an expired suppression group will be ignored. - /// By default, a warning is generated. - /// When set to Error, an error is thrown. - /// When set to Debug, a message is written to the debug log. - /// When set to Ignore, no output will be displayed. - /// - [DefaultValue(null)] - public ExecutionActionPreference? SuppressionGroupExpired { get; set; } - - /// - /// Determines how to handle rules that are excluded. - /// By default, excluded rules do not generated any output. - /// When set to Error, an error is thrown. - /// When set to Warn, a warning is generated. - /// When set to Debug, a message is written to the debug log. - /// - [DefaultValue(null)] - public ExecutionActionPreference? RuleExcluded { get; set; } - - /// - /// Determines how to handle rules that are suppressed. - /// By default, a warning is generated. - /// When set to Error, an error is thrown. - /// When set to Debug, a message is written to the debug log. - /// When set to Ignore, no output will be displayed. - /// - [DefaultValue(null)] - public ExecutionActionPreference? RuleSuppressed { get; set; } - - /// - /// Determines how to handle when an alias to a resource is used. - /// By default, a warning is generated. - /// When set to Error, an error is thrown. - /// When set to Debug, a message is written to the debug log. - /// When set to Ignore, no output will be displayed. - /// - [DefaultValue(null)] - public ExecutionActionPreference? AliasReference { get; set; } - - /// - /// Determines how to handle rules that generate inconclusive results. - /// By default, a warning is generated. - /// When set to Error, an error is thrown. - /// When set to Debug, a message is written to the debug log. - /// When set to Ignore, no output will be displayed. - /// - [DefaultValue(null)] - public ExecutionActionPreference? RuleInconclusive { get; set; } - - /// - /// Determines how to report when an invariant culture is used. - /// By default, a warning is generated. - /// When set to Error, an error is thrown. - /// When set to Debug, a message is written to the debug log. - /// When set to Ignore, no output will be displayed. - /// - [DefaultValue(null)] - public ExecutionActionPreference? InvariantCulture { get; set; } - - /// - /// Determines how to report objects that are not processed by any rule. - /// By default, a warning is generated. - /// When set to Error, an error is thrown. - /// When set to Debug, a message is written to the debug log. - /// When set to Ignore, no output will be displayed. - /// - [DefaultValue(null)] - public ExecutionActionPreference? UnprocessedObject { get; set; } - - #region IExecutionOption - - ExecutionActionPreference IExecutionOption.DuplicateResourceId => DuplicateResourceId ?? DEFAULT_DUPLICATERESOURCEID; - - HashAlgorithm IExecutionOption.HashAlgorithm => HashAlgorithm ?? DEFAULT_HASHALGORITHM; - - LanguageMode IExecutionOption.LanguageMode => LanguageMode ?? DEFAULT_LANGUAGEMODE; - - SessionState IExecutionOption.InitialSessionState => InitialSessionState ?? DEFAULT_INITIALSESSIONSTATE; - - ExecutionActionPreference IExecutionOption.SuppressionGroupExpired => SuppressionGroupExpired ?? DEFAULT_SUPPRESSIONGROUPEXPIRED; - - ExecutionActionPreference IExecutionOption.RuleExcluded => RuleExcluded ?? DEFAULT_RULEEXCLUDED; - - ExecutionActionPreference IExecutionOption.RuleSuppressed => RuleSuppressed ?? DEFAULT_RULESUPPRESSED; - - ExecutionActionPreference IExecutionOption.AliasReference => AliasReference ?? DEFAULT_ALIASREFERENCE; - - ExecutionActionPreference IExecutionOption.RuleInconclusive => RuleInconclusive ?? DEFAULT_RULEINCONCLUSIVE; - - ExecutionActionPreference IExecutionOption.InvariantCulture => InvariantCulture ?? DEFAULT_INVARIANTCULTURE; - - ExecutionActionPreference IExecutionOption.UnprocessedObject => UnprocessedObject ?? DEFAULT_UNPROCESSEDOBJECT; - - #endregion IExecutionOption - - /// - /// Load from environment variables. - /// - internal void Load() - { - if (Environment.TryEnum("PSRULE_EXECUTION_HASHALGORITHM", out HashAlgorithm hashAlgorithm)) - HashAlgorithm = hashAlgorithm; + /// + /// The langauge mode to execute PowerShell code with. + /// + [DefaultValue(null)] + public LanguageMode? LanguageMode { get; set; } - if (Environment.TryEnum("PSRULE_EXECUTION_DUPLICATERESOURCEID", out ExecutionActionPreference duplicateResourceId)) - DuplicateResourceId = duplicateResourceId; + /// + /// Determines how the initial session state for executing PowerShell code is created. + /// The default is . + /// + [DefaultValue(null)] + public SessionState? InitialSessionState { get; set; } - if (Environment.TryEnum("PSRULE_EXECUTION_LANGUAGEMODE", out LanguageMode languageMode)) - LanguageMode = languageMode; + /// + /// Determines how to handle expired suppression groups. + /// Regardless of the value, an expired suppression group will be ignored. + /// By default, a warning is generated. + /// When set to Error, an error is thrown. + /// When set to Debug, a message is written to the debug log. + /// When set to Ignore, no output will be displayed. + /// + [DefaultValue(null)] + public ExecutionActionPreference? SuppressionGroupExpired { get; set; } - if (Environment.TryEnum("PSRULE_EXECUTION_INITIALSESSIONSTATE", out SessionState initialSessionState)) - InitialSessionState = initialSessionState; + /// + /// Determines how to handle rules that are excluded. + /// By default, excluded rules do not generated any output. + /// When set to Error, an error is thrown. + /// When set to Warn, a warning is generated. + /// When set to Debug, a message is written to the debug log. + /// + [DefaultValue(null)] + public ExecutionActionPreference? RuleExcluded { get; set; } - if (Environment.TryEnum("PSRULE_EXECUTION_SUPPRESSIONGROUPEXPIRED", out ExecutionActionPreference suppressionGroupExpired)) - SuppressionGroupExpired = suppressionGroupExpired; + /// + /// Determines how to handle rules that are suppressed. + /// By default, a warning is generated. + /// When set to Error, an error is thrown. + /// When set to Debug, a message is written to the debug log. + /// When set to Ignore, no output will be displayed. + /// + [DefaultValue(null)] + public ExecutionActionPreference? RuleSuppressed { get; set; } - if (Environment.TryEnum("PSRULE_EXECUTION_RULEEXCLUDED", out ExecutionActionPreference ruleExcluded)) - RuleExcluded = ruleExcluded; + /// + /// Determines how to handle when an alias to a resource is used. + /// By default, a warning is generated. + /// When set to Error, an error is thrown. + /// When set to Debug, a message is written to the debug log. + /// When set to Ignore, no output will be displayed. + /// + [DefaultValue(null)] + public ExecutionActionPreference? AliasReference { get; set; } + + /// + /// Determines how to handle rules that generate inconclusive results. + /// By default, a warning is generated. + /// When set to Error, an error is thrown. + /// When set to Debug, a message is written to the debug log. + /// When set to Ignore, no output will be displayed. + /// + [DefaultValue(null)] + public ExecutionActionPreference? RuleInconclusive { get; set; } - if (Environment.TryEnum("PSRULE_EXECUTION_RULESUPPRESSED", out ExecutionActionPreference ruleSuppressed)) - RuleSuppressed = ruleSuppressed; + /// + /// Determines how to report when an invariant culture is used. + /// By default, a warning is generated. + /// When set to Error, an error is thrown. + /// When set to Debug, a message is written to the debug log. + /// When set to Ignore, no output will be displayed. + /// + [DefaultValue(null)] + public ExecutionActionPreference? InvariantCulture { get; set; } - if (Environment.TryEnum("PSRULE_EXECUTION_ALIASREFERENCE", out ExecutionActionPreference aliasReference)) - AliasReference = aliasReference; + /// + /// Determines how to report objects that are not processed by any rule. + /// By default, a warning is generated. + /// When set to Error, an error is thrown. + /// When set to Debug, a message is written to the debug log. + /// When set to Ignore, no output will be displayed. + /// + [DefaultValue(null)] + public ExecutionActionPreference? UnprocessedObject { get; set; } - if (Environment.TryEnum("PSRULE_EXECUTION_RULEINCONCLUSIVE", out ExecutionActionPreference ruleInconclusive)) - RuleInconclusive = ruleInconclusive; + #region IExecutionOption - if (Environment.TryEnum("PSRULE_EXECUTION_INVARIANTCULTURE", out ExecutionActionPreference invariantCulture)) - InvariantCulture = invariantCulture; + ExecutionActionPreference IExecutionOption.DuplicateResourceId => DuplicateResourceId ?? DEFAULT_DUPLICATERESOURCEID; - if (Environment.TryEnum("PSRULE_EXECUTION_UNPROCESSEDOBJECT", out ExecutionActionPreference unprocessedObject)) - UnprocessedObject = unprocessedObject; - } + HashAlgorithm IExecutionOption.HashAlgorithm => HashAlgorithm ?? DEFAULT_HASHALGORITHM; - /// - /// Load from dictionary. - /// - internal void Load(Dictionary index) - { - if (index.TryPopEnum("Execution.HashAlgorithm", out HashAlgorithm hashAlgorithm)) - HashAlgorithm = hashAlgorithm; + LanguageMode IExecutionOption.LanguageMode => LanguageMode ?? DEFAULT_LANGUAGEMODE; - if (index.TryPopEnum("Execution.DuplicateResourceId", out ExecutionActionPreference duplicateResourceId)) - DuplicateResourceId = duplicateResourceId; + SessionState IExecutionOption.InitialSessionState => InitialSessionState ?? DEFAULT_INITIALSESSIONSTATE; - if (index.TryPopEnum("Execution.LanguageMode", out LanguageMode languageMode)) - LanguageMode = languageMode; + ExecutionActionPreference IExecutionOption.SuppressionGroupExpired => SuppressionGroupExpired ?? DEFAULT_SUPPRESSIONGROUPEXPIRED; - if (index.TryPopEnum("Execution.InitialSessionState", out SessionState initialSessionState)) - InitialSessionState = initialSessionState; + ExecutionActionPreference IExecutionOption.RuleExcluded => RuleExcluded ?? DEFAULT_RULEEXCLUDED; - if (index.TryPopEnum("Execution.SuppressionGroupExpired", out ExecutionActionPreference suppressionGroupExpired)) - SuppressionGroupExpired = suppressionGroupExpired; + ExecutionActionPreference IExecutionOption.RuleSuppressed => RuleSuppressed ?? DEFAULT_RULESUPPRESSED; - if (index.TryPopEnum("Execution.RuleExcluded", out ExecutionActionPreference ruleExcluded)) - RuleExcluded = ruleExcluded; + ExecutionActionPreference IExecutionOption.AliasReference => AliasReference ?? DEFAULT_ALIASREFERENCE; - if (index.TryPopEnum("Execution.RuleSuppressed", out ExecutionActionPreference ruleSuppressed)) - RuleSuppressed = ruleSuppressed; + ExecutionActionPreference IExecutionOption.RuleInconclusive => RuleInconclusive ?? DEFAULT_RULEINCONCLUSIVE; - if (index.TryPopEnum("Execution.AliasReference", out ExecutionActionPreference aliasReference)) - AliasReference = aliasReference; + ExecutionActionPreference IExecutionOption.InvariantCulture => InvariantCulture ?? DEFAULT_INVARIANTCULTURE; - if (index.TryPopEnum("Execution.RuleInconclusive", out ExecutionActionPreference ruleInconclusive)) - RuleInconclusive = ruleInconclusive; + ExecutionActionPreference IExecutionOption.UnprocessedObject => UnprocessedObject ?? DEFAULT_UNPROCESSEDOBJECT; - if (index.TryPopEnum("Execution.InvariantCulture", out ExecutionActionPreference invariantCulture)) - InvariantCulture = invariantCulture; + #endregion IExecutionOption - if (index.TryPopEnum("Execution.UnprocessedObject", out ExecutionActionPreference unprocessedObject)) - UnprocessedObject = unprocessedObject; - } + /// + /// Load from environment variables. + /// + internal void Load() + { + if (Environment.TryEnum("PSRULE_EXECUTION_HASHALGORITHM", out HashAlgorithm hashAlgorithm)) + HashAlgorithm = hashAlgorithm; + + if (Environment.TryEnum("PSRULE_EXECUTION_DUPLICATERESOURCEID", out ExecutionActionPreference duplicateResourceId)) + DuplicateResourceId = duplicateResourceId; + + if (Environment.TryEnum("PSRULE_EXECUTION_LANGUAGEMODE", out LanguageMode languageMode)) + LanguageMode = languageMode; + + if (Environment.TryEnum("PSRULE_EXECUTION_INITIALSESSIONSTATE", out SessionState initialSessionState)) + InitialSessionState = initialSessionState; + + if (Environment.TryEnum("PSRULE_EXECUTION_SUPPRESSIONGROUPEXPIRED", out ExecutionActionPreference suppressionGroupExpired)) + SuppressionGroupExpired = suppressionGroupExpired; + + if (Environment.TryEnum("PSRULE_EXECUTION_RULEEXCLUDED", out ExecutionActionPreference ruleExcluded)) + RuleExcluded = ruleExcluded; + + if (Environment.TryEnum("PSRULE_EXECUTION_RULESUPPRESSED", out ExecutionActionPreference ruleSuppressed)) + RuleSuppressed = ruleSuppressed; + + if (Environment.TryEnum("PSRULE_EXECUTION_ALIASREFERENCE", out ExecutionActionPreference aliasReference)) + AliasReference = aliasReference; + + if (Environment.TryEnum("PSRULE_EXECUTION_RULEINCONCLUSIVE", out ExecutionActionPreference ruleInconclusive)) + RuleInconclusive = ruleInconclusive; + + if (Environment.TryEnum("PSRULE_EXECUTION_INVARIANTCULTURE", out ExecutionActionPreference invariantCulture)) + InvariantCulture = invariantCulture; + + if (Environment.TryEnum("PSRULE_EXECUTION_UNPROCESSEDOBJECT", out ExecutionActionPreference unprocessedObject)) + UnprocessedObject = unprocessedObject; + } + + /// + /// Load from dictionary. + /// + internal void Load(Dictionary index) + { + if (index.TryPopEnum("Execution.HashAlgorithm", out HashAlgorithm hashAlgorithm)) + HashAlgorithm = hashAlgorithm; + + if (index.TryPopEnum("Execution.DuplicateResourceId", out ExecutionActionPreference duplicateResourceId)) + DuplicateResourceId = duplicateResourceId; + + if (index.TryPopEnum("Execution.LanguageMode", out LanguageMode languageMode)) + LanguageMode = languageMode; + + if (index.TryPopEnum("Execution.InitialSessionState", out SessionState initialSessionState)) + InitialSessionState = initialSessionState; + + if (index.TryPopEnum("Execution.SuppressionGroupExpired", out ExecutionActionPreference suppressionGroupExpired)) + SuppressionGroupExpired = suppressionGroupExpired; + + if (index.TryPopEnum("Execution.RuleExcluded", out ExecutionActionPreference ruleExcluded)) + RuleExcluded = ruleExcluded; + + if (index.TryPopEnum("Execution.RuleSuppressed", out ExecutionActionPreference ruleSuppressed)) + RuleSuppressed = ruleSuppressed; + + if (index.TryPopEnum("Execution.AliasReference", out ExecutionActionPreference aliasReference)) + AliasReference = aliasReference; + + if (index.TryPopEnum("Execution.RuleInconclusive", out ExecutionActionPreference ruleInconclusive)) + RuleInconclusive = ruleInconclusive; + + if (index.TryPopEnum("Execution.InvariantCulture", out ExecutionActionPreference invariantCulture)) + InvariantCulture = invariantCulture; + + if (index.TryPopEnum("Execution.UnprocessedObject", out ExecutionActionPreference unprocessedObject)) + UnprocessedObject = unprocessedObject; } } diff --git a/src/PSRule.Types/Options/HashAlgorithm.cs b/src/PSRule.Types/Options/HashAlgorithm.cs index 9891677f97..eff6bedcd2 100644 --- a/src/PSRule.Types/Options/HashAlgorithm.cs +++ b/src/PSRule.Types/Options/HashAlgorithm.cs @@ -4,27 +4,26 @@ using Newtonsoft.Json; using Newtonsoft.Json.Converters; -namespace PSRule.Options +namespace PSRule.Options; + +/// +/// Configures the hashing algorithm used by the PSRule runtime. +/// +[JsonConverter(typeof(StringEnumConverter))] +public enum HashAlgorithm { /// - /// Configures the hashing algorithm used by the PSRule runtime. + /// Use SHA256. /// - [JsonConverter(typeof(StringEnumConverter))] - public enum HashAlgorithm - { - /// - /// Use SHA256. - /// - SHA256, + SHA256, - /// - /// Use SHA384. - /// - SHA384, + /// + /// Use SHA384. + /// + SHA384, - /// - /// Use SHA512. - /// - SHA512 - } + /// + /// Use SHA512. + /// + SHA512 } diff --git a/src/PSRule.Types/Options/IOption.cs b/src/PSRule.Types/Options/IOption.cs index 6a0b146c56..38ecc0faf8 100644 --- a/src/PSRule.Types/Options/IOption.cs +++ b/src/PSRule.Types/Options/IOption.cs @@ -1,12 +1,11 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -namespace PSRule.Options +namespace PSRule.Options; + +/// +/// An interface for an option. +/// +public interface IOption { - /// - /// An interface for an option. - /// - public interface IOption - { - } } diff --git a/src/PSRule.Types/Options/LanguageMode.cs b/src/PSRule.Types/Options/LanguageMode.cs index d03a30bdb8..5996b0e73c 100644 --- a/src/PSRule.Types/Options/LanguageMode.cs +++ b/src/PSRule.Types/Options/LanguageMode.cs @@ -1,23 +1,22 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -namespace PSRule.Options +namespace PSRule.Options; + +/// +/// Configures the language mode PowerShell code executes as within PSRule runtime. +/// Does not affect YAML or JSON expressions. +/// +/// +public enum LanguageMode { /// - /// Configures the language mode PowerShell code executes as within PSRule runtime. - /// Does not affect YAML or JSON expressions. - /// + /// PowerShell code executes unconstrained. /// - public enum LanguageMode - { - /// - /// PowerShell code executes unconstrained. - /// - FullLanguage = 0, + FullLanguage = 0, - /// - /// PowerShell code executes in constrained language mode that restricts the types and methods that can be used. - /// - ConstrainedLanguage = 1 - } + /// + /// PowerShell code executes in constrained language mode that restricts the types and methods that can be used. + /// + ConstrainedLanguage = 1 } diff --git a/src/PSRule.Types/Options/SessionState.cs b/src/PSRule.Types/Options/SessionState.cs index e1c31bfd93..a96e27469f 100644 --- a/src/PSRule.Types/Options/SessionState.cs +++ b/src/PSRule.Types/Options/SessionState.cs @@ -1,23 +1,22 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -namespace PSRule.Options +namespace PSRule.Options; + +/// +/// Configures how the initial PowerShell sandbox for executing rules is created. +/// +public enum SessionState { /// - /// Configures how the initial PowerShell sandbox for executing rules is created. + /// Create the initial session state with all built-in cmdlets loaded. + /// See CreateDefault. /// - public enum SessionState - { - /// - /// Create the initial session state with all built-in cmdlets loaded. - /// See CreateDefault. - /// - BuiltIn = 0, + BuiltIn = 0, - /// - /// Create the initial session state with only cmdlets loaded for hosting PowerShell. - /// See CreateDefault2. - /// - Minimal = 1 - } + /// + /// Create the initial session state with only cmdlets loaded for hosting PowerShell. + /// See CreateDefault2. + /// + Minimal = 1 } diff --git a/src/PSRule.Types/StringExtensions.cs b/src/PSRule.Types/StringExtensions.cs index 32555cca39..60e3145cc9 100644 --- a/src/PSRule.Types/StringExtensions.cs +++ b/src/PSRule.Types/StringExtensions.cs @@ -3,157 +3,156 @@ using System.Text; -namespace PSRule +namespace PSRule; + +/// +/// Extension methods for strings. +/// +public static class StringExtensions { + private const string HTTP_SCHEME = "http://"; + private const string HTTPS_SCHEME = "https://"; + + private static readonly char[] LINE_STOPCHARACTERS = new char[] { '\r', '\n' }; + /// - /// Extension methods for strings. + /// Determine if the string is a URL. /// - public static class StringExtensions + /// The string to check. + /// Returns true if the string starts with a http:// or https://. + public static bool IsURL(this string s) { - private const string HTTP_SCHEME = "http://"; - private const string HTTPS_SCHEME = "https://"; + return s.StartsWith(HTTP_SCHEME, StringComparison.OrdinalIgnoreCase) || + s.StartsWith(HTTPS_SCHEME, StringComparison.OrdinalIgnoreCase); + } - private static readonly char[] LINE_STOPCHARACTERS = new char[] { '\r', '\n' }; + /// + /// Split a string semantically based on a maximum width. + /// + /// The string to split. + /// The maximum width to split lines along. + /// Returns an array of strings that have been semantically split. + public static string[]? SplitSemantic(this string s, int width = 80) + { + if (s == null) + return null; - /// - /// Determine if the string is a URL. - /// - /// The string to check. - /// Returns true if the string starts with a http:// or https://. - public static bool IsURL(this string s) - { - return s.StartsWith(HTTP_SCHEME, StringComparison.OrdinalIgnoreCase) || - s.StartsWith(HTTPS_SCHEME, StringComparison.OrdinalIgnoreCase); - } + if (s.Length <= width) + return new string[] { s }; - /// - /// Split a string semantically based on a maximum width. - /// - /// The string to split. - /// The maximum width to split lines along. - /// Returns an array of strings that have been semantically split. - public static string[]? SplitSemantic(this string s, int width = 80) + var result = new List(); + var pos = 0; + while (pos < s.Length) { - if (s == null) - return null; - - if (s.Length <= width) - return new string[] { s }; + var i = pos + width - 1; + if (i >= s.Length) + i = s.Length - 1; - var result = new List(); - var pos = 0; - while (pos < s.Length) + var breaks = s.IndexOfAny(LINE_STOPCHARACTERS, pos, i - pos); + if (breaks > -1) { - var i = pos + width - 1; - if (i >= s.Length) - i = s.Length - 1; - - var breaks = s.IndexOfAny(LINE_STOPCHARACTERS, pos, i - pos); - if (breaks > -1) - { - i = breaks; - } - else - { - while (!IsSemanticStopChar(s[i]) && i > pos) - i--; - } - - if (i == pos) - { - // move forward - } - if (char.IsPunctuation(s[i])) - i++; - - while (i > pos && i < s.Length && IsLineBreak(s[i])) + i = breaks; + } + else + { + while (!IsSemanticStopChar(s[i]) && i > pos) i--; + } - if (pos != i) - result.Add(s.Substring(pos, i - pos)); + if (i == pos) + { + // move forward + } + if (char.IsPunctuation(s[i])) + i++; - while (i < s.Length && IsLineBreak(s[i])) - i++; + while (i > pos && i < s.Length && IsLineBreak(s[i])) + i--; - pos = i + 1; - } - return result.ToArray(); - } + if (pos != i) + result.Add(s.Substring(pos, i - pos)); - /// - /// Convert a string to camel case. - /// - /// The input string to convert. - /// The converted string. - public static string ToCamelCase(this string s) - { - return !string.IsNullOrEmpty(s) ? char.ToLowerInvariant(s[0]) + s.Substring(1) : string.Empty; - } + while (i < s.Length && IsLineBreak(s[i])) + i++; - /// - /// Determine if the string contains a specific substring. - /// - /// - /// - /// - /// - public static bool Contains(this string s, string value, StringComparison comparison) - { - return s?.IndexOf(value, comparison) >= 0; + pos = i + 1; } + return result.ToArray(); + } - /// - /// Replace an old string with a new string. - /// - /// - /// - /// - /// - /// - public static string Replace(this string s, string oldString, string newString, bool caseSensitive) - { - if (string.IsNullOrEmpty(s) || string.IsNullOrEmpty(oldString) || s.Length < oldString.Length) - return s; - - if (caseSensitive) - return s.Replace(oldString, newString); + /// + /// Convert a string to camel case. + /// + /// The input string to convert. + /// The converted string. + public static string ToCamelCase(this string s) + { + return !string.IsNullOrEmpty(s) ? char.ToLowerInvariant(s[0]) + s.Substring(1) : string.Empty; + } - var sb = new StringBuilder(s.Length); - var pos = 0; - var replaceWithEmpty = string.IsNullOrEmpty(newString); - int indexAt; - while ((indexAt = s.IndexOf(oldString, pos, StringComparison.OrdinalIgnoreCase)) != -1) - { - sb.Append(s, pos, indexAt - pos); - if (!replaceWithEmpty) - sb.Append(newString); + /// + /// Determine if the string contains a specific substring. + /// + /// + /// + /// + /// + public static bool Contains(this string s, string value, StringComparison comparison) + { + return s?.IndexOf(value, comparison) >= 0; + } - pos = indexAt + oldString.Length; - } - if (pos < s.Length) - sb.Append(s, pos, s.Length - pos); + /// + /// Replace an old string with a new string. + /// + /// + /// + /// + /// + /// + public static string Replace(this string s, string oldString, string newString, bool caseSensitive) + { + if (string.IsNullOrEmpty(s) || string.IsNullOrEmpty(oldString) || s.Length < oldString.Length) + return s; - return sb.ToString(); - } + if (caseSensitive) + return s.Replace(oldString, newString); - /// - /// - /// - /// - /// - private static bool IsSemanticStopChar(char c) + var sb = new StringBuilder(s.Length); + var pos = 0; + var replaceWithEmpty = string.IsNullOrEmpty(newString); + int indexAt; + while ((indexAt = s.IndexOf(oldString, pos, StringComparison.OrdinalIgnoreCase)) != -1) { - return char.IsWhiteSpace(c) || (char.IsPunctuation(c) && char.GetUnicodeCategory(c) != System.Globalization.UnicodeCategory.DashPunctuation); - } + sb.Append(s, pos, indexAt - pos); + if (!replaceWithEmpty) + sb.Append(newString); - /// - /// - /// - /// - /// - private static bool IsLineBreak(char c) - { - return char.GetUnicodeCategory(c) == System.Globalization.UnicodeCategory.LineSeparator; + pos = indexAt + oldString.Length; } + if (pos < s.Length) + sb.Append(s, pos, s.Length - pos); + + return sb.ToString(); + } + + /// + /// + /// + /// + /// + private static bool IsSemanticStopChar(char c) + { + return char.IsWhiteSpace(c) || (char.IsPunctuation(c) && char.GetUnicodeCategory(c) != System.Globalization.UnicodeCategory.DashPunctuation); + } + + /// + /// + /// + /// + /// + private static bool IsLineBreak(char c) + { + return char.GetUnicodeCategory(c) == System.Globalization.UnicodeCategory.LineSeparator; } } diff --git a/src/PSRule.Types/TypeExtensions.cs b/src/PSRule.Types/TypeExtensions.cs index d479fc7e24..44e3c03158 100644 --- a/src/PSRule.Types/TypeExtensions.cs +++ b/src/PSRule.Types/TypeExtensions.cs @@ -3,22 +3,21 @@ using System.Reflection; -namespace PSRule +namespace PSRule; + +internal static class TypeExtensions { - internal static class TypeExtensions + public static bool TryGetPropertyInfo(this Type type, string propertyName, out PropertyInfo? value) { - public static bool TryGetPropertyInfo(this Type type, string propertyName, out PropertyInfo? value) - { - value = null; - if (type == null || propertyName == null) - return false; + value = null; + if (type == null || propertyName == null) + return false; - var propertyInfo = type.GetProperty(propertyName); - if (propertyInfo == null) - return false; + var propertyInfo = type.GetProperty(propertyName); + if (propertyInfo == null) + return false; - value = propertyInfo; - return true; - } + value = propertyInfo; + return true; } } diff --git a/src/PSRule.Types/packages.lock.json b/src/PSRule.Types/packages.lock.json new file mode 100644 index 0000000000..cf426a0738 --- /dev/null +++ b/src/PSRule.Types/packages.lock.json @@ -0,0 +1,59 @@ +{ + "version": 1, + "dependencies": { + ".NETStandard,Version=v2.0": { + "Microsoft.CodeAnalysis.NetAnalyzers": { + "type": "Direct", + "requested": "[8.0.0, )", + "resolved": "8.0.0", + "contentHash": "DxiTgkCl3CGq1rYmBX2wjY7XGbxiBdL4J+/AJIAFLKy5z70NxhnVRnPghnicXZ8oF6JKVXlW3xwznRbI3ioEKg==" + }, + "Microsoft.SourceLink.GitHub": { + "type": "Direct", + "requested": "[8.0.0, )", + "resolved": "8.0.0", + "contentHash": "G5q7OqtwIyGTkeIOAc3u2ZuV/kicQaec5EaRnc0pIeSnh9LUjj+PYQrJYBURvDt7twGl2PKA7nSN0kz1Zw5bnQ==", + "dependencies": { + "Microsoft.Build.Tasks.Git": "8.0.0", + "Microsoft.SourceLink.Common": "8.0.0" + } + }, + "NETStandard.Library": { + "type": "Direct", + "requested": "[2.0.3, )", + "resolved": "2.0.3", + "contentHash": "st47PosZSHrjECdjeIzZQbzivYBJFv6P2nv4cj2ypdI204DO+vZ7l5raGMiX4eXMJ53RfOIg+/s4DHVZ54Nu2A==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0" + } + }, + "Newtonsoft.Json": { + "type": "Direct", + "requested": "[13.0.3, )", + "resolved": "13.0.3", + "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==" + }, + "YamlDotNet": { + "type": "Direct", + "requested": "[13.7.1, )", + "resolved": "13.7.1", + "contentHash": "X4m1PnFcJwvAj1sCDMntg/eZcX96CJLrWMiYnq41KqhFVZPuw63ZTSxIGqgdCwHWHvCAyTxheELC/VDf1HsU2A==" + }, + "Microsoft.Build.Tasks.Git": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ==" + }, + "Microsoft.NETCore.Platforms": { + "type": "Transitive", + "resolved": "1.1.0", + "contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==" + }, + "Microsoft.SourceLink.Common": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw==" + } + } + } +} \ No newline at end of file diff --git a/src/PSRule/Annotations/CommentMetadata.cs b/src/PSRule/Annotations/CommentMetadata.cs index 0bb70ad57e..03a2d773cd 100644 --- a/src/PSRule/Annotations/CommentMetadata.cs +++ b/src/PSRule/Annotations/CommentMetadata.cs @@ -3,14 +3,13 @@ using System.Diagnostics; -namespace PSRule.Annotations +namespace PSRule.Annotations; + +/// +/// Metadata properties that can be exposed by comment help. +/// +[DebuggerDisplay("Synopsis = {Synopsis}")] +internal sealed class CommentMetadata { - /// - /// Metadata properties that can be exposed by comment help. - /// - [DebuggerDisplay("Synopsis = {Synopsis}")] - internal sealed class CommentMetadata - { - public string Synopsis; - } + public string Synopsis; } diff --git a/src/PSRule/Badges/BadgeBuilder.cs b/src/PSRule/Badges/BadgeBuilder.cs index 2826580ad1..1b3b3b59d7 100644 --- a/src/PSRule/Badges/BadgeBuilder.cs +++ b/src/PSRule/Badges/BadgeBuilder.cs @@ -5,233 +5,232 @@ using PSRule.Resources; using PSRule.Rules; -namespace PSRule.Badges +namespace PSRule.Badges; + +/// +/// The type of badge. +/// +public enum BadgeType { /// - /// The type of badge. + /// A badge that reports an unknown state. /// - public enum BadgeType - { - /// - /// A badge that reports an unknown state. - /// - Unknown = 0, - - /// - /// A badge reporting a successful state. - /// - Success = 1, - - /// - /// A bagde reporting a failed state. - /// - Failure = 2 - } + Unknown = 0, /// - /// An instance of a badge created by the badge API. + /// A badge reporting a successful state. /// - public interface IBadge - { - /// - /// Get the badge as SVG text content. - /// - string ToSvg(); - - /// - /// Write the SVG badge content directly to disk. - /// - void ToFile(string path); - } + Success = 1, /// - /// A builder for the badge API. + /// A bagde reporting a failed state. /// - public interface IBadgeBuilder - { - /// - /// Create a badge for the worst case of an analyzed object. - /// - /// A single result. The worst case for all records of an object is used for the badge. - /// An instance of a badge. - IBadge Create(InvokeResult result); - - /// - /// Create a badge for the worst case of all analyzed objects. - /// - /// A enumeration of results. The worst case from all results is used for the badge. - /// An instance of a badge. - IBadge Create(IEnumerable result); - - /// - /// Create a custom badge. - /// - /// The left badge text. - /// Determines if the result is Unknown, Success, or Failure. - /// The right badge text. - /// An instance of a badge. - IBadge Create(string title, BadgeType type, string label); - } + Failure = 2 +} +/// +/// An instance of a badge created by the badge API. +/// +public interface IBadge +{ /// - /// An instance of a badge created by the Badge API. + /// Get the badge as SVG text content. /// - internal sealed class Badge : IBadge - { - private readonly string _LeftText; - private readonly string _RightText; - private readonly double _LeftWidth; - private readonly double _RightWidth; - private readonly int _MidPadding; - private readonly int _BorderPadding; - private readonly string _Fill; - - internal Badge(string left, string right, string fill) - { - _LeftWidth = BadgeResources.Measure(left); - _RightWidth = BadgeResources.Measure(right); - - _LeftText = left; - _RightText = right; - _MidPadding = 3; - _BorderPadding = 7; - _Fill = fill; - } - - /// - public override string ToString() - { - return ToSvg(); - } + string ToSvg(); - /// - public string ToSvg() - { - var w = (int)Math.Round(_LeftWidth + _RightWidth + 2 * _BorderPadding + 2 * _MidPadding); - var x = (int)Math.Round(_LeftWidth + _BorderPadding + _MidPadding); - - var builder = new SvgBuilder( - width: w, - height: 20, - textScale: 10, - midPoint: x, - rounding: 2, - borderPadding: _BorderPadding, - midPadding: _MidPadding); - builder.Begin(string.Concat(_LeftText, ": ", _RightText)); - builder.Backfill(_Fill); - builder.TextBlock(_LeftText, _RightText, 110); - builder.End(); - return builder.ToString(); - } + /// + /// Write the SVG badge content directly to disk. + /// + void ToFile(string path); +} - /// - public void ToFile(string path) - { - path = Environment.GetRootedPath(path); - var parentPath = Directory.GetParent(path); - if (!parentPath.Exists) - Directory.CreateDirectory(path: parentPath.FullName); +/// +/// A builder for the badge API. +/// +public interface IBadgeBuilder +{ + /// + /// Create a badge for the worst case of an analyzed object. + /// + /// A single result. The worst case for all records of an object is used for the badge. + /// An instance of a badge. + IBadge Create(InvokeResult result); - File.WriteAllText(path, contents: ToSvg()); - } - } + /// + /// Create a badge for the worst case of all analyzed objects. + /// + /// A enumeration of results. The worst case from all results is used for the badge. + /// An instance of a badge. + IBadge Create(IEnumerable result); /// - /// A badge builder that implements the Badge API within PSRule. + /// Create a custom badge. /// - internal sealed class BadgeBuilder : IBadgeBuilder + /// The left badge text. + /// Determines if the result is Unknown, Success, or Failure. + /// The right badge text. + /// An instance of a badge. + IBadge Create(string title, BadgeType type, string label); +} + +/// +/// An instance of a badge created by the Badge API. +/// +internal sealed class Badge : IBadge +{ + private readonly string _LeftText; + private readonly string _RightText; + private readonly double _LeftWidth; + private readonly double _RightWidth; + private readonly int _MidPadding; + private readonly int _BorderPadding; + private readonly string _Fill; + + internal Badge(string left, string right, string fill) { - private const string BADGE_FILL_GREEN = "#4CAF50"; - private const string BADGE_FILL_RED = "#E91E63"; - private const string BADGE_FILL_GREY = "#9E9E9E"; + _LeftWidth = BadgeResources.Measure(left); + _RightWidth = BadgeResources.Measure(right); + + _LeftText = left; + _RightText = right; + _MidPadding = 3; + _BorderPadding = 7; + _Fill = fill; + } - #region IBadgeBuilder + /// + public override string ToString() + { + return ToSvg(); + } - /// - public IBadge Create(string title, BadgeType type, string label) - { - return CreateCustom(title, label, GetTypeFill(type)); - } + /// + public string ToSvg() + { + var w = (int)Math.Round(_LeftWidth + _RightWidth + 2 * _BorderPadding + 2 * _MidPadding); + var x = (int)Math.Round(_LeftWidth + _BorderPadding + _MidPadding); + + var builder = new SvgBuilder( + width: w, + height: 20, + textScale: 10, + midPoint: x, + rounding: 2, + borderPadding: _BorderPadding, + midPadding: _MidPadding); + builder.Begin(string.Concat(_LeftText, ": ", _RightText)); + builder.Backfill(_Fill); + builder.TextBlock(_LeftText, _RightText, 110); + builder.End(); + return builder.ToString(); + } - /// - public IBadge Create(InvokeResult result) - { - return CreateInternal(GetOutcome(result)); - } + /// + public void ToFile(string path) + { + path = Environment.GetRootedPath(path); + var parentPath = Directory.GetParent(path); + if (!parentPath.Exists) + Directory.CreateDirectory(path: parentPath.FullName); + + File.WriteAllText(path, contents: ToSvg()); + } +} + +/// +/// A badge builder that implements the Badge API within PSRule. +/// +internal sealed class BadgeBuilder : IBadgeBuilder +{ + private const string BADGE_FILL_GREEN = "#4CAF50"; + private const string BADGE_FILL_RED = "#E91E63"; + private const string BADGE_FILL_GREY = "#9E9E9E"; + + #region IBadgeBuilder + + /// + public IBadge Create(string title, BadgeType type, string label) + { + return CreateCustom(title, label, GetTypeFill(type)); + } - /// - public IBadge Create(IEnumerable result) + /// + public IBadge Create(InvokeResult result) + { + return CreateInternal(GetOutcome(result)); + } + + /// + public IBadge Create(IEnumerable result) + { + var worstCase = RuleOutcome.Pass; + var i = 0; + if (result != null) { - var worstCase = RuleOutcome.Pass; - var i = 0; - if (result != null) + foreach (var r in result) { - foreach (var r in result) - { - if (!r.IsSuccess()) - worstCase = worstCase.GetWorstCase(r.Outcome); + if (!r.IsSuccess()) + worstCase = worstCase.GetWorstCase(r.Outcome); - i++; - } + i++; } - return CreateInternal(i == 0 ? RuleOutcome.None : worstCase); } + return CreateInternal(i == 0 ? RuleOutcome.None : worstCase); + } - #endregion IBadgeBuilder - - #region Private helper methods + #endregion IBadgeBuilder - private static string GetOutcomeFill(RuleOutcome outcome) - { - if (outcome == RuleOutcome.Pass) - return BADGE_FILL_GREEN; + #region Private helper methods - if (outcome == RuleOutcome.Fail) - return BADGE_FILL_RED; + private static string GetOutcomeFill(RuleOutcome outcome) + { + if (outcome == RuleOutcome.Pass) + return BADGE_FILL_GREEN; - return outcome == RuleOutcome.Error ? BADGE_FILL_RED : BADGE_FILL_GREY; - } + if (outcome == RuleOutcome.Fail) + return BADGE_FILL_RED; - private static string GetOutcomeLabel(RuleOutcome outcome) - { - if (outcome == RuleOutcome.Pass) - return PSRuleResources.OutcomePass; + return outcome == RuleOutcome.Error ? BADGE_FILL_RED : BADGE_FILL_GREY; + } - if (outcome == RuleOutcome.Fail) - return PSRuleResources.OutcomeFail; + private static string GetOutcomeLabel(RuleOutcome outcome) + { + if (outcome == RuleOutcome.Pass) + return PSRuleResources.OutcomePass; - return outcome == RuleOutcome.Error ? PSRuleResources.OutcomeError : PSRuleResources.OutcomeUnknown; - } + if (outcome == RuleOutcome.Fail) + return PSRuleResources.OutcomeFail; - private static RuleOutcome GetOutcome(InvokeResult result) - { - return result == null || !result.IsProcessed() ? RuleOutcome.None : result.Outcome; - } + return outcome == RuleOutcome.Error ? PSRuleResources.OutcomeError : PSRuleResources.OutcomeUnknown; + } - private static string GetTypeFill(BadgeType type) - { - if (type == BadgeType.Success) - return BADGE_FILL_GREEN; + private static RuleOutcome GetOutcome(InvokeResult result) + { + return result == null || !result.IsProcessed() ? RuleOutcome.None : result.Outcome; + } - if (type == BadgeType.Failure) - return BADGE_FILL_RED; + private static string GetTypeFill(BadgeType type) + { + if (type == BadgeType.Success) + return BADGE_FILL_GREEN; - return BADGE_FILL_GREY; - } + if (type == BadgeType.Failure) + return BADGE_FILL_RED; - private static IBadge CreateCustom(string title, string label, string fill) - { - return new Badge(title, label, fill); - } + return BADGE_FILL_GREY; + } - private static IBadge CreateInternal(RuleOutcome outcome) - { - var outcomeLabel = GetOutcomeLabel(outcome); - var outcomeFill = GetOutcomeFill(outcome); - return CreateCustom("PSRule", outcomeLabel, outcomeFill); - } + private static IBadge CreateCustom(string title, string label, string fill) + { + return new Badge(title, label, fill); + } - #endregion Private helper methods + private static IBadge CreateInternal(RuleOutcome outcome) + { + var outcomeLabel = GetOutcomeLabel(outcome); + var outcomeFill = GetOutcomeFill(outcome); + return CreateCustom("PSRule", outcomeLabel, outcomeFill); } + + #endregion Private helper methods } diff --git a/src/PSRule/Commands/AssertAllOfCommand.cs b/src/PSRule/Commands/AssertAllOfCommand.cs index 2673a36b8c..a25d390709 100644 --- a/src/PSRule/Commands/AssertAllOfCommand.cs +++ b/src/PSRule/Commands/AssertAllOfCommand.cs @@ -4,31 +4,30 @@ using System.Management.Automation; using PSRule.Runtime; -namespace PSRule.Commands +namespace PSRule.Commands; + +/// +/// The AllOf keyword. +/// +[Cmdlet(VerbsLifecycle.Assert, RuleLanguageNouns.AllOf)] +internal sealed class AssertAllOfCommand : RuleKeyword { - /// - /// The AllOf keyword. - /// - [Cmdlet(VerbsLifecycle.Assert, RuleLanguageNouns.AllOf)] - internal sealed class AssertAllOfCommand : RuleKeyword - { - [Parameter(Mandatory = true, Position = 0)] - public ScriptBlock Body { get; set; } + [Parameter(Mandatory = true, Position = 0)] + public ScriptBlock Body { get; set; } - protected override void ProcessRecord() - { - if (!IsConditionScope()) - throw ConditionScopeException(LanguageKeywords.AllOf); + protected override void ProcessRecord() + { + if (!IsConditionScope()) + throw ConditionScopeException(LanguageKeywords.AllOf); - var invokeResult = RuleConditionHelper.Create(Body.Invoke()); - var result = invokeResult.AllOf(); + var invokeResult = RuleConditionHelper.Create(Body.Invoke()); + var result = invokeResult.AllOf(); - RunspaceContext.CurrentThread.VerboseConditionResult( - condition: RuleLanguageNouns.AllOf, - pass: invokeResult.Pass, - count: invokeResult.Count, - outcome: result); - WriteObject(result); - } + RunspaceContext.CurrentThread.VerboseConditionResult( + condition: RuleLanguageNouns.AllOf, + pass: invokeResult.Pass, + count: invokeResult.Count, + outcome: result); + WriteObject(result); } } diff --git a/src/PSRule/Commands/AssertAnyOfCommand.cs b/src/PSRule/Commands/AssertAnyOfCommand.cs index 245f207b2b..6e1bece6a1 100644 --- a/src/PSRule/Commands/AssertAnyOfCommand.cs +++ b/src/PSRule/Commands/AssertAnyOfCommand.cs @@ -4,31 +4,30 @@ using System.Management.Automation; using PSRule.Runtime; -namespace PSRule.Commands +namespace PSRule.Commands; + +/// +/// The AnyOf keyword. +/// +[Cmdlet(VerbsLifecycle.Assert, RuleLanguageNouns.AnyOf)] +internal sealed class AssertAnyOfCommand : RuleKeyword { - /// - /// The AnyOf keyword. - /// - [Cmdlet(VerbsLifecycle.Assert, RuleLanguageNouns.AnyOf)] - internal sealed class AssertAnyOfCommand : RuleKeyword - { - [Parameter(Mandatory = true, Position = 0)] - public ScriptBlock Body { get; set; } + [Parameter(Mandatory = true, Position = 0)] + public ScriptBlock Body { get; set; } - protected override void ProcessRecord() - { - if (!IsConditionScope()) - throw ConditionScopeException(LanguageKeywords.AnyOf); + protected override void ProcessRecord() + { + if (!IsConditionScope()) + throw ConditionScopeException(LanguageKeywords.AnyOf); - var invokeResult = RuleConditionHelper.Create(Body.Invoke()); - var result = invokeResult.AnyOf(); + var invokeResult = RuleConditionHelper.Create(Body.Invoke()); + var result = invokeResult.AnyOf(); - RunspaceContext.CurrentThread.VerboseConditionResult( - condition: RuleLanguageNouns.AnyOf, - pass: invokeResult.Pass, - count: invokeResult.Count, - outcome: result); - WriteObject(result); - } + RunspaceContext.CurrentThread.VerboseConditionResult( + condition: RuleLanguageNouns.AnyOf, + pass: invokeResult.Pass, + count: invokeResult.Count, + outcome: result); + WriteObject(result); } } diff --git a/src/PSRule/Commands/AssertExistsCommand.cs b/src/PSRule/Commands/AssertExistsCommand.cs index 43dad8f642..df5089ca5d 100644 --- a/src/PSRule/Commands/AssertExistsCommand.cs +++ b/src/PSRule/Commands/AssertExistsCommand.cs @@ -6,84 +6,83 @@ using PSRule.Resources; using PSRule.Runtime; -namespace PSRule.Commands +namespace PSRule.Commands; + +/// +/// The Exists keyword. +/// +[Cmdlet(VerbsLifecycle.Assert, RuleLanguageNouns.Exists)] +internal sealed class AssertExistsCommand : RuleKeyword { - /// - /// The Exists keyword. - /// - [Cmdlet(VerbsLifecycle.Assert, RuleLanguageNouns.Exists)] - internal sealed class AssertExistsCommand : RuleKeyword + public AssertExistsCommand() { - public AssertExistsCommand() - { - CaseSensitive = false; - Not = false; - All = false; - } + CaseSensitive = false; + Not = false; + All = false; + } - [Parameter(Mandatory = true, Position = 0)] - public string[] Field { get; set; } + [Parameter(Mandatory = true, Position = 0)] + public string[] Field { get; set; } - [Parameter(Mandatory = false)] - public string Reason { get; set; } + [Parameter(Mandatory = false)] + public string Reason { get; set; } - [Parameter(Mandatory = false)] - [PSDefaultValue(Value = false)] - public SwitchParameter CaseSensitive { get; set; } + [Parameter(Mandatory = false)] + [PSDefaultValue(Value = false)] + public SwitchParameter CaseSensitive { get; set; } - [Parameter(Mandatory = false)] - [PSDefaultValue(Value = false)] - public SwitchParameter Not { get; set; } + [Parameter(Mandatory = false)] + [PSDefaultValue(Value = false)] + public SwitchParameter Not { get; set; } - [Parameter(Mandatory = false)] - [PSDefaultValue(Value = false)] - public SwitchParameter All { get; set; } + [Parameter(Mandatory = false)] + [PSDefaultValue(Value = false)] + public SwitchParameter All { get; set; } - [Parameter(Mandatory = false, ValueFromPipeline = true)] - public PSObject InputObject { get; set; } + [Parameter(Mandatory = false, ValueFromPipeline = true)] + public PSObject InputObject { get; set; } - protected override void ProcessRecord() - { - if (!IsRuleScope()) - throw RuleScopeException(LanguageKeywords.Exists); + protected override void ProcessRecord() + { + if (!IsRuleScope()) + throw RuleScopeException(LanguageKeywords.Exists); - var targetObject = InputObject ?? GetTargetObject(); - var foundFields = new List(); - var notFoundFields = new List(); - var found = 0; - var required = All ? Field.Length : 1; + var targetObject = InputObject ?? GetTargetObject(); + var foundFields = new List(); + var notFoundFields = new List(); + var found = 0; + var required = All ? Field.Length : 1; - for (var i = 0; i < Field.Length && found < required; i++) + for (var i = 0; i < Field.Length && found < required; i++) + { + if (ObjectHelper.GetPath( + bindingContext: PipelineContext.CurrentThread, + targetObject: targetObject, + path: Field[i], + caseSensitive: CaseSensitive, + value: out object _)) { - if (ObjectHelper.GetPath( - bindingContext: PipelineContext.CurrentThread, - targetObject: targetObject, - path: Field[i], - caseSensitive: CaseSensitive, - value: out object _)) - { - RunspaceContext.CurrentThread.VerboseConditionMessage( - condition: RuleLanguageNouns.Exists, - message: PSRuleResources.ExistsTrue, - args: Field[i]); - foundFields.Add(Field[i]); - found++; - } - else - notFoundFields.Add(Field[i]); + RunspaceContext.CurrentThread.VerboseConditionMessage( + condition: RuleLanguageNouns.Exists, + message: PSRuleResources.ExistsTrue, + args: Field[i]); + foundFields.Add(Field[i]); + found++; } + else + notFoundFields.Add(Field[i]); + } - var result = Not ? found < required : found == required; - RunspaceContext.CurrentThread.VerboseConditionResult(condition: RuleLanguageNouns.Exists, outcome: result); - if (!(result || TryReason(null, Reason, null))) - { - WriteReason( - path: null, - text: Not ? ReasonStrings.ExistsNot : ReasonStrings.Exists, - args: Not ? string.Join(", ", foundFields) : string.Join(", ", notFoundFields) - ); - } - WriteObject(result); + var result = Not ? found < required : found == required; + RunspaceContext.CurrentThread.VerboseConditionResult(condition: RuleLanguageNouns.Exists, outcome: result); + if (!(result || TryReason(null, Reason, null))) + { + WriteReason( + path: null, + text: Not ? ReasonStrings.ExistsNot : ReasonStrings.Exists, + args: Not ? string.Join(", ", foundFields) : string.Join(", ", notFoundFields) + ); } + WriteObject(result); } } diff --git a/src/PSRule/Commands/AssertMatchCommand.cs b/src/PSRule/Commands/AssertMatchCommand.cs index b995db943f..210e5a2f09 100644 --- a/src/PSRule/Commands/AssertMatchCommand.cs +++ b/src/PSRule/Commands/AssertMatchCommand.cs @@ -7,97 +7,96 @@ using PSRule.Resources; using PSRule.Runtime; -namespace PSRule.Commands +namespace PSRule.Commands; + +/// +/// The Match keyword. +/// +[Cmdlet(VerbsLifecycle.Assert, RuleLanguageNouns.Match)] +internal sealed class AssertMatchCommand : RuleKeyword { - /// - /// The Match keyword. - /// - [Cmdlet(VerbsLifecycle.Assert, RuleLanguageNouns.Match)] - internal sealed class AssertMatchCommand : RuleKeyword - { - private Regex[] _Expressions; + private Regex[] _Expressions; - public AssertMatchCommand() - { - CaseSensitive = false; - Not = false; - } + public AssertMatchCommand() + { + CaseSensitive = false; + Not = false; + } - [Parameter(Mandatory = true, Position = 0)] - public string Field { get; set; } + [Parameter(Mandatory = true, Position = 0)] + public string Field { get; set; } - [Parameter(Mandatory = true, Position = 1)] - public string[] Expression { get; set; } + [Parameter(Mandatory = true, Position = 1)] + public string[] Expression { get; set; } - [Parameter(Mandatory = false)] - public string Reason { get; set; } + [Parameter(Mandatory = false)] + public string Reason { get; set; } - [Parameter(Mandatory = false)] - [PSDefaultValue(Value = false)] - public SwitchParameter CaseSensitive { get; set; } + [Parameter(Mandatory = false)] + [PSDefaultValue(Value = false)] + public SwitchParameter CaseSensitive { get; set; } - [Parameter(Mandatory = false)] - [PSDefaultValue(Value = false)] - public SwitchParameter Not { get; set; } + [Parameter(Mandatory = false)] + [PSDefaultValue(Value = false)] + public SwitchParameter Not { get; set; } - [Parameter(Mandatory = false, ValueFromPipeline = true)] - public PSObject InputObject { get; set; } + [Parameter(Mandatory = false, ValueFromPipeline = true)] + public PSObject InputObject { get; set; } - protected override void BeginProcessing() - { - // Setup regex expressions - _Expressions = new Regex[Expression.Length]; - var regexOption = CaseSensitive ? RegexOptions.None : RegexOptions.IgnoreCase; + protected override void BeginProcessing() + { + // Setup regex expressions + _Expressions = new Regex[Expression.Length]; + var regexOption = CaseSensitive ? RegexOptions.None : RegexOptions.IgnoreCase; - for (var i = 0; i < _Expressions.Length; i++) - _Expressions[i] = new Regex(Expression[i], regexOption, TimeSpan.FromSeconds(5)); - } + for (var i = 0; i < _Expressions.Length; i++) + _Expressions[i] = new Regex(Expression[i], regexOption, TimeSpan.FromSeconds(5)); + } - protected override void ProcessRecord() + protected override void ProcessRecord() + { + if (!IsRuleScope()) + throw RuleScopeException(LanguageKeywords.Match); + + var targetObject = InputObject ?? GetTargetObject(); + var expected = !Not; + var match = false; + var found = string.Empty; + + // Pass with any match, or (-Not) fail with any match + + if (ObjectHelper.GetPath( + bindingContext: PipelineContext.CurrentThread, + targetObject: targetObject, + path: Field, + caseSensitive: false, + value: out object fieldValue)) { - if (!IsRuleScope()) - throw RuleScopeException(LanguageKeywords.Match); - - var targetObject = InputObject ?? GetTargetObject(); - var expected = !Not; - var match = false; - var found = string.Empty; - - // Pass with any match, or (-Not) fail with any match - - if (ObjectHelper.GetPath( - bindingContext: PipelineContext.CurrentThread, - targetObject: targetObject, - path: Field, - caseSensitive: false, - value: out object fieldValue)) + var s = fieldValue.ToString(); + for (var i = 0; i < _Expressions.Length && !match; i++) { - var s = fieldValue.ToString(); - for (var i = 0; i < _Expressions.Length && !match; i++) + if (_Expressions[i].IsMatch(s)) { - if (_Expressions[i].IsMatch(s)) - { - match = true; - RunspaceContext.CurrentThread.VerboseConditionMessage( - condition: RuleLanguageNouns.Match, - message: PSRuleResources.MatchTrue, - args: fieldValue); - found = Expression[i]; - } + match = true; + RunspaceContext.CurrentThread.VerboseConditionMessage( + condition: RuleLanguageNouns.Match, + message: PSRuleResources.MatchTrue, + args: fieldValue); + found = Expression[i]; } } + } - var result = expected == match; - RunspaceContext.CurrentThread.VerboseConditionResult(condition: RuleLanguageNouns.Match, outcome: result); - if (!(result || TryReason(null, Reason, null))) - { - WriteReason( - path: null, - text: Not ? ReasonStrings.MatchNot : ReasonStrings.Match, - args: Not ? found : string.Join(", ", Expression) - ); - } - WriteObject(result); + var result = expected == match; + RunspaceContext.CurrentThread.VerboseConditionResult(condition: RuleLanguageNouns.Match, outcome: result); + if (!(result || TryReason(null, Reason, null))) + { + WriteReason( + path: null, + text: Not ? ReasonStrings.MatchNot : ReasonStrings.Match, + args: Not ? found : string.Join(", ", Expression) + ); } + WriteObject(result); } } diff --git a/src/PSRule/Commands/AssertTypeOfCommand.cs b/src/PSRule/Commands/AssertTypeOfCommand.cs index f6408d1c28..4fb926499e 100644 --- a/src/PSRule/Commands/AssertTypeOfCommand.cs +++ b/src/PSRule/Commands/AssertTypeOfCommand.cs @@ -5,47 +5,46 @@ using PSRule.Resources; using PSRule.Runtime; -namespace PSRule.Commands +namespace PSRule.Commands; + +/// +/// The TypeOf keyword. +/// +[Cmdlet(VerbsLifecycle.Assert, RuleLanguageNouns.TypeOf)] +internal sealed class AssertTypeOfCommand : RuleKeyword { - /// - /// The TypeOf keyword. - /// - [Cmdlet(VerbsLifecycle.Assert, RuleLanguageNouns.TypeOf)] - internal sealed class AssertTypeOfCommand : RuleKeyword + [Parameter(Mandatory = true, Position = 0)] + public string[] TypeName { get; set; } + + [Parameter(Mandatory = false)] + public string Reason { get; set; } + + [Parameter(Mandatory = false, ValueFromPipeline = true)] + public PSObject InputObject { get; set; } + + protected override void ProcessRecord() { - [Parameter(Mandatory = true, Position = 0)] - public string[] TypeName { get; set; } + if (!IsRuleScope()) + throw RuleScopeException(LanguageKeywords.TypeOf); - [Parameter(Mandatory = false)] - public string Reason { get; set; } + var inputObject = InputObject ?? GetTargetObject(); + var result = false; - [Parameter(Mandatory = false, ValueFromPipeline = true)] - public PSObject InputObject { get; set; } + if (inputObject != null) + { + var actualTypeNames = PSObject.AsPSObject(inputObject).TypeNames.ToArray(); + result = (actualTypeNames.Intersect(TypeName).Any()); + } - protected override void ProcessRecord() + RunspaceContext.CurrentThread.VerboseConditionResult(condition: RuleLanguageNouns.TypeOf, outcome: result); + if (!(result || TryReason(null, Reason, null))) { - if (!IsRuleScope()) - throw RuleScopeException(LanguageKeywords.TypeOf); - - var inputObject = InputObject ?? GetTargetObject(); - var result = false; - - if (inputObject != null) - { - var actualTypeNames = PSObject.AsPSObject(inputObject).TypeNames.ToArray(); - result = (actualTypeNames.Intersect(TypeName).Any()); - } - - RunspaceContext.CurrentThread.VerboseConditionResult(condition: RuleLanguageNouns.TypeOf, outcome: result); - if (!(result || TryReason(null, Reason, null))) - { - WriteReason( - path: null, - text: ReasonStrings.TypeOf, - args: string.Join(", ", TypeName) - ); - } - WriteObject(result); + WriteReason( + path: null, + text: ReasonStrings.TypeOf, + args: string.Join(", ", TypeName) + ); } + WriteObject(result); } } diff --git a/src/PSRule/Commands/AssertWithinCommand.cs b/src/PSRule/Commands/AssertWithinCommand.cs index 1c5ba0afa4..5d2f5f7f7a 100644 --- a/src/PSRule/Commands/AssertWithinCommand.cs +++ b/src/PSRule/Commands/AssertWithinCommand.cs @@ -6,167 +6,166 @@ using PSRule.Resources; using PSRule.Runtime; -namespace PSRule.Commands +namespace PSRule.Commands; + +/// +/// The Within keyword. +/// +[Cmdlet(VerbsLifecycle.Assert, RuleLanguageNouns.Within)] +internal sealed class AssertWithinCommand : RuleKeyword { - /// - /// The Within keyword. - /// - [Cmdlet(VerbsLifecycle.Assert, RuleLanguageNouns.Within)] - internal sealed class AssertWithinCommand : RuleKeyword - { - private StringComparer _Comparer; - private WildcardPattern[] _LikePattern; + private StringComparer _Comparer; + private WildcardPattern[] _LikePattern; - public AssertWithinCommand() - { - CaseSensitive = false; - Like = false; - } + public AssertWithinCommand() + { + CaseSensitive = false; + Like = false; + } - [Parameter(Mandatory = true, Position = 0)] - public string Field { get; set; } + [Parameter(Mandatory = true, Position = 0)] + public string Field { get; set; } - [Parameter(Mandatory = true, Position = 1)] - [Alias("AllowedValue")] - [AllowNull()] - public PSObject[] Value { get; set; } + [Parameter(Mandatory = true, Position = 1)] + [Alias("AllowedValue")] + [AllowNull()] + public PSObject[] Value { get; set; } - [Parameter(Mandatory = false)] - public string Reason { get; set; } + [Parameter(Mandatory = false)] + public string Reason { get; set; } - [Parameter(Mandatory = false)] - [PSDefaultValue(Value = false)] - public SwitchParameter Not { get; set; } + [Parameter(Mandatory = false)] + [PSDefaultValue(Value = false)] + public SwitchParameter Not { get; set; } - [Parameter(Mandatory = false)] - [PSDefaultValue(Value = false)] - public SwitchParameter CaseSensitive { get; set; } + [Parameter(Mandatory = false)] + [PSDefaultValue(Value = false)] + public SwitchParameter CaseSensitive { get; set; } - [Parameter(Mandatory = false)] - [PSDefaultValue(Value = false)] - public SwitchParameter Like { get; set; } + [Parameter(Mandatory = false)] + [PSDefaultValue(Value = false)] + public SwitchParameter Like { get; set; } - [Parameter(Mandatory = false, ValueFromPipeline = true)] - public PSObject InputObject { get; set; } + [Parameter(Mandatory = false, ValueFromPipeline = true)] + public PSObject InputObject { get; set; } - protected override void BeginProcessing() - { - _Comparer = CaseSensitive ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase; - BuildPattern(); - } + protected override void BeginProcessing() + { + _Comparer = CaseSensitive ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase; + BuildPattern(); + } - protected override void ProcessRecord() + protected override void ProcessRecord() + { + if (!IsRuleScope()) + throw RuleScopeException(LanguageKeywords.Within); + + var targetObject = InputObject ?? GetTargetObject(); + var expected = !Not; + var match = false; + var found = string.Empty; + + // Pass with any match, or (-Not) fail with any match + + if (ObjectHelper.GetPath( + bindingContext: PipelineContext.CurrentThread, + targetObject: targetObject, + path: Field, + caseSensitive: false, + value: out object fieldValue)) { - if (!IsRuleScope()) - throw RuleScopeException(LanguageKeywords.Within); - - var targetObject = InputObject ?? GetTargetObject(); - var expected = !Not; - var match = false; - var found = string.Empty; - - // Pass with any match, or (-Not) fail with any match - - if (ObjectHelper.GetPath( - bindingContext: PipelineContext.CurrentThread, - targetObject: targetObject, - path: Field, - caseSensitive: false, - value: out object fieldValue)) + for (var i = 0; (Value == null || i < Value.Length) && !match; i++) { - for (var i = 0; (Value == null || i < Value.Length) && !match; i++) + // Null compare + if (fieldValue == null || Value == null || Value[i] == null) { - // Null compare - if (fieldValue == null || Value == null || Value[i] == null) + if (fieldValue == null && (Value == null || Value[i] == null)) { - if (fieldValue == null && (Value == null || Value[i] == null)) - { - match = true; - RunspaceContext.CurrentThread.VerboseConditionMessage( - condition: RuleLanguageNouns.Within, - message: PSRuleResources.WithinTrue, - args: fieldValue); - } - else - { - break; - } + match = true; + RunspaceContext.CurrentThread.VerboseConditionMessage( + condition: RuleLanguageNouns.Within, + message: PSRuleResources.WithinTrue, + args: fieldValue); } - // String compare - else if (fieldValue is string strValue && Value[i].BaseObject is string) + else { - if ((_LikePattern == null && _Comparer.Equals(Value[i].BaseObject, strValue)) || (_LikePattern != null && _LikePattern[i].IsMatch(strValue))) - { - match = true; - RunspaceContext.CurrentThread.VerboseConditionMessage( - condition: RuleLanguageNouns.Within, - message: PSRuleResources.WithinTrue, - args: strValue); - found = Value[i].BaseObject.ToString(); - } + break; } - // Everything else - else if (Value[i].Equals(fieldValue)) + } + // String compare + else if (fieldValue is string strValue && Value[i].BaseObject is string) + { + if ((_LikePattern == null && _Comparer.Equals(Value[i].BaseObject, strValue)) || (_LikePattern != null && _LikePattern[i].IsMatch(strValue))) { match = true; RunspaceContext.CurrentThread.VerboseConditionMessage( condition: RuleLanguageNouns.Within, message: PSRuleResources.WithinTrue, - args: fieldValue); - found = Value[i].ToString(); + args: strValue); + found = Value[i].BaseObject.ToString(); } } + // Everything else + else if (Value[i].Equals(fieldValue)) + { + match = true; + RunspaceContext.CurrentThread.VerboseConditionMessage( + condition: RuleLanguageNouns.Within, + message: PSRuleResources.WithinTrue, + args: fieldValue); + found = Value[i].ToString(); + } } - - var result = expected == match; - RunspaceContext.CurrentThread.VerboseConditionResult(condition: RuleLanguageNouns.Within, outcome: result); - if (!(result || TryReason(null, Reason, null))) - { - WriteReason( - path: null, - text: Not ? ReasonStrings.WithinNot : ReasonStrings.Within, - args: Not ? found : null - ); - } - WriteObject(result); } - private void BuildPattern() + var result = expected == match; + RunspaceContext.CurrentThread.VerboseConditionResult(condition: RuleLanguageNouns.Within, outcome: result); + if (!(result || TryReason(null, Reason, null))) { - if (!Like || Value.Length == 0) - return; + WriteReason( + path: null, + text: Not ? ReasonStrings.WithinNot : ReasonStrings.Within, + args: Not ? found : null + ); + } + WriteObject(result); + } + + private void BuildPattern() + { + if (!Like || Value.Length == 0) + return; - if (TryExpressionCache()) - return; + if (TryExpressionCache()) + return; - _LikePattern = new WildcardPattern[Value.Length]; - for (var i = 0; i < _LikePattern.Length; i++) + _LikePattern = new WildcardPattern[Value.Length]; + for (var i = 0; i < _LikePattern.Length; i++) + { + if (!TryStringValue(Value[i], out var value)) { - if (!TryStringValue(Value[i], out var value)) - { - throw new RuleException(PSRuleResources.WithinLikeNotString); - } - _LikePattern[i] = WildcardPattern.Get(value, CaseSensitive ? WildcardOptions.None : WildcardOptions.IgnoreCase); + throw new RuleException(PSRuleResources.WithinLikeNotString); } - PipelineContext.CurrentThread.ExpressionCache[MyInvocation.PositionMessage] = _LikePattern; + _LikePattern[i] = WildcardPattern.Get(value, CaseSensitive ? WildcardOptions.None : WildcardOptions.IgnoreCase); } + PipelineContext.CurrentThread.ExpressionCache[MyInvocation.PositionMessage] = _LikePattern; + } - private bool TryExpressionCache() - { - if (!PipelineContext.CurrentThread.ExpressionCache.TryGetValue(MyInvocation.PositionMessage, out var cacheValue)) - return false; + private bool TryExpressionCache() + { + if (!PipelineContext.CurrentThread.ExpressionCache.TryGetValue(MyInvocation.PositionMessage, out var cacheValue)) + return false; - _LikePattern = (WildcardPattern[])cacheValue; - return true; - } + _LikePattern = (WildcardPattern[])cacheValue; + return true; + } - private static bool TryStringValue(PSObject o, out string value) - { - value = null; - if (o == null || o.BaseObject is not string) - return false; - value = o.BaseObject.ToString(); - return true; - } + private static bool TryStringValue(PSObject o, out string value) + { + value = null; + if (o == null || o.BaseObject is not string) + return false; + value = o.BaseObject.ToString(); + return true; } } diff --git a/src/PSRule/Commands/ExportConventionCommand.cs b/src/PSRule/Commands/ExportConventionCommand.cs index 1a9e30ccff..8be732463a 100644 --- a/src/PSRule/Commands/ExportConventionCommand.cs +++ b/src/PSRule/Commands/ExportConventionCommand.cs @@ -6,107 +6,106 @@ using PSRule.Definitions.Conventions; using PSRule.Runtime; -namespace PSRule.Commands +namespace PSRule.Commands; + +[Cmdlet(VerbsData.Export, RuleLanguageNouns.Convention)] +internal sealed class ExportConventionCommand : LanguageBlock { - [Cmdlet(VerbsData.Export, RuleLanguageNouns.Convention)] - internal sealed class ExportConventionCommand : LanguageBlock + private const string CmdletName = "Invoke-PSRuleConvention"; + private const string Cmdlet_IfParameter = "If"; + private const string Cmdlet_BodyParameter = "Body"; + private const string Cmdlet_ScopeParameter = "Scope"; + + [Parameter(Mandatory = true, Position = 0)] + [ValidateNotNullOrEmpty()] + [ValidateLength(3, 128)] + public string Name { get; set; } + + /// + /// A script block to call once before any objects are processed. + /// + [Parameter(Mandatory = false)] + [ValidateNotNullOrEmpty()] + public ScriptBlock Initialize { get; set; } + + /// + /// A script block to call once per object before being processed by any rule. + /// + [Parameter(Mandatory = false)] + [ValidateNotNullOrEmpty()] + public ScriptBlock Begin { get; set; } + + /// + /// A script block to call once per object after rules are processed. + /// + [Parameter(Mandatory = false, Position = 1)] + [ValidateNotNullOrEmpty()] + public ScriptBlock Process { get; set; } + + /// + /// A script block to call once after all rules and all objects are processed. + /// + [Parameter(Mandatory = false)] + [ValidateNotNullOrEmpty()] + public ScriptBlock End { get; set; } + + /// + /// An optional pre-condition before the convention is evaluated. + /// + [Parameter(Mandatory = false)] + public ScriptBlock If { get; set; } + + protected override void ProcessRecord() { - private const string CmdletName = "Invoke-PSRuleConvention"; - private const string Cmdlet_IfParameter = "If"; - private const string Cmdlet_BodyParameter = "Body"; - private const string Cmdlet_ScopeParameter = "Scope"; - - [Parameter(Mandatory = true, Position = 0)] - [ValidateNotNullOrEmpty()] - [ValidateLength(3, 128)] - public string Name { get; set; } - - /// - /// A script block to call once before any objects are processed. - /// - [Parameter(Mandatory = false)] - [ValidateNotNullOrEmpty()] - public ScriptBlock Initialize { get; set; } - - /// - /// A script block to call once per object before being processed by any rule. - /// - [Parameter(Mandatory = false)] - [ValidateNotNullOrEmpty()] - public ScriptBlock Begin { get; set; } - - /// - /// A script block to call once per object after rules are processed. - /// - [Parameter(Mandatory = false, Position = 1)] - [ValidateNotNullOrEmpty()] - public ScriptBlock Process { get; set; } - - /// - /// A script block to call once after all rules and all objects are processed. - /// - [Parameter(Mandatory = false)] - [ValidateNotNullOrEmpty()] - public ScriptBlock End { get; set; } - - /// - /// An optional pre-condition before the convention is evaluated. - /// - [Parameter(Mandatory = false)] - public ScriptBlock If { get; set; } - - protected override void ProcessRecord() + //if (!IsScriptScope()) + // throw new RuleException(string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.KeywordScriptScope, LanguageKeywords.Rule)); + + var context = RunspaceContext.CurrentThread; + var errorPreference = GetErrorActionPreference(); + var commentMetadata = GetCommentMetadata(MyInvocation.ScriptName, MyInvocation.ScriptLineNumber, MyInvocation.OffsetInLine); + var source = context.Source.File; + var metadata = new ResourceMetadata { - //if (!IsScriptScope()) - // throw new RuleException(string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.KeywordScriptScope, LanguageKeywords.Rule)); - - var context = RunspaceContext.CurrentThread; - var errorPreference = GetErrorActionPreference(); - var commentMetadata = GetCommentMetadata(MyInvocation.ScriptName, MyInvocation.ScriptLineNumber, MyInvocation.OffsetInLine); - var source = context.Source.File; - var metadata = new ResourceMetadata - { - Name = Name - }; - var extent = new SourceExtent( - file: source.Path, - line: MyInvocation.ScriptLineNumber, - position: MyInvocation.OffsetInLine - ); - - context.VerboseFoundResource(name: Name, moduleName: source.Module, scriptName: MyInvocation.ScriptName); - - var helpInfo = new ResourceHelpInfo(Name, Name, new InfoString(commentMetadata.Synopsis), new InfoString()); + Name = Name + }; + var extent = new SourceExtent( + file: source.Path, + line: MyInvocation.ScriptLineNumber, + position: MyInvocation.OffsetInLine + ); + + context.VerboseFoundResource(name: Name, moduleName: source.Module, scriptName: MyInvocation.ScriptName); + + var helpInfo = new ResourceHelpInfo(Name, Name, new InfoString(commentMetadata.Synopsis), new InfoString()); #pragma warning disable CA2000 // Dispose objects before losing scope, needs to be passed to pipeline - var block = new ScriptBlockConvention( - source: source, - metadata: metadata, - info: helpInfo, - initialize: ConventionBlock(context, Initialize, RunspaceScope.ConventionInitialize), - begin: ConventionBlock(context, Begin, RunspaceScope.ConventionBegin), - process: ConventionBlock(context, Process, RunspaceScope.ConventionProcess), - end: ConventionBlock(context, End, RunspaceScope.ConventionEnd), - errorPreference: errorPreference, - flags: ResourceFlags.None, - extent: extent - ); + var block = new ScriptBlockConvention( + source: source, + metadata: metadata, + info: helpInfo, + initialize: ConventionBlock(context, Initialize, RunspaceScope.ConventionInitialize), + begin: ConventionBlock(context, Begin, RunspaceScope.ConventionBegin), + process: ConventionBlock(context, Process, RunspaceScope.ConventionProcess), + end: ConventionBlock(context, End, RunspaceScope.ConventionEnd), + errorPreference: errorPreference, + flags: ResourceFlags.None, + extent: extent + ); #pragma warning restore CA2000 // Dispose objects before losing scope, needs to be passed to pipeline - WriteObject(block); - } + WriteObject(block); + } - private LanguageScriptBlock ConventionBlock(RunspaceContext context, ScriptBlock block, RunspaceScope scope) - { - if (block == null) - return null; - - // Create PS instance for execution - var ps = context.GetPowerShell(); - ps.AddCommand(new CmdletInfo(CmdletName, typeof(InvokeConventionCommand))); - ps.AddParameter(Cmdlet_IfParameter, If); - ps.AddParameter(Cmdlet_BodyParameter, block); - ps.AddParameter(Cmdlet_ScopeParameter, scope); - return new LanguageScriptBlock(ps); - } + private LanguageScriptBlock ConventionBlock(RunspaceContext context, ScriptBlock block, RunspaceScope scope) + { + if (block == null) + return null; + + // Create PS instance for execution + var ps = context.GetPowerShell(); + ps.AddCommand(new CmdletInfo(CmdletName, typeof(InvokeConventionCommand))); + ps.AddParameter(Cmdlet_IfParameter, If); + ps.AddParameter(Cmdlet_BodyParameter, block); + ps.AddParameter(Cmdlet_ScopeParameter, scope); + return new LanguageScriptBlock(ps); } } diff --git a/src/PSRule/Commands/InvokeConventionCommand.cs b/src/PSRule/Commands/InvokeConventionCommand.cs index 6b3fd07991..90b254e5a3 100644 --- a/src/PSRule/Commands/InvokeConventionCommand.cs +++ b/src/PSRule/Commands/InvokeConventionCommand.cs @@ -6,68 +6,67 @@ using PSRule.Resources; using PSRule.Runtime; -namespace PSRule.Commands +namespace PSRule.Commands; + +internal sealed class InvokeConventionCommand : Cmdlet { - internal sealed class InvokeConventionCommand : Cmdlet - { - [Parameter()] - public ScriptBlock If; + [Parameter()] + public ScriptBlock If; - [Parameter()] - public ScriptBlock Body; + [Parameter()] + public ScriptBlock Body; - [Parameter()] - public RunspaceScope Scope; + [Parameter()] + public RunspaceScope Scope; - protected override void ProcessRecord() + protected override void ProcessRecord() + { + var context = RunspaceContext.CurrentThread; + try { - var context = RunspaceContext.CurrentThread; - try - { - if (Body == null) - return; + if (Body == null) + return; - // Evaluate script pre-condition - if (If != null) + // Evaluate script pre-condition + if (If != null) + { + try { - try - { - context.PushScope(RunspaceScope.Precondition); - var ifResult = RuleConditionHelper.Create(If.Invoke()); - if (!ifResult.AllOf()) - { - context.Writer.DebugMessage(PSRuleResources.DebugTargetIfMismatch); - return; - } - } - finally + context.PushScope(RunspaceScope.Precondition); + var ifResult = RuleConditionHelper.Create(If.Invoke()); + if (!ifResult.AllOf()) { - context.PopScope(RunspaceScope.Precondition); + context.Writer.DebugMessage(PSRuleResources.DebugTargetIfMismatch); + return; } } - - try - { - // Evaluate script block - context.PushScope(Scope); - Body.Invoke(); - } finally { - context.PopScope(Scope); + context.PopScope(RunspaceScope.Precondition); } } - catch (ActionPreferenceStopException ex) + + try { - context.Error(ex); + // Evaluate script block + context.PushScope(Scope); + Body.Invoke(); } - catch (System.Management.Automation.RuntimeException ex) + finally { - if (ex.ErrorRecord.FullyQualifiedErrorId == "MethodInvocationNotSupportedInConstrainedLanguage") - throw; - - context.Error(ex); + context.PopScope(Scope); } } + catch (ActionPreferenceStopException ex) + { + context.Error(ex); + } + catch (System.Management.Automation.RuntimeException ex) + { + if (ex.ErrorRecord.FullyQualifiedErrorId == "MethodInvocationNotSupportedInConstrainedLanguage") + throw; + + context.Error(ex); + } } } diff --git a/src/PSRule/Commands/InvokeRuleBlockCommand.cs b/src/PSRule/Commands/InvokeRuleBlockCommand.cs index 7d62d23bb9..edbb6db9fa 100644 --- a/src/PSRule/Commands/InvokeRuleBlockCommand.cs +++ b/src/PSRule/Commands/InvokeRuleBlockCommand.cs @@ -8,122 +8,121 @@ using PSRule.Resources; using PSRule.Runtime; -namespace PSRule.Commands +namespace PSRule.Commands; + +/// +/// An internal language command used to evaluate a rule script block. +/// +internal sealed class InvokeRuleBlockCommand : Cmdlet { - /// - /// An internal language command used to evaluate a rule script block. - /// - internal sealed class InvokeRuleBlockCommand : Cmdlet - { - [Parameter()] - public string[] Type; + [Parameter()] + public string[] Type; - [Parameter()] - public ResourceId[] With; + [Parameter()] + public ResourceId[] With; - [Parameter()] - public ScriptBlock If; + [Parameter()] + public ScriptBlock If; - [Parameter()] - public ScriptBlock Body; + [Parameter()] + public ScriptBlock Body; - [Parameter()] - public SourceFile Source; + [Parameter()] + public SourceFile Source; - protected override void ProcessRecord() + protected override void ProcessRecord() + { + var context = RunspaceContext.CurrentThread; + try { - var context = RunspaceContext.CurrentThread; - try - { - if (Body == null) - return; + if (Body == null) + return; - // Evaluate selector pre-condition - if (!AcceptsWith()) - { - context.Writer.DebugMessage(PSRuleResources.DebugTargetTypeMismatch); - return; - } - - // Evaluate type pre-condition - if (!AcceptsType()) - { - context.Writer.DebugMessage(PSRuleResources.DebugTargetTypeMismatch); - return; - } + // Evaluate selector pre-condition + if (!AcceptsWith()) + { + context.Writer.DebugMessage(PSRuleResources.DebugTargetTypeMismatch); + return; + } - // Evaluate script pre-condition - if (If != null) - { - try - { - context.PushScope(RunspaceScope.Precondition); - context.EnterLanguageScope(Source); - var ifResult = RuleConditionHelper.Create(If.Invoke()); - if (!ifResult.AllOf()) - { - context.Writer.DebugMessage(PSRuleResources.DebugTargetIfMismatch); - return; - } - } - finally - { - context.PopScope(RunspaceScope.Precondition); - } - } + // Evaluate type pre-condition + if (!AcceptsType()) + { + context.Writer.DebugMessage(PSRuleResources.DebugTargetTypeMismatch); + return; + } + // Evaluate script pre-condition + if (If != null) + { try { - // Evaluate script block - context.PushScope(RunspaceScope.Rule); + context.PushScope(RunspaceScope.Precondition); context.EnterLanguageScope(Source); - var invokeResult = RuleConditionHelper.Create(Body.Invoke()); - WriteObject(invokeResult); + var ifResult = RuleConditionHelper.Create(If.Invoke()); + if (!ifResult.AllOf()) + { + context.Writer.DebugMessage(PSRuleResources.DebugTargetIfMismatch); + return; + } } finally { - context.PopScope(RunspaceScope.Rule); + context.PopScope(RunspaceScope.Precondition); } } - catch (ActionPreferenceStopException ex) + + try { - context.Error(ex); + // Evaluate script block + context.PushScope(RunspaceScope.Rule); + context.EnterLanguageScope(Source); + var invokeResult = RuleConditionHelper.Create(Body.Invoke()); + WriteObject(invokeResult); } - catch (System.Management.Automation.RuntimeException ex) + finally { - if (ex.ErrorRecord.FullyQualifiedErrorId == "MethodInvocationNotSupportedInConstrainedLanguage") - throw; - - context.Error(ex); + context.PopScope(RunspaceScope.Rule); } } - - private bool AcceptsType() + catch (ActionPreferenceStopException ex) { - if (Type == null) - return true; + context.Error(ex); + } + catch (System.Management.Automation.RuntimeException ex) + { + if (ex.ErrorRecord.FullyQualifiedErrorId == "MethodInvocationNotSupportedInConstrainedLanguage") + throw; - var comparer = RunspaceContext.CurrentThread.LanguageScope.Binding.GetComparer(); - var targetType = RunspaceContext.CurrentThread.RuleRecord.TargetType; - for (var i = 0; i < Type.Length; i++) - { - if (comparer.Equals(targetType, Type[i])) - return true; - } - return false; + context.Error(ex); } + } - private bool AcceptsWith() + private bool AcceptsType() + { + if (Type == null) + return true; + + var comparer = RunspaceContext.CurrentThread.LanguageScope.Binding.GetComparer(); + var targetType = RunspaceContext.CurrentThread.RuleRecord.TargetType; + for (var i = 0; i < Type.Length; i++) { - if (With == null || With.Length == 0) + if (comparer.Equals(targetType, Type[i])) return true; + } + return false; + } - for (var i = 0; i < With.Length; i++) - { - if (RunspaceContext.CurrentThread.TrySelector(With[i])) - return true; - } - return false; + private bool AcceptsWith() + { + if (With == null || With.Length == 0) + return true; + + for (var i = 0; i < With.Length; i++) + { + if (RunspaceContext.CurrentThread.TrySelector(With[i])) + return true; } + return false; } } diff --git a/src/PSRule/Commands/LanguageBlock.cs b/src/PSRule/Commands/LanguageBlock.cs index d016e96584..6aa2bda456 100644 --- a/src/PSRule/Commands/LanguageBlock.cs +++ b/src/PSRule/Commands/LanguageBlock.cs @@ -8,41 +8,40 @@ using PSRule.Host; using PSRule.Runtime; -namespace PSRule.Commands +namespace PSRule.Commands; + +/// +/// A base class for language blocks. +/// +internal abstract class LanguageBlock : PSCmdlet { - /// - /// A base class for language blocks. - /// - internal abstract class LanguageBlock : PSCmdlet + private const string ErrorActionParameter = "ErrorAction"; + + protected static CommentMetadata GetCommentMetadata(string path, int lineNumber, int offset) + { + return HostHelper.GetCommentMeta(path, lineNumber - 2, offset); + } + + protected static ResourceTags GetTag(Hashtable hashtable) + { + return ResourceTags.FromHashtable(hashtable); + } + + protected static bool IsSourceScope() + { + return RunspaceContext.CurrentThread.IsScope(RunspaceScope.Source); + } + + protected ActionPreference GetErrorActionPreference() + { + var preference = GetBoundPreference(ErrorActionParameter) ?? ActionPreference.Stop; + // Ignore not supported on older PowerShell versions + return preference == ActionPreference.Ignore ? ActionPreference.SilentlyContinue : preference; + } + + protected ActionPreference? GetBoundPreference(string name) { - private const string ErrorActionParameter = "ErrorAction"; - - protected static CommentMetadata GetCommentMetadata(string path, int lineNumber, int offset) - { - return HostHelper.GetCommentMeta(path, lineNumber - 2, offset); - } - - protected static ResourceTags GetTag(Hashtable hashtable) - { - return ResourceTags.FromHashtable(hashtable); - } - - protected static bool IsSourceScope() - { - return RunspaceContext.CurrentThread.IsScope(RunspaceScope.Source); - } - - protected ActionPreference GetErrorActionPreference() - { - var preference = GetBoundPreference(ErrorActionParameter) ?? ActionPreference.Stop; - // Ignore not supported on older PowerShell versions - return preference == ActionPreference.Ignore ? ActionPreference.SilentlyContinue : preference; - } - - protected ActionPreference? GetBoundPreference(string name) - { - return MyInvocation.BoundParameters.TryGetValue(name, out var o) && - Enum.TryParse(o.ToString(), out ActionPreference value) ? value : null; - } + return MyInvocation.BoundParameters.TryGetValue(name, out var o) && + Enum.TryParse(o.ToString(), out ActionPreference value) ? value : null; } } diff --git a/src/PSRule/Commands/LanguageKeywords.cs b/src/PSRule/Commands/LanguageKeywords.cs index beba02a5c9..bea69610f8 100644 --- a/src/PSRule/Commands/LanguageKeywords.cs +++ b/src/PSRule/Commands/LanguageKeywords.cs @@ -1,18 +1,17 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -namespace PSRule.Commands +namespace PSRule.Commands; + +internal static class LanguageKeywords { - internal static class LanguageKeywords - { - public const string Exists = "Exists"; - public const string Match = "Match"; - public const string Within = "Within"; - public const string TypeOf = "TypeOf"; - public const string AnyOf = "AnyOf"; - public const string AllOf = "AllOf"; - public const string Recommend = "Recommend"; - public const string Reason = "Reason"; - public const string Rule = "Rule"; - } + public const string Exists = "Exists"; + public const string Match = "Match"; + public const string Within = "Within"; + public const string TypeOf = "TypeOf"; + public const string AnyOf = "AnyOf"; + public const string AllOf = "AllOf"; + public const string Recommend = "Recommend"; + public const string Reason = "Reason"; + public const string Rule = "Rule"; } diff --git a/src/PSRule/Commands/NewRuleDefinitionCommand.cs b/src/PSRule/Commands/NewRuleDefinitionCommand.cs index 7cd9cbe6dc..d7bd556fc6 100644 --- a/src/PSRule/Commands/NewRuleDefinitionCommand.cs +++ b/src/PSRule/Commands/NewRuleDefinitionCommand.cs @@ -10,175 +10,174 @@ using PSRule.Rules; using PSRule.Runtime; -namespace PSRule.Commands +namespace PSRule.Commands; + +/// +/// A Rule language block. +/// +[Cmdlet(VerbsCommon.New, RuleLanguageNouns.RuleDefinition)] +internal sealed class NewRuleDefinitionCommand : LanguageBlock { + private const string CmdletName = "Invoke-RuleBlock"; + private const string Cmdlet_TypeParameter = "Type"; + private const string Cmdlet_IfParameter = "If"; + private const string Cmdlet_WithParameter = "With"; + private const string Cmdlet_BodyParameter = "Body"; + private const string Cmdlet_SourceParameter = "Source"; + + /// + /// The name of the rule. + /// + [Parameter(Mandatory = true, Position = 0)] + [ValidateNotNullOrEmpty()] + [ValidateLength(3, 128)] + public string Name { get; set; } + + /// + /// If the rule fails, how serious is the result. + /// + [Parameter(Mandatory = false)] + public SeverityLevel? Level { get; set; } + + /// + /// The definition of the deployment. + /// + [Parameter(Mandatory = false, Position = 1)] + public ScriptBlock Body { get; set; } + + /// + /// A set of tags with additional metadata for the rule. + /// + [Parameter(Mandatory = false)] + public Hashtable Tag { get; set; } + + /// + /// An optional script precondition before the rule is evaluated. + /// + [Parameter(Mandatory = false)] + public ScriptBlock If { get; set; } + + /// + /// An optional type precondition before the rule is evaluated. + /// + [Parameter(Mandatory = false)] + public string[] Type { get; set; } + + /// + /// An optional selector precondition before the rule is evaluated. + /// + [Parameter(Mandatory = false)] + public string[] With { get; set; } + + /// + /// Deployments that this deployment depends on. + /// + [Parameter(Mandatory = false)] + [ValidateNotNullOrEmpty()] + public string[] DependsOn { get; set; } + + /// + /// A set of default configuration values. + /// + [Parameter(Mandatory = false)] + public Hashtable Configure { get; set; } + + /// + /// Any aliases for the rule. + /// + [Parameter(Mandatory = false)] + public string[] Alias { get; set; } + + /// + /// An optional reference identifer for the resource. + /// + [Parameter(Mandatory = false)] + [ValidateLength(3, 128)] + public string Ref { get; set; } + /// - /// A Rule language block. + /// Any taxonomy references. /// - [Cmdlet(VerbsCommon.New, RuleLanguageNouns.RuleDefinition)] - internal sealed class NewRuleDefinitionCommand : LanguageBlock + [Parameter(Mandatory = false)] + public Hashtable Labels { get; set; } + + protected override void ProcessRecord() { - private const string CmdletName = "Invoke-RuleBlock"; - private const string Cmdlet_TypeParameter = "Type"; - private const string Cmdlet_IfParameter = "If"; - private const string Cmdlet_WithParameter = "With"; - private const string Cmdlet_BodyParameter = "Body"; - private const string Cmdlet_SourceParameter = "Source"; - - /// - /// The name of the rule. - /// - [Parameter(Mandatory = true, Position = 0)] - [ValidateNotNullOrEmpty()] - [ValidateLength(3, 128)] - public string Name { get; set; } - - /// - /// If the rule fails, how serious is the result. - /// - [Parameter(Mandatory = false)] - public SeverityLevel? Level { get; set; } - - /// - /// The definition of the deployment. - /// - [Parameter(Mandatory = false, Position = 1)] - public ScriptBlock Body { get; set; } - - /// - /// A set of tags with additional metadata for the rule. - /// - [Parameter(Mandatory = false)] - public Hashtable Tag { get; set; } - - /// - /// An optional script precondition before the rule is evaluated. - /// - [Parameter(Mandatory = false)] - public ScriptBlock If { get; set; } - - /// - /// An optional type precondition before the rule is evaluated. - /// - [Parameter(Mandatory = false)] - public string[] Type { get; set; } - - /// - /// An optional selector precondition before the rule is evaluated. - /// - [Parameter(Mandatory = false)] - public string[] With { get; set; } - - /// - /// Deployments that this deployment depends on. - /// - [Parameter(Mandatory = false)] - [ValidateNotNullOrEmpty()] - public string[] DependsOn { get; set; } - - /// - /// A set of default configuration values. - /// - [Parameter(Mandatory = false)] - public Hashtable Configure { get; set; } - - /// - /// Any aliases for the rule. - /// - [Parameter(Mandatory = false)] - public string[] Alias { get; set; } - - /// - /// An optional reference identifer for the resource. - /// - [Parameter(Mandatory = false)] - [ValidateLength(3, 128)] - public string Ref { get; set; } - - /// - /// Any taxonomy references. - /// - [Parameter(Mandatory = false)] - public Hashtable Labels { get; set; } - - protected override void ProcessRecord() - { - if (!IsSourceScope()) - throw new RuleException(string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.KeywordSourceScope, LanguageKeywords.Rule)); - - var context = RunspaceContext.CurrentThread; - var errorPreference = GetErrorActionPreference(); - var metadata = GetCommentMetadata(MyInvocation.ScriptName, MyInvocation.ScriptLineNumber, MyInvocation.OffsetInLine); - var level = ResourceHelper.GetLevel(Level); - var tag = GetTag(Tag); - var source = context.Source.File; - var extent = new SourceExtent( - file: source.Path, - line: MyInvocation.ScriptLineNumber, - position: MyInvocation.OffsetInLine - ); - var flags = ResourceFlags.None; - var id = new ResourceId(source.Module, Name, ResourceIdKind.Id); - var labels = ResourceLabels.FromHashtable(Labels); - - context.VerboseFoundResource(name: Name, moduleName: source.Module, scriptName: MyInvocation.ScriptName); - - CheckDependsOn(); - var ps = GetCondition(context, id, source, errorPreference); - var info = PSRule.Host.HostHelper.GetRuleHelpInfo(context, Name, metadata.Synopsis, null, null, null) ?? new RuleHelpInfo( - name: Name, - displayName: Name, - moduleName: source.Module - ); + if (!IsSourceScope()) + throw new RuleException(string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.KeywordSourceScope, LanguageKeywords.Rule)); + + var context = RunspaceContext.CurrentThread; + var errorPreference = GetErrorActionPreference(); + var metadata = GetCommentMetadata(MyInvocation.ScriptName, MyInvocation.ScriptLineNumber, MyInvocation.OffsetInLine); + var level = ResourceHelper.GetLevel(Level); + var tag = GetTag(Tag); + var source = context.Source.File; + var extent = new SourceExtent( + file: source.Path, + line: MyInvocation.ScriptLineNumber, + position: MyInvocation.OffsetInLine + ); + var flags = ResourceFlags.None; + var id = new ResourceId(source.Module, Name, ResourceIdKind.Id); + var labels = ResourceLabels.FromHashtable(Labels); + + context.VerboseFoundResource(name: Name, moduleName: source.Module, scriptName: MyInvocation.ScriptName); + + CheckDependsOn(); + var ps = GetCondition(context, id, source, errorPreference); + var info = PSRule.Host.HostHelper.GetRuleHelpInfo(context, Name, metadata.Synopsis, null, null, null) ?? new RuleHelpInfo( + name: Name, + displayName: Name, + moduleName: source.Module + ); #pragma warning disable CA2000 // Dispose objects before losing scope, needs to be passed to pipeline - var block = new RuleBlock( - source: source, - id: id, - @ref: ResourceHelper.GetIdNullable(source.Module, Ref, ResourceIdKind.Ref), - level: level, - info: info, - condition: ps, - tag: tag, - alias: ResourceHelper.GetRuleId(source.Module, Alias, ResourceIdKind.Alias), - dependsOn: ResourceHelper.GetRuleId(source.Module, DependsOn, ResourceIdKind.Unknown), - configuration: Configure, - extent: extent, - flags: flags, - labels: labels - ); + var block = new RuleBlock( + source: source, + id: id, + @ref: ResourceHelper.GetIdNullable(source.Module, Ref, ResourceIdKind.Ref), + level: level, + info: info, + condition: ps, + tag: tag, + alias: ResourceHelper.GetRuleId(source.Module, Alias, ResourceIdKind.Alias), + dependsOn: ResourceHelper.GetRuleId(source.Module, DependsOn, ResourceIdKind.Unknown), + configuration: Configure, + extent: extent, + flags: flags, + labels: labels + ); #pragma warning restore CA2000 // Dispose objects before losing scope, needs to be passed to pipeline - WriteObject(block); - } + WriteObject(block); + } - private PowerShellCondition GetCondition(RunspaceContext context, ResourceId id, SourceFile source, ActionPreference errorAction) - { - var result = context.GetPowerShell(); - result.AddCommand(new CmdletInfo(CmdletName, typeof(InvokeRuleBlockCommand))); - result.AddParameter(Cmdlet_TypeParameter, Type); - result.AddParameter(Cmdlet_WithParameter, GetScopedSelectors(source)); - result.AddParameter(Cmdlet_IfParameter, If); - result.AddParameter(Cmdlet_BodyParameter, Body); - result.AddParameter(Cmdlet_SourceParameter, source); - return new PowerShellCondition(id, source, result, errorAction); - } + private PowerShellCondition GetCondition(RunspaceContext context, ResourceId id, SourceFile source, ActionPreference errorAction) + { + var result = context.GetPowerShell(); + result.AddCommand(new CmdletInfo(CmdletName, typeof(InvokeRuleBlockCommand))); + result.AddParameter(Cmdlet_TypeParameter, Type); + result.AddParameter(Cmdlet_WithParameter, GetScopedSelectors(source)); + result.AddParameter(Cmdlet_IfParameter, If); + result.AddParameter(Cmdlet_BodyParameter, Body); + result.AddParameter(Cmdlet_SourceParameter, source); + return new PowerShellCondition(id, source, result, errorAction); + } - private void CheckDependsOn() + private void CheckDependsOn() + { + if (MyInvocation.BoundParameters.ContainsKey(nameof(DependsOn)) && (DependsOn == null || DependsOn.Length == 0)) { - if (MyInvocation.BoundParameters.ContainsKey(nameof(DependsOn)) && (DependsOn == null || DependsOn.Length == 0)) - { - WriteError(new ErrorRecord( - exception: new ArgumentNullException(paramName: nameof(DependsOn)), - errorId: "PSRule.Runtime.ArgumentNull", - errorCategory: ErrorCategory.InvalidArgument, - targetObject: null - )); - } + WriteError(new ErrorRecord( + exception: new ArgumentNullException(paramName: nameof(DependsOn)), + errorId: "PSRule.Runtime.ArgumentNull", + errorCategory: ErrorCategory.InvalidArgument, + targetObject: null + )); } + } - private ResourceId[] GetScopedSelectors(SourceFile source) - { - return ResourceHelper.GetRuleId(source.Module, With, ResourceIdKind.Unknown); - } + private ResourceId[] GetScopedSelectors(SourceFile source) + { + return ResourceHelper.GetRuleId(source.Module, With, ResourceIdKind.Unknown); } } diff --git a/src/PSRule/Commands/RuleKeyword.cs b/src/PSRule/Commands/RuleKeyword.cs index f83a6cb662..b051284a3f 100644 --- a/src/PSRule/Commands/RuleKeyword.cs +++ b/src/PSRule/Commands/RuleKeyword.cs @@ -9,109 +9,108 @@ using PSRule.Rules; using PSRule.Runtime; -namespace PSRule.Commands +namespace PSRule.Commands; + +/// +/// A base class for Rule keywords. +/// +internal abstract class RuleKeyword : PSCmdlet { - /// - /// A base class for Rule keywords. - /// - internal abstract class RuleKeyword : PSCmdlet + protected static RuleRecord GetResult() { - protected static RuleRecord GetResult() - { - return RunspaceContext.CurrentThread.RuleRecord; - } + return RunspaceContext.CurrentThread.RuleRecord; + } - protected static PSObject GetTargetObject() - { - return RunspaceContext.CurrentThread.TargetObject.Value; - } + protected static PSObject GetTargetObject() + { + return RunspaceContext.CurrentThread.TargetObject.Value; + } - protected static bool GetField(object targetObject, string name, bool caseSensitive, out object value) + protected static bool GetField(object targetObject, string name, bool caseSensitive, out object value) + { + value = null; + if (targetObject == null) { value = null; - if (targetObject == null) - { - value = null; - return false; - } + return false; + } - var comparer = caseSensitive ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase; - var baseObject = ExpressionHelpers.GetBaseObject(targetObject); - var baseType = baseObject.GetType(); + var comparer = caseSensitive ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase; + var baseObject = ExpressionHelpers.GetBaseObject(targetObject); + var baseType = baseObject.GetType(); - // Handle dictionaries and hashtables - if (typeof(IDictionary).IsAssignableFrom(baseType)) + // Handle dictionaries and hashtables + if (typeof(IDictionary).IsAssignableFrom(baseType)) + { + var dictionary = (IDictionary)baseObject; + foreach (var key in dictionary.Keys) { - var dictionary = (IDictionary)baseObject; - foreach (var key in dictionary.Keys) + if (comparer.Equals(name, key)) { - if (comparer.Equals(name, key)) - { - value = dictionary[key]; - return true; - } + value = dictionary[key]; + return true; } } - // Handle PSObjects - else if (targetObject is PSObject pso) + } + // Handle PSObjects + else if (targetObject is PSObject pso) + { + foreach (var prop in pso.Properties) { - foreach (var prop in pso.Properties) + if (comparer.Equals(name, prop.Name)) { - if (comparer.Equals(name, prop.Name)) - { - value = prop.Value; - return true; - } + value = prop.Value; + return true; } } - // Handle all other CLR types - else + } + // Handle all other CLR types + else + { + foreach (var p in baseType.GetProperties()) { - foreach (var p in baseType.GetProperties()) + if (comparer.Equals(name, p.Name)) { - if (comparer.Equals(name, p.Name)) - { - value = p.GetValue(targetObject); - return true; - } + value = p.GetValue(targetObject); + return true; } } - return false; } + return false; + } - protected static void WriteReason(string path, string text, params object[] args) - { - RunspaceContext.CurrentThread.WriteReason(new ResultReason(RunspaceContext.CurrentThread.TargetObject.Path, Operand.FromPath(path), text, args)); - } + protected static void WriteReason(string path, string text, params object[] args) + { + RunspaceContext.CurrentThread.WriteReason(new ResultReason(RunspaceContext.CurrentThread.TargetObject.Path, Operand.FromPath(path), text, args)); + } - protected static bool TryReason(string path, string text, object[] args) - { - if (string.IsNullOrEmpty(text)) - return false; + protected static bool TryReason(string path, string text, object[] args) + { + if (string.IsNullOrEmpty(text)) + return false; - WriteReason(path, text, args); - return true; - } + WriteReason(path, text, args); + return true; + } - protected static bool IsRuleScope() - { - return RunspaceContext.CurrentThread.IsScope(RunspaceScope.Rule) || - RunspaceContext.CurrentThread.IsScope(RunspaceScope.Precondition); - } + protected static bool IsRuleScope() + { + return RunspaceContext.CurrentThread.IsScope(RunspaceScope.Rule) || + RunspaceContext.CurrentThread.IsScope(RunspaceScope.Precondition); + } - protected static bool IsConditionScope() - { - return RunspaceContext.CurrentThread.IsScope(RunspaceScope.Rule); - } + protected static bool IsConditionScope() + { + return RunspaceContext.CurrentThread.IsScope(RunspaceScope.Rule); + } - protected static RuleException RuleScopeException(string keyword) - { - return new RuleException(string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.KeywordRuleScope, keyword)); - } + protected static RuleException RuleScopeException(string keyword) + { + return new RuleException(string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.KeywordRuleScope, keyword)); + } - protected static RuleException ConditionScopeException(string keyword) - { - return new RuleException(string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.KeywordConditionScope, keyword)); - } + protected static RuleException ConditionScopeException(string keyword) + { + return new RuleException(string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.KeywordConditionScope, keyword)); } } diff --git a/src/PSRule/Commands/RuleLanguageNouns.cs b/src/PSRule/Commands/RuleLanguageNouns.cs index 35b5c50c22..10d7f90880 100644 --- a/src/PSRule/Commands/RuleLanguageNouns.cs +++ b/src/PSRule/Commands/RuleLanguageNouns.cs @@ -1,19 +1,18 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -namespace PSRule.Commands +namespace PSRule.Commands; + +internal static class RuleLanguageNouns { - internal static class RuleLanguageNouns - { - public const string Exists = "Exists"; - public const string Match = "Match"; - public const string Within = "Within"; - public const string TypeOf = "TypeOf"; - public const string AnyOf = "AnyOf"; - public const string AllOf = "AllOf"; - public const string RuleDefinition = "RuleDefinition"; - public const string Recommendation = "Recommendation"; - public const string Reason = "Reason"; - public const string Convention = "PSRuleConvention"; - } + public const string Exists = "Exists"; + public const string Match = "Match"; + public const string Within = "Within"; + public const string TypeOf = "TypeOf"; + public const string AnyOf = "AnyOf"; + public const string AllOf = "AllOf"; + public const string RuleDefinition = "RuleDefinition"; + public const string Recommendation = "Recommendation"; + public const string Reason = "Reason"; + public const string Convention = "PSRuleConvention"; } diff --git a/src/PSRule/Commands/WriteReasonCommand.cs b/src/PSRule/Commands/WriteReasonCommand.cs index 21d938e3b9..4e2979054f 100644 --- a/src/PSRule/Commands/WriteReasonCommand.cs +++ b/src/PSRule/Commands/WriteReasonCommand.cs @@ -3,27 +3,26 @@ using System.Management.Automation; -namespace PSRule.Commands +namespace PSRule.Commands; + +/// +/// The Reason keyword. +/// +[Cmdlet(VerbsCommunications.Write, RuleLanguageNouns.Reason)] +internal sealed class WriteReasonCommand : RuleKeyword { - /// - /// The Reason keyword. - /// - [Cmdlet(VerbsCommunications.Write, RuleLanguageNouns.Reason)] - internal sealed class WriteReasonCommand : RuleKeyword - { - [Parameter(Mandatory = false, Position = 0)] - public string Text { get; set; } + [Parameter(Mandatory = false, Position = 0)] + public string Text { get; set; } - protected override void ProcessRecord() - { - if (!IsConditionScope()) - throw ConditionScopeException(LanguageKeywords.Reason); + protected override void ProcessRecord() + { + if (!IsConditionScope()) + throw ConditionScopeException(LanguageKeywords.Reason); - if (MyInvocation.BoundParameters.ContainsKey(nameof(Text))) - WriteReason( - path: null, - text: Text - ); - } + if (MyInvocation.BoundParameters.ContainsKey(nameof(Text))) + WriteReason( + path: null, + text: Text + ); } } diff --git a/src/PSRule/Commands/WriteRecommendCommand.cs b/src/PSRule/Commands/WriteRecommendCommand.cs index 289acbf073..f3246fd1bc 100644 --- a/src/PSRule/Commands/WriteRecommendCommand.cs +++ b/src/PSRule/Commands/WriteRecommendCommand.cs @@ -3,27 +3,26 @@ using System.Management.Automation; -namespace PSRule.Commands +namespace PSRule.Commands; + +/// +/// The Recommend keyword. +/// +[Cmdlet(VerbsCommunications.Write, RuleLanguageNouns.Recommendation)] +internal sealed class WriteRecommendationCommand : RuleKeyword { - /// - /// The Recommend keyword. - /// - [Cmdlet(VerbsCommunications.Write, RuleLanguageNouns.Recommendation)] - internal sealed class WriteRecommendationCommand : RuleKeyword - { - [Parameter(Mandatory = false, Position = 0)] - [Alias(aliasNames: "Message")] - public string Text { get; set; } + [Parameter(Mandatory = false, Position = 0)] + [Alias(aliasNames: "Message")] + public string Text { get; set; } - protected override void ProcessRecord() - { - if (!IsConditionScope()) - throw ConditionScopeException(LanguageKeywords.Recommend); + protected override void ProcessRecord() + { + if (!IsConditionScope()) + throw ConditionScopeException(LanguageKeywords.Recommend); - var result = GetResult(); + var result = GetResult(); - if (MyInvocation.BoundParameters.ContainsKey(nameof(Text)) && string.IsNullOrEmpty(result.Info.Recommendation?.Text)) - result.Info.Recommendation.Text = Text; - } + if (MyInvocation.BoundParameters.ContainsKey(nameof(Text)) && string.IsNullOrEmpty(result.Info.Recommendation?.Text)) + result.Info.Recommendation.Text = Text; } } diff --git a/src/PSRule/Common/ArrayExtensions.cs b/src/PSRule/Common/ArrayExtensions.cs index f1c85277c7..803c6721b6 100644 --- a/src/PSRule/Common/ArrayExtensions.cs +++ b/src/PSRule/Common/ArrayExtensions.cs @@ -1,21 +1,20 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -namespace PSRule.Common +namespace PSRule.Common; + +/// +/// Extension methods for arrays. +/// +internal static class ArrayExtensions { - /// - /// Extension methods for arrays. - /// - internal static class ArrayExtensions + internal static object Last(this Array array) { - internal static object Last(this Array array) - { - return array.Length > 0 ? array.GetValue(array.Length - 1) : null; - } + return array.Length > 0 ? array.GetValue(array.Length - 1) : null; + } - internal static object First(this Array array) - { - return array.Length > 0 ? array.GetValue(0) : null; - } + internal static object First(this Array array) + { + return array.Length > 0 ? array.GetValue(0) : null; } } diff --git a/src/PSRule/Common/BaselineJsonSerializationMapper.cs b/src/PSRule/Common/BaselineJsonSerializationMapper.cs index fce90ff665..ba4bdc21d8 100644 --- a/src/PSRule/Common/BaselineJsonSerializationMapper.cs +++ b/src/PSRule/Common/BaselineJsonSerializationMapper.cs @@ -7,217 +7,216 @@ using PSRule.Definitions; using PSRule.Definitions.Baselines; -namespace PSRule +namespace PSRule; + +internal static class BaselineJsonSerializationMapper { - internal static class BaselineJsonSerializationMapper - { - private const string SYNOPSIS_COMMENT = "Synopsis: "; + private const string SYNOPSIS_COMMENT = "Synopsis: "; - internal static void MapBaseline(JsonWriter writer, JsonSerializer serializer, Baseline baseline) + internal static void MapBaseline(JsonWriter writer, JsonSerializer serializer, Baseline baseline) + { + writer.WriteStartObject(); + writer.WriteComment(string.Concat(SYNOPSIS_COMMENT, baseline.Synopsis)); + if (baseline != null) { - writer.WriteStartObject(); - writer.WriteComment(string.Concat(SYNOPSIS_COMMENT, baseline.Synopsis)); - if (baseline != null) - { - MapProperty(writer, nameof(baseline.ApiVersion), baseline.ApiVersion); - MapProperty(writer, nameof(baseline.Kind), baseline.Kind); - MapResourceMetadata(writer, serializer, nameof(baseline.Metadata), baseline.Metadata); - MapBaselineSpec(writer, serializer, nameof(baseline.Spec), baseline.Spec); - } - writer.WriteEndObject(); + MapProperty(writer, nameof(baseline.ApiVersion), baseline.ApiVersion); + MapProperty(writer, nameof(baseline.Kind), baseline.Kind); + MapResourceMetadata(writer, serializer, nameof(baseline.Metadata), baseline.Metadata); + MapBaselineSpec(writer, serializer, nameof(baseline.Spec), baseline.Spec); } + writer.WriteEndObject(); + } - private static void MapResourceMetadata(JsonWriter writer, JsonSerializer serializer, string propertyName, ResourceMetadata resourceMetadata) - { - if (resourceMetadata == null) - return; - - MapPropertyName(writer, propertyName); - writer.WriteStartObject(); - MapProperty(writer, serializer, nameof(resourceMetadata.Annotations), resourceMetadata.Annotations); - MapProperty(writer, nameof(resourceMetadata.Name), resourceMetadata.Name); - MapProperty(writer, serializer, nameof(resourceMetadata.Tags), resourceMetadata.Tags); - writer.WriteEndObject(); - } + private static void MapResourceMetadata(JsonWriter writer, JsonSerializer serializer, string propertyName, ResourceMetadata resourceMetadata) + { + if (resourceMetadata == null) + return; + + MapPropertyName(writer, propertyName); + writer.WriteStartObject(); + MapProperty(writer, serializer, nameof(resourceMetadata.Annotations), resourceMetadata.Annotations); + MapProperty(writer, nameof(resourceMetadata.Name), resourceMetadata.Name); + MapProperty(writer, serializer, nameof(resourceMetadata.Tags), resourceMetadata.Tags); + writer.WriteEndObject(); + } - private static void MapBaselineSpec(JsonWriter writer, JsonSerializer serializer, string propertyName, BaselineSpec baselineSpec) - { - if (baselineSpec == null) - return; - - MapPropertyName(writer, propertyName); - writer.WriteStartObject(); - MapProperty(writer, serializer, nameof(baselineSpec.Binding), baselineSpec.Binding); - MapProperty(writer, serializer, nameof(baselineSpec.Configuration), baselineSpec.Configuration); - MapProperty(writer, nameof(baselineSpec.Convention), baselineSpec.Convention); - MapProperty(writer, serializer, nameof(baselineSpec.Rule), baselineSpec.Rule); - writer.WriteEndObject(); - } + private static void MapBaselineSpec(JsonWriter writer, JsonSerializer serializer, string propertyName, BaselineSpec baselineSpec) + { + if (baselineSpec == null) + return; + + MapPropertyName(writer, propertyName); + writer.WriteStartObject(); + MapProperty(writer, serializer, nameof(baselineSpec.Binding), baselineSpec.Binding); + MapProperty(writer, serializer, nameof(baselineSpec.Configuration), baselineSpec.Configuration); + MapProperty(writer, nameof(baselineSpec.Convention), baselineSpec.Convention); + MapProperty(writer, serializer, nameof(baselineSpec.Rule), baselineSpec.Rule); + writer.WriteEndObject(); + } - private static void MapPropertyName(JsonWriter writer, string propertyName) - { - writer.WritePropertyName(propertyName.ToCamelCase()); - } + private static void MapPropertyName(JsonWriter writer, string propertyName) + { + writer.WritePropertyName(propertyName.ToCamelCase()); + } - /// - /// Map a dictionary property. - /// - private static void MapProperty(JsonWriter writer, JsonSerializer serializer, string propertyName, IDictionary value) - { - if (value.NullOrEmpty()) - return; + /// + /// Map a dictionary property. + /// + private static void MapProperty(JsonWriter writer, JsonSerializer serializer, string propertyName, IDictionary value) + { + if (value.NullOrEmpty()) + return; - MapPropertyName(writer, propertyName); - MapDictionary(writer, serializer, value); - } + MapPropertyName(writer, propertyName); + MapDictionary(writer, serializer, value); + } - /// - /// Map a nullable boolean property. - /// - private static void MapProperty(JsonWriter writer, JsonSerializer serializer, string propertyName, bool? value) - { - if (!value.HasValue) - return; + /// + /// Map a nullable boolean property. + /// + private static void MapProperty(JsonWriter writer, JsonSerializer serializer, string propertyName, bool? value) + { + if (!value.HasValue) + return; - MapPropertyName(writer, propertyName); - serializer.Serialize(writer, value); - } + MapPropertyName(writer, propertyName); + serializer.Serialize(writer, value); + } - /// - /// Map an enum property. - /// - private static void MapProperty(JsonWriter writer, string propertyName, T value) where T : Enum - { - if (value == null) - return; + /// + /// Map an enum property. + /// + private static void MapProperty(JsonWriter writer, string propertyName, T value) where T : Enum + { + if (value == null) + return; - MapPropertyName(writer, propertyName); - writer.WriteValue(Enum.GetName(typeof(T), value)); - } + MapPropertyName(writer, propertyName); + writer.WriteValue(Enum.GetName(typeof(T), value)); + } - /// - /// Map a string array property. - /// - private static void MapProperty(JsonWriter writer, string propertyName, string[] value) - { - if (value == null) - return; + /// + /// Map a string array property. + /// + private static void MapProperty(JsonWriter writer, string propertyName, string[] value) + { + if (value == null) + return; - MapPropertyName(writer, propertyName); - MapStringArraySequence(writer, value); - } + MapPropertyName(writer, propertyName); + MapStringArraySequence(writer, value); + } - /// - /// Map a string property. - /// - private static void MapProperty(JsonWriter writer, string propertyName, string value) - { - if (value == null) - return; + /// + /// Map a string property. + /// + private static void MapProperty(JsonWriter writer, string propertyName, string value) + { + if (value == null) + return; - MapPropertyName(writer, propertyName); - writer.WriteValue(value); - } + MapPropertyName(writer, propertyName); + writer.WriteValue(value); + } - /// - /// Map a BindingOption property. - /// - private static void MapProperty(JsonWriter writer, JsonSerializer serializer, string propertyName, BindingOption value) - { - if (value == null) - return; - - MapPropertyName(writer, propertyName); - writer.WriteStartObject(); - MapProperty(writer, serializer, nameof(value.Field), value.Field?.GetFieldMap); - MapProperty(writer, serializer, nameof(value.IgnoreCase), value.IgnoreCase); - MapProperty(writer, nameof(value.NameSeparator), value.NameSeparator); - MapProperty(writer, serializer, nameof(value.PreferTargetInfo), value.PreferTargetInfo); - MapProperty(writer, nameof(value.TargetName), value.TargetName); - MapProperty(writer, nameof(value.TargetType), value.TargetType); - MapProperty(writer, serializer, nameof(value.UseQualifiedName), value.UseQualifiedName); - writer.WriteEndObject(); - } + /// + /// Map a BindingOption property. + /// + private static void MapProperty(JsonWriter writer, JsonSerializer serializer, string propertyName, BindingOption value) + { + if (value == null) + return; + + MapPropertyName(writer, propertyName); + writer.WriteStartObject(); + MapProperty(writer, serializer, nameof(value.Field), value.Field?.GetFieldMap); + MapProperty(writer, serializer, nameof(value.IgnoreCase), value.IgnoreCase); + MapProperty(writer, nameof(value.NameSeparator), value.NameSeparator); + MapProperty(writer, serializer, nameof(value.PreferTargetInfo), value.PreferTargetInfo); + MapProperty(writer, nameof(value.TargetName), value.TargetName); + MapProperty(writer, nameof(value.TargetType), value.TargetType); + MapProperty(writer, serializer, nameof(value.UseQualifiedName), value.UseQualifiedName); + writer.WriteEndObject(); + } - /// - /// Map a ConventionOption property. - /// - private static void MapProperty(JsonWriter writer, string propertyName, ConventionOption value) - { - if (value == null) - return; + /// + /// Map a ConventionOption property. + /// + private static void MapProperty(JsonWriter writer, string propertyName, ConventionOption value) + { + if (value == null) + return; - MapPropertyName(writer, propertyName); - writer.WriteStartObject(); - MapProperty(writer, nameof(value.Include), value.Include); - writer.WriteEndObject(); - } + MapPropertyName(writer, propertyName); + writer.WriteStartObject(); + MapProperty(writer, nameof(value.Include), value.Include); + writer.WriteEndObject(); + } - /// - /// Map a RuleOption property. - /// - private static void MapProperty(JsonWriter writer, JsonSerializer serializer, string propertyName, RuleOption value) - { - if (value == null) - return; - - MapPropertyName(writer, propertyName); - writer.WriteStartObject(); - MapProperty(writer, nameof(value.Exclude), value.Exclude); - MapProperty(writer, nameof(value.Include), value.Include); - MapProperty(writer, serializer, nameof(value.IncludeLocal), value.IncludeLocal); - MapProperty(writer, serializer, nameof(value.Tag), value.Tag?.ToDictionary()); - writer.WriteEndObject(); - } + /// + /// Map a RuleOption property. + /// + private static void MapProperty(JsonWriter writer, JsonSerializer serializer, string propertyName, RuleOption value) + { + if (value == null) + return; + + MapPropertyName(writer, propertyName); + writer.WriteStartObject(); + MapProperty(writer, nameof(value.Exclude), value.Exclude); + MapProperty(writer, nameof(value.Include), value.Include); + MapProperty(writer, serializer, nameof(value.IncludeLocal), value.IncludeLocal); + MapProperty(writer, serializer, nameof(value.Tag), value.Tag?.ToDictionary()); + writer.WriteEndObject(); + } - private static void MapDictionary(JsonWriter writer, JsonSerializer serializer, IDictionary dictionary) + private static void MapDictionary(JsonWriter writer, JsonSerializer serializer, IDictionary dictionary) + { + writer.WriteStartObject(); + foreach (var kvp in dictionary.ToSortedDictionary()) { - writer.WriteStartObject(); - foreach (var kvp in dictionary.ToSortedDictionary()) - { - MapPropertyName(writer, kvp.Key); - if (kvp.Value is string stringValue) - writer.WriteValue(stringValue); - else if (kvp.Value is string[] stringValues) - MapStringArraySequence(writer, stringValues); - else if (kvp.Value is PSObject[] psObjects) - MapPSObjectArraySequence(writer, serializer, psObjects); - else - serializer.Serialize(writer, kvp.Value); - } - writer.WriteEndObject(); + MapPropertyName(writer, kvp.Key); + if (kvp.Value is string stringValue) + writer.WriteValue(stringValue); + else if (kvp.Value is string[] stringValues) + MapStringArraySequence(writer, stringValues); + else if (kvp.Value is PSObject[] psObjects) + MapPSObjectArraySequence(writer, serializer, psObjects); + else + serializer.Serialize(writer, kvp.Value); } + writer.WriteEndObject(); + } - private static void MapStringArraySequence(JsonWriter writer, string[] sequence) - { - writer.WriteStartArray(); - var sortedSequence = sequence.OrderBy(item => item); - foreach (var item in sortedSequence) - writer.WriteValue(item); + private static void MapStringArraySequence(JsonWriter writer, string[] sequence) + { + writer.WriteStartArray(); + var sortedSequence = sequence.OrderBy(item => item); + foreach (var item in sortedSequence) + writer.WriteValue(item); - writer.WriteEndArray(); - } + writer.WriteEndArray(); + } - private static void MapPSObjectArraySequence(JsonWriter writer, JsonSerializer serializer, PSObject[] sequence) + private static void MapPSObjectArraySequence(JsonWriter writer, JsonSerializer serializer, PSObject[] sequence) + { + writer.WriteStartArray(); + foreach (var obj in sequence) { - writer.WriteStartArray(); - foreach (var obj in sequence) + if (obj.BaseObject == null || obj.HasNoteProperty()) { - if (obj.BaseObject == null || obj.HasNoteProperty()) + writer.WriteStartObject(); + var sortedProperties = obj.Properties.OrderBy(prop => prop.Name); + foreach (var propertyInfo in sortedProperties) { - writer.WriteStartObject(); - var sortedProperties = obj.Properties.OrderBy(prop => prop.Name); - foreach (var propertyInfo in sortedProperties) - { - MapPropertyName(writer, propertyInfo.Name); - serializer.Serialize(writer, propertyInfo.Value); - } - writer.WriteEndObject(); + MapPropertyName(writer, propertyInfo.Name); + serializer.Serialize(writer, propertyInfo.Value); } - else - serializer.Serialize(writer, obj.BaseObject); + writer.WriteEndObject(); } - writer.WriteEndArray(); + else + serializer.Serialize(writer, obj.BaseObject); } + writer.WriteEndArray(); } } diff --git a/src/PSRule/Common/BaselineYamlSerializationMapper.cs b/src/PSRule/Common/BaselineYamlSerializationMapper.cs index ef2cb5f8af..aa23557d4e 100644 --- a/src/PSRule/Common/BaselineYamlSerializationMapper.cs +++ b/src/PSRule/Common/BaselineYamlSerializationMapper.cs @@ -8,219 +8,218 @@ using YamlDotNet.Core; using YamlDotNet.Core.Events; -namespace PSRule +namespace PSRule; + +internal static class BaselineYamlSerializationMapper { - internal static class BaselineYamlSerializationMapper + private const string SYNOPSIS_COMMENT = "Synopsis: "; + + internal static void MapBaseline(IEmitter emitter, Baseline baseline) { - private const string SYNOPSIS_COMMENT = "Synopsis: "; + emitter.Emit(new MappingStart()); + emitter.Emit(new Comment(string.Concat(SYNOPSIS_COMMENT, baseline.Synopsis), isInline: false)); - internal static void MapBaseline(IEmitter emitter, Baseline baseline) + if (baseline != null) { - emitter.Emit(new MappingStart()); - emitter.Emit(new Comment(string.Concat(SYNOPSIS_COMMENT, baseline.Synopsis), isInline: false)); - - if (baseline != null) - { - MapProperty(emitter, nameof(baseline.ApiVersion), baseline.ApiVersion); - MapProperty(emitter, nameof(baseline.Kind), baseline.Kind); - MapResourceMetadata(emitter, nameof(baseline.Metadata), baseline.Metadata); - MapBaselineSpec(emitter, nameof(baseline.Spec), baseline.Spec); - } - - emitter.Emit(new MappingEnd()); + MapProperty(emitter, nameof(baseline.ApiVersion), baseline.ApiVersion); + MapProperty(emitter, nameof(baseline.Kind), baseline.Kind); + MapResourceMetadata(emitter, nameof(baseline.Metadata), baseline.Metadata); + MapBaselineSpec(emitter, nameof(baseline.Spec), baseline.Spec); } - private static void MapResourceMetadata(IEmitter emitter, string propertyName, ResourceMetadata resourceMetadata) - { - if (resourceMetadata == null) - return; - - MapPropertyName(emitter, propertyName); - emitter.Emit(new MappingStart()); - MapProperty(emitter, nameof(resourceMetadata.Annotations), resourceMetadata.Annotations); - MapProperty(emitter, nameof(resourceMetadata.Name), resourceMetadata.Name); - MapProperty(emitter, nameof(resourceMetadata.Tags), resourceMetadata.Tags); - emitter.Emit(new MappingEnd()); - } + emitter.Emit(new MappingEnd()); + } - private static void MapBaselineSpec(IEmitter emitter, string propertyName, BaselineSpec baselineSpec) - { - if (baselineSpec == null) - return; - - MapPropertyName(emitter, propertyName); - emitter.Emit(new MappingStart()); - MapProperty(emitter, nameof(baselineSpec.Binding), baselineSpec.Binding); - MapProperty(emitter, nameof(baselineSpec.Configuration), baselineSpec.Configuration); - MapProperty(emitter, nameof(baselineSpec.Convention), baselineSpec.Convention); - MapProperty(emitter, nameof(baselineSpec.Rule), baselineSpec.Rule); - emitter.Emit(new MappingEnd()); - } + private static void MapResourceMetadata(IEmitter emitter, string propertyName, ResourceMetadata resourceMetadata) + { + if (resourceMetadata == null) + return; + + MapPropertyName(emitter, propertyName); + emitter.Emit(new MappingStart()); + MapProperty(emitter, nameof(resourceMetadata.Annotations), resourceMetadata.Annotations); + MapProperty(emitter, nameof(resourceMetadata.Name), resourceMetadata.Name); + MapProperty(emitter, nameof(resourceMetadata.Tags), resourceMetadata.Tags); + emitter.Emit(new MappingEnd()); + } - private static void MapPropertyName(IEmitter emitter, string propertyName) - { - emitter.Emit(new Scalar(propertyName.ToCamelCase())); - } + private static void MapBaselineSpec(IEmitter emitter, string propertyName, BaselineSpec baselineSpec) + { + if (baselineSpec == null) + return; + + MapPropertyName(emitter, propertyName); + emitter.Emit(new MappingStart()); + MapProperty(emitter, nameof(baselineSpec.Binding), baselineSpec.Binding); + MapProperty(emitter, nameof(baselineSpec.Configuration), baselineSpec.Configuration); + MapProperty(emitter, nameof(baselineSpec.Convention), baselineSpec.Convention); + MapProperty(emitter, nameof(baselineSpec.Rule), baselineSpec.Rule); + emitter.Emit(new MappingEnd()); + } - /// - /// Map a dictionary property. - /// - private static void MapProperty(IEmitter emitter, string propertyName, IDictionary value) - { - if (value.NullOrEmpty()) - return; + private static void MapPropertyName(IEmitter emitter, string propertyName) + { + emitter.Emit(new Scalar(propertyName.ToCamelCase())); + } - MapPropertyName(emitter, propertyName); - MapDictionary(emitter, value); - } + /// + /// Map a dictionary property. + /// + private static void MapProperty(IEmitter emitter, string propertyName, IDictionary value) + { + if (value.NullOrEmpty()) + return; - /// - /// Map a nullable boolean property. - /// - private static void MapProperty(IEmitter emitter, string propertyName, bool? value) - { - if (!value.HasValue) - return; + MapPropertyName(emitter, propertyName); + MapDictionary(emitter, value); + } - MapPropertyName(emitter, propertyName); - emitter.Emit(new Scalar(value.ToString())); - } + /// + /// Map a nullable boolean property. + /// + private static void MapProperty(IEmitter emitter, string propertyName, bool? value) + { + if (!value.HasValue) + return; - /// - /// Map an enum property. - /// - private static void MapProperty(IEmitter emitter, string propertyName, T value) where T : Enum - { - if (value == null) - return; + MapPropertyName(emitter, propertyName); + emitter.Emit(new Scalar(value.ToString())); + } - MapPropertyName(emitter, propertyName); - emitter.Emit(new Scalar(Enum.GetName(typeof(T), value))); - } + /// + /// Map an enum property. + /// + private static void MapProperty(IEmitter emitter, string propertyName, T value) where T : Enum + { + if (value == null) + return; - /// - /// Map a string array property. - /// - private static void MapProperty(IEmitter emitter, string propertyName, string[] value) - { - if (value == null) - return; + MapPropertyName(emitter, propertyName); + emitter.Emit(new Scalar(Enum.GetName(typeof(T), value))); + } - MapPropertyName(emitter, propertyName); - MapStringArraySequence(emitter, value); - } + /// + /// Map a string array property. + /// + private static void MapProperty(IEmitter emitter, string propertyName, string[] value) + { + if (value == null) + return; - /// - /// Map a string property. - /// - private static void MapProperty(IEmitter emitter, string propertyName, string value) - { - if (value == null) - return; + MapPropertyName(emitter, propertyName); + MapStringArraySequence(emitter, value); + } - MapPropertyName(emitter, propertyName); - emitter.Emit(new Scalar(value)); - } + /// + /// Map a string property. + /// + private static void MapProperty(IEmitter emitter, string propertyName, string value) + { + if (value == null) + return; - /// - /// Map a BindingOption property. - /// - private static void MapProperty(IEmitter emitter, string propertyName, BindingOption value) - { - if (value == null) - return; - - MapPropertyName(emitter, propertyName); - emitter.Emit(new MappingStart()); - MapProperty(emitter, nameof(value.Field), value.Field?.GetFieldMap); - MapProperty(emitter, nameof(value.IgnoreCase), value.IgnoreCase); - MapProperty(emitter, nameof(value.NameSeparator), value.NameSeparator); - MapProperty(emitter, nameof(value.PreferTargetInfo), value.PreferTargetInfo); - MapProperty(emitter, nameof(value.TargetName), value.TargetName); - MapProperty(emitter, nameof(value.TargetType), value.TargetType); - MapProperty(emitter, nameof(value.UseQualifiedName), value.UseQualifiedName); - emitter.Emit(new MappingEnd()); - } + MapPropertyName(emitter, propertyName); + emitter.Emit(new Scalar(value)); + } - /// - /// Map a ConventionOption property. - /// - private static void MapProperty(IEmitter emitter, string propertyName, ConventionOption value) - { - if (value == null) - return; + /// + /// Map a BindingOption property. + /// + private static void MapProperty(IEmitter emitter, string propertyName, BindingOption value) + { + if (value == null) + return; + + MapPropertyName(emitter, propertyName); + emitter.Emit(new MappingStart()); + MapProperty(emitter, nameof(value.Field), value.Field?.GetFieldMap); + MapProperty(emitter, nameof(value.IgnoreCase), value.IgnoreCase); + MapProperty(emitter, nameof(value.NameSeparator), value.NameSeparator); + MapProperty(emitter, nameof(value.PreferTargetInfo), value.PreferTargetInfo); + MapProperty(emitter, nameof(value.TargetName), value.TargetName); + MapProperty(emitter, nameof(value.TargetType), value.TargetType); + MapProperty(emitter, nameof(value.UseQualifiedName), value.UseQualifiedName); + emitter.Emit(new MappingEnd()); + } - MapPropertyName(emitter, propertyName); - emitter.Emit(new MappingStart()); - MapProperty(emitter, propertyName, value.Include); - emitter.Emit(new MappingEnd()); - } + /// + /// Map a ConventionOption property. + /// + private static void MapProperty(IEmitter emitter, string propertyName, ConventionOption value) + { + if (value == null) + return; - /// - /// Map a RuleOption property. - /// - private static void MapProperty(IEmitter emitter, string propertyName, RuleOption value) - { - if (value == null) - return; - - MapPropertyName(emitter, propertyName); - emitter.Emit(new MappingStart()); - MapProperty(emitter, nameof(value.Exclude), value.Exclude); - MapProperty(emitter, nameof(value.Include), value.Include); - MapProperty(emitter, nameof(value.IncludeLocal), value.IncludeLocal); - MapProperty(emitter, nameof(value.Tag), value.Tag?.ToDictionary()); - emitter.Emit(new MappingEnd()); - } + MapPropertyName(emitter, propertyName); + emitter.Emit(new MappingStart()); + MapProperty(emitter, propertyName, value.Include); + emitter.Emit(new MappingEnd()); + } + + /// + /// Map a RuleOption property. + /// + private static void MapProperty(IEmitter emitter, string propertyName, RuleOption value) + { + if (value == null) + return; + + MapPropertyName(emitter, propertyName); + emitter.Emit(new MappingStart()); + MapProperty(emitter, nameof(value.Exclude), value.Exclude); + MapProperty(emitter, nameof(value.Include), value.Include); + MapProperty(emitter, nameof(value.IncludeLocal), value.IncludeLocal); + MapProperty(emitter, nameof(value.Tag), value.Tag?.ToDictionary()); + emitter.Emit(new MappingEnd()); + } - private static void MapDictionary(IEmitter emitter, IDictionary dictionary) + private static void MapDictionary(IEmitter emitter, IDictionary dictionary) + { + emitter.Emit(new MappingStart()); + foreach (var kvp in dictionary.ToSortedDictionary()) { - emitter.Emit(new MappingStart()); - foreach (var kvp in dictionary.ToSortedDictionary()) - { - MapPropertyName(emitter, kvp.Key); - if (kvp.Value is string stringValue) - emitter.Emit(new Scalar(stringValue)); - else if (kvp.Value is string[] stringValues) - MapStringArraySequence(emitter, stringValues); - else if (kvp.Value is PSObject[] psObjects) - MapPSObjectArraySequence(emitter, psObjects); - else - emitter.Emit(new Scalar(kvp.Value.ToString())); - } - emitter.Emit(new MappingEnd()); + MapPropertyName(emitter, kvp.Key); + if (kvp.Value is string stringValue) + emitter.Emit(new Scalar(stringValue)); + else if (kvp.Value is string[] stringValues) + MapStringArraySequence(emitter, stringValues); + else if (kvp.Value is PSObject[] psObjects) + MapPSObjectArraySequence(emitter, psObjects); + else + emitter.Emit(new Scalar(kvp.Value.ToString())); } + emitter.Emit(new MappingEnd()); + } - private static void MapStringArraySequence(IEmitter emitter, string[] sequence) - { - emitter.Emit(new SequenceStart(anchor: null, tag: null, isImplicit: false, style: SequenceStyle.Block)); - var sortedSequence = sequence.OrderBy(item => item); - foreach (var item in sortedSequence) - emitter.Emit(new Scalar(item)); + private static void MapStringArraySequence(IEmitter emitter, string[] sequence) + { + emitter.Emit(new SequenceStart(anchor: null, tag: null, isImplicit: false, style: SequenceStyle.Block)); + var sortedSequence = sequence.OrderBy(item => item); + foreach (var item in sortedSequence) + emitter.Emit(new Scalar(item)); - emitter.Emit(new SequenceEnd()); - } + emitter.Emit(new SequenceEnd()); + } - private static void MapPSObjectArraySequence(IEmitter emitter, PSObject[] sequence) + private static void MapPSObjectArraySequence(IEmitter emitter, PSObject[] sequence) + { + emitter.Emit(new SequenceStart(anchor: null, tag: null, isImplicit: false, style: SequenceStyle.Block)); + foreach (var obj in sequence) { - emitter.Emit(new SequenceStart(anchor: null, tag: null, isImplicit: false, style: SequenceStyle.Block)); - foreach (var obj in sequence) + if (obj.BaseObject == null || obj.HasNoteProperty()) { - if (obj.BaseObject == null || obj.HasNoteProperty()) + emitter.Emit(new MappingStart()); + var sortedProperties = obj.Properties.OrderBy(prop => prop.Name); + foreach (var propertyInfo in sortedProperties) { - emitter.Emit(new MappingStart()); - var sortedProperties = obj.Properties.OrderBy(prop => prop.Name); - foreach (var propertyInfo in sortedProperties) - { - MapPropertyName(emitter, propertyInfo.Name); - emitter.Emit(new Scalar(propertyInfo.Value.ToString())); - } - emitter.Emit(new MappingEnd()); + MapPropertyName(emitter, propertyInfo.Name); + emitter.Emit(new Scalar(propertyInfo.Value.ToString())); } - else - emitter.Emit(new Scalar(obj.BaseObject.ToString())); + emitter.Emit(new MappingEnd()); } - emitter.Emit(new SequenceEnd()); + else + emitter.Emit(new Scalar(obj.BaseObject.ToString())); } + emitter.Emit(new SequenceEnd()); } } diff --git a/src/PSRule/Common/ConditionResultExtensions.cs b/src/PSRule/Common/ConditionResultExtensions.cs index 5980c6dff5..4a9245f132 100644 --- a/src/PSRule/Common/ConditionResultExtensions.cs +++ b/src/PSRule/Common/ConditionResultExtensions.cs @@ -3,23 +3,22 @@ using PSRule.Definitions; -namespace PSRule +namespace PSRule; + +internal static class ConditionResultExtensions { - internal static class ConditionResultExtensions + public static bool AllOf(this IConditionResult result) { - public static bool AllOf(this IConditionResult result) - { - return result != null && result.Count > 0 && result.Pass == result.Count; - } + return result != null && result.Count > 0 && result.Pass == result.Count; + } - public static bool AnyOf(this IConditionResult result) - { - return result != null && result.Pass > 0; - } + public static bool AnyOf(this IConditionResult result) + { + return result != null && result.Pass > 0; + } - public static bool Skipped(this IConditionResult result) - { - return result == null; - } + public static bool Skipped(this IConditionResult result) + { + return result == null; } } diff --git a/src/PSRule/Common/Engine.cs b/src/PSRule/Common/Engine.cs index d677432be6..111388ba92 100644 --- a/src/PSRule/Common/Engine.cs +++ b/src/PSRule/Common/Engine.cs @@ -1,13 +1,12 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -namespace PSRule +namespace PSRule; + +internal static partial class Engine { - internal static partial class Engine + internal static string GetVersion() { - internal static string GetVersion() - { - return _Version; - } + return _Version; } } diff --git a/src/PSRule/Common/ExpressionContextExtensions.cs b/src/PSRule/Common/ExpressionContextExtensions.cs index bb1f692922..95c008fbe2 100644 --- a/src/PSRule/Common/ExpressionContextExtensions.cs +++ b/src/PSRule/Common/ExpressionContextExtensions.cs @@ -4,22 +4,21 @@ using PSRule.Definitions.Expressions; using PSRule.Resources; -namespace PSRule +namespace PSRule; + +internal static class ExpressionContextExtensions { - internal static class ExpressionContextExtensions + public static bool ExpressionTrace(this ExpressionContext context, string name, object operand, object value) { - public static bool ExpressionTrace(this ExpressionContext context, string name, object operand, object value) - { - var type = context.Kind == Definitions.ResourceKind.Rule ? 'R' : 'S'; - context.Debug(PSRuleResources.LanguageExpressionTraceP3, type, name, operand, value); - return true; - } + var type = context.Kind == Definitions.ResourceKind.Rule ? 'R' : 'S'; + context.Debug(PSRuleResources.LanguageExpressionTraceP3, type, name, operand, value); + return true; + } - public static bool ExpressionTrace(this ExpressionContext context, string name, object value) - { - var type = context.Kind == Definitions.ResourceKind.Rule ? 'R' : 'S'; - context.Debug(PSRuleResources.LanguageExpressionTraceP2, type, name, value); - return true; - } + public static bool ExpressionTrace(this ExpressionContext context, string name, object value) + { + var type = context.Kind == Definitions.ResourceKind.Rule ? 'R' : 'S'; + context.Debug(PSRuleResources.LanguageExpressionTraceP2, type, name, value); + return true; } } diff --git a/src/PSRule/Common/ExpressionHelpers.cs b/src/PSRule/Common/ExpressionHelpers.cs index 97f2445593..226b94da02 100644 --- a/src/PSRule/Common/ExpressionHelpers.cs +++ b/src/PSRule/Common/ExpressionHelpers.cs @@ -11,583 +11,582 @@ using PSRule.Pipeline; using PSRule.Runtime; -namespace PSRule +namespace PSRule; + +internal static class ExpressionHelpers { - internal static class ExpressionHelpers - { - private const string CACHE_MATCH = "MatchRegex"; - private const string CACHE_MATCH_C = "MatchRegexCaseSensitive"; + private const string CACHE_MATCH = "MatchRegex"; + private const string CACHE_MATCH_C = "MatchRegexCaseSensitive"; - private const char Backslash = '\\'; - private const char Slash = '/'; + private const char Backslash = '\\'; + private const char Slash = '/'; - internal static bool NullOrEmpty(object o) - { - if (o == null) - return true; + internal static bool NullOrEmpty(object o) + { + if (o == null) + return true; - o = GetBaseObject(o); - return (o is ICollection c && c.Count == 0) || - (TryString(o, out var s) && string.IsNullOrEmpty(s)); - } + o = GetBaseObject(o); + return (o is ICollection c && c.Count == 0) || + (TryString(o, out var s) && string.IsNullOrEmpty(s)); + } - internal static bool Exists(IBindingContext bindingContext, object inputObject, string field, bool caseSensitive) - { - return ObjectHelper.GetPath(bindingContext, inputObject, field, caseSensitive, out object _); - } + internal static bool Exists(IBindingContext bindingContext, object inputObject, string field, bool caseSensitive) + { + return ObjectHelper.GetPath(bindingContext, inputObject, field, caseSensitive, out object _); + } - internal static bool Equal(object expectedValue, object actualValue, bool caseSensitive, bool convertExpected = false, bool convertActual = false) - { - if (expectedValue == null && actualValue == null) - return true; + internal static bool Equal(object expectedValue, object actualValue, bool caseSensitive, bool convertExpected = false, bool convertActual = false) + { + if (expectedValue == null && actualValue == null) + return true; - if (expectedValue == null || actualValue == null) - return false; + if (expectedValue == null || actualValue == null) + return false; - if (TryString(expectedValue, out var s1) && TryString(actualValue, convertActual, out var s2)) - return StringEqual(s1, s2, caseSensitive); + if (TryString(expectedValue, out var s1) && TryString(actualValue, convertActual, out var s2)) + return StringEqual(s1, s2, caseSensitive); - if (TryBool(expectedValue, convertExpected, out var b1) && TryBool(actualValue, convertActual, out var b2)) - return b1 == b2; + if (TryBool(expectedValue, convertExpected, out var b1) && TryBool(actualValue, convertActual, out var b2)) + return b1 == b2; - if (TryLong(expectedValue, convertExpected, out var l1) && TryLong(actualValue, convertActual, out var l2)) - return l1 == l2; + if (TryLong(expectedValue, convertExpected, out var l1) && TryLong(actualValue, convertActual, out var l2)) + return l1 == l2; - if (TryInt(expectedValue, convertExpected, out var i1) && TryInt(actualValue, convertActual, out var i2)) - return i1 == i2; + if (TryInt(expectedValue, convertExpected, out var i1) && TryInt(actualValue, convertActual, out var i2)) + return i1 == i2; - if (TryArray(expectedValue, out var a1) && TryArray(actualValue, out var a2)) - return SequenceEqual(a1, a2); + if (TryArray(expectedValue, out var a1) && TryArray(actualValue, out var a2)) + return SequenceEqual(a1, a2); - var expectedBase = GetBaseObject(expectedValue); - var actualBase = GetBaseObject(actualValue); - if (expectedBase == null || actualBase == null) - return expectedBase == null && actualBase == null; + var expectedBase = GetBaseObject(expectedValue); + var actualBase = GetBaseObject(actualValue); + if (expectedBase == null || actualBase == null) + return expectedBase == null && actualBase == null; - return expectedBase.Equals(actualBase) || expectedValue.Equals(actualValue); - } + return expectedBase.Equals(actualBase) || expectedValue.Equals(actualValue); + } - internal static bool SequenceEqual(Array array1, Array array2) + internal static bool SequenceEqual(Array array1, Array array2) + { + if (array1.Length != array2.Length) + return false; + + for (var i = 0; i < array1.Length; i++) { - if (array1.Length != array2.Length) + if (!Equal(array1.GetValue(i), array2.GetValue(i))) return false; - - for (var i = 0; i < array1.Length; i++) - { - if (!Equal(array1.GetValue(i), array2.GetValue(i))) - return false; - } - return true; } + return true; + } - internal static bool Equal(object o1, object o2) - { - // One null - if (o1 == null || o2 == null) - return o1 == o2; + internal static bool Equal(object o1, object o2) + { + // One null + if (o1 == null || o2 == null) + return o1 == o2; + + // Arrays + if (o1 is Array array1 && o2 is Array array2) + return SequenceEqual(array1, array2); + else if (o1 is Array || o2 is Array) + return false; - // Arrays - if (o1 is Array array1 && o2 is Array array2) - return SequenceEqual(array1, array2); - else if (o1 is Array || o2 is Array) - return false; + // String and int + if (TryString(o1, out var s1) && TryString(o2, out var s2)) + return s1 == s2; + else if (TryString(o1, out _) || TryString(o2, out _)) + return false; + else if (TryLong(o1, false, out var i1) && TryLong(o2, false, out var i2)) + return i1 == i2; + else if (TryLong(o1, false, out var _) || TryLong(o2, false, out var _)) + return false; - // String and int - if (TryString(o1, out var s1) && TryString(o2, out var s2)) - return s1 == s2; - else if (TryString(o1, out _) || TryString(o2, out _)) - return false; - else if (TryLong(o1, false, out var i1) && TryLong(o2, false, out var i2)) - return i1 == i2; - else if (TryLong(o1, false, out var _) || TryLong(o2, false, out var _)) - return false; + // JTokens + if (o1 is JToken t1 && o2 is JToken t2) + return JTokenEquals(t1, t2); - // JTokens - if (o1 is JToken t1 && o2 is JToken t2) - return JTokenEquals(t1, t2); + // Objects + return ObjectEquals(o1, o2); + } - // Objects - return ObjectEquals(o1, o2); - } + private static bool JTokenEquals(JToken t1, JToken t2) + { + return JToken.DeepEquals(t1, t2); + } - private static bool JTokenEquals(JToken t1, JToken t2) - { - return JToken.DeepEquals(t1, t2); - } + internal static bool ObjectEquals(object o1, object o2) + { + var objectType = o1.GetType(); + if (objectType != o2.GetType()) + return false; - internal static bool ObjectEquals(object o1, object o2) + var props = objectType.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.GetProperty); + for (var i = 0; i < props.Length; i++) { - var objectType = o1.GetType(); - if (objectType != o2.GetType()) + if (!object.Equals(props[i].GetValue(o1), props[i].GetValue(o2))) return false; - - var props = objectType.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.GetProperty); - for (var i = 0; i < props.Length; i++) - { - if (!object.Equals(props[i].GetValue(o1), props[i].GetValue(o2))) - return false; - } - return true; } + return true; + } - internal static int Compare(object left, object right) - { - if (TryString(left, out var stringLeft) && TryString(right, out var stringRight)) - return StringComparer.Ordinal.Compare(stringLeft, stringRight); - else if (CompareNumeric(left, right, convert: false, out var compare, out _)) - return compare; + internal static int Compare(object left, object right) + { + if (TryString(left, out var stringLeft) && TryString(right, out var stringRight)) + return StringComparer.Ordinal.Compare(stringLeft, stringRight); + else if (CompareNumeric(left, right, convert: false, out var compare, out _)) + return compare; - return Comparer.Default.Compare(left, right); - } + return Comparer.Default.Compare(left, right); + } - internal static bool CompareNumeric(object actual, object expected, bool convert, out int compare, out object value) + internal static bool CompareNumeric(object actual, object expected, bool convert, out int compare, out object value) + { + if (TryInt(actual, convert, out var actualInt) && TryInt(expected, convert: true, value: out var expectedInt)) { - if (TryInt(actual, convert, out var actualInt) && TryInt(expected, convert: true, value: out var expectedInt)) - { - compare = Comparer.Default.Compare(actualInt, expectedInt); - value = actualInt; - return true; - } - else if (TryLong(actual, convert, out var actualLong) && TryLong(expected, convert: true, value: out var expectedLong)) - { - compare = Comparer.Default.Compare(actualLong, expectedLong); - value = actualLong; - return true; - } - else if (TryFloat(actual, convert, out var actualFloat) && TryFloat(expected, convert: true, value: out var expectedFloat)) - { - compare = Comparer.Default.Compare(actualFloat, expectedFloat); - value = actualFloat; - return true; - } - else if (TryDateTime(actual, convert, out var actualDateTime) && TryDateTime(expected, convert: true, value: out var expectedDateTime)) - { - compare = Comparer.Default.Compare(actualDateTime, expectedDateTime); - value = actualDateTime; - return true; - } - else if ((TryStringLength(actual, out actualInt) || - TryEnumerableLength(actual, out actualInt)) && - TryInt(expected, convert: true, value: out expectedInt)) - { - compare = Comparer.Default.Compare(actualInt, expectedInt); - value = actualInt; - return true; - } - compare = 0; - value = 0; - return false; + compare = Comparer.Default.Compare(actualInt, expectedInt); + value = actualInt; + return true; } - - internal static bool TryString(object o, out string value) + else if (TryLong(actual, convert, out var actualLong) && TryLong(expected, convert: true, value: out var expectedLong)) { - return TypeConverter.TryString(GetBaseObject(o), out value); + compare = Comparer.Default.Compare(actualLong, expectedLong); + value = actualLong; + return true; } - - internal static bool TryString(object o, bool convert, out string value) + else if (TryFloat(actual, convert, out var actualFloat) && TryFloat(expected, convert: true, value: out var expectedFloat)) { - if (TryString(o, out value)) - return true; - - if (convert && o is Enum evalue) - { - value = evalue.ToString(); - return true; - } - else if (convert && TryLong(o, false, out var l_value)) - { - value = l_value.ToString(Thread.CurrentThread.CurrentCulture); - return true; - } - else if (convert && TryBool(o, false, out var b_value)) - { - value = b_value.ToString(Thread.CurrentThread.CurrentCulture); - return true; - } - else if (convert && TryInt(o, false, out var i_value)) - { - value = i_value.ToString(Thread.CurrentThread.CurrentCulture); - return true; - } - return false; + compare = Comparer.Default.Compare(actualFloat, expectedFloat); + value = actualFloat; + return true; } - - internal static bool TryArray(object o, out Array value) + else if (TryDateTime(actual, convert, out var actualDateTime) && TryDateTime(expected, convert: true, value: out var expectedDateTime)) { - return TypeConverter.TryArray(GetBaseObject(o), out value); + compare = Comparer.Default.Compare(actualDateTime, expectedDateTime); + value = actualDateTime; + return true; } - - internal static bool TryStringOrArray(object o, bool convert, out string[] value) + else if ((TryStringLength(actual, out actualInt) || + TryEnumerableLength(actual, out actualInt)) && + TryInt(expected, convert: true, value: out expectedInt)) { - // Handle single string - if (TryString(o, convert, value: out var s)) - { - value = new string[] { s }; - return true; - } - - // Handle multiple strings - return TryStringArray(o, convert, out value); + compare = Comparer.Default.Compare(actualInt, expectedInt); + value = actualInt; + return true; } + compare = 0; + value = 0; + return false; + } - internal static bool TryStringArray(object o, bool convert, out string[] value) - { - value = null; - if (o is Array array) - { - value = new string[array.Length]; - for (var i = 0; i < array.Length; i++) - { - if (TryString(array.GetValue(i), convert, value: out var s)) - value[i] = s; - } - } - else if (o is JArray jArray) - { - value = new string[jArray.Count]; - for (var i = 0; i < jArray.Count; i++) - { - if (TryString(jArray[i], convert, out var s)) - value[i] = s; - } - } - else if (o is IEnumerable enumerable) - { - value = enumerable.ToArray(); - } - else if (o is IEnumerable e) - { - value = e.OfType().ToArray(); - } - return value != null; - } + internal static bool TryString(object o, out string value) + { + return TypeConverter.TryString(GetBaseObject(o), out value); + } - /// - /// Try to get an int from the existing object. - /// - internal static bool TryInt(object o, bool convert, out int value) - { - return TypeConverter.TryInt(GetBaseObject(o), convert, out value); - } + internal static bool TryString(object o, bool convert, out string value) + { + if (TryString(o, out value)) + return true; - internal static bool TryBool(object o, bool convert, out bool value) + if (convert && o is Enum evalue) { - return TypeConverter.TryBool(GetBaseObject(o), convert, out value); + value = evalue.ToString(); + return true; } - - internal static bool TryByte(object o, bool convert, out byte value) + else if (convert && TryLong(o, false, out var l_value)) { - return TypeConverter.TryByte(GetBaseObject(o), convert, out value); + value = l_value.ToString(Thread.CurrentThread.CurrentCulture); + return true; } - - internal static bool TryLong(object o, bool convert, out long value) + else if (convert && TryBool(o, false, out var b_value)) { - return TypeConverter.TryLong(GetBaseObject(o), convert, out value); + value = b_value.ToString(Thread.CurrentThread.CurrentCulture); + return true; } - - internal static bool TryFloat(object o, bool convert, out float value) + else if (convert && TryInt(o, false, out var i_value)) { - return TypeConverter.TryFloat(GetBaseObject(o), convert, out value); + value = i_value.ToString(Thread.CurrentThread.CurrentCulture); + return true; } + return false; + } - internal static bool TryDouble(object o, bool convert, out double value) + internal static bool TryArray(object o, out Array value) + { + return TypeConverter.TryArray(GetBaseObject(o), out value); + } + + internal static bool TryStringOrArray(object o, bool convert, out string[] value) + { + // Handle single string + if (TryString(o, convert, value: out var s)) { - return TypeConverter.TryDouble(GetBaseObject(o), convert, out value); + value = new string[] { s }; + return true; } - internal static bool TryStringLength(object o, out int value) + // Handle multiple strings + return TryStringArray(o, convert, out value); + } + + internal static bool TryStringArray(object o, bool convert, out string[] value) + { + value = null; + if (o is Array array) { - o = GetBaseObject(o); - if (o is string s) + value = new string[array.Length]; + for (var i = 0; i < array.Length; i++) { - value = s.Length; - return true; + if (TryString(array.GetValue(i), convert, value: out var s)) + value[i] = s; } - value = 0; - return false; } - - internal static bool TryEnumerableLength(object o, out int value) + else if (o is JArray jArray) { - o = GetBaseObject(o); - if (o is Array array) + value = new string[jArray.Count]; + for (var i = 0; i < jArray.Count; i++) { - value = array.Length; - return true; - } - if (o is ICollection collection) - { - value = collection.Count; - return true; - } - if (o is JArray jArray) - { - value = jArray.Count; - return true; - } - if (o is IEnumerable enumerable) - { - value = enumerable.OfType().Count(); - return true; + if (TryString(jArray[i], convert, out var s)) + value[i] = s; } - value = 0; - return false; } - - internal static bool TryDateTime(object o, bool convert, out DateTime value) + else if (o is IEnumerable enumerable) { - o = GetBaseObject(o); - if (o is DateTime dvalue) - { - value = dvalue; - return true; - } - else if (o is JToken token && token.Type == JTokenType.Date) - { - value = token.Value(); - return true; - } - else if (convert && TryString(o, out var s) && DateTime.TryParse(s, out dvalue)) - { - value = dvalue; - return true; - } - else if (convert && TryInt(o, convert: false, out var daysOffset)) - { - value = DateTime.Now.AddDays(daysOffset); - return true; - } - value = default; - return false; + value = enumerable.ToArray(); } - - internal static bool Match(string pattern, string value, bool caseSensitive) + else if (o is IEnumerable e) { - var expression = GetRegularExpression(pattern, caseSensitive); - return expression.IsMatch(value); + value = e.OfType().ToArray(); } + return value != null; + } + + /// + /// Try to get an int from the existing object. + /// + internal static bool TryInt(object o, bool convert, out int value) + { + return TypeConverter.TryInt(GetBaseObject(o), convert, out value); + } + + internal static bool TryBool(object o, bool convert, out bool value) + { + return TypeConverter.TryBool(GetBaseObject(o), convert, out value); + } + + internal static bool TryByte(object o, bool convert, out byte value) + { + return TypeConverter.TryByte(GetBaseObject(o), convert, out value); + } + + internal static bool TryLong(object o, bool convert, out long value) + { + return TypeConverter.TryLong(GetBaseObject(o), convert, out value); + } - internal static bool Match(object pattern, object value, bool caseSensitive) + internal static bool TryFloat(object o, bool convert, out float value) + { + return TypeConverter.TryFloat(GetBaseObject(o), convert, out value); + } + + internal static bool TryDouble(object o, bool convert, out double value) + { + return TypeConverter.TryDouble(GetBaseObject(o), convert, out value); + } + + internal static bool TryStringLength(object o, out int value) + { + o = GetBaseObject(o); + if (o is string s) { - return TryString(pattern, out var patternString) && TryString(value, out var s) && Match(patternString, s, caseSensitive); + value = s.Length; + return true; } + value = 0; + return false; + } - internal static bool AnyValue(object actualValue, object expectedValue, bool caseSensitive, out object foundValue) + internal static bool TryEnumerableLength(object o, out int value) + { + o = GetBaseObject(o); + if (o is Array array) { - foundValue = actualValue; - var expectedBase = GetBaseObject(expectedValue); - if (actualValue is IEnumerable items && actualValue is not string) - { - foreach (var item in items) - { - foundValue = item; - if (Equal(expectedBase, item, caseSensitive)) - return true; - } - } - if (Equal(expectedBase, actualValue, caseSensitive)) - { - foundValue = actualValue; - return true; - } - return false; + value = array.Length; + return true; } - - internal static bool CountValue(object actualValue, object expectedValue, bool caseSensitive, out int count) + if (o is ICollection collection) { - count = 0; - var expectedBase = GetBaseObject(expectedValue); - var actualBase = GetBaseObject(actualValue); - if (actualBase is IEnumerable items) - { - foreach (var item in items) - { - if (Equal(expectedBase, item, caseSensitive)) - count++; - } - return count > 0; - } - else if (Equal(expectedBase, actualValue, caseSensitive)) - { - count = 1; - return true; - } - return false; + value = collection.Count; + return true; } - - internal static bool WithinPath(string actualPath, string expectedPath, bool caseSensitive) + if (o is JArray jArray) { - var expected = Environment.GetRootedBasePath(expectedPath, normalize: true); - var actual = Environment.GetRootedPath(actualPath, normalize: true); - return actual.StartsWith(expected, ignoreCase: !caseSensitive, Thread.CurrentThread.CurrentCulture); + value = jArray.Count; + return true; } - - internal static string NormalizePath(string basePath, string path, bool caseSensitive = true) + if (o is IEnumerable enumerable) { - path = Environment.GetRootedPath(path, normalize: true, basePath: basePath); - basePath = Environment.GetRootedBasePath(basePath, normalize: true); - return path.Length >= basePath.Length && - path.StartsWith(basePath, caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase) ? path.Substring(basePath.Length).Replace(Backslash, Slash) : path; + value = enumerable.OfType().Count(); + return true; } + value = 0; + return false; + } - internal static string GetObjectOriginPath(object o) + internal static bool TryDateTime(object o, bool convert, out DateTime value) + { + o = GetBaseObject(o); + if (o is DateTime dvalue) { - var baseObject = GetBaseObject(o); - var targetInfo = GetTargetInfo(o); - if (baseObject is InputFileInfo inputFileInfo) - { - return Environment.GetRootedPath(inputFileInfo.FullName, normalize: true); - } - else if (baseObject is FileInfo fileInfo) - { - return Environment.GetRootedPath(fileInfo.FullName, normalize: true); - } - else if (baseObject is TargetSourceInfo sourceInfo && !string.IsNullOrEmpty(sourceInfo.File)) - { - return Environment.GetRootedPath(sourceInfo.File, normalize: true); - } - else if (targetInfo != null) - { - return Environment.GetRootedPath(targetInfo.File, normalize: true); - } - else if (baseObject is string s) - { - return Environment.GetRootedPath(s, normalize: true); - } - return null; + value = dvalue; + return true; } - - private static string NormalizeSchemaUri(string value, bool ignoreScheme) + else if (o is JToken token && token.Type == JTokenType.Date) { - if (!Uri.TryCreate(value, UriKind.RelativeOrAbsolute, out var uri)) - return value; + value = token.Value(); + return true; + } + else if (convert && TryString(o, out var s) && DateTime.TryParse(s, out dvalue)) + { + value = dvalue; + return true; + } + else if (convert && TryInt(o, convert: false, out var daysOffset)) + { + value = DateTime.Now.AddDays(daysOffset); + return true; + } + value = default; + return false; + } - var result = uri.IsAbsoluteUri ? uri.AbsoluteUri : uri.ToString(); - if (ignoreScheme && result.StartsWith(Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase)) - result = result.Remove(0, 8); - else if (ignoreScheme && result.StartsWith(Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase)) - result = result.Remove(0, 7); + internal static bool Match(string pattern, string value, bool caseSensitive) + { + var expression = GetRegularExpression(pattern, caseSensitive); + return expression.IsMatch(value); + } - return uri.IsAbsoluteUri && uri.Fragment == "#" ? result.TrimEnd('#') : result; - } + internal static bool Match(object pattern, object value, bool caseSensitive) + { + return TryString(pattern, out var patternString) && TryString(value, out var s) && Match(patternString, s, caseSensitive); + } - internal static bool AnySchema(string actualValue, Array expectedValue, bool ignoreScheme, bool caseSensitive) + internal static bool AnyValue(object actualValue, object expectedValue, bool caseSensitive, out object foundValue) + { + foundValue = actualValue; + var expectedBase = GetBaseObject(expectedValue); + if (actualValue is IEnumerable items && actualValue is not string) { - var actualNormal = NormalizeSchemaUri(actualValue, ignoreScheme); - var comparer = caseSensitive ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase; - for (var i = 0; expectedValue != null && i < expectedValue.Length; i++) + foreach (var item in items) { - if (expectedValue.GetValue(i) is string uri && comparer.Equals(actualNormal, NormalizeSchemaUri(uri, ignoreScheme))) + foundValue = item; + if (Equal(expectedBase, item, caseSensitive)) return true; } - return false; } - - private static Regex GetRegularExpression(string pattern, bool caseSensitive) + if (Equal(expectedBase, actualValue, caseSensitive)) { - if (!TryPipelineCache(caseSensitive ? CACHE_MATCH_C : CACHE_MATCH, pattern, out Regex expression)) - { - var options = caseSensitive ? RegexOptions.None : RegexOptions.IgnoreCase; - expression = new Regex(pattern, options, TimeSpan.FromSeconds(5)); - SetPipelineCache(CACHE_MATCH, pattern, expression); - } - return expression; + foundValue = actualValue; + return true; } + return false; + } - /// - /// Try to retrieve the cached key from the pipeline cache. - /// - private static bool TryPipelineCache(string prefix, string key, out T value) + internal static bool CountValue(object actualValue, object expectedValue, bool caseSensitive, out int count) + { + count = 0; + var expectedBase = GetBaseObject(expectedValue); + var actualBase = GetBaseObject(actualValue); + if (actualBase is IEnumerable items) { - value = default; - if (PipelineContext.CurrentThread.ExpressionCache.TryGetValue(string.Concat(prefix, key), out var ovalue)) + foreach (var item in items) { - value = (T)ovalue; - return true; + if (Equal(expectedBase, item, caseSensitive)) + count++; } - return false; + return count > 0; } - - private static void SetPipelineCache(string prefix, string key, T value) + else if (Equal(expectedBase, actualValue, caseSensitive)) { - PipelineContext.CurrentThread.ExpressionCache[string.Concat(prefix, key)] = value; + count = 1; + return true; } + return false; + } + + internal static bool WithinPath(string actualPath, string expectedPath, bool caseSensitive) + { + var expected = Environment.GetRootedBasePath(expectedPath, normalize: true); + var actual = Environment.GetRootedPath(actualPath, normalize: true); + return actual.StartsWith(expected, ignoreCase: !caseSensitive, Thread.CurrentThread.CurrentCulture); + } + + internal static string NormalizePath(string basePath, string path, bool caseSensitive = true) + { + path = Environment.GetRootedPath(path, normalize: true, basePath: basePath); + basePath = Environment.GetRootedBasePath(basePath, normalize: true); + return path.Length >= basePath.Length && + path.StartsWith(basePath, caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase) ? path.Substring(basePath.Length).Replace(Backslash, Slash) : path; + } - /// - /// Get the base object. - /// - internal static object GetBaseObject(object o) + internal static string GetObjectOriginPath(object o) + { + var baseObject = GetBaseObject(o); + var targetInfo = GetTargetInfo(o); + if (baseObject is InputFileInfo inputFileInfo) { - return o is PSObject pso && pso.BaseObject != null && pso.BaseObject is not PSCustomObject ? pso.BaseObject : o; + return Environment.GetRootedPath(inputFileInfo.FullName, normalize: true); } - - private static PSRuleTargetInfo GetTargetInfo(object o) + else if (baseObject is FileInfo fileInfo) { - return o is PSObject pso && pso.TryTargetInfo(out var targetInfo) ? targetInfo : null; + return Environment.GetRootedPath(fileInfo.FullName, normalize: true); } - - private static bool StringEqual(string expectedValue, string actualValue, bool caseSensitive) + else if (baseObject is TargetSourceInfo sourceInfo && !string.IsNullOrEmpty(sourceInfo.File)) { - return caseSensitive - ? StringComparer.Ordinal.Equals(expectedValue, actualValue) - : StringComparer.OrdinalIgnoreCase.Equals(expectedValue, actualValue); + return Environment.GetRootedPath(sourceInfo.File, normalize: true); } - - internal static bool StartsWith(string actualValue, object expectedValue, bool caseSensitive) + else if (targetInfo != null) + { + return Environment.GetRootedPath(targetInfo.File, normalize: true); + } + else if (baseObject is string s) { - return TryString(expectedValue, out var expected) && - actualValue.StartsWith(expected, caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase); + return Environment.GetRootedPath(s, normalize: true); } + return null; + } - internal static bool EndsWith(string actualValue, object expectedValue, bool caseSensitive) + private static string NormalizeSchemaUri(string value, bool ignoreScheme) + { + if (!Uri.TryCreate(value, UriKind.RelativeOrAbsolute, out var uri)) + return value; + + var result = uri.IsAbsoluteUri ? uri.AbsoluteUri : uri.ToString(); + if (ignoreScheme && result.StartsWith(Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase)) + result = result.Remove(0, 8); + else if (ignoreScheme && result.StartsWith(Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase)) + result = result.Remove(0, 7); + + return uri.IsAbsoluteUri && uri.Fragment == "#" ? result.TrimEnd('#') : result; + } + + internal static bool AnySchema(string actualValue, Array expectedValue, bool ignoreScheme, bool caseSensitive) + { + var actualNormal = NormalizeSchemaUri(actualValue, ignoreScheme); + var comparer = caseSensitive ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase; + for (var i = 0; expectedValue != null && i < expectedValue.Length; i++) { - return TryString(expectedValue, out var expected) - && actualValue.EndsWith(expected, caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase); + if (expectedValue.GetValue(i) is string uri && comparer.Equals(actualNormal, NormalizeSchemaUri(uri, ignoreScheme))) + return true; } + return false; + } - internal static bool Contains(string actualValue, object expectedValue, bool caseSensitive) + private static Regex GetRegularExpression(string pattern, bool caseSensitive) + { + if (!TryPipelineCache(caseSensitive ? CACHE_MATCH_C : CACHE_MATCH, pattern, out Regex expression)) { - return TryString(expectedValue, out var expected) - && actualValue.IndexOf(expected, caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase) >= 0; + var options = caseSensitive ? RegexOptions.None : RegexOptions.IgnoreCase; + expression = new Regex(pattern, options, TimeSpan.FromSeconds(5)); + SetPipelineCache(CACHE_MATCH, pattern, expression); } + return expression; + } - internal static bool IsLower(string actualValue, bool requireLetters, out bool notLetter) + /// + /// Try to retrieve the cached key from the pipeline cache. + /// + private static bool TryPipelineCache(string prefix, string key, out T value) + { + value = default; + if (PipelineContext.CurrentThread.ExpressionCache.TryGetValue(string.Concat(prefix, key), out var ovalue)) { - notLetter = false; - for (var i = 0; i < actualValue.Length; i++) - { - if (!char.IsLetter(actualValue, i) && requireLetters) - { - notLetter = true; - return false; - } - if (char.IsLetter(actualValue, i) && !char.IsLower(actualValue, i)) - return false; - } + value = (T)ovalue; return true; } + return false; + } + + private static void SetPipelineCache(string prefix, string key, T value) + { + PipelineContext.CurrentThread.ExpressionCache[string.Concat(prefix, key)] = value; + } + + /// + /// Get the base object. + /// + internal static object GetBaseObject(object o) + { + return o is PSObject pso && pso.BaseObject != null && pso.BaseObject is not PSCustomObject ? pso.BaseObject : o; + } + + private static PSRuleTargetInfo GetTargetInfo(object o) + { + return o is PSObject pso && pso.TryTargetInfo(out var targetInfo) ? targetInfo : null; + } + + private static bool StringEqual(string expectedValue, string actualValue, bool caseSensitive) + { + return caseSensitive + ? StringComparer.Ordinal.Equals(expectedValue, actualValue) + : StringComparer.OrdinalIgnoreCase.Equals(expectedValue, actualValue); + } + + internal static bool StartsWith(string actualValue, object expectedValue, bool caseSensitive) + { + return TryString(expectedValue, out var expected) && + actualValue.StartsWith(expected, caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase); + } - internal static bool IsUpper(string actualValue, bool requireLetters, out bool notLetter) + internal static bool EndsWith(string actualValue, object expectedValue, bool caseSensitive) + { + return TryString(expectedValue, out var expected) + && actualValue.EndsWith(expected, caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase); + } + + internal static bool Contains(string actualValue, object expectedValue, bool caseSensitive) + { + return TryString(expectedValue, out var expected) + && actualValue.IndexOf(expected, caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase) >= 0; + } + + internal static bool IsLower(string actualValue, bool requireLetters, out bool notLetter) + { + notLetter = false; + for (var i = 0; i < actualValue.Length; i++) { - notLetter = false; - for (var i = 0; i < actualValue.Length; i++) + if (!char.IsLetter(actualValue, i) && requireLetters) { - if (!char.IsLetter(actualValue, i) && requireLetters) - { - notLetter = true; - return false; - } - if (char.IsLetter(actualValue, i) && !char.IsUpper(actualValue, i)) - return false; + notLetter = true; + return false; } - return true; + if (char.IsLetter(actualValue, i) && !char.IsLower(actualValue, i)) + return false; } + return true; + } - internal static bool Like(string actualValue, string pattern, bool caseSensitive) + internal static bool IsUpper(string actualValue, bool requireLetters, out bool notLetter) + { + notLetter = false; + for (var i = 0; i < actualValue.Length; i++) { - var options = caseSensitive ? WildcardOptions.CultureInvariant : WildcardOptions.CultureInvariant | WildcardOptions.IgnoreCase; - var p = WildcardPattern.Get(pattern, options); - return p.IsMatch(actualValue); + if (!char.IsLetter(actualValue, i) && requireLetters) + { + notLetter = true; + return false; + } + if (char.IsLetter(actualValue, i) && !char.IsUpper(actualValue, i)) + return false; } + return true; + } + + internal static bool Like(string actualValue, string pattern, bool caseSensitive) + { + var options = caseSensitive ? WildcardOptions.CultureInvariant : WildcardOptions.CultureInvariant | WildcardOptions.IgnoreCase; + var p = WildcardPattern.Get(pattern, options); + return p.IsMatch(actualValue); } } diff --git a/src/PSRule/Common/ExternalToolHelper.cs b/src/PSRule/Common/ExternalToolHelper.cs index c94065f051..11b388a4d0 100644 --- a/src/PSRule/Common/ExternalToolHelper.cs +++ b/src/PSRule/Common/ExternalToolHelper.cs @@ -4,174 +4,173 @@ using System.Diagnostics; using System.Text; -namespace PSRule +namespace PSRule; + +internal sealed class ExternalTool : IDisposable { - internal sealed class ExternalTool : IDisposable + private Process _Process; + private readonly StringBuilder _Output; + private readonly StringBuilder _Error; + private readonly AutoResetEvent _ErrorWait; + private readonly AutoResetEvent _OutputWait; + private readonly int _Interval; + private readonly int _Timeout; + private readonly string _BinaryPath; + private bool _Disposed; + + private ExternalTool(string binaryPath, int timeout, string version = null) { - private Process _Process; - private readonly StringBuilder _Output; - private readonly StringBuilder _Error; - private readonly AutoResetEvent _ErrorWait; - private readonly AutoResetEvent _OutputWait; - private readonly int _Interval; - private readonly int _Timeout; - private readonly string _BinaryPath; - private bool _Disposed; - - private ExternalTool(string binaryPath, int timeout, string version = null) - { - _Output = new StringBuilder(); - _Error = new StringBuilder(); - _Interval = 1000; - _Timeout = timeout; - _BinaryPath = binaryPath; - - _ErrorWait = new AutoResetEvent(false); - _OutputWait = new AutoResetEvent(false); - } + _Output = new StringBuilder(); + _Error = new StringBuilder(); + _Interval = 1000; + _Timeout = timeout; + _BinaryPath = binaryPath; + + _ErrorWait = new AutoResetEvent(false); + _OutputWait = new AutoResetEvent(false); + } - public bool HasExited => _Process.HasExited; + public bool HasExited => _Process.HasExited; - internal static ExternalTool Get(string defaultPath, string binary) - { - if (!TryPathFromDefault(defaultPath, binary, out var binaryPath) && !TryPathFromEnvironment(binary, out binaryPath)) - return null; + internal static ExternalTool Get(string defaultPath, string binary) + { + if (!TryPathFromDefault(defaultPath, binary, out var binaryPath) && !TryPathFromEnvironment(binary, out binaryPath)) + return null; - return new ExternalTool(binaryPath, 0, null); - } + return new ExternalTool(binaryPath, 0, null); + } - private static bool TryPathFromDefault(string defaultPath, string binary, out string binaryPath) + private static bool TryPathFromDefault(string defaultPath, string binary, out string binaryPath) + { + return TryPath(binary, defaultPath, out binaryPath); + } + + public bool WaitForExit(string args, out int exitCode) + { + var startInfo = new ProcessStartInfo(_BinaryPath, args) { - return TryPath(binary, defaultPath, out binaryPath); + CreateNoWindow = true, + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + WorkingDirectory = Environment.GetWorkingPath(), + }; + _Process = Process.Start(startInfo); + _Process.ErrorDataReceived += Bicep_ErrorDataReceived; + _Process.OutputDataReceived += Bicep_OutputDataReceived; + + _Process.BeginErrorReadLine(); + _Process.BeginOutputReadLine(); + + _ErrorWait.Reset(); + _OutputWait.Reset(); + + if (!_Process.HasExited) + { + var timeoutCount = 0; + while (!_Process.WaitForExit(_Interval) && !_Process.HasExited && timeoutCount < _Timeout) + timeoutCount++; } - public bool WaitForExit(string args, out int exitCode) - { - var startInfo = new ProcessStartInfo(_BinaryPath, args) - { - CreateNoWindow = true, - RedirectStandardOutput = true, - RedirectStandardError = true, - UseShellExecute = false, - WorkingDirectory = Environment.GetWorkingPath(), - }; - _Process = Process.Start(startInfo); - _Process.ErrorDataReceived += Bicep_ErrorDataReceived; - _Process.OutputDataReceived += Bicep_OutputDataReceived; - - _Process.BeginErrorReadLine(); - _Process.BeginOutputReadLine(); - - _ErrorWait.Reset(); - _OutputWait.Reset(); - - if (!_Process.HasExited) - { - var timeoutCount = 0; - while (!_Process.WaitForExit(_Interval) && !_Process.HasExited && timeoutCount < _Timeout) - timeoutCount++; - } + exitCode = _Process.HasExited ? _Process.ExitCode : -1; + return _Process.HasExited && _ErrorWait.WaitOne(_Interval) && _OutputWait.WaitOne(); + } - exitCode = _Process.HasExited ? _Process.ExitCode : -1; - return _Process.HasExited && _ErrorWait.WaitOne(_Interval) && _OutputWait.WaitOne(); - } + public string GetOutput() + { + return _Output.ToString(); + } - public string GetOutput() - { - return _Output.ToString(); - } + public string GetError() + { + return _Error.ToString(); + } - public string GetError() + private void Bicep_OutputDataReceived(object sender, DataReceivedEventArgs e) + { + if (e.Data == null) { - return _Error.ToString(); + _OutputWait.Set(); } - - private void Bicep_OutputDataReceived(object sender, DataReceivedEventArgs e) + else { - if (e.Data == null) - { - _OutputWait.Set(); - } - else - { - _Output.AppendLine(e.Data); - } + _Output.AppendLine(e.Data); } + } - private void Bicep_ErrorDataReceived(object sender, DataReceivedEventArgs e) + private void Bicep_ErrorDataReceived(object sender, DataReceivedEventArgs e) + { + if (e.Data == null) { - if (e.Data == null) - { - _ErrorWait.Set(); - } - else - { - var errors = GetErrorLine(e.Data); - for (var i = 0; i < errors.Length; i++) - _Error.AppendLine(errors[i]); - } + _ErrorWait.Set(); } - - private static string[] GetErrorLine(string input) + else { - var lines = input.Split(new string[] { System.Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries); - var result = new List(); - for (var i = 0; i < lines.Length; i++) - if (!lines[i].Contains(": Warning ") && !lines[i].Contains(": Info ")) - result.Add(lines[i]); - - return result.ToArray(); + var errors = GetErrorLine(e.Data); + for (var i = 0; i < errors.Length; i++) + _Error.AppendLine(errors[i]); } + } - private static bool TryPathFromEnvironment(string binary, out string binaryPath) - { - binaryPath = null; - if (!Environment.TryPathEnvironmentVariable(out var path)) - return false; + private static string[] GetErrorLine(string input) + { + var lines = input.Split(new string[] { System.Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries); + var result = new List(); + for (var i = 0; i < lines.Length; i++) + if (!lines[i].Contains(": Warning ") && !lines[i].Contains(": Info ")) + result.Add(lines[i]); - for (var i = 0; path != null && i < path.Length; i++) - if (TryPath(binary, path[i], out binaryPath)) - return true; + return result.ToArray(); + } - binaryPath = null; + private static bool TryPathFromEnvironment(string binary, out string binaryPath) + { + binaryPath = null; + if (!Environment.TryPathEnvironmentVariable(out var path)) return false; - } - private static bool TryPath(string binary, string path, out string binaryPath) - { - binaryPath = null; - if (string.IsNullOrEmpty(path)) - return false; - - binaryPath = Path.Combine(path, binary); - if (File.Exists(binaryPath)) + for (var i = 0; path != null && i < path.Length; i++) + if (TryPath(binary, path[i], out binaryPath)) return true; - binaryPath = null; + binaryPath = null; + return false; + } + + private static bool TryPath(string binary, string path, out string binaryPath) + { + binaryPath = null; + if (string.IsNullOrEmpty(path)) return false; - } - private void Dispose(bool disposing) + binaryPath = Path.Combine(path, binary); + if (File.Exists(binaryPath)) + return true; + + binaryPath = null; + return false; + } + + private void Dispose(bool disposing) + { + if (!_Disposed) { - if (!_Disposed) + if (disposing) { - if (disposing) - { - _ErrorWait.Dispose(); - _OutputWait.Dispose(); - _Process.Dispose(); - } - _Error.Clear(); - _Output.Clear(); - _Disposed = true; + _ErrorWait.Dispose(); + _OutputWait.Dispose(); + _Process.Dispose(); } + _Error.Clear(); + _Output.Clear(); + _Disposed = true; } + } - public void Dispose() - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(disposing: true); - GC.SuppressFinalize(this); - } + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); } } diff --git a/src/PSRule/Common/GitHelper.cs b/src/PSRule/Common/GitHelper.cs index a54d98c506..8dacc73ae3 100644 --- a/src/PSRule/Common/GitHelper.cs +++ b/src/PSRule/Common/GitHelper.cs @@ -3,201 +3,200 @@ using System.Runtime.InteropServices; -namespace PSRule +namespace PSRule; + +/// +/// Helper for working with Git and CI tools. +/// +/// +/// Docs for Azure Pipelines and +/// GitHub Actions. +/// +internal static class GitHelper { + private const string GIT_HEAD = "HEAD"; + private const string GIT_REF_PREFIX = "ref: "; + private const string GIT_DEFAULT_PATH = ".git/"; + private const string GIT_REF_HEAD = "refs/heads/"; + + private const string GITHUB_BASE_URL = "https://github.com/"; + + // Environment variables for PSRule + private const string ENV_PSRULE_REPO_REF = "PSRULE_REPOSITORY_REF"; + private const string ENV_PSRULE_REPO_HEADREF = "PSRULE_REPOSITORY_HEADREF"; + private const string ENV_PSRULE_REPO_BASEREF = "PSRULE_REPOSITORY_BASEREF"; + private const string ENV_PSRULE_REPO_REVISION = "PSRULE_REPOSITORY_REVISION"; + private const string ENV_PSRULE_REPO_URL = "PSRULE_REPOSITORY_URL"; + + // Environment variables for Azure Pipelines + private const string ENV_ADO_REPO_REF = "BUILD_SOURCEBRANCH"; + private const string ENV_ADO_REPO_BASEREF = "SYSTEM_PULLREQUEST_TARGETBRANCH"; + private const string ENV_ADO_REPO_REVISION = "BUILD_SOURCEVERSION"; + private const string ENV_ADO_REPO_URL = "BUILD_REPOSITORY_URI"; + + // Environment variables for GitHub Actions + private const string ENV_GITHUB_REPO_REF = "GITHUB_REF"; + private const string ENV_GITHUB_REPO_HEADREF = "GITHUB_HEAD_REF"; + private const string ENV_GITHUB_REPO_BASEREF = "GITHUB_BASE_REF"; + private const string ENV_GITHUB_REPO_REVISION = "GITHUB_SHA"; + private const string ENV_GITHUB_REPO_URL = "GITHUB_REPOSITORY"; + /// - /// Helper for working with Git and CI tools. + /// The get HEAD ref. /// - /// - /// Docs for Azure Pipelines and - /// GitHub Actions. - /// - internal static class GitHelper + public static bool TryHeadRef(out string value, string path = null) { - private const string GIT_HEAD = "HEAD"; - private const string GIT_REF_PREFIX = "ref: "; - private const string GIT_DEFAULT_PATH = ".git/"; - private const string GIT_REF_HEAD = "refs/heads/"; - - private const string GITHUB_BASE_URL = "https://github.com/"; - - // Environment variables for PSRule - private const string ENV_PSRULE_REPO_REF = "PSRULE_REPOSITORY_REF"; - private const string ENV_PSRULE_REPO_HEADREF = "PSRULE_REPOSITORY_HEADREF"; - private const string ENV_PSRULE_REPO_BASEREF = "PSRULE_REPOSITORY_BASEREF"; - private const string ENV_PSRULE_REPO_REVISION = "PSRULE_REPOSITORY_REVISION"; - private const string ENV_PSRULE_REPO_URL = "PSRULE_REPOSITORY_URL"; - - // Environment variables for Azure Pipelines - private const string ENV_ADO_REPO_REF = "BUILD_SOURCEBRANCH"; - private const string ENV_ADO_REPO_BASEREF = "SYSTEM_PULLREQUEST_TARGETBRANCH"; - private const string ENV_ADO_REPO_REVISION = "BUILD_SOURCEVERSION"; - private const string ENV_ADO_REPO_URL = "BUILD_REPOSITORY_URI"; - - // Environment variables for GitHub Actions - private const string ENV_GITHUB_REPO_REF = "GITHUB_REF"; - private const string ENV_GITHUB_REPO_HEADREF = "GITHUB_HEAD_REF"; - private const string ENV_GITHUB_REPO_BASEREF = "GITHUB_BASE_REF"; - private const string ENV_GITHUB_REPO_REVISION = "GITHUB_SHA"; - private const string ENV_GITHUB_REPO_URL = "GITHUB_REPOSITORY"; - - /// - /// The get HEAD ref. - /// - public static bool TryHeadRef(out string value, string path = null) - { - // Try PSRule - if (Environment.TryString(ENV_PSRULE_REPO_REF, out value) || - Environment.TryString(ENV_PSRULE_REPO_HEADREF, out value)) - return true; - - // Try Azure Pipelines - if (Environment.TryString(ENV_ADO_REPO_REF, out value)) - return true; - - // Try GitHub Actions - if (Environment.TryString(ENV_GITHUB_REPO_REF, out value) || - Environment.TryString(ENV_GITHUB_REPO_HEADREF, out value)) - return true; - - // Try .git/ - return TryReadHead(path, out value); - } + // Try PSRule + if (Environment.TryString(ENV_PSRULE_REPO_REF, out value) || + Environment.TryString(ENV_PSRULE_REPO_HEADREF, out value)) + return true; - /// - /// Get the HEAD branch name. - /// - public static bool TryHeadBranch(out string value, string path = null) - { - value = TryHeadRef(out value, path) && value.StartsWith(GIT_REF_HEAD) ? value.Substring(11) : value; - return !string.IsNullOrEmpty(value); - } + // Try Azure Pipelines + if (Environment.TryString(ENV_ADO_REPO_REF, out value)) + return true; - /// - /// Get the target ref. - /// - public static bool TryBaseRef(out string value, string path = null) - { - // Try PSRule - if (Environment.TryString(ENV_PSRULE_REPO_BASEREF, out value)) - return true; + // Try GitHub Actions + if (Environment.TryString(ENV_GITHUB_REPO_REF, out value) || + Environment.TryString(ENV_GITHUB_REPO_HEADREF, out value)) + return true; - // Try Azure Pipelines - if (Environment.TryString(ENV_ADO_REPO_BASEREF, out value)) - return true; + // Try .git/ + return TryReadHead(path, out value); + } - // Try GitHub Actions - if (Environment.TryString(ENV_GITHUB_REPO_BASEREF, out value)) - return true; + /// + /// Get the HEAD branch name. + /// + public static bool TryHeadBranch(out string value, string path = null) + { + value = TryHeadRef(out value, path) && value.StartsWith(GIT_REF_HEAD) ? value.Substring(11) : value; + return !string.IsNullOrEmpty(value); + } - // Try .git/ - return TryReadHead(path, out value); - } + /// + /// Get the target ref. + /// + public static bool TryBaseRef(out string value, string path = null) + { + // Try PSRule + if (Environment.TryString(ENV_PSRULE_REPO_BASEREF, out value)) + return true; - public static bool TryRevision(out string value, string path = null) - { - // Try PSRule - if (Environment.TryString(ENV_PSRULE_REPO_REVISION, out value)) - return true; + // Try Azure Pipelines + if (Environment.TryString(ENV_ADO_REPO_BASEREF, out value)) + return true; - // Try Azure Pipelines - if (Environment.TryString(ENV_ADO_REPO_REVISION, out value)) - return true; + // Try GitHub Actions + if (Environment.TryString(ENV_GITHUB_REPO_BASEREF, out value)) + return true; - // Try GitHub Actions - if (Environment.TryString(ENV_GITHUB_REPO_REVISION, out value)) - return true; + // Try .git/ + return TryReadHead(path, out value); + } - // Try .git/ - return TryReadCommit(path, out value); - } + public static bool TryRevision(out string value, string path = null) + { + // Try PSRule + if (Environment.TryString(ENV_PSRULE_REPO_REVISION, out value)) + return true; - public static bool TryRepository(out string value, string path = null) - { - // Try PSRule - if (Environment.TryString(ENV_PSRULE_REPO_URL, out value)) - return true; - - // Try Azure Pipelines - if (Environment.TryString(ENV_ADO_REPO_URL, out value)) - return true; - - // Try GitHub Actions - if (Environment.TryString(ENV_GITHUB_REPO_URL, out value)) - { - value = string.Concat(GITHUB_BASE_URL, value); - return true; - } - - // Try .git/ - return false; - } + // Try Azure Pipelines + if (Environment.TryString(ENV_ADO_REPO_REVISION, out value)) + return true; - public static bool TryGetChangedFiles(string baseRef, string filter, string options, out string[] files) - { - // Get current tip - var source = TryRevision(out var source_sha) ? source_sha : "HEAD"; - var target = !string.IsNullOrEmpty(baseRef) ? baseRef : "HEAD^"; + // Try GitHub Actions + if (Environment.TryString(ENV_GITHUB_REPO_REVISION, out value)) + return true; - var bin = GetGitBinary(); - var args = GetGitArgs(target, source, filter, options); - var tool = ExternalTool.Get(null, bin); + // Try .git/ + return TryReadCommit(path, out value); + } - files = Array.Empty(); - if (!tool.WaitForExit(args, out var exitCode) || exitCode != 0) - return false; + public static bool TryRepository(out string value, string path = null) + { + // Try PSRule + if (Environment.TryString(ENV_PSRULE_REPO_URL, out value)) + return true; - files = tool.GetOutput().Split(new string[] { System.Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries); + // Try Azure Pipelines + if (Environment.TryString(ENV_ADO_REPO_URL, out value)) return true; - } - private static string GetGitBinary() + // Try GitHub Actions + if (Environment.TryString(ENV_GITHUB_REPO_URL, out value)) { - return RuntimeInformation.IsOSPlatform(OSPlatform.OSX) || - RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? "git" : "git.exe"; + value = string.Concat(GITHUB_BASE_URL, value); + return true; } - private static string GetGitArgs(string target, string source, string filter, string options) - { - return $"diff --diff-filter={filter} --ignore-submodules=all --name-only --no-renames {target}"; - } + // Try .git/ + return false; + } - private static bool TryReadHead(string path, out string value) - { - value = null; - return TryGitFile(path, GIT_HEAD, out var filePath) && TryCommit(filePath, out value, out _); - } + public static bool TryGetChangedFiles(string baseRef, string filter, string options, out string[] files) + { + // Get current tip + var source = TryRevision(out var source_sha) ? source_sha : "HEAD"; + var target = !string.IsNullOrEmpty(baseRef) ? baseRef : "HEAD^"; - private static bool TryReadCommit(string path, out string value) - { - value = null; - if (!TryGitFile(path, GIT_HEAD, out var filePath)) - return false; + var bin = GetGitBinary(); + var args = GetGitArgs(target, source, filter, options); + var tool = ExternalTool.Get(null, bin); - while (TryCommit(filePath, out value, out var isRef) && isRef) - TryGitFile(path, value, out filePath); + files = Array.Empty(); + if (!tool.WaitForExit(args, out var exitCode) || exitCode != 0) + return false; - return value != null; - } + files = tool.GetOutput().Split(new string[] { System.Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries); + return true; + } - private static bool TryGitFile(string path, string file, out string filePath) - { - path ??= Environment.GetRootedBasePath(GIT_DEFAULT_PATH); - filePath = Path.Combine(path, file); - return File.Exists(filePath); - } + private static string GetGitBinary() + { + return RuntimeInformation.IsOSPlatform(OSPlatform.OSX) || + RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? "git" : "git.exe"; + } - private static bool TryCommit(string path, out string value, out bool isRef) - { - value = null; - isRef = false; - var lines = File.ReadAllLines(path); - if (lines == null || lines.Length == 0) - return false; - - isRef = lines[0].StartsWith(GIT_REF_PREFIX, System.StringComparison.OrdinalIgnoreCase); - value = isRef ? lines[0].Substring(5) : lines[0]; - return true; - } + private static string GetGitArgs(string target, string source, string filter, string options) + { + return $"diff --diff-filter={filter} --ignore-submodules=all --name-only --no-renames {target}"; + } + + private static bool TryReadHead(string path, out string value) + { + value = null; + return TryGitFile(path, GIT_HEAD, out var filePath) && TryCommit(filePath, out value, out _); + } + + private static bool TryReadCommit(string path, out string value) + { + value = null; + if (!TryGitFile(path, GIT_HEAD, out var filePath)) + return false; + + while (TryCommit(filePath, out value, out var isRef) && isRef) + TryGitFile(path, value, out filePath); + + return value != null; + } + + private static bool TryGitFile(string path, string file, out string filePath) + { + path ??= Environment.GetRootedBasePath(GIT_DEFAULT_PATH); + filePath = Path.Combine(path, file); + return File.Exists(filePath); + } + + private static bool TryCommit(string path, out string value, out bool isRef) + { + value = null; + isRef = false; + var lines = File.ReadAllLines(path); + if (lines == null || lines.Length == 0) + return false; + + isRef = lines[0].StartsWith(GIT_REF_PREFIX, System.StringComparison.OrdinalIgnoreCase); + value = isRef ? lines[0].Substring(5) : lines[0]; + return true; } } diff --git a/src/PSRule/Common/HashAlgorithmExtensions.cs b/src/PSRule/Common/HashAlgorithmExtensions.cs index 1428edacd8..31a9e5125f 100644 --- a/src/PSRule/Common/HashAlgorithmExtensions.cs +++ b/src/PSRule/Common/HashAlgorithmExtensions.cs @@ -3,14 +3,13 @@ using System.Security.Cryptography; -namespace PSRule +namespace PSRule; + +internal static class HashAlgorithmExtensions { - internal static class HashAlgorithmExtensions + public static string GetDigest(this HashAlgorithm algorithm, byte[] buffer) { - public static string GetDigest(this HashAlgorithm algorithm, byte[] buffer) - { - var hash = algorithm.ComputeHash(buffer); - return string.Join("", hash.Select(b => b.ToString("x2")).ToArray()); - } + var hash = algorithm.ComputeHash(buffer); + return string.Join("", hash.Select(b => b.ToString("x2")).ToArray()); } } diff --git a/src/PSRule/Common/HashSetExtensions.cs b/src/PSRule/Common/HashSetExtensions.cs index 5d729481fb..c9ecd3104a 100644 --- a/src/PSRule/Common/HashSetExtensions.cs +++ b/src/PSRule/Common/HashSetExtensions.cs @@ -3,94 +3,93 @@ using PSRule.Definitions; -namespace PSRule +namespace PSRule; + +internal static class HashSetExtensions { - internal static class HashSetExtensions + internal static bool ContainsIds(this HashSet hashset, ResourceId id, ResourceId? @ref, ResourceId[] aliases, out ResourceId? duplicate) { - internal static bool ContainsIds(this HashSet hashset, ResourceId id, ResourceId? @ref, ResourceId[] aliases, out ResourceId? duplicate) - { - duplicate = null; - if (hashset == null || hashset.Count == 0) - return false; + duplicate = null; + if (hashset == null || hashset.Count == 0) + return false; - if (hashset.Contains(id)) - { - duplicate = id; - return true; - } - if (@ref.HasValue && hashset.Contains(@ref.Value)) + if (hashset.Contains(id)) + { + duplicate = id; + return true; + } + if (@ref.HasValue && hashset.Contains(@ref.Value)) + { + duplicate = @ref.Value; + return true; + } + for (var i = 0; aliases != null && i < aliases.Length; i++) + { + if (hashset.Contains(aliases[i])) { - duplicate = @ref.Value; + duplicate = aliases[i]; return true; } - for (var i = 0; aliases != null && i < aliases.Length; i++) - { - if (hashset.Contains(aliases[i])) - { - duplicate = aliases[i]; - return true; - } - } - return false; } + return false; + } - internal static bool ContainsNames(this HashSet hashset, ResourceId id, ResourceId? @ref, ResourceId[] aliases, out string duplicate) - { - duplicate = null; - if (hashset == null || hashset.Count == 0) - return false; + internal static bool ContainsNames(this HashSet hashset, ResourceId id, ResourceId? @ref, ResourceId[] aliases, out string duplicate) + { + duplicate = null; + if (hashset == null || hashset.Count == 0) + return false; - if (hashset.Contains(id.Name)) - { - duplicate = id.Name; - return true; - } - if (@ref.HasValue && hashset.Contains(@ref.Value.Name)) + if (hashset.Contains(id.Name)) + { + duplicate = id.Name; + return true; + } + if (@ref.HasValue && hashset.Contains(@ref.Value.Name)) + { + duplicate = @ref.Value.Name; + return true; + } + for (var i = 0; aliases != null && i < aliases.Length; i++) + { + if (hashset.Contains(aliases[i].Name)) { - duplicate = @ref.Value.Name; + duplicate = aliases[i].Name; return true; } - for (var i = 0; aliases != null && i < aliases.Length; i++) - { - if (hashset.Contains(aliases[i].Name)) - { - duplicate = aliases[i].Name; - return true; - } - } - return false; } + return false; + } - internal static void AddIds(this HashSet hashset, ResourceId id, ResourceId? @ref, ResourceId[] aliases) - { - if (hashset == null) - return; + internal static void AddIds(this HashSet hashset, ResourceId id, ResourceId? @ref, ResourceId[] aliases) + { + if (hashset == null) + return; - if (!hashset.Contains(id)) - hashset.Add(id); + if (!hashset.Contains(id)) + hashset.Add(id); - if (@ref.HasValue && !hashset.Contains(@ref.Value)) - hashset.Add(@ref.Value); + if (@ref.HasValue && !hashset.Contains(@ref.Value)) + hashset.Add(@ref.Value); - for (var i = 0; aliases != null && i < aliases.Length; i++) - if (!hashset.Contains(aliases[i])) - hashset.Add(aliases[i]); - } + for (var i = 0; aliases != null && i < aliases.Length; i++) + if (!hashset.Contains(aliases[i])) + hashset.Add(aliases[i]); + } - internal static void AddNames(this HashSet hashset, ResourceId id, ResourceId? @ref, ResourceId[] aliases) - { - if (hashset == null) - return; + internal static void AddNames(this HashSet hashset, ResourceId id, ResourceId? @ref, ResourceId[] aliases) + { + if (hashset == null) + return; - if (!hashset.Contains(id.Name)) - hashset.Add(id.Name); + if (!hashset.Contains(id.Name)) + hashset.Add(id.Name); - if (@ref.HasValue && !hashset.Contains(@ref.Value.Name)) - hashset.Add(@ref.Value.Name); + if (@ref.HasValue && !hashset.Contains(@ref.Value.Name)) + hashset.Add(@ref.Value.Name); - for (var i = 0; aliases != null && i < aliases.Length; i++) - if (!hashset.Contains(aliases[i].Name)) - hashset.Add(aliases[i].Name); - } + for (var i = 0; aliases != null && i < aliases.Length; i++) + if (!hashset.Contains(aliases[i].Name)) + hashset.Add(aliases[i].Name); } } diff --git a/src/PSRule/Common/HashtableExtensions.cs b/src/PSRule/Common/HashtableExtensions.cs index 15c7bc0b9d..7ef067ea41 100644 --- a/src/PSRule/Common/HashtableExtensions.cs +++ b/src/PSRule/Common/HashtableExtensions.cs @@ -4,36 +4,35 @@ using System.Collections; using System.Diagnostics; -namespace PSRule +namespace PSRule; + +internal static class HashtableExtensions { - internal static class HashtableExtensions + public static IDictionary ToDictionary(this Hashtable hashtable) { - public static IDictionary ToDictionary(this Hashtable hashtable) - { - return hashtable - .Cast() - .ToDictionary( - kvp => kvp.Key.ToString(), - kvp => kvp.Value - ); - } + return hashtable + .Cast() + .ToDictionary( + kvp => kvp.Key.ToString(), + kvp => kvp.Value + ); + } - [DebuggerStepThrough] - public static bool TryGetStringArray(this Hashtable hashtable, string key, out string[] value) - { - value = null; - return hashtable.TryGetValue(key, out var o) && ExpressionHelpers.TryStringOrArray(o, convert: true, value: out value); - } + [DebuggerStepThrough] + public static bool TryGetStringArray(this Hashtable hashtable, string key, out string[] value) + { + value = null; + return hashtable.TryGetValue(key, out var o) && ExpressionHelpers.TryStringOrArray(o, convert: true, value: out value); + } - [DebuggerStepThrough] - public static bool TryGetValue(this Hashtable hashtable, object key, out object value) - { - value = null; - if (!hashtable.ContainsKey(key)) - return false; + [DebuggerStepThrough] + public static bool TryGetValue(this Hashtable hashtable, object key, out object value) + { + value = null; + if (!hashtable.ContainsKey(key)) + return false; - value = hashtable[key]; - return true; - } + value = hashtable[key]; + return true; } } diff --git a/src/PSRule/Common/InfoStringExtensions.cs b/src/PSRule/Common/InfoStringExtensions.cs index 1047f2e3ca..2a048c48b3 100644 --- a/src/PSRule/Common/InfoStringExtensions.cs +++ b/src/PSRule/Common/InfoStringExtensions.cs @@ -3,17 +3,16 @@ using PSRule.Definitions; -namespace PSRule +namespace PSRule; + +internal static class InfoStringExtensions { - internal static class InfoStringExtensions + internal static void Update(this InfoString i1, InfoString i2) { - internal static void Update(this InfoString i1, InfoString i2) - { - if (i1 == null || i2 == null || !i2.HasValue) - return; + if (i1 == null || i2 == null || !i2.HasValue) + return; - i1.Text = i2.Text; - i1.Markdown = i2.Markdown; - } + i1.Text = i2.Text; + i1.Markdown = i2.Markdown; } } diff --git a/src/PSRule/Common/JsonCommentWriter.cs b/src/PSRule/Common/JsonCommentWriter.cs index 1b385eb886..0192a63c71 100644 --- a/src/PSRule/Common/JsonCommentWriter.cs +++ b/src/PSRule/Common/JsonCommentWriter.cs @@ -3,25 +3,24 @@ using Newtonsoft.Json; -namespace PSRule +namespace PSRule; + +internal sealed class JsonCommentWriter : JsonTextWriter { - internal sealed class JsonCommentWriter : JsonTextWriter - { - public JsonCommentWriter(TextWriter textWriter) - : base(textWriter) { } + public JsonCommentWriter(TextWriter textWriter) + : base(textWriter) { } - public override void WriteComment(string text) - { - SetWriteState(JsonToken.Comment, text); - if (Indentation > 0 && Formatting == Formatting.Indented) - WriteIndent(); - else - WriteRaw(System.Environment.NewLine); + public override void WriteComment(string text) + { + SetWriteState(JsonToken.Comment, text); + if (Indentation > 0 && Formatting == Formatting.Indented) + WriteIndent(); + else + WriteRaw(System.Environment.NewLine); - WriteRaw("// "); - WriteRaw(text); - if (Indentation == 0 || Formatting == Formatting.None) - WriteRaw(System.Environment.NewLine); - } + WriteRaw("// "); + WriteRaw(text); + if (Indentation == 0 || Formatting == Formatting.None) + WriteRaw(System.Environment.NewLine); } } diff --git a/src/PSRule/Common/JsonConverters.cs b/src/PSRule/Common/JsonConverters.cs index 2a6d4136be..ff7fec4562 100644 --- a/src/PSRule/Common/JsonConverters.cs +++ b/src/PSRule/Common/JsonConverters.cs @@ -14,982 +14,981 @@ using PSRule.Resources; using PSRule.Runtime; -namespace PSRule +namespace PSRule; + +/// +/// A base converter. +/// +internal abstract class PSObjectBaseConverter : JsonConverter { /// - /// A base converter. + /// Skip JSON comments. /// - internal abstract class PSObjectBaseConverter : JsonConverter + protected static bool SkipComments(JsonReader reader) { - /// - /// Skip JSON comments. - /// - protected static bool SkipComments(JsonReader reader) - { - var hasComments = false; - while (reader.TokenType == JsonToken.Comment && reader.Read()) - hasComments = true; + var hasComments = false; + while (reader.TokenType == JsonToken.Comment && reader.Read()) + hasComments = true; - return hasComments; - } + return hasComments; + } - protected static void ReadObject(PSObject value, JsonReader reader, bool bindTargetInfo, TargetSourceInfo sourceInfo) - { - SkipComments(reader); - var path = reader.Path; - if (reader.TokenType != JsonToken.StartObject || !reader.Read()) - throw new PipelineSerializationException(PSRuleResources.ReadJsonFailedExpectedToken, Enum.GetName(typeof(JsonToken), reader.TokenType), reader.Path); + protected static void ReadObject(PSObject value, JsonReader reader, bool bindTargetInfo, TargetSourceInfo sourceInfo) + { + SkipComments(reader); + var path = reader.Path; + if (reader.TokenType != JsonToken.StartObject || !reader.Read()) + throw new PipelineSerializationException(PSRuleResources.ReadJsonFailedExpectedToken, Enum.GetName(typeof(JsonToken), reader.TokenType), reader.Path); - string name = null; - var lineNumber = 0; - var linePosition = 0; + string name = null; + var lineNumber = 0; + var linePosition = 0; - if (bindTargetInfo && reader is IJsonLineInfo lineInfo && lineInfo.HasLineInfo()) - { - lineNumber = lineInfo.LineNumber; - linePosition = lineInfo.LinePosition; - } + if (bindTargetInfo && reader is IJsonLineInfo lineInfo && lineInfo.HasLineInfo()) + { + lineNumber = lineInfo.LineNumber; + linePosition = lineInfo.LinePosition; + } - // Read each token - while (reader.TokenType != JsonToken.EndObject) + // Read each token + while (reader.TokenType != JsonToken.EndObject) + { + switch (reader.TokenType) { - switch (reader.TokenType) - { - case JsonToken.PropertyName: - name = reader.Value.ToString(); - if (string.IsNullOrEmpty(name)) - { - reader.Skip(); - } - else if (name == PSRuleTargetInfo.PropertyName) - { - var targetInfo = ReadInfo(reader); - if (targetInfo != null) - value.SetTargetInfo(targetInfo); - } - break; + case JsonToken.PropertyName: + name = reader.Value.ToString(); + if (string.IsNullOrEmpty(name)) + { + reader.Skip(); + } + else if (name == PSRuleTargetInfo.PropertyName) + { + var targetInfo = ReadInfo(reader); + if (targetInfo != null) + value.SetTargetInfo(targetInfo); + } + break; - case JsonToken.StartObject: - var item = new PSObject(); - ReadObject(item, reader, bindTargetInfo: false, sourceInfo: null); - value.Properties.Add(new PSNoteProperty(name, value: item)); - break; + case JsonToken.StartObject: + var item = new PSObject(); + ReadObject(item, reader, bindTargetInfo: false, sourceInfo: null); + value.Properties.Add(new PSNoteProperty(name, value: item)); + break; - case JsonToken.StartArray: - var items = ReadArray(reader: reader); - value.Properties.Add(new PSNoteProperty(name, value: items)); - break; + case JsonToken.StartArray: + var items = ReadArray(reader: reader); + value.Properties.Add(new PSNoteProperty(name, value: items)); + break; - case JsonToken.Comment: - break; + case JsonToken.Comment: + break; - default: - value.Properties.Add(new PSNoteProperty(name, value: reader.Value)); - break; - } - if (!reader.Read() || reader.TokenType == JsonToken.None) - throw new PipelineSerializationException(PSRuleResources.ReadJsonFailedExpectedToken, Enum.GetName(typeof(JsonToken), reader.TokenType), reader.Path); - } - if (bindTargetInfo) - { - value.UseTargetInfo(out var info); - info.SetSource(sourceInfo?.File, lineNumber, linePosition); - if (string.IsNullOrEmpty(info.Path)) - info.Path = path; + default: + value.Properties.Add(new PSNoteProperty(name, value: reader.Value)); + break; } + if (!reader.Read() || reader.TokenType == JsonToken.None) + throw new PipelineSerializationException(PSRuleResources.ReadJsonFailedExpectedToken, Enum.GetName(typeof(JsonToken), reader.TokenType), reader.Path); } - - protected static PSRuleTargetInfo ReadInfo(JsonReader reader) + if (bindTargetInfo) { - if (!reader.Read() || reader.TokenType == JsonToken.None || reader.TokenType != JsonToken.StartObject) - return null; - - var s = JsonSerializer.Create(); - return s.Deserialize(reader); + value.UseTargetInfo(out var info); + info.SetSource(sourceInfo?.File, lineNumber, linePosition); + if (string.IsNullOrEmpty(info.Path)) + info.Path = path; } + } - protected static PSObject[] ReadArray(JsonReader reader) - { - SkipComments(reader); - if (reader.TokenType != JsonToken.StartArray || !reader.Read()) - throw new PipelineSerializationException(PSRuleResources.ReadJsonFailedExpectedToken, Enum.GetName(typeof(JsonToken), reader.TokenType), reader.Path); - - var result = new List(); - - // Read until the end of the array - while (reader.TokenType != JsonToken.EndArray) - { - switch (reader.TokenType) - { - case JsonToken.StartObject: - var item = new PSObject(); - ReadObject(item, reader, bindTargetInfo: false, sourceInfo: null); - result.Add(item); - break; + protected static PSRuleTargetInfo ReadInfo(JsonReader reader) + { + if (!reader.Read() || reader.TokenType == JsonToken.None || reader.TokenType != JsonToken.StartObject) + return null; - case JsonToken.StartArray: - result.Add(PSObject.AsPSObject(ReadArray(reader))); - break; + var s = JsonSerializer.Create(); + return s.Deserialize(reader); + } - case JsonToken.Null: - result.Add(null); - break; + protected static PSObject[] ReadArray(JsonReader reader) + { + SkipComments(reader); + if (reader.TokenType != JsonToken.StartArray || !reader.Read()) + throw new PipelineSerializationException(PSRuleResources.ReadJsonFailedExpectedToken, Enum.GetName(typeof(JsonToken), reader.TokenType), reader.Path); - case JsonToken.Comment: - break; + var result = new List(); - default: - result.Add(PSObject.AsPSObject(reader.Value)); - break; - } - if (!reader.Read() || reader.TokenType == JsonToken.None) - throw new PipelineSerializationException(PSRuleResources.ReadJsonFailedExpectedToken, Enum.GetName(typeof(JsonToken), reader.TokenType), reader.Path); + // Read until the end of the array + while (reader.TokenType != JsonToken.EndArray) + { + switch (reader.TokenType) + { + case JsonToken.StartObject: + var item = new PSObject(); + ReadObject(item, reader, bindTargetInfo: false, sourceInfo: null); + result.Add(item); + break; + + case JsonToken.StartArray: + result.Add(PSObject.AsPSObject(ReadArray(reader))); + break; + + case JsonToken.Null: + result.Add(null); + break; + + case JsonToken.Comment: + break; + + default: + result.Add(PSObject.AsPSObject(reader.Value)); + break; } - return result.ToArray(); + if (!reader.Read() || reader.TokenType == JsonToken.None) + throw new PipelineSerializationException(PSRuleResources.ReadJsonFailedExpectedToken, Enum.GetName(typeof(JsonToken), reader.TokenType), reader.Path); } + return result.ToArray(); } +} - /// - /// A custom serializer to correctly convert PSObject properties to JSON instead of CLIXML. - /// - internal sealed class PSObjectJsonConverter : PSObjectBaseConverter +/// +/// A custom serializer to correctly convert PSObject properties to JSON instead of CLIXML. +/// +internal sealed class PSObjectJsonConverter : PSObjectBaseConverter +{ + public override bool CanConvert(Type objectType) { - public override bool CanConvert(Type objectType) - { - return objectType == typeof(PSObject); - } - - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - if (value is not PSObject obj) - throw new ArgumentException(message: PSRuleResources.SerializeNullPSObject, paramName: nameof(value)); - - if (WriteFileSystemInfo(writer, value, serializer) || WriteBaseObject(writer, obj, serializer)) - return; + return objectType == typeof(PSObject); + } - writer.WriteStartObject(); - foreach (var property in obj.Properties) - { - // Ignore properties that are not readable or can cause race condition - if (!property.IsGettable || property.Value is PSDriveInfo || property.Value is ProviderInfo || property.Value is DirectoryInfo) - continue; + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + if (value is not PSObject obj) + throw new ArgumentException(message: PSRuleResources.SerializeNullPSObject, paramName: nameof(value)); - writer.WritePropertyName(property.Name); - serializer.Serialize(writer, property.Value); - } - writer.WriteEndObject(); - } + if (WriteFileSystemInfo(writer, value, serializer) || WriteBaseObject(writer, obj, serializer)) + return; - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + writer.WriteStartObject(); + foreach (var property in obj.Properties) { - // Create target object based on JObject - var result = existingValue as PSObject ?? new PSObject(); + // Ignore properties that are not readable or can cause race condition + if (!property.IsGettable || property.Value is PSDriveInfo || property.Value is ProviderInfo || property.Value is DirectoryInfo) + continue; - // Read tokens - ReadObject(result, reader, bindTargetInfo: true, sourceInfo: null); - return result; + writer.WritePropertyName(property.Name); + serializer.Serialize(writer, property.Value); } + writer.WriteEndObject(); + } - /// - /// Serialize a file system info object. - /// - private static bool WriteFileSystemInfo(JsonWriter writer, object value, JsonSerializer serializer) - { - if (value is not FileSystemInfo fileSystemInfo) - return false; + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + // Create target object based on JObject + var result = existingValue as PSObject ?? new PSObject(); - serializer.Serialize(writer, fileSystemInfo.FullName); - return true; - } + // Read tokens + ReadObject(result, reader, bindTargetInfo: true, sourceInfo: null); + return result; + } - /// - /// Serialize the base object. - /// - private static bool WriteBaseObject(JsonWriter writer, PSObject value, JsonSerializer serializer) - { - if (value.BaseObject == null || value.HasNoteProperty()) - return false; + /// + /// Serialize a file system info object. + /// + private static bool WriteFileSystemInfo(JsonWriter writer, object value, JsonSerializer serializer) + { + if (value is not FileSystemInfo fileSystemInfo) + return false; - serializer.Serialize(writer, value.BaseObject); - return true; - } + serializer.Serialize(writer, fileSystemInfo.FullName); + return true; } /// - /// A custom serializer to convert PSObjects that may or may not be in a JSON array to an a PSObject array. + /// Serialize the base object. /// - internal sealed class PSObjectArrayJsonConverter : PSObjectBaseConverter + private static bool WriteBaseObject(JsonWriter writer, PSObject value, JsonSerializer serializer) { - private readonly TargetSourceInfo _SourceInfo; + if (value.BaseObject == null || value.HasNoteProperty()) + return false; - public PSObjectArrayJsonConverter(TargetSourceInfo sourceInfo) - { - _SourceInfo = sourceInfo; - } + serializer.Serialize(writer, value.BaseObject); + return true; + } +} - public override bool CanConvert(Type objectType) - { - return objectType == typeof(PSObject[]); - } +/// +/// A custom serializer to convert PSObjects that may or may not be in a JSON array to an a PSObject array. +/// +internal sealed class PSObjectArrayJsonConverter : PSObjectBaseConverter +{ + private readonly TargetSourceInfo _SourceInfo; - public override bool CanWrite => false; + public PSObjectArrayJsonConverter(TargetSourceInfo sourceInfo) + { + _SourceInfo = sourceInfo; + } - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - throw new NotImplementedException(); - } + public override bool CanConvert(Type objectType) + { + return objectType == typeof(PSObject[]); + } - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - SkipComments(reader); - if (reader.TokenType != JsonToken.StartObject && reader.TokenType != JsonToken.StartArray) - throw new PipelineSerializationException(PSRuleResources.ReadJsonFailedExpectedToken, Enum.GetName(typeof(JsonToken), reader.TokenType), reader.Path); + public override bool CanWrite => false; - var result = new List(); - var isArray = reader.TokenType == JsonToken.StartArray; + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + throw new NotImplementedException(); + } - if (isArray) - reader.Read(); + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + SkipComments(reader); + if (reader.TokenType != JsonToken.StartObject && reader.TokenType != JsonToken.StartArray) + throw new PipelineSerializationException(PSRuleResources.ReadJsonFailedExpectedToken, Enum.GetName(typeof(JsonToken), reader.TokenType), reader.Path); - while (reader.TokenType != JsonToken.None && (!isArray || (isArray && reader.TokenType != JsonToken.EndArray))) - { - if (SkipComments(reader)) - continue; + var result = new List(); + var isArray = reader.TokenType == JsonToken.StartArray; - var value = new PSObject(); - ReadObject(value, reader, bindTargetInfo: true, sourceInfo: _SourceInfo); - result.Add(value); + if (isArray) + reader.Read(); - // Consume the EndObject token - reader.Read(); - } - return result.ToArray(); + while (reader.TokenType != JsonToken.None && (!isArray || (isArray && reader.TokenType != JsonToken.EndArray))) + { + if (SkipComments(reader)) + continue; + + var value = new PSObject(); + ReadObject(value, reader, bindTargetInfo: true, sourceInfo: _SourceInfo); + result.Add(value); + + // Consume the EndObject token + reader.Read(); } + return result.ToArray(); } +} - /// - /// A custom serializer to convert ErrorCategory to a string. - /// - internal sealed class ErrorCategoryJsonConverter : JsonConverter +/// +/// A custom serializer to convert ErrorCategory to a string. +/// +internal sealed class ErrorCategoryJsonConverter : JsonConverter +{ + public override bool CanConvert(Type objectType) { - public override bool CanConvert(Type objectType) - { - return objectType == typeof(ErrorCategory); - } + return objectType == typeof(ErrorCategory); + } - public override bool CanWrite => true; + public override bool CanWrite => true; - public override bool CanRead => false; + public override bool CanRead => false; - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - writer.WriteValue(Enum.GetName(typeof(ErrorCategory), value)); - } - - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - throw new NotImplementedException(); - } + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + writer.WriteValue(Enum.GetName(typeof(ErrorCategory), value)); } - /// - /// A custom serializer to convert Baseline object to JSON - /// - internal sealed class BaselineJsonConverter : JsonConverter + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { - public override bool CanConvert(Type objectType) - { - return objectType == typeof(Baseline); - } + throw new NotImplementedException(); + } +} - public override bool CanWrite => true; +/// +/// A custom serializer to convert Baseline object to JSON +/// +internal sealed class BaselineJsonConverter : JsonConverter +{ + public override bool CanConvert(Type objectType) + { + return objectType == typeof(Baseline); + } - public override bool CanRead => false; + public override bool CanWrite => true; - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - BaselineJsonSerializationMapper.MapBaseline(writer, serializer, value as Baseline); - } + public override bool CanRead => false; - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - throw new NotImplementedException(); - } + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + BaselineJsonSerializationMapper.MapBaseline(writer, serializer, value as Baseline); } - /// - /// A contract resolver to order properties alphabetically - /// - internal sealed class OrderedPropertiesContractResolver : DefaultContractResolver + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { - public OrderedPropertiesContractResolver() : base() - { - NamingStrategy = new CamelCaseNamingStrategy - { - ProcessDictionaryKeys = true, - OverrideSpecifiedNames = true - }; - } + throw new NotImplementedException(); + } +} - protected override IList CreateProperties(Type type, MemberSerialization memberSerialization) +/// +/// A contract resolver to order properties alphabetically +/// +internal sealed class OrderedPropertiesContractResolver : DefaultContractResolver +{ + public OrderedPropertiesContractResolver() : base() + { + NamingStrategy = new CamelCaseNamingStrategy { - return base - .CreateProperties(type, memberSerialization) - .OrderBy(prop => prop.PropertyName) - .OrderBy(prop => prop.Order) - .ToList(); - } + ProcessDictionaryKeys = true, + OverrideSpecifiedNames = true + }; } - /// - /// A custom deserializer to convert JSON into a . - /// - internal sealed class ResourceObjectJsonConverter : JsonConverter + protected override IList CreateProperties(Type type, MemberSerialization memberSerialization) { - private const string FIELD_APIVERSION = "apiVersion"; - private const string FIELD_KIND = "kind"; - private const string FIELD_METADATA = "metadata"; - private const string FIELD_SPEC = "spec"; - private const string FIELD_SYNOPSIS = "Synopsis: "; + return base + .CreateProperties(type, memberSerialization) + .OrderBy(prop => prop.PropertyName) + .OrderBy(prop => prop.Order) + .ToList(); + } +} - public override bool CanRead => true; +/// +/// A custom deserializer to convert JSON into a . +/// +internal sealed class ResourceObjectJsonConverter : JsonConverter +{ + private const string FIELD_APIVERSION = "apiVersion"; + private const string FIELD_KIND = "kind"; + private const string FIELD_METADATA = "metadata"; + private const string FIELD_SPEC = "spec"; + private const string FIELD_SYNOPSIS = "Synopsis: "; - public override bool CanWrite => false; + public override bool CanRead => true; - private readonly SpecFactory _Factory; + public override bool CanWrite => false; - public ResourceObjectJsonConverter() - { - _Factory = new SpecFactory(); - } + private readonly SpecFactory _Factory; - public override bool CanConvert(Type objectType) - { - return objectType == typeof(ResourceObject); - } + public ResourceObjectJsonConverter() + { + _Factory = new SpecFactory(); + } - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - var resource = MapResource(reader, serializer); - return new ResourceObject(resource); - } + public override bool CanConvert(Type objectType) + { + return objectType == typeof(ResourceObject); + } - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - throw new NotImplementedException(); - } + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var resource = MapResource(reader, serializer); + return new ResourceObject(resource); + } - private IResource MapResource(JsonReader reader, JsonSerializer serializer) - { - reader.GetSourceExtent(RunspaceContext.CurrentThread.Source.File.Path, out var extent); - reader.SkipComments(out _); - if (reader.TokenType != JsonToken.StartObject || !reader.Read()) - throw new PipelineSerializationException(PSRuleResources.ReadJsonFailed); + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + throw new NotImplementedException(); + } + + private IResource MapResource(JsonReader reader, JsonSerializer serializer) + { + reader.GetSourceExtent(RunspaceContext.CurrentThread.Source.File.Path, out var extent); + reader.SkipComments(out _); + if (reader.TokenType != JsonToken.StartObject || !reader.Read()) + throw new PipelineSerializationException(PSRuleResources.ReadJsonFailed); - IResource result = null; - string apiVersion = null; - string kind = null; - ResourceMetadata metadata = null; - CommentMetadata comment = null; + IResource result = null; + string apiVersion = null; + string kind = null; + ResourceMetadata metadata = null; + CommentMetadata comment = null; - while (reader.TokenType != JsonToken.EndObject) + while (reader.TokenType != JsonToken.EndObject) + { + if (reader.TokenType == JsonToken.PropertyName) { - if (reader.TokenType == JsonToken.PropertyName) - { - var propertyName = reader.Value.ToString(); + var propertyName = reader.Value.ToString(); - // Read apiVersion - if (TryApiVersion(reader, propertyName, apiVersion: out var apiVersionValue)) - { - apiVersion = apiVersionValue; - } + // Read apiVersion + if (TryApiVersion(reader, propertyName, apiVersion: out var apiVersionValue)) + { + apiVersion = apiVersionValue; + } - // Read kind - else if (TryKind(reader, propertyName, kind: out var kindValue)) - { - kind = kindValue; - } + // Read kind + else if (TryKind(reader, propertyName, kind: out var kindValue)) + { + kind = kindValue; + } - // Read metadata - else if (TryMetadata( - reader: reader, - serializer: serializer, - propertyName: propertyName, - metadata: out var metadataValue)) - { - metadata = metadataValue; - } + // Read metadata + else if (TryMetadata( + reader: reader, + serializer: serializer, + propertyName: propertyName, + metadata: out var metadataValue)) + { + metadata = metadataValue; + } - // Try Spec - else if (kind != null && TrySpec( - reader: reader, - serializer: serializer, - propertyName: propertyName, - apiVersion: apiVersion, - kind: kind, - metadata: metadata, - comment: comment, - extent: extent, - spec: out var specValue)) - { - result = specValue; + // Try Spec + else if (kind != null && TrySpec( + reader: reader, + serializer: serializer, + propertyName: propertyName, + apiVersion: apiVersion, + kind: kind, + metadata: metadata, + comment: comment, + extent: extent, + spec: out var specValue)) + { + result = specValue; - // Break out of loop if result is populated - // Needed so we don't read more than we have to - if (result != null && reader.TokenType == JsonToken.EndObject) - break; - } - else - { - reader.Skip(); - } + // Break out of loop if result is populated + // Needed so we don't read more than we have to + if (result != null && reader.TokenType == JsonToken.EndObject) + break; } + else + { + reader.Skip(); + } + } - else if (reader.TokenType == JsonToken.Comment) + else if (reader.TokenType == JsonToken.Comment) + { + var commentLine = reader.Value.ToString().TrimStart(); + if (commentLine.Length > FIELD_SYNOPSIS.Length && commentLine.StartsWith(FIELD_SYNOPSIS)) { - var commentLine = reader.Value.ToString().TrimStart(); - if (commentLine.Length > FIELD_SYNOPSIS.Length && commentLine.StartsWith(FIELD_SYNOPSIS)) + comment = new CommentMetadata { - comment = new CommentMetadata - { - Synopsis = commentLine.Substring(FIELD_SYNOPSIS.Length) - }; - } + Synopsis = commentLine.Substring(FIELD_SYNOPSIS.Length) + }; } - reader.Read(); } - return result; + reader.Read(); } + return result; + } - private static bool TryApiVersion(JsonReader reader, string propertyName, out string apiVersion) + private static bool TryApiVersion(JsonReader reader, string propertyName, out string apiVersion) + { + apiVersion = null; + if (propertyName == FIELD_APIVERSION) { - apiVersion = null; - if (propertyName == FIELD_APIVERSION) - { - apiVersion = reader.ReadAsString(); - return true; - } - return false; + apiVersion = reader.ReadAsString(); + return true; } + return false; + } - private static bool TryKind(JsonReader reader, string propertyName, out string kind) + private static bool TryKind(JsonReader reader, string propertyName, out string kind) + { + kind = null; + if (propertyName == FIELD_KIND) { - kind = null; - if (propertyName == FIELD_KIND) - { - kind = reader.ReadAsString(); - return true; - } - return false; + kind = reader.ReadAsString(); + return true; } + return false; + } - private static bool TryMetadata(JsonReader reader, JsonSerializer serializer, string propertyName, out ResourceMetadata metadata) + private static bool TryMetadata(JsonReader reader, JsonSerializer serializer, string propertyName, out ResourceMetadata metadata) + { + metadata = null; + if (propertyName == FIELD_METADATA) { - metadata = null; - if (propertyName == FIELD_METADATA) + if (reader.Read() && reader.TokenType == JsonToken.StartObject) { - if (reader.Read() && reader.TokenType == JsonToken.StartObject) - { - metadata = serializer.Deserialize(reader); - return true; - } + metadata = serializer.Deserialize(reader); + return true; } - return false; } + return false; + } - private bool TrySpec( - JsonReader reader, - JsonSerializer serializer, - string propertyName, - string apiVersion, - string kind, - ResourceMetadata metadata, - CommentMetadata comment, - ISourceExtent extent, - out IResource spec) + private bool TrySpec( + JsonReader reader, + JsonSerializer serializer, + string propertyName, + string apiVersion, + string kind, + ResourceMetadata metadata, + CommentMetadata comment, + ISourceExtent extent, + out IResource spec) + { + spec = null; + if (propertyName == FIELD_SPEC && _Factory.TryDescriptor(apiVersion: apiVersion, name: kind, descriptor: out var descriptor)) { - spec = null; - if (propertyName == FIELD_SPEC && _Factory.TryDescriptor(apiVersion: apiVersion, name: kind, descriptor: out var descriptor)) + if (reader.Read() && reader.TokenType == JsonToken.StartObject) { - if (reader.Read() && reader.TokenType == JsonToken.StartObject) - { - reader.SkipComments(out _); - var deserializedSpec = serializer.Deserialize(reader, objectType: descriptor.SpecType); - spec = descriptor.CreateInstance( - source: RunspaceContext.CurrentThread.Source.File, - metadata: metadata, - comment: comment, - extent: extent, - spec: deserializedSpec - ); - return true; - } + reader.SkipComments(out _); + var deserializedSpec = serializer.Deserialize(reader, objectType: descriptor.SpecType); + spec = descriptor.CreateInstance( + source: RunspaceContext.CurrentThread.Source.File, + metadata: metadata, + comment: comment, + extent: extent, + spec: deserializedSpec + ); + return true; } - return false; } + return false; } +} - /// - /// A JSON converter for de/serializing a field map. - /// - internal sealed class FieldMapJsonConverter : JsonConverter - { - public override bool CanRead => true; +/// +/// A JSON converter for de/serializing a field map. +/// +internal sealed class FieldMapJsonConverter : JsonConverter +{ + public override bool CanRead => true; - public override bool CanWrite => false; + public override bool CanWrite => false; - public override bool CanConvert(Type objectType) - { - return objectType == typeof(FieldMap); - } + public override bool CanConvert(Type objectType) + { + return objectType == typeof(FieldMap); + } - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - var fieldMap = existingValue as FieldMap ?? new FieldMap(); - ReadFieldMap(fieldMap, reader); - return fieldMap; - } + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var fieldMap = existingValue as FieldMap ?? new FieldMap(); + ReadFieldMap(fieldMap, reader); + return fieldMap; + } - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - throw new NotImplementedException(); - } + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + throw new NotImplementedException(); + } + + private static void ReadFieldMap(FieldMap map, JsonReader reader) + { + if (reader.TokenType != JsonToken.StartObject || !reader.Read()) + throw new PipelineSerializationException(PSRuleResources.ReadJsonFailed); - private static void ReadFieldMap(FieldMap map, JsonReader reader) + string propertyName = null; + while (reader.TokenType != JsonToken.EndObject) { - if (reader.TokenType != JsonToken.StartObject || !reader.Read()) - throw new PipelineSerializationException(PSRuleResources.ReadJsonFailed); + if (reader.TokenType == JsonToken.PropertyName) + { + propertyName = reader.Value.ToString(); + } - string propertyName = null; - while (reader.TokenType != JsonToken.EndObject) + else if (reader.TokenType == JsonToken.StartArray) { - if (reader.TokenType == JsonToken.PropertyName) - { - propertyName = reader.Value.ToString(); - } + var items = new List(); - else if (reader.TokenType == JsonToken.StartArray) + while (reader.TokenType != JsonToken.EndArray) { - var items = new List(); + if (SkipComments(reader)) + continue; - while (reader.TokenType != JsonToken.EndArray) + var item = reader.ReadAsString(); + if (!string.IsNullOrEmpty(item)) { - if (SkipComments(reader)) - continue; - - var item = reader.ReadAsString(); - if (!string.IsNullOrEmpty(item)) - { - items.Add(item); - } + items.Add(item); } - - map.Set(propertyName, items.ToArray()); } - reader.Read(); + map.Set(propertyName, items.ToArray()); } - } - - /// - /// Skip JSON comments. - /// - private static bool SkipComments(JsonReader reader) - { - var hasComments = false; - while (reader.TokenType == JsonToken.Comment && reader.Read()) - hasComments = true; - return hasComments; + reader.Read(); } } /// - /// A JSON converter that handles string to string array. + /// Skip JSON comments. /// - internal sealed class StringArrayJsonConverter : JsonConverter + private static bool SkipComments(JsonReader reader) { - public override bool CanRead => true; - public override bool CanWrite => false; + var hasComments = false; + while (reader.TokenType == JsonToken.Comment && reader.Read()) + hasComments = true; - public override bool CanConvert(Type objectType) - { - return typeof(string[]).IsAssignableFrom(objectType); - } + return hasComments; + } +} - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) +/// +/// A JSON converter that handles string to string array. +/// +internal sealed class StringArrayJsonConverter : JsonConverter +{ + public override bool CanRead => true; + public override bool CanWrite => false; + + public override bool CanConvert(Type objectType) + { + return typeof(string[]).IsAssignableFrom(objectType); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + if (reader.TryConsume(JsonToken.StartArray)) { - if (reader.TryConsume(JsonToken.StartArray)) + reader.SkipComments(out _); + var result = new List(); + while (reader.TryConsume(JsonToken.String, out var s_object) && s_object is string s) { + result.Add(s); reader.SkipComments(out _); - var result = new List(); - while (reader.TryConsume(JsonToken.String, out var s_object) && s_object is string s) - { - result.Add(s); - reader.SkipComments(out _); - } - return result.ToArray(); - } - else if (reader.TokenType == JsonToken.String && reader.Value is string s) - { - return new string[] { s }; } - return null; + return result.ToArray(); } - - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + else if (reader.TokenType == JsonToken.String && reader.Value is string s) { - throw new NotImplementedException(); + return new string[] { s }; } + return null; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + throw new NotImplementedException(); + } +} + +/// +/// A custom converter for deserializing JSON into a language expression. +/// +internal sealed class LanguageExpressionJsonConverter : JsonConverter +{ + private const string OPERATOR_IF = "if"; + + private readonly LanguageExpressionFactory _Factory; + private readonly FunctionBuilder _FunctionBuilder; + + public LanguageExpressionJsonConverter() + { + _Factory = new LanguageExpressionFactory(); + _FunctionBuilder = new FunctionBuilder(); + } + + public override bool CanRead => true; + public override bool CanWrite => false; + + public override bool CanConvert(Type objectType) + { + return typeof(LanguageExpression).IsAssignableFrom(objectType); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var expression = MapOperator(OPERATOR_IF, null, null, reader); + return new LanguageIf(expression); + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + throw new NotImplementedException(); } /// - /// A custom converter for deserializing JSON into a language expression. + /// Map an operator. /// - internal sealed class LanguageExpressionJsonConverter : JsonConverter + private LanguageOperator MapOperator(string type, LanguageExpression.PropertyBag properties, LanguageExpression subselector, JsonReader reader) { - private const string OPERATOR_IF = "if"; + if (TryExpression(type, properties, out LanguageOperator result)) + { + reader.SkipComments(out _); - private readonly LanguageExpressionFactory _Factory; - private readonly FunctionBuilder _FunctionBuilder; + // If and Not + if (reader.TryConsume(JsonToken.StartObject)) + { + result.Add(MapExpression(reader)); + if (type != "if") + reader.Consume(JsonToken.EndObject); + } + // AllOf and AnyOf + else if (reader.TryConsume(JsonToken.StartArray)) + { + while (reader.TokenType != JsonToken.EndArray) + { + if (reader.SkipComments(out var hasComments) && hasComments) + continue; - public LanguageExpressionJsonConverter() - { - _Factory = new LanguageExpressionFactory(); - _FunctionBuilder = new FunctionBuilder(); + if (reader.TryConsume(JsonToken.StartObject)) + { + result.Add(MapExpression(reader)); + reader.Consume(JsonToken.EndObject); + } + if (reader.TokenType == JsonToken.EndObject) + throw new PipelineSerializationException(PSRuleResources.ReadJsonFailedExpectedToken, Enum.GetName(typeof(JsonToken), reader.TokenType), reader.Path); + } + reader.Consume(JsonToken.EndArray); + reader.SkipComments(out _); + } + result.Subselector = subselector; } + return result; + } - public override bool CanRead => true; - public override bool CanWrite => false; - - public override bool CanConvert(Type objectType) + private LanguageExpression MapCondition(string type, LanguageExpression.PropertyBag properties, JsonReader reader) + { + if (TryExpression(type, null, out LanguageCondition result)) { - return typeof(LanguageExpression).IsAssignableFrom(objectType); + while (reader.TokenType != JsonToken.EndObject) + { + MapProperty(properties, reader, out _, out _); + } + result.Add(properties); } + return result; + } - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + private LanguageExpression MapExpression(JsonReader reader) + { + LanguageExpression result = null; + var properties = new LanguageExpression.PropertyBag(); + reader.SkipComments(out _); + MapProperty(properties, reader, out var key, out var subselector); + if (key != null && TryCondition(key)) { - var expression = MapOperator(OPERATOR_IF, null, null, reader); - return new LanguageIf(expression); + result = MapCondition(key, properties, reader); } - - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + else if ((reader.TokenType == JsonToken.StartObject || reader.TokenType == JsonToken.StartArray) && + TryOperator(key)) { - throw new NotImplementedException(); + var op = MapOperator(key, properties, subselector, reader); + MapProperty(properties, reader, out _, out subselector); + if (subselector != null) + op.Subselector = subselector; + + result = op; } + return result; + } - /// - /// Map an operator. - /// - private LanguageOperator MapOperator(string type, LanguageExpression.PropertyBag properties, LanguageExpression subselector, JsonReader reader) + private ExpressionFnOuter MapFunction(string type, JsonReader reader) + { + _FunctionBuilder.Push(); + while (reader.TokenType != JsonToken.EndObject) { - if (TryExpression(type, properties, out LanguageOperator result)) + if (reader.Value is string name) { - reader.SkipComments(out _); - - // If and Not + reader.Consume(JsonToken.PropertyName); if (reader.TryConsume(JsonToken.StartObject)) { - result.Add(MapExpression(reader)); - if (type != "if") - reader.Consume(JsonToken.EndObject); + var child = MapFunction(name, reader); + _FunctionBuilder.Add(name, child); + reader.Consume(JsonToken.EndObject); } - // AllOf and AnyOf else if (reader.TryConsume(JsonToken.StartArray)) { - while (reader.TokenType != JsonToken.EndArray) - { - if (reader.SkipComments(out var hasComments) && hasComments) - continue; - - if (reader.TryConsume(JsonToken.StartObject)) - { - result.Add(MapExpression(reader)); - reader.Consume(JsonToken.EndObject); - } - if (reader.TokenType == JsonToken.EndObject) - throw new PipelineSerializationException(PSRuleResources.ReadJsonFailedExpectedToken, Enum.GetName(typeof(JsonToken), reader.TokenType), reader.Path); - } + var sequence = MapSequence(name, reader); + _FunctionBuilder.Add(name, sequence); reader.Consume(JsonToken.EndArray); - reader.SkipComments(out _); } - result.Subselector = subselector; - } - return result; - } - - private LanguageExpression MapCondition(string type, LanguageExpression.PropertyBag properties, JsonReader reader) - { - if (TryExpression(type, null, out LanguageCondition result)) - { - while (reader.TokenType != JsonToken.EndObject) + else { - MapProperty(properties, reader, out _, out _); + _FunctionBuilder.Add(name, reader.Value); + reader.Read(); } - result.Add(properties); } - return result; } + var result = _FunctionBuilder.Pop(); + return result; + } - private LanguageExpression MapExpression(JsonReader reader) + private object MapSequence(string name, JsonReader reader) + { + var result = new List(); + while (reader.TokenType != JsonToken.EndArray) { - LanguageExpression result = null; - var properties = new LanguageExpression.PropertyBag(); - reader.SkipComments(out _); - MapProperty(properties, reader, out var key, out var subselector); - if (key != null && TryCondition(key)) + if (reader.TryConsume(JsonToken.StartObject)) { - result = MapCondition(key, properties, reader); + var child = MapFunction(name, reader); + result.Add(child); + reader.Consume(JsonToken.EndObject); } - else if ((reader.TokenType == JsonToken.StartObject || reader.TokenType == JsonToken.StartArray) && - TryOperator(key)) + else { - var op = MapOperator(key, properties, subselector, reader); - MapProperty(properties, reader, out _, out subselector); - if (subselector != null) - op.Subselector = subselector; - - result = op; + result.Add(reader.Value); + reader.Read(); } - return result; } + return result.ToArray(); + } + + private void MapProperty(LanguageExpression.PropertyBag properties, JsonReader reader, out string name, out LanguageExpression subselector) + { + //if (reader.TokenType != JsonToken.StartObject || !reader.Read()) + // throw new PipelineSerializationException(PSRuleResources.ReadJsonFailedExpectedToken, Enum.GetName(typeof(JsonToken), reader.TokenType)); - private ExpressionFnOuter MapFunction(string type, JsonReader reader) + name = null; + subselector = null; + reader.SkipComments(out _); + while (reader.TokenType == JsonToken.PropertyName) { - _FunctionBuilder.Push(); - while (reader.TokenType != JsonToken.EndObject) + var key = reader.Value.ToString(); + if (TryCondition(key) || TryOperator(key)) + name = key; + + if (reader.Read()) { - if (reader.Value is string name) + // value: + if (TryValue(key, reader, out var value)) { - reader.Consume(JsonToken.PropertyName); - if (reader.TryConsume(JsonToken.StartObject)) - { - var child = MapFunction(name, reader); - _FunctionBuilder.Add(name, child); - reader.Consume(JsonToken.EndObject); - } - else if (reader.TryConsume(JsonToken.StartArray)) - { - var sequence = MapSequence(name, reader); - _FunctionBuilder.Add(name, sequence); - reader.Consume(JsonToken.EndArray); - } - else - { - _FunctionBuilder.Add(name, reader.Value); - reader.Read(); - } + properties[key] = value; + reader.Read(); } - } - var result = _FunctionBuilder.Pop(); - return result; - } + else if (TryCondition(key) && reader.TryConsume(JsonToken.StartObject)) + { + if (TryFunction(reader, key, out var fn)) + properties.Add(key, fn); - private object MapSequence(string name, JsonReader reader) - { - var result = new List(); - while (reader.TokenType != JsonToken.EndArray) - { - if (reader.TryConsume(JsonToken.StartObject)) + reader.Consume(JsonToken.EndObject); + } + // where: + else if (TrySubSelector(key) && reader.TryConsume(JsonToken.StartObject)) { - var child = MapFunction(name, reader); - result.Add(child); + subselector = MapExpression(reader); reader.Consume(JsonToken.EndObject); } - else + else if (reader.TokenType == JsonToken.StartObject) { - result.Add(reader.Value); - reader.Read(); + break; } - } - return result.ToArray(); - } - - private void MapProperty(LanguageExpression.PropertyBag properties, JsonReader reader, out string name, out LanguageExpression subselector) - { - //if (reader.TokenType != JsonToken.StartObject || !reader.Read()) - // throw new PipelineSerializationException(PSRuleResources.ReadJsonFailedExpectedToken, Enum.GetName(typeof(JsonToken), reader.TokenType)); - - name = null; - subselector = null; - reader.SkipComments(out _); - while (reader.TokenType == JsonToken.PropertyName) - { - var key = reader.Value.ToString(); - if (TryCondition(key) || TryOperator(key)) - name = key; - - if (reader.Read()) + else if (reader.TokenType == JsonToken.StartArray) { - // value: - if (TryValue(key, reader, out var value)) - { - properties[key] = value; - reader.Read(); - } - else if (TryCondition(key) && reader.TryConsume(JsonToken.StartObject)) - { - if (TryFunction(reader, key, out var fn)) - properties.Add(key, fn); - - reader.Consume(JsonToken.EndObject); - } - // where: - else if (TrySubSelector(key) && reader.TryConsume(JsonToken.StartObject)) - { - subselector = MapExpression(reader); - reader.Consume(JsonToken.EndObject); - } - else if (reader.TokenType == JsonToken.StartObject) - { + if (!TryCondition(key)) break; - } - else if (reader.TokenType == JsonToken.StartArray) - { - if (!TryCondition(key)) - break; - - var objects = new List(); - while (reader.TokenType != JsonToken.EndArray) - { - if (reader.SkipComments(out var hasComments) && hasComments) - continue; - - var item = reader.ReadAsString(); - if (!string.IsNullOrEmpty(item)) - objects.Add(item); - } - properties.Add(key, objects.ToArray()); - reader.Consume(JsonToken.EndArray); - } - else + + var objects = new List(); + while (reader.TokenType != JsonToken.EndArray) { - properties.Add(key, reader.Value); - reader.Read(); + if (reader.SkipComments(out var hasComments) && hasComments) + continue; + + var item = reader.ReadAsString(); + if (!string.IsNullOrEmpty(item)) + objects.Add(item); } + properties.Add(key, objects.ToArray()); + reader.Consume(JsonToken.EndArray); + } + else + { + properties.Add(key, reader.Value); + reader.Read(); } - reader.SkipComments(out _); } + reader.SkipComments(out _); } + } - private bool TrySubSelector(string key) - { - return _Factory.IsSubselector(key); - } - - private bool TryOperator(string key) - { - return _Factory.IsOperator(key); - } + private bool TrySubSelector(string key) + { + return _Factory.IsSubselector(key); + } - private bool TryCondition(string key) - { - return _Factory.IsCondition(key); - } + private bool TryOperator(string key) + { + return _Factory.IsOperator(key); + } - private bool TryValue(string key, JsonReader reader, out object value) - { - value = null; - if (key != "value") - return false; + private bool TryCondition(string key) + { + return _Factory.IsCondition(key); + } - if (reader.TryConsume(JsonToken.StartObject) && - TryFunction(reader, reader.Value as string, out var fn)) - { - value = fn; - return true; - } + private bool TryValue(string key, JsonReader reader, out object value) + { + value = null; + if (key != "value") return false; - } - private bool TryFunction(JsonReader reader, string key, out ExpressionFnOuter fn) + if (reader.TryConsume(JsonToken.StartObject) && + TryFunction(reader, reader.Value as string, out var fn)) { - fn = null; - if (!IsFunction(reader)) - return false; - - reader.Consume(JsonToken.PropertyName); - reader.Consume(JsonToken.StartObject); - fn = MapFunction("$", reader); - if (fn == null) - throw new Exception(); - - reader.Consume(JsonToken.EndObject); + value = fn; return true; } + return false; + } - private static bool IsFunction(JsonReader reader) - { - return reader.TokenType == JsonToken.PropertyName && - reader.Value is string s && - s == "$"; - } - - private bool TryExpression(string type, LanguageExpression.PropertyBag properties, out T expression) where T : LanguageExpression - { - expression = null; - if (_Factory.TryDescriptor(type, out var descriptor)) - { - expression = (T)descriptor.CreateInstance( - source: RunspaceContext.CurrentThread.Source.File, - properties: properties - ); - return expression != null; - } + private bool TryFunction(JsonReader reader, string key, out ExpressionFnOuter fn) + { + fn = null; + if (!IsFunction(reader)) return false; - } + + reader.Consume(JsonToken.PropertyName); + reader.Consume(JsonToken.StartObject); + fn = MapFunction("$", reader); + if (fn == null) + throw new Exception(); + + reader.Consume(JsonToken.EndObject); + return true; } - /// - /// A converter for converting to/ from JSON. - /// - internal sealed class ResourceIdConverter : JsonConverter + private static bool IsFunction(JsonReader reader) { - public override ResourceId ReadJson(JsonReader reader, Type objectType, ResourceId existingValue, bool hasExistingValue, JsonSerializer serializer) - { - var value = reader.ReadAsString(); - return value != null ? ResourceId.Parse(value) : default; - } + return reader.TokenType == JsonToken.PropertyName && + reader.Value is string s && + s == "$"; + } - public override void WriteJson(JsonWriter writer, ResourceId value, JsonSerializer serializer) + private bool TryExpression(string type, LanguageExpression.PropertyBag properties, out T expression) where T : LanguageExpression + { + expression = null; + if (_Factory.TryDescriptor(type, out var descriptor)) { - writer.WriteValue(value.Value); + expression = (T)descriptor.CreateInstance( + source: RunspaceContext.CurrentThread.Source.File, + properties: properties + ); + return expression != null; } + return false; } +} - /// - /// A converter for converting to/ from JSON. - /// - internal sealed class SemanticVersionConverter : JsonConverter +/// +/// A converter for converting to/ from JSON. +/// +internal sealed class ResourceIdConverter : JsonConverter +{ + public override ResourceId ReadJson(JsonReader reader, Type objectType, ResourceId existingValue, bool hasExistingValue, JsonSerializer serializer) { - public override SemanticVersion.Version ReadJson(JsonReader reader, Type objectType, SemanticVersion.Version existingValue, bool hasExistingValue, JsonSerializer serializer) - { - return reader.TokenType == JsonToken.String && SemanticVersion.TryParseVersion(reader.Value as string, out var version) ? version : default; - } + var value = reader.ReadAsString(); + return value != null ? ResourceId.Parse(value) : default; + } - public override void WriteJson(JsonWriter writer, SemanticVersion.Version value, JsonSerializer serializer) - { - writer.WriteValue(value?.ToString()); - } + public override void WriteJson(JsonWriter writer, ResourceId value, JsonSerializer serializer) + { + writer.WriteValue(value.Value); } +} - internal sealed class CaseInsensitiveDictionaryConverter : JsonConverter +/// +/// A converter for converting to/ from JSON. +/// +internal sealed class SemanticVersionConverter : JsonConverter +{ + public override SemanticVersion.Version ReadJson(JsonReader reader, Type objectType, SemanticVersion.Version existingValue, bool hasExistingValue, JsonSerializer serializer) { - public override bool CanConvert(Type objectType) - { - return objectType == typeof(Dictionary) || objectType == typeof(IDictionary); - } + return reader.TokenType == JsonToken.String && SemanticVersion.TryParseVersion(reader.Value as string, out var version) ? version : default; + } - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - existingValue ??= new Dictionary(StringComparer.OrdinalIgnoreCase); - serializer.Deserialize>(reader); - return existingValue; - } + public override void WriteJson(JsonWriter writer, SemanticVersion.Version value, JsonSerializer serializer) + { + writer.WriteValue(value?.ToString()); + } +} - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - serializer.Serialize(writer, value); - } +internal sealed class CaseInsensitiveDictionaryConverter : JsonConverter +{ + public override bool CanConvert(Type objectType) + { + return objectType == typeof(Dictionary) || objectType == typeof(IDictionary); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + existingValue ??= new Dictionary(StringComparer.OrdinalIgnoreCase); + serializer.Deserialize>(reader); + return existingValue; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + serializer.Serialize(writer, value); } } diff --git a/src/PSRule/Common/JsonHelper.cs b/src/PSRule/Common/JsonHelper.cs index 1606fe179d..1f8796d163 100644 --- a/src/PSRule/Common/JsonHelper.cs +++ b/src/PSRule/Common/JsonHelper.cs @@ -5,20 +5,19 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; -namespace PSRule +namespace PSRule; + +internal static class JsonHelper { - internal static class JsonHelper + internal static PSObject ToPSObject(JToken token) { - internal static PSObject ToPSObject(JToken token) - { - return token.ToObject(SingleObjectSerializer()); - } + return token.ToObject(SingleObjectSerializer()); + } - private static JsonSerializer SingleObjectSerializer() - { - var s = new JsonSerializer(); - s.Converters.Add(new PSObjectJsonConverter()); - return s; - } + private static JsonSerializer SingleObjectSerializer() + { + var s = new JsonSerializer(); + s.Converters.Add(new PSObjectJsonConverter()); + return s; } } diff --git a/src/PSRule/Common/JsonReaderExtensions.cs b/src/PSRule/Common/JsonReaderExtensions.cs index 604e0a6d21..68fc1061f3 100644 --- a/src/PSRule/Common/JsonReaderExtensions.cs +++ b/src/PSRule/Common/JsonReaderExtensions.cs @@ -7,79 +7,78 @@ using PSRule.Pipeline; using PSRule.Resources; -namespace PSRule +namespace PSRule; + +internal static class JsonReaderExtensions { - internal static class JsonReaderExtensions + public static bool TryLineInfo(this JsonReader reader, out int lineNumber, out int linePosition) { - public static bool TryLineInfo(this JsonReader reader, out int lineNumber, out int linePosition) - { - lineNumber = 0; - linePosition = 0; - if (!(reader is IJsonLineInfo lineInfo && lineInfo.HasLineInfo())) - return false; + lineNumber = 0; + linePosition = 0; + if (!(reader is IJsonLineInfo lineInfo && lineInfo.HasLineInfo())) + return false; - lineNumber = lineInfo.LineNumber; - linePosition = lineInfo.LinePosition; - return true; - } + lineNumber = lineInfo.LineNumber; + linePosition = lineInfo.LinePosition; + return true; + } - public static bool GetSourceExtent(this JsonReader reader, string file, out ISourceExtent extent) - { - extent = null; - if (string.IsNullOrEmpty(file) || !TryLineInfo(reader, out var lineNumber, out var linePosition)) - return false; + public static bool GetSourceExtent(this JsonReader reader, string file, out ISourceExtent extent) + { + extent = null; + if (string.IsNullOrEmpty(file) || !TryLineInfo(reader, out var lineNumber, out var linePosition)) + return false; - extent = new SourceExtent(file, lineNumber, linePosition); - return true; - } + extent = new SourceExtent(file, lineNumber, linePosition); + return true; + } - [DebuggerStepThrough] - public static bool TryConsume(this JsonReader reader, JsonToken token) - { - if (reader.TokenType != token) - return false; + [DebuggerStepThrough] + public static bool TryConsume(this JsonReader reader, JsonToken token) + { + if (reader.TokenType != token) + return false; - reader.Read(); - return true; - } + reader.Read(); + return true; + } - [DebuggerStepThrough] - public static bool TryConsume(this JsonReader reader, JsonToken token, out object value) - { - value = null; - if (reader.TokenType != token) - return false; + [DebuggerStepThrough] + public static bool TryConsume(this JsonReader reader, JsonToken token, out object value) + { + value = null; + if (reader.TokenType != token) + return false; - value = reader.Value; - reader.Read(); - return true; - } + value = reader.Value; + reader.Read(); + return true; + } - [DebuggerStepThrough] - public static void Consume(this JsonReader reader, JsonToken token) - { - if (reader.TokenType != token) - throw new PipelineSerializationException(PSRuleResources.ReadJsonFailedExpectedToken, Enum.GetName(typeof(JsonToken), reader.TokenType), reader.Path); + [DebuggerStepThrough] + public static void Consume(this JsonReader reader, JsonToken token) + { + if (reader.TokenType != token) + throw new PipelineSerializationException(PSRuleResources.ReadJsonFailedExpectedToken, Enum.GetName(typeof(JsonToken), reader.TokenType), reader.Path); - reader.Read(); - } + reader.Read(); + } - /// - /// Skip JSON comments. - /// - [DebuggerStepThrough] - public static bool SkipComments(this JsonReader reader, out bool hasComments) + /// + /// Skip JSON comments. + /// + [DebuggerStepThrough] + public static bool SkipComments(this JsonReader reader, out bool hasComments) + { + hasComments = false; + while (reader.TokenType == JsonToken.Comment || reader.TokenType == JsonToken.None) { - hasComments = false; - while (reader.TokenType == JsonToken.Comment || reader.TokenType == JsonToken.None) - { - if (reader.TokenType == JsonToken.Comment) - hasComments = true; + if (reader.TokenType == JsonToken.Comment) + hasComments = true; - if (!reader.Read()) - return false; - } - return true; + if (!reader.Read()) + return false; } + return true; } } diff --git a/src/PSRule/Common/KeyMapDictionary.cs b/src/PSRule/Common/KeyMapDictionary.cs index 8a0b090ea5..ced9fa21cd 100644 --- a/src/PSRule/Common/KeyMapDictionary.cs +++ b/src/PSRule/Common/KeyMapDictionary.cs @@ -4,242 +4,241 @@ using System.Collections; using System.Dynamic; -namespace PSRule +namespace PSRule; + +/// +/// A dictionary of key/ value pairs indexed by a string key that is case-insensitive. +/// +/// +public abstract class KeyMapDictionary : DynamicObject, IDictionary { + private readonly Dictionary _Map; + /// - /// A dictionary of key/ value pairs indexed by a string key that is case-insensitive. + /// Create an empty map. /// - /// - public abstract class KeyMapDictionary : DynamicObject, IDictionary + protected internal KeyMapDictionary() { - private readonly Dictionary _Map; - - /// - /// Create an empty map. - /// - protected internal KeyMapDictionary() - { - _Map = new Dictionary(StringComparer.OrdinalIgnoreCase); - } + _Map = new Dictionary(StringComparer.OrdinalIgnoreCase); + } - /// - /// 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) - { - _Map = map == null ? - new Dictionary(StringComparer.OrdinalIgnoreCase) : - new Dictionary(map._Map, StringComparer.OrdinalIgnoreCase); - } + /// + /// 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) + { + _Map = map == null ? + new Dictionary(StringComparer.OrdinalIgnoreCase) : + new Dictionary(map._Map, StringComparer.OrdinalIgnoreCase); + } - /// - /// Create a map initially populated with values copied from a dictionary. - /// - /// An existing dictionary to copy key/ values from. - protected internal KeyMapDictionary(IDictionary dictionary) - { - _Map = dictionary == null ? - new Dictionary(StringComparer.OrdinalIgnoreCase) : - new Dictionary(dictionary, StringComparer.OrdinalIgnoreCase); - } + /// + /// Create a map initially populated with values copied from a dictionary. + /// + /// An existing dictionary to copy key/ values from. + protected internal KeyMapDictionary(IDictionary dictionary) + { + _Map = dictionary == null ? + new Dictionary(StringComparer.OrdinalIgnoreCase) : + new Dictionary(dictionary, StringComparer.OrdinalIgnoreCase); + } - /// - /// Create a map initially populated with values copied from a hashtable. - /// - /// An existing hashtable to copy key/ values from. - protected internal KeyMapDictionary(Hashtable hashtable) - : this() - { - Load(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) + : this() + { + Load(hashtable); + } - /// - public TValue this[string key] - { - get => _Map[key]; - set => _Map[key] = value; - } + /// + public TValue this[string key] + { + get => _Map[key]; + set => _Map[key] = value; + } - /// - public ICollection Keys => _Map.Keys; + /// + public ICollection Keys => _Map.Keys; - /// - public ICollection Values => _Map.Values; + /// + public ICollection Values => _Map.Values; - /// - public int Count => _Map.Count; + /// + public int Count => _Map.Count; - /// - public bool IsReadOnly => false; + /// + public bool IsReadOnly => false; - /// - public void Add(string key, TValue value) - { - _Map.Add(key, value); - } + /// + public void Add(string key, TValue value) + { + _Map.Add(key, value); + } - /// - public void Add(KeyValuePair item) - { - Add(item.Key, item.Value); - } + /// + public void Add(KeyValuePair item) + { + Add(item.Key, item.Value); + } - /// - /// Clear the map of all keys and values. - /// - public void Clear() - { - _Map.Clear(); - } + /// + /// Clear the map of all keys and values. + /// + public void Clear() + { + _Map.Clear(); + } - /// - public bool Contains(KeyValuePair item) - { - return ((IDictionary)_Map).Contains(item); - } + /// + public bool Contains(KeyValuePair item) + { + return ((IDictionary)_Map).Contains(item); + } - /// - /// Determines if a specified key exists in the map. - /// - /// The key map. - /// - public bool ContainsKey(string key) - { - return _Map.ContainsKey(key); - } + /// + /// Determines if a specified key exists in the map. + /// + /// The key map. + /// + public bool ContainsKey(string key) + { + return _Map.ContainsKey(key); + } - /// - public void CopyTo(KeyValuePair[] array, int arrayIndex) - { - ((IDictionary)_Map).CopyTo(array, arrayIndex); - } + /// + public void CopyTo(KeyValuePair[] array, int arrayIndex) + { + ((IDictionary)_Map).CopyTo(array, arrayIndex); + } - /// - public IEnumerator> GetEnumerator() - { - return _Map.GetEnumerator(); - } + /// + public IEnumerator> GetEnumerator() + { + return _Map.GetEnumerator(); + } - /// - /// Remove the key/ value from the map by key. - /// - /// The key of the key/ value to remove. - /// Returns true if the element was found and removed. - public bool Remove(string key) - { - return _Map.Remove(key); - } + /// + /// Remove the key/ value from the map by key. + /// + /// The key of the key/ value to remove. + /// Returns true if the element was found and removed. + public bool Remove(string key) + { + return _Map.Remove(key); + } - /// - public bool Remove(KeyValuePair item) - { - return ((IDictionary)_Map).Remove(item); - } + /// + public bool Remove(KeyValuePair item) + { + return ((IDictionary)_Map).Remove(item); + } - /// - /// Try to get the value from the specified key. - /// - /// The specific key to find in the map. - /// The value of the specific key. - /// Returns true if the key was found and returned. - public bool TryGetValue(string key, out TValue value) - { - return _Map.TryGetValue(key, out value); - } + /// + /// Try to get the value from the specified key. + /// + /// The specific key to find in the map. + /// The value of the specific key. + /// Returns true if the key was found and returned. + public bool TryGetValue(string key, out TValue value) + { + return _Map.TryGetValue(key, out value); + } - /// - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } + /// + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } - /// - /// Load options from a hashtable. - /// - /// Is raised if the hashtable is null. - protected void Load(Hashtable hashtable) - { - if (hashtable == null) - throw new ArgumentNullException(nameof(hashtable)); + /// + /// Load options from a hashtable. + /// + /// Is raised if the hashtable is null. + protected void Load(Hashtable hashtable) + { + if (hashtable == null) + throw new ArgumentNullException(nameof(hashtable)); - foreach (DictionaryEntry entry in hashtable) - _Map.Add(entry.Key.ToString(), (TValue)entry.Value); - } + foreach (DictionaryEntry entry in hashtable) + _Map.Add(entry.Key.ToString(), (TValue)entry.Value); + } - /// - /// Load values from environment variables into the option. - /// Keys that appear in both will replaced by environment variable values. - /// - /// Is raised if the environment helper is null. - internal void Load(string prefix, Func format = null) + /// + /// Load values from environment variables into the option. + /// Keys that appear in both will replaced by environment variable values. + /// + /// Is raised if the environment helper is null. + internal void Load(string prefix, Func format = null) + { + foreach (var variable in Environment.GetByPrefix(prefix)) { - foreach (var variable in Environment.GetByPrefix(prefix)) + if (TryKeyPrefix(variable.Key, prefix, out var suffix)) { - if (TryKeyPrefix(variable.Key, prefix, out var suffix)) - { - if (format != null) - suffix = format(suffix); + if (format != null) + suffix = format(suffix); - _Map[suffix] = (TValue)variable.Value; - } + _Map[suffix] = (TValue)variable.Value; } } + } - /// - /// Load values from a key/ value dictionary into the option. - /// Keys that appear in both will replaced by dictionary values. - /// - /// Is raised if the dictionary is null. - protected void Load(string prefix, IDictionary dictionary) - { - if (dictionary == null) - throw new ArgumentNullException(nameof(dictionary)); + /// + /// Load values from a key/ value dictionary into the option. + /// Keys that appear in both will replaced by dictionary values. + /// + /// Is raised if the dictionary is null. + protected void Load(string prefix, IDictionary dictionary) + { + if (dictionary == null) + throw new ArgumentNullException(nameof(dictionary)); - if (dictionary.Count == 0) - return; + if (dictionary.Count == 0) + return; - var keys = dictionary.Keys.ToArray(); - for (var i = 0; i < keys.Length; i++) - { - if (TryKeyPrefix(keys[i], prefix, out var suffix) && dictionary.TryPopValue(keys[i], out var value)) - _Map[suffix] = (TValue)value; - } + var keys = dictionary.Keys.ToArray(); + for (var i = 0; i < keys.Length; i++) + { + if (TryKeyPrefix(keys[i], prefix, out var suffix) && dictionary.TryPopValue(keys[i], out var value)) + _Map[suffix] = (TValue)value; } + } - /// - /// Try a key prefix. - /// - private static bool TryKeyPrefix(string key, string prefix, out string suffix) - { - suffix = key; - if (prefix == null || prefix.Length == 0) - return true; + /// + /// Try a key prefix. + /// + private static bool TryKeyPrefix(string key, string prefix, out string suffix) + { + suffix = key; + if (prefix == null || prefix.Length == 0) + return true; - if (key.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) - { - suffix = key.Substring(prefix.Length); - return true; - } - return false; + if (key.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) + { + suffix = key.Substring(prefix.Length); + return true; } + return false; + } - /// - /// Get the value of a dynamic object member. - /// - /// A dynamic binder object. - /// The value of the member. - /// Returns true if the member was found and false if the member was not. - /// Is raised if the binder is null. - public sealed override bool TryGetMember(GetMemberBinder binder, out object result) - { - if (binder == null) - throw new ArgumentNullException(nameof(binder)); + /// + /// Get the value of a dynamic object member. + /// + /// A dynamic binder object. + /// The value of the member. + /// Returns true if the member was found and false if the member was not. + /// Is raised if the binder is null. + public sealed override bool TryGetMember(GetMemberBinder binder, out object result) + { + if (binder == null) + throw new ArgumentNullException(nameof(binder)); - var found = _Map.TryGetValue(binder.Name, out var value); - result = value; - return found; - } + var found = _Map.TryGetValue(binder.Name, out var value); + result = value; + return found; } } diff --git a/src/PSRule/Common/ListExtensions.cs b/src/PSRule/Common/ListExtensions.cs index 4150442863..675f0c784b 100644 --- a/src/PSRule/Common/ListExtensions.cs +++ b/src/PSRule/Common/ListExtensions.cs @@ -1,26 +1,25 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -namespace PSRule +namespace PSRule; + +internal static class ListExtensions { - internal static class ListExtensions + public static void AddOrInsert(this IList list, int index, T item) { - public static void AddOrInsert(this IList list, int index, T item) - { - if (list.Count <= index) - list.Add(item); - else - list.Insert(index, item); - } + if (list.Count <= index) + list.Add(item); + else + list.Insert(index, item); + } - public static void AddUnique(this IList list, IList other) - { - if (other == null || other.Count == 0) - return; + public static void AddUnique(this IList list, IList other) + { + if (other == null || other.Count == 0) + return; - for (var i = 0; i < other.Count; i++) - if (!list.Contains(other[i])) - list.Add(other[i]); - } + for (var i = 0; i < other.Count; i++) + if (!list.Contains(other[i])) + list.Add(other[i]); } } diff --git a/src/PSRule/Common/LoggerExtensions.cs b/src/PSRule/Common/LoggerExtensions.cs index eb0ff19857..fbc7c0c138 100644 --- a/src/PSRule/Common/LoggerExtensions.cs +++ b/src/PSRule/Common/LoggerExtensions.cs @@ -6,29 +6,28 @@ using PSRule.Resources; using PSRule.Runtime; -namespace PSRule +namespace PSRule; + +internal static class LoggerExtensions { - internal static class LoggerExtensions + internal static void WarnResourceObsolete(this ILogger logger, ResourceKind kind, string id) { - internal static void WarnResourceObsolete(this ILogger logger, ResourceKind kind, string id) - { - if (logger == null || !logger.ShouldLog(LogLevel.Warning)) - return; + if (logger == null || !logger.ShouldLog(LogLevel.Warning)) + return; - logger.Warning(PSRuleResources.ResourceObsolete, Enum.GetName(typeof(ResourceKind), kind), id); - } + logger.Warning(PSRuleResources.ResourceObsolete, Enum.GetName(typeof(ResourceKind), kind), id); + } - internal static void ErrorResourceUnresolved(this ILogger logger, ResourceKind kind, string id) - { - if (logger == null || !logger.ShouldLog(LogLevel.Error)) - return; + internal static void ErrorResourceUnresolved(this ILogger logger, ResourceKind kind, string id) + { + if (logger == null || !logger.ShouldLog(LogLevel.Error)) + return; - logger.Error(new PipelineBuilderException(string.Format( - Thread.CurrentThread.CurrentCulture, - PSRuleResources.PSR0004, - Enum.GetName(typeof(ResourceKind), - kind), id - )), "PSR0004"); - } + logger.Error(new PipelineBuilderException(string.Format( + Thread.CurrentThread.CurrentCulture, + PSRuleResources.PSR0004, + Enum.GetName(typeof(ResourceKind), + kind), id + )), "PSR0004"); } } diff --git a/src/PSRule/Common/OutputOptionExtensions.cs b/src/PSRule/Common/OutputOptionExtensions.cs index a08f78fffb..b2fdc9126d 100644 --- a/src/PSRule/Common/OutputOptionExtensions.cs +++ b/src/PSRule/Common/OutputOptionExtensions.cs @@ -4,27 +4,26 @@ using System.Text; using PSRule.Configuration; -namespace PSRule +namespace PSRule; + +internal static class OutputOptionExtensions { - internal static class OutputOptionExtensions + /// + /// Get the character encoding for the specified output encoding. + /// + public static Encoding GetEncoding(this OutputOption option) { - /// - /// Get the character encoding for the specified output encoding. - /// - public static Encoding GetEncoding(this OutputOption option) - { - var defaultEncoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false); - return option == null || !option.Encoding.HasValue - ? defaultEncoding - : option.Encoding switch - { - OutputEncoding.UTF8 => Encoding.UTF8, - OutputEncoding.UTF7 => Encoding.UTF7, - OutputEncoding.Unicode => Encoding.Unicode, - OutputEncoding.UTF32 => Encoding.UTF32, - OutputEncoding.ASCII => Encoding.ASCII, - _ => defaultEncoding, - }; - } + var defaultEncoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false); + return option == null || !option.Encoding.HasValue + ? defaultEncoding + : option.Encoding switch + { + OutputEncoding.UTF8 => Encoding.UTF8, + OutputEncoding.UTF7 => Encoding.UTF7, + OutputEncoding.Unicode => Encoding.Unicode, + OutputEncoding.UTF32 => Encoding.UTF32, + OutputEncoding.ASCII => Encoding.ASCII, + _ => defaultEncoding, + }; } } diff --git a/src/PSRule/Common/PSObjectExtensions.cs b/src/PSRule/Common/PSObjectExtensions.cs index cf979690f2..a81747e07b 100644 --- a/src/PSRule/Common/PSObjectExtensions.cs +++ b/src/PSRule/Common/PSObjectExtensions.cs @@ -9,254 +9,253 @@ using PSRule.Data; using PSRule.Runtime; -namespace PSRule +namespace PSRule; + +internal static class PSObjectExtensions { - internal static class PSObjectExtensions + private const string PROPERTY_SOURCE = "source"; + private const string PROPERTY_ISSUE = "issue"; + private const string PROPERTY_NAME = "name"; + private const string PROPERTY_TYPE = "type"; + private const string PROPERTY_SCOPE = "scope"; + private const string PROPERTY_PATH = "path"; + private const string PROPERTY_FILE = "file"; + private const string PROPERTY_LINE = "line"; + private const string PROPERTY_POSITION = "position"; + private const string PROPERTY_MESSAGE = "message"; + + public static T PropertyValue(this PSObject o, string propertyName) { - private const string PROPERTY_SOURCE = "source"; - private const string PROPERTY_ISSUE = "issue"; - private const string PROPERTY_NAME = "name"; - private const string PROPERTY_TYPE = "type"; - private const string PROPERTY_SCOPE = "scope"; - private const string PROPERTY_PATH = "path"; - private const string PROPERTY_FILE = "file"; - private const string PROPERTY_LINE = "line"; - private const string PROPERTY_POSITION = "position"; - private const string PROPERTY_MESSAGE = "message"; - - public static T PropertyValue(this PSObject o, string propertyName) - { - return o.BaseObject is Hashtable hashtable - ? ConvertValue(hashtable[propertyName]) - : ConvertValue(o.Properties[propertyName].Value); - } - - public static PSObject PropertyValue(this PSObject o, string propertyName) - { - return o.BaseObject is Hashtable hashtable - ? PSObject.AsPSObject(hashtable[propertyName]) - : PSObject.AsPSObject(o.Properties[propertyName].Value); - } + return o.BaseObject is Hashtable hashtable + ? ConvertValue(hashtable[propertyName]) + : ConvertValue(o.Properties[propertyName].Value); + } - public static bool HasProperty(this PSObject o, string propertyName) - { - return o.Properties[propertyName] != null; - } + public static PSObject PropertyValue(this PSObject o, string propertyName) + { + return o.BaseObject is Hashtable hashtable + ? PSObject.AsPSObject(hashtable[propertyName]) + : PSObject.AsPSObject(o.Properties[propertyName].Value); + } - /// - /// Determines if the PSObject has any note properties. - /// - public static bool HasNoteProperty(this PSObject o) - { - foreach (var property in o.Properties) - { - if (property.MemberType == PSMemberTypes.NoteProperty) - return true; - } - return false; - } + public static bool HasProperty(this PSObject o, string propertyName) + { + return o.Properties[propertyName] != null; + } - public static bool TryProperty(this PSObject o, string name, out T value) + /// + /// Determines if the PSObject has any note properties. + /// + public static bool HasNoteProperty(this PSObject o) + { + foreach (var property in o.Properties) { - value = default; - var pValue = ConvertValue(o.Properties[name]?.Value); - if (pValue is T tValue) - { - value = tValue; + if (property.MemberType == PSMemberTypes.NoteProperty) return true; - } - return false; } + return false; + } - public static string ToJson(this PSObject o) + public static bool TryProperty(this PSObject o, string name, out T value) + { + value = default; + var pValue = ConvertValue(o.Properties[name]?.Value); + if (pValue is T tValue) { - var settings = new JsonSerializerSettings - { - Formatting = Formatting.None, - TypeNameHandling = TypeNameHandling.None, - MaxDepth = 1024, - Culture = CultureInfo.InvariantCulture - }; - settings.Converters.Insert(0, new PSObjectJsonConverter()); - return JsonConvert.SerializeObject(o, settings); + value = tValue; + return true; } + return false; + } - public static bool TryTargetInfo(this PSObject o, out PSRuleTargetInfo targetInfo) + public static string ToJson(this PSObject o) + { + var settings = new JsonSerializerSettings { - return TryMember(o, PSRuleTargetInfo.PropertyName, out targetInfo); - } + Formatting = Formatting.None, + TypeNameHandling = TypeNameHandling.None, + MaxDepth = 1024, + Culture = CultureInfo.InvariantCulture + }; + settings.Converters.Insert(0, new PSObjectJsonConverter()); + return JsonConvert.SerializeObject(o, settings); + } - public static void UseTargetInfo(this PSObject o, out PSRuleTargetInfo targetInfo) - { - if (TryTargetInfo(o, out targetInfo)) - return; + public static bool TryTargetInfo(this PSObject o, out PSRuleTargetInfo targetInfo) + { + return TryMember(o, PSRuleTargetInfo.PropertyName, out targetInfo); + } - o.Members.Add(new PSRuleTargetInfo()); - TryTargetInfo(o, out targetInfo); - } + public static void UseTargetInfo(this PSObject o, out PSRuleTargetInfo targetInfo) + { + if (TryTargetInfo(o, out targetInfo)) + return; - public static void SetTargetInfo(this PSObject o, PSRuleTargetInfo targetInfo) - { - if (TryTargetInfo(o, out var orginalInfo)) - { - targetInfo.Combine(orginalInfo); - o.Members[PSRuleTargetInfo.PropertyName].Value = targetInfo; - return; - } - o.Members.Add(targetInfo); - } + o.Members.Add(new PSRuleTargetInfo()); + TryTargetInfo(o, out targetInfo); + } - public static TargetSourceInfo[] GetSourceInfo(this PSObject o) + public static void SetTargetInfo(this PSObject o, PSRuleTargetInfo targetInfo) + { + if (TryTargetInfo(o, out var orginalInfo)) { - return o.TryTargetInfo(out var targetInfo) ? targetInfo.Source.ToArray() : Array.Empty(); + targetInfo.Combine(orginalInfo); + o.Members[PSRuleTargetInfo.PropertyName].Value = targetInfo; + return; } + o.Members.Add(targetInfo); + } - public static TargetIssueInfo[] GetIssueInfo(this PSObject o) - { - return o.TryTargetInfo(out var targetInfo) ? targetInfo.Issue.ToArray() : Array.Empty(); - } + public static TargetSourceInfo[] GetSourceInfo(this PSObject o) + { + return o.TryTargetInfo(out var targetInfo) ? targetInfo.Source.ToArray() : Array.Empty(); + } - public static string GetTargetName(this PSObject o) - { - return o != null && o.TryTargetInfo(out var targetInfo) ? targetInfo.TargetName : null; - } + public static TargetIssueInfo[] GetIssueInfo(this PSObject o) + { + return o.TryTargetInfo(out var targetInfo) ? targetInfo.Issue.ToArray() : Array.Empty(); + } - public static string GetTargetType(this PSObject o) - { - return o != null && o.TryTargetInfo(out var targetInfo) ? targetInfo.TargetType : null; - } + public static string GetTargetName(this PSObject o) + { + return o != null && o.TryTargetInfo(out var targetInfo) ? targetInfo.TargetName : null; + } - public static string[] GetScope(this PSObject o) - { - return o != null && o.TryTargetInfo(out var targetInfo) ? targetInfo.Scope : null; - } + public static string GetTargetType(this PSObject o) + { + return o != null && o.TryTargetInfo(out var targetInfo) ? targetInfo.TargetType : null; + } - public static string GetTargetPath(this PSObject o) - { - if (o == null) - return string.Empty; + public static string[] GetScope(this PSObject o) + { + return o != null && o.TryTargetInfo(out var targetInfo) ? targetInfo.Scope : null; + } - if (o.TryTargetInfo(out var targetInfo)) - return targetInfo.Path; + public static string GetTargetPath(this PSObject o) + { + if (o == null) + return string.Empty; - var baseObject = o.BaseObject; - return baseObject is JToken token ? token.Path : string.Empty; - } + if (o.TryTargetInfo(out var targetInfo)) + return targetInfo.Path; - public static void ConvertTargetInfoProperty(this PSObject o) - { - if (o == null || !TryProperty(o, PSRuleTargetInfo.PropertyName, out PSObject value)) - return; + var baseObject = o.BaseObject; + return baseObject is JToken token ? token.Path : string.Empty; + } - UseTargetInfo(o, out var targetInfo); - if (TryProperty(value, PROPERTY_NAME, out string name) && targetInfo.TargetName == null) - targetInfo.TargetName = name; + public static void ConvertTargetInfoProperty(this PSObject o) + { + if (o == null || !TryProperty(o, PSRuleTargetInfo.PropertyName, out PSObject value)) + return; - if (TryProperty(value, PROPERTY_TYPE, out string type) && targetInfo.TargetType == null) - targetInfo.TargetType = type; + UseTargetInfo(o, out var targetInfo); + if (TryProperty(value, PROPERTY_NAME, out string name) && targetInfo.TargetName == null) + targetInfo.TargetName = name; - if (TryProperty(value, PROPERTY_SCOPE, out string[] scope) && targetInfo.Scope == null) - targetInfo.Scope = scope; + if (TryProperty(value, PROPERTY_TYPE, out string type) && targetInfo.TargetType == null) + targetInfo.TargetType = type; - if (TryProperty(value, PROPERTY_PATH, out string path) && targetInfo.Path == null) - targetInfo.Path = path; + if (TryProperty(value, PROPERTY_SCOPE, out string[] scope) && targetInfo.Scope == null) + targetInfo.Scope = scope; - if (TryProperty(value, PROPERTY_SOURCE, out Array sources)) - { - for (var i = 0; i < sources.Length; i++) - { - var source = CreateSourceInfo(sources.GetValue(i)); - targetInfo.WithSource(source); - } - } - if (TryProperty(value, PROPERTY_ISSUE, out Array issues)) - { - for (var i = 0; i < issues.Length; i++) - { - var issue = CreateIssueInfo(issues.GetValue(i)); - targetInfo.WithIssue(issue); - } - } - } + if (TryProperty(value, PROPERTY_PATH, out string path) && targetInfo.Path == null) + targetInfo.Path = path; - public static void ConvertTargetInfoType(this PSObject o) + if (TryProperty(value, PROPERTY_SOURCE, out Array sources)) { - var info = o?.BaseObject; - if (info is FileInfo fileInfo) + for (var i = 0; i < sources.Length; i++) { - UseTargetInfo(o, out var targetInfo); - targetInfo.WithSource(new TargetSourceInfo(fileInfo)); + var source = CreateSourceInfo(sources.GetValue(i)); + targetInfo.WithSource(source); } - if (info is InputFileInfo inputFileInfo) + } + if (TryProperty(value, PROPERTY_ISSUE, out Array issues)) + { + for (var i = 0; i < issues.Length; i++) { - UseTargetInfo(o, out var targetInfo); - targetInfo.WithSource(new TargetSourceInfo(inputFileInfo)); + var issue = CreateIssueInfo(issues.GetValue(i)); + targetInfo.WithIssue(issue); } } + } - private static TargetIssueInfo CreateIssueInfo(object o) + public static void ConvertTargetInfoType(this PSObject o) + { + var info = o?.BaseObject; + if (info is FileInfo fileInfo) { - return o is PSObject pso ? CreateIssueInfo(pso) : null; + UseTargetInfo(o, out var targetInfo); + targetInfo.WithSource(new TargetSourceInfo(fileInfo)); } - - private static TargetIssueInfo CreateIssueInfo(PSObject o) + if (info is InputFileInfo inputFileInfo) { - var result = new TargetIssueInfo(); - if (o.TryProperty(PROPERTY_TYPE, out string type)) - result.Type = type; + UseTargetInfo(o, out var targetInfo); + targetInfo.WithSource(new TargetSourceInfo(inputFileInfo)); + } + } - if (o.TryProperty(PROPERTY_NAME, out string name)) - result.Name = name; + private static TargetIssueInfo CreateIssueInfo(object o) + { + return o is PSObject pso ? CreateIssueInfo(pso) : null; + } - if (o.TryProperty(PROPERTY_PATH, out string path)) - result.Path = path; + private static TargetIssueInfo CreateIssueInfo(PSObject o) + { + var result = new TargetIssueInfo(); + if (o.TryProperty(PROPERTY_TYPE, out string type)) + result.Type = type; - if (o.TryProperty(PROPERTY_MESSAGE, out string message)) - result.Message = message; + if (o.TryProperty(PROPERTY_NAME, out string name)) + result.Name = name; - return result; - } + if (o.TryProperty(PROPERTY_PATH, out string path)) + result.Path = path; - private static TargetSourceInfo CreateSourceInfo(object o) - { - return o is PSObject pso ? CreateSourceInfo(pso) : null; - } + if (o.TryProperty(PROPERTY_MESSAGE, out string message)) + result.Message = message; - private static TargetSourceInfo CreateSourceInfo(PSObject o) - { - var result = new TargetSourceInfo(); - if (o.TryProperty(PROPERTY_FILE, out string file)) - result.File = file; + return result; + } - if (o.TryProperty(PROPERTY_LINE, out int line)) - result.Line = line; + private static TargetSourceInfo CreateSourceInfo(object o) + { + return o is PSObject pso ? CreateSourceInfo(pso) : null; + } - if (o.TryProperty(PROPERTY_POSITION, out int position)) - result.Position = position; + private static TargetSourceInfo CreateSourceInfo(PSObject o) + { + var result = new TargetSourceInfo(); + if (o.TryProperty(PROPERTY_FILE, out string file)) + result.File = file; - if (o.TryProperty(PROPERTY_TYPE, out string type)) - result.Type = type; + if (o.TryProperty(PROPERTY_LINE, out int line)) + result.Line = line; - return result; - } + if (o.TryProperty(PROPERTY_POSITION, out int position)) + result.Position = position; - private static T ConvertValue(object value) - { - if (value == null) - return default; + if (o.TryProperty(PROPERTY_TYPE, out string type)) + result.Type = type; - return typeof(T).IsValueType ? (T)Convert.ChangeType(value, typeof(T), Thread.CurrentThread.CurrentCulture) : (T)value; - } + return result; + } + + private static T ConvertValue(object value) + { + if (value == null) + return default; - private static bool TryMember(PSObject o, string name, out T value) + return typeof(T).IsValueType ? (T)Convert.ChangeType(value, typeof(T), Thread.CurrentThread.CurrentCulture) : (T)value; + } + + private static bool TryMember(PSObject o, string name, out T value) + { + value = default; + if (o.Members[name]?.Value is T tValue) { - value = default; - if (o.Members[name]?.Value is T tValue) - { - value = tValue; - return true; - } - return false; + value = tValue; + return true; } + return false; } } diff --git a/src/PSRule/Common/ReadOnlyHashtable.cs b/src/PSRule/Common/ReadOnlyHashtable.cs index 18ec3bbb3c..18055ca594 100644 --- a/src/PSRule/Common/ReadOnlyHashtable.cs +++ b/src/PSRule/Common/ReadOnlyHashtable.cs @@ -3,18 +3,17 @@ using System.Collections; -namespace PSRule +namespace PSRule; + +/// +/// Defined a readonly hashtable. +/// +public sealed class ReadOnlyHashtable : Hashtable { - /// - /// Defined a readonly hashtable. - /// - public sealed class ReadOnlyHashtable : Hashtable - { - internal ReadOnlyHashtable(IDictionary dictionary, IEqualityComparer equalityComparer) - : base(dictionary, equalityComparer) { } + internal ReadOnlyHashtable(IDictionary dictionary, IEqualityComparer equalityComparer) + : base(dictionary, equalityComparer) { } - /// - public override bool IsReadOnly => true; - } + /// + public override bool IsReadOnly => true; } diff --git a/src/PSRule/Common/ReasonExtensions.cs b/src/PSRule/Common/ReasonExtensions.cs index 9f39c999fe..8abcf5772f 100644 --- a/src/PSRule/Common/ReasonExtensions.cs +++ b/src/PSRule/Common/ReasonExtensions.cs @@ -3,21 +3,20 @@ using PSRule.Definitions; -namespace PSRule +namespace PSRule; + +internal static class ReasonExtensions { - internal static class ReasonExtensions + internal static string[] GetStrings(this IList reason) { - internal static string[] GetStrings(this IList reason) - { - if (reason == null || reason.Count == 0) - return Array.Empty(); - - var result = new string[reason.Count]; - for (var i = 0; i < reason.Count; i++) - result[i] = reason[i].ToString(); + if (reason == null || reason.Count == 0) + return Array.Empty(); - return result; - } + var result = new string[reason.Count]; + for (var i = 0; i < reason.Count; i++) + result[i] = reason[i].ToString(); + return result; } + } diff --git a/src/PSRule/Common/ResourceExtensions.cs b/src/PSRule/Common/ResourceExtensions.cs index eef9850253..1ff542ddf1 100644 --- a/src/PSRule/Common/ResourceExtensions.cs +++ b/src/PSRule/Common/ResourceExtensions.cs @@ -4,38 +4,37 @@ using PSRule.Definitions; using PSRule.Definitions.Baselines; -namespace PSRule +namespace PSRule; + +internal static class ResourceExtensions { - internal static class ResourceExtensions + internal static bool TryValidateResourceAnnotation(this IResource resource, out ValidateResourceAnnotation value) + { + value = null; + if (resource is not IAnnotated annotated) + return false; + + value = annotated.GetAnnotation(); + return value != null; + } + + internal static bool Match(this IResourceFilter filter, Baseline resource) + { + return filter.Match(resource); + } + + internal static bool IsLocalScope(this IResource resource) + { + return string.IsNullOrEmpty(resource.Source.Module); + } + + internal static IEnumerable GetIds(this IResource resource) { - internal static bool TryValidateResourceAnnotation(this IResource resource, out ValidateResourceAnnotation value) - { - value = null; - if (resource is not IAnnotated annotated) - return false; - - value = annotated.GetAnnotation(); - return value != null; - } - - internal static bool Match(this IResourceFilter filter, Baseline resource) - { - return filter.Match(resource); - } - - internal static bool IsLocalScope(this IResource resource) - { - return string.IsNullOrEmpty(resource.Source.Module); - } - - internal static IEnumerable GetIds(this IResource resource) - { - yield return resource.Id; - if (resource.Ref.HasValue) - yield return resource.Ref.Value; - - for (var i = 0; resource.Alias != null && i < resource.Alias.Length; i++) - yield return resource.Alias[i]; - } + yield return resource.Id; + if (resource.Ref.HasValue) + yield return resource.Ref.Value; + + for (var i = 0; resource.Alias != null && i < resource.Alias.Length; i++) + yield return resource.Alias[i]; } } diff --git a/src/PSRule/Common/ResourceHelpInfoExtensions.cs b/src/PSRule/Common/ResourceHelpInfoExtensions.cs index b24ebdc65f..e0d1208e76 100644 --- a/src/PSRule/Common/ResourceHelpInfoExtensions.cs +++ b/src/PSRule/Common/ResourceHelpInfoExtensions.cs @@ -3,17 +3,16 @@ using PSRule.Definitions; -namespace PSRule +namespace PSRule; + +internal static class ResourceHelpInfoExtensions { - internal static class ResourceHelpInfoExtensions + internal static void Update(this IResourceHelpInfo info, IResourceHelpInfo other) { - internal static void Update(this IResourceHelpInfo info, IResourceHelpInfo other) - { - if (info == null || other == null) - return; + if (info == null || other == null) + return; - info.Synopsis.Update(other.Synopsis); - info.Description.Update(other.Description); - } + info.Synopsis.Update(other.Synopsis); + info.Description.Update(other.Description); } } diff --git a/src/PSRule/Common/RuleOutcomeExtensions.cs b/src/PSRule/Common/RuleOutcomeExtensions.cs index de71c6bb88..f195c62de6 100644 --- a/src/PSRule/Common/RuleOutcomeExtensions.cs +++ b/src/PSRule/Common/RuleOutcomeExtensions.cs @@ -3,25 +3,24 @@ using PSRule.Rules; -namespace PSRule +namespace PSRule; + +internal static class RuleOutcomeExtensions { - internal static class RuleOutcomeExtensions + public static RuleOutcome GetWorstCase(this RuleOutcome o1, RuleOutcome o2) { - public static RuleOutcome GetWorstCase(this RuleOutcome o1, RuleOutcome o2) + if (o2 == RuleOutcome.Error || o1 == RuleOutcome.Error) + { + return RuleOutcome.Error; + } + else if (o2 == RuleOutcome.Fail || o1 == RuleOutcome.Fail) + { + return RuleOutcome.Fail; + } + else if (o2 == RuleOutcome.Pass || o1 == RuleOutcome.Pass) { - if (o2 == RuleOutcome.Error || o1 == RuleOutcome.Error) - { - return RuleOutcome.Error; - } - else if (o2 == RuleOutcome.Fail || o1 == RuleOutcome.Fail) - { - return RuleOutcome.Fail; - } - else if (o2 == RuleOutcome.Pass || o1 == RuleOutcome.Pass) - { - return RuleOutcome.Pass; - } - return o2; + return RuleOutcome.Pass; } + return o2; } } diff --git a/src/PSRule/Common/RunspaceContextDiagnosticExtensions.cs b/src/PSRule/Common/RunspaceContextDiagnosticExtensions.cs index c02f863ede..cab4b90c36 100644 --- a/src/PSRule/Common/RunspaceContextDiagnosticExtensions.cs +++ b/src/PSRule/Common/RunspaceContextDiagnosticExtensions.cs @@ -7,108 +7,107 @@ using PSRule.Resources; using PSRule.Runtime; -namespace PSRule +namespace PSRule; + +internal static class RunspaceContextDiagnosticExtensions { - internal static class RunspaceContextDiagnosticExtensions + private const string WARN_KEY_PROPERTY = "Property"; + + internal static void WarnPropertyObsolete(this RunspaceContext context, string variableName, string propertyName) + { + context.DebugPropertyObsolete(variableName, propertyName); + if (context.Writer == null || !context.Writer.ShouldWriteWarning() || !context.ShouldWarnOnce(WARN_KEY_PROPERTY, variableName, propertyName)) + return; + + context.Writer.WriteWarning(PSRuleResources.PropertyObsolete, variableName, propertyName); + } + + internal static void WarnRuleNotFound(this RunspaceContext context) + { + if (context.Writer == null || !context.Writer.ShouldWriteWarning()) + return; + + context.Writer.WriteWarning(PSRuleResources.RuleNotFound); + } + + /// + /// The option '{0}' is deprecated and will be removed with PSRule v3. See http://aka.ms/ps-rule/deprecations for more detail. + /// + internal static void WarnDeprecatedOption(this RunspaceContext context, string option) + { + if (context.Writer == null || !context.Writer.ShouldWriteWarning()) + return; + + context.Writer.WriteWarning(PSRuleResources.DeprecatedOption, option); + } + + internal static void WarnDuplicateRuleName(this RunspaceContext context, string ruleName) + { + if (context == null || context.Writer == null || !context.Writer.ShouldWriteWarning()) + return; + + context.Writer.WriteWarning(PSRuleResources.DuplicateRuleName, ruleName); + } + + internal static void DuplicateResourceId(this RunspaceContext context, ResourceId id, ResourceId duplicateId) + { + if (context == null || context.Pipeline == null) + return; + + var action = context.Pipeline.Option.Execution.DuplicateResourceId.GetValueOrDefault(ExecutionOption.Default.DuplicateResourceId.Value); + context.Throw(action, PSRuleResources.DuplicateResourceId, id.Value, duplicateId.Value); + } + + internal static void SuppressionGroupExpired(this RunspaceContext context, ResourceId suppressionGroupId) + { + if (context == null || context.Pipeline == null) + return; + + var action = context.Pipeline.Option.Execution.SuppressionGroupExpired.GetValueOrDefault(ExecutionOption.Default.SuppressionGroupExpired.Value); + context.Throw(action, PSRuleResources.SuppressionGroupExpired, suppressionGroupId.Value); + } + + internal static void RuleExcluded(this RunspaceContext context, ResourceId ruleId) + { + if (context == null || context.Pipeline == null) + return; + + var action = context.Pipeline.Option.Execution.RuleExcluded.GetValueOrDefault(ExecutionOption.Default.RuleExcluded.Value); + context.Throw(action, PSRuleResources.RuleExcluded, ruleId.Value); + } + + internal static void Throw(this RunspaceContext context, ExecutionActionPreference action, string message, params object[] args) + { + if (context == null || action == ExecutionActionPreference.Ignore) + return; + + if (action == ExecutionActionPreference.Error) + throw new RuleException(string.Format(Thread.CurrentThread.CurrentCulture, message, args)); + + else if (action == ExecutionActionPreference.Warn && context.Writer != null && context.Writer.ShouldWriteWarning()) + context.Writer.WriteWarning(message, args); + + else if (action == ExecutionActionPreference.Debug && context.Writer != null && context.Writer.ShouldWriteDebug()) + context.Writer.WriteDebug(message, args); + } + + internal static void DebugPropertyObsolete(this RunspaceContext context, string variableName, string propertyName) + { + if (context == null || context.Writer == null || !context.Writer.ShouldWriteDebug()) + return; + + context.Writer.WriteDebug(PSRuleResources.DebugPropertyObsolete, context.RuleBlock.Name, variableName, propertyName); + } + + internal static void WarnAliasReference(this RunspaceContext context, ResourceKind kind, string resourceId, string targetId, string alias) + { + var action = context.Pipeline.Option.Execution.AliasReference.GetValueOrDefault(ExecutionOption.Default.AliasReference.Value); + Throw(context, action, PSRuleResources.AliasReference, kind.ToString(), resourceId, targetId, alias); + } + + internal static void WarnAliasSuppression(this RunspaceContext context, string targetId, string alias) { - private const string WARN_KEY_PROPERTY = "Property"; - - internal static void WarnPropertyObsolete(this RunspaceContext context, string variableName, string propertyName) - { - context.DebugPropertyObsolete(variableName, propertyName); - if (context.Writer == null || !context.Writer.ShouldWriteWarning() || !context.ShouldWarnOnce(WARN_KEY_PROPERTY, variableName, propertyName)) - return; - - context.Writer.WriteWarning(PSRuleResources.PropertyObsolete, variableName, propertyName); - } - - internal static void WarnRuleNotFound(this RunspaceContext context) - { - if (context.Writer == null || !context.Writer.ShouldWriteWarning()) - return; - - context.Writer.WriteWarning(PSRuleResources.RuleNotFound); - } - - /// - /// The option '{0}' is deprecated and will be removed with PSRule v3. See http://aka.ms/ps-rule/deprecations for more detail. - /// - internal static void WarnDeprecatedOption(this RunspaceContext context, string option) - { - if (context.Writer == null || !context.Writer.ShouldWriteWarning()) - return; - - context.Writer.WriteWarning(PSRuleResources.DeprecatedOption, option); - } - - internal static void WarnDuplicateRuleName(this RunspaceContext context, string ruleName) - { - if (context == null || context.Writer == null || !context.Writer.ShouldWriteWarning()) - return; - - context.Writer.WriteWarning(PSRuleResources.DuplicateRuleName, ruleName); - } - - internal static void DuplicateResourceId(this RunspaceContext context, ResourceId id, ResourceId duplicateId) - { - if (context == null || context.Pipeline == null) - return; - - var action = context.Pipeline.Option.Execution.DuplicateResourceId.GetValueOrDefault(ExecutionOption.Default.DuplicateResourceId.Value); - context.Throw(action, PSRuleResources.DuplicateResourceId, id.Value, duplicateId.Value); - } - - internal static void SuppressionGroupExpired(this RunspaceContext context, ResourceId suppressionGroupId) - { - if (context == null || context.Pipeline == null) - return; - - var action = context.Pipeline.Option.Execution.SuppressionGroupExpired.GetValueOrDefault(ExecutionOption.Default.SuppressionGroupExpired.Value); - context.Throw(action, PSRuleResources.SuppressionGroupExpired, suppressionGroupId.Value); - } - - internal static void RuleExcluded(this RunspaceContext context, ResourceId ruleId) - { - if (context == null || context.Pipeline == null) - return; - - var action = context.Pipeline.Option.Execution.RuleExcluded.GetValueOrDefault(ExecutionOption.Default.RuleExcluded.Value); - context.Throw(action, PSRuleResources.RuleExcluded, ruleId.Value); - } - - internal static void Throw(this RunspaceContext context, ExecutionActionPreference action, string message, params object[] args) - { - if (context == null || action == ExecutionActionPreference.Ignore) - return; - - if (action == ExecutionActionPreference.Error) - throw new RuleException(string.Format(Thread.CurrentThread.CurrentCulture, message, args)); - - else if (action == ExecutionActionPreference.Warn && context.Writer != null && context.Writer.ShouldWriteWarning()) - context.Writer.WriteWarning(message, args); - - else if (action == ExecutionActionPreference.Debug && context.Writer != null && context.Writer.ShouldWriteDebug()) - context.Writer.WriteDebug(message, args); - } - - internal static void DebugPropertyObsolete(this RunspaceContext context, string variableName, string propertyName) - { - if (context == null || context.Writer == null || !context.Writer.ShouldWriteDebug()) - return; - - context.Writer.WriteDebug(PSRuleResources.DebugPropertyObsolete, context.RuleBlock.Name, variableName, propertyName); - } - - internal static void WarnAliasReference(this RunspaceContext context, ResourceKind kind, string resourceId, string targetId, string alias) - { - var action = context.Pipeline.Option.Execution.AliasReference.GetValueOrDefault(ExecutionOption.Default.AliasReference.Value); - Throw(context, action, PSRuleResources.AliasReference, kind.ToString(), resourceId, targetId, alias); - } - - internal static void WarnAliasSuppression(this RunspaceContext context, string targetId, string alias) - { - var action = context.Pipeline.Option.Execution.AliasReference.GetValueOrDefault(ExecutionOption.Default.AliasReference.Value); - Throw(context, action, PSRuleResources.AliasSuppression, targetId, alias); - } + var action = context.Pipeline.Option.Execution.AliasReference.GetValueOrDefault(ExecutionOption.Default.AliasReference.Value); + Throw(context, action, PSRuleResources.AliasSuppression, targetId, alias); } } diff --git a/src/PSRule/Common/SeverityLevelExtensions.cs b/src/PSRule/Common/SeverityLevelExtensions.cs index 2f854b6b6b..b86cd4be91 100644 --- a/src/PSRule/Common/SeverityLevelExtensions.cs +++ b/src/PSRule/Common/SeverityLevelExtensions.cs @@ -3,18 +3,17 @@ using PSRule.Definitions.Rules; -namespace PSRule +namespace PSRule; + +internal static class SeverityLevelExtensions { - internal static class SeverityLevelExtensions + public static SeverityLevel GetWorstCase(this SeverityLevel o1, SeverityLevel o2) { - public static SeverityLevel GetWorstCase(this SeverityLevel o1, SeverityLevel o2) - { - if (o2 == SeverityLevel.Error || o1 == SeverityLevel.Error) - return SeverityLevel.Error; - else if (o2 == SeverityLevel.Warning || o1 == SeverityLevel.Warning) - return SeverityLevel.Warning; + if (o2 == SeverityLevel.Error || o1 == SeverityLevel.Error) + return SeverityLevel.Error; + else if (o2 == SeverityLevel.Warning || o1 == SeverityLevel.Warning) + return SeverityLevel.Warning; - return o2 == SeverityLevel.Information || o1 == SeverityLevel.Information ? SeverityLevel.Information : SeverityLevel.None; - } + return o2 == SeverityLevel.Information || o1 == SeverityLevel.Information ? SeverityLevel.Information : SeverityLevel.None; } } diff --git a/src/PSRule/Common/StringBuilderExtensions.cs b/src/PSRule/Common/StringBuilderExtensions.cs index 98b041bd43..52b747b43d 100644 --- a/src/PSRule/Common/StringBuilderExtensions.cs +++ b/src/PSRule/Common/StringBuilderExtensions.cs @@ -3,40 +3,39 @@ using System.Text; -namespace PSRule +namespace PSRule; + +internal static class StringBuilderExtensions { - internal static class StringBuilderExtensions - { - private const char Backtick = '`'; - private const char BracketOpen = '['; - private const char BracketClose = ']'; - private const char ParenthesesOpen = '('; - private const char ParenthesesClose = ')'; - private const char AngleOpen = '<'; - private const char AngleClose = '>'; - private const char Backslash = '\\'; + private const char Backtick = '`'; + private const char BracketOpen = '['; + private const char BracketClose = ']'; + private const char ParenthesesOpen = '('; + private const char ParenthesesClose = ')'; + private const char AngleOpen = '<'; + private const char AngleClose = '>'; + private const char Backslash = '\\'; - public static void AppendMarkdownText(this StringBuilder builder, string value) + public static void AppendMarkdownText(this StringBuilder builder, string value) + { + for (var i = 0; i < value.Length; i++) { - for (var i = 0; i < value.Length; i++) - { - if (IsEscapableCharacter(value[i])) - builder.Append(Backslash); + if (IsEscapableCharacter(value[i])) + builder.Append(Backslash); - builder.Append(value[i]); - } + builder.Append(value[i]); } + } - private static bool IsEscapableCharacter(char c) - { - return c == Backslash || - c == BracketOpen || - c == ParenthesesOpen || - c == AngleOpen || - c == AngleClose || - c == Backtick || - c == BracketClose || - c == ParenthesesClose; - } + private static bool IsEscapableCharacter(char c) + { + return c == Backslash || + c == BracketOpen || + c == ParenthesesOpen || + c == AngleOpen || + c == AngleClose || + c == Backtick || + c == BracketClose || + c == ParenthesesClose; } } diff --git a/src/PSRule/Common/YamlConverters.cs b/src/PSRule/Common/YamlConverters.cs index 0633eed95f..302f3ae2a4 100644 --- a/src/PSRule/Common/YamlConverters.cs +++ b/src/PSRule/Common/YamlConverters.cs @@ -18,909 +18,908 @@ using YamlDotNet.Serialization.TypeInspectors; using YamlDotNet.Serialization.TypeResolvers; -namespace PSRule +namespace PSRule; + +/// +/// A YAML converter that allows short and full notation of suppression rules. +/// +internal sealed class SuppressionRuleYamlTypeConverter : IYamlTypeConverter { - /// - /// A YAML converter that allows short and full notation of suppression rules. - /// - internal sealed class SuppressionRuleYamlTypeConverter : IYamlTypeConverter + public bool Accepts(Type type) { - public bool Accepts(Type type) - { - return type == typeof(SuppressionRule); - } + return type == typeof(SuppressionRule); + } - public object ReadYaml(IParser parser, Type type) + public object ReadYaml(IParser parser, Type type) + { + var result = new SuppressionRule(); + if (parser.TryConsume(out _)) { - var result = new SuppressionRule(); - if (parser.TryConsume(out _)) - { - var targetNames = new List(); - while (parser.TryConsume(out Scalar scalar)) - targetNames.Add(scalar.Value); + var targetNames = new List(); + while (parser.TryConsume(out Scalar scalar)) + targetNames.Add(scalar.Value); - result.TargetName = targetNames.ToArray(); - parser.MoveNext(); - } - else if (parser.TryConsume(out _)) + result.TargetName = targetNames.ToArray(); + parser.MoveNext(); + } + else if (parser.TryConsume(out _)) + { + while (parser.TryConsume(out Scalar scalar)) { - while (parser.TryConsume(out Scalar scalar)) + var name = scalar.Value; + if (name == "targetName" && parser.TryConsume(out _)) { - var name = scalar.Value; - if (name == "targetName" && parser.TryConsume(out _)) - { - var targetNames = new List(); - while (parser.TryConsume(out Scalar item)) - targetNames.Add(item.Value); + var targetNames = new List(); + while (parser.TryConsume(out Scalar item)) + targetNames.Add(item.Value); - result.TargetName = targetNames.ToArray(); - parser.MoveNext(); - } + result.TargetName = targetNames.ToArray(); + parser.MoveNext(); } - parser.MoveNext(); } - return result; + parser.MoveNext(); } + return result; + } - public void WriteYaml(IEmitter emitter, object value, Type type) - { - throw new NotImplementedException(); - } + public void WriteYaml(IEmitter emitter, object value, Type type) + { + throw new NotImplementedException(); } +} - /// - /// A YAML converter for de/serializing a field map. - /// - internal sealed class FieldMapYamlTypeConverter : IYamlTypeConverter +/// +/// A YAML converter for de/serializing a field map. +/// +internal sealed class FieldMapYamlTypeConverter : IYamlTypeConverter +{ + public bool Accepts(Type type) { - public bool Accepts(Type type) - { - return type == typeof(FieldMap); - } + return type == typeof(FieldMap); + } - public object ReadYaml(IParser parser, Type type) + public object ReadYaml(IParser parser, Type type) + { + var result = new FieldMap(); + if (parser.TryConsume(out _)) { - var result = new FieldMap(); - if (parser.TryConsume(out _)) + while (parser.TryConsume(out Scalar scalar)) { - while (parser.TryConsume(out Scalar scalar)) + var fieldName = scalar.Value; + if (parser.TryConsume(out _)) { - var fieldName = scalar.Value; - if (parser.TryConsume(out _)) + var fields = new List(); + while (!parser.Accept(out _)) { - var fields = new List(); - while (!parser.Accept(out _)) - { - if (parser.TryConsume(out scalar)) - fields.Add(scalar.Value); - } - result.Set(fieldName, fields.ToArray()); - parser.Require(); - parser.MoveNext(); + if (parser.TryConsume(out scalar)) + fields.Add(scalar.Value); } + result.Set(fieldName, fields.ToArray()); + parser.Require(); + parser.MoveNext(); } - parser.Require(); - parser.MoveNext(); } - return result; + parser.Require(); + parser.MoveNext(); } + return result; + } - public void WriteYaml(IEmitter emitter, object value, Type type) + public void WriteYaml(IEmitter emitter, object value, Type type) + { + if (type == typeof(FieldMap) && value == null) { - if (type == typeof(FieldMap) && value == null) - { - emitter.Emit(new MappingStart()); - emitter.Emit(new MappingEnd()); - } - if (value is not FieldMap map) - return; - emitter.Emit(new MappingStart()); - foreach (var field in map) + emitter.Emit(new MappingEnd()); + } + if (value is not FieldMap map) + return; + + emitter.Emit(new MappingStart()); + foreach (var field in map) + { + emitter.Emit(new Scalar(field.Key)); + emitter.Emit(new SequenceStart(null, null, false, SequenceStyle.Block)); + for (var i = 0; i < field.Value.Length; i++) { - emitter.Emit(new Scalar(field.Key)); - emitter.Emit(new SequenceStart(null, null, false, SequenceStyle.Block)); - for (var i = 0; i < field.Value.Length; i++) - { - emitter.Emit(new Scalar(field.Value[i])); - } - emitter.Emit(new SequenceEnd()); + emitter.Emit(new Scalar(field.Value[i])); } - emitter.Emit(new MappingEnd()); + emitter.Emit(new SequenceEnd()); } + emitter.Emit(new MappingEnd()); } +} - /// - /// A YAML converter to deserialize a map/ object as a PSObject. - /// - internal sealed class PSObjectYamlTypeConverter : MappingTypeConverter, IYamlTypeConverter +/// +/// A YAML converter to deserialize a map/ object as a PSObject. +/// +internal sealed class PSObjectYamlTypeConverter : MappingTypeConverter, IYamlTypeConverter +{ + public bool Accepts(Type type) + { + return type == typeof(PSObject); + } + + public object ReadYaml(IParser parser, Type type) { - public bool Accepts(Type type) + // Handle empty objects + if (parser.TryConsume(out Scalar scalar)) { - return type == typeof(PSObject); + return PSObject.AsPSObject(scalar.Value); } - public object ReadYaml(IParser parser, Type type) + var result = new PSObject(); + if (parser.TryConsume(out _)) { - // Handle empty objects - if (parser.TryConsume(out Scalar scalar)) + while (parser.TryConsume(out scalar)) { - return PSObject.AsPSObject(scalar.Value); + var name = scalar.Value; + var property = ReadNoteProperty(parser, name); + if (property == null) + throw new NotImplementedException(); + + result.Properties.Add(property); } + parser.Require(); + parser.MoveNext(); + } + return result; + } - var result = new PSObject(); - if (parser.TryConsume(out _)) + public void WriteYaml(IEmitter emitter, object value, Type type) + { + Map(emitter, value); + } + + private PSNoteProperty ReadNoteProperty(IParser parser, string name) + { + if (parser.TryConsume(out _)) + { + var values = new List(); + while (parser.Current is not SequenceEnd) { - while (parser.TryConsume(out scalar)) + if (parser.Current is MappingStart) { - var name = scalar.Value; - var property = ReadNoteProperty(parser, name); - if (property == null) - throw new NotImplementedException(); - - result.Properties.Add(property); + values.Add(PSObject.AsPSObject(ReadYaml(parser, typeof(PSObject)))); + } + else if (parser.TryConsume(out Scalar scalar)) + { + values.Add(PSObject.AsPSObject(scalar.Value)); } - parser.Require(); - parser.MoveNext(); } - return result; + parser.Require(); + parser.MoveNext(); + return new PSNoteProperty(name, values.ToArray()); } - - public void WriteYaml(IEmitter emitter, object value, Type type) + else if (parser.Current is MappingStart) { - Map(emitter, value); + return new PSNoteProperty(name, ReadYaml(parser, typeof(PSObject))); } + else if (parser.TryConsume(out Scalar scalar)) + { + return new PSNoteProperty(name, scalar.Value); + } + return null; + } +} - private PSNoteProperty ReadNoteProperty(IParser parser, string name) +internal abstract class MappingTypeConverter +{ + protected void Map(IEmitter emitter, object value) + { + emitter.Emit(new MappingStart()); + foreach (var kv in GetKV(value)) { - if (parser.TryConsume(out _)) - { - var values = new List(); - while (parser.Current is not SequenceEnd) - { - if (parser.Current is MappingStart) - { - values.Add(PSObject.AsPSObject(ReadYaml(parser, typeof(PSObject)))); - } - else if (parser.TryConsume(out Scalar scalar)) - { - values.Add(PSObject.AsPSObject(scalar.Value)); - } - } - parser.Require(); - parser.MoveNext(); - return new PSNoteProperty(name, values.ToArray()); - } - else if (parser.Current is MappingStart) - { - return new PSNoteProperty(name, ReadYaml(parser, typeof(PSObject))); - } - else if (parser.TryConsume(out Scalar scalar)) - { - return new PSNoteProperty(name, scalar.Value); - } - return null; + emitter.Emit(new Scalar(kv.Key)); + Primitive(emitter, kv.Value); } + emitter.Emit(new MappingEnd()); } - internal abstract class MappingTypeConverter + protected void Primitive(IEmitter emitter, object value) { - protected void Map(IEmitter emitter, object value) + if (value == null) + return; + + value = ExpressionHelpers.GetBaseObject(value); + if (value is string s) { - emitter.Emit(new MappingStart()); - foreach (var kv in GetKV(value)) - { - emitter.Emit(new Scalar(kv.Key)); - Primitive(emitter, kv.Value); - } - emitter.Emit(new MappingEnd()); + emitter.Emit(new Scalar(s)); + return; } - protected void Primitive(IEmitter emitter, object value) + if (value is int || value is long || value is bool) { - if (value == null) - return; + emitter.Emit(new Scalar(null, null, value.ToString(), ScalarStyle.Plain, false, false)); + return; + } - value = ExpressionHelpers.GetBaseObject(value); - if (value is string s) - { - emitter.Emit(new Scalar(s)); - return; - } + if (value is IEnumerable enumerable) + { + emitter.Emit(new SequenceStart(null, null, false, SequenceStyle.Block)); + foreach (var item in enumerable) + Primitive(emitter, item); - if (value is int || value is long || value is bool) - { - emitter.Emit(new Scalar(null, null, value.ToString(), ScalarStyle.Plain, false, false)); - return; - } + emitter.Emit(new SequenceEnd()); + return; + } - if (value is IEnumerable enumerable) - { - emitter.Emit(new SequenceStart(null, null, false, SequenceStyle.Block)); - foreach (var item in enumerable) - Primitive(emitter, item); + if (value is PSObject || value is IDictionary) + { + Map(emitter, value); + return; + } - emitter.Emit(new SequenceEnd()); - return; - } + emitter.Emit(new Scalar(value.ToString())); + } - if (value is PSObject || value is IDictionary) - { - Map(emitter, value); - return; - } + private static IEnumerable> GetKV(object value) + { + var o = ExpressionHelpers.GetBaseObject(value); + if (o is IDictionary d) + foreach (DictionaryEntry kv in d) + yield return new KeyValuePair(kv.Key.ToString(), kv.Value); + + if (o is PSObject psObject) + foreach (var p in psObject.Properties) + yield return new KeyValuePair(p.Name, p.Value); + } +} - emitter.Emit(new Scalar(value.ToString())); +/// +/// A YAML resolver to convert any dictionary types to PSObjects instead. +/// +internal sealed class PSObjectYamlTypeResolver : INodeTypeResolver +{ + public bool Resolve(NodeEvent nodeEvent, ref Type currentType) + { + if (nodeEvent is SequenceStart) + { + currentType = typeof(PSObject[]); + return true; } - - private static IEnumerable> GetKV(object value) + else if (currentType == typeof(Dictionary) || nodeEvent is MappingStart) { - var o = ExpressionHelpers.GetBaseObject(value); - if (o is IDictionary d) - foreach (DictionaryEntry kv in d) - yield return new KeyValuePair(kv.Key.ToString(), kv.Value); - - if (o is PSObject psObject) - foreach (var p in psObject.Properties) - yield return new KeyValuePair(p.Name, p.Value); + currentType = typeof(PSObject); + return true; } + return false; } +} - /// - /// A YAML resolver to convert any dictionary types to PSObjects instead. - /// - internal sealed class PSObjectYamlTypeResolver : INodeTypeResolver +internal sealed class PSOptionYamlTypeResolver : INodeTypeResolver +{ + public bool Resolve(NodeEvent nodeEvent, ref Type currentType) { - public bool Resolve(NodeEvent nodeEvent, ref Type currentType) + if (currentType == typeof(object) && nodeEvent is SequenceStart) { - if (nodeEvent is SequenceStart) - { - currentType = typeof(PSObject[]); - return true; - } - else if (currentType == typeof(Dictionary) || nodeEvent is MappingStart) - { - currentType = typeof(PSObject); - return true; - } - return false; + currentType = typeof(PSObject[]); + return true; } + return false; } +} - internal sealed class PSOptionYamlTypeResolver : INodeTypeResolver +/// +/// A YAML type inspector to order properties alphabetically +/// +internal sealed class OrderedPropertiesTypeInspector : TypeInspectorSkeleton +{ + private readonly ITypeInspector _InnerTypeDescriptor; + + public OrderedPropertiesTypeInspector(ITypeInspector innerTypeDescriptor) { - public bool Resolve(NodeEvent nodeEvent, ref Type currentType) - { - if (currentType == typeof(object) && nodeEvent is SequenceStart) - { - currentType = typeof(PSObject[]); - return true; - } - return false; - } + _InnerTypeDescriptor = innerTypeDescriptor; } - /// - /// A YAML type inspector to order properties alphabetically - /// - internal sealed class OrderedPropertiesTypeInspector : TypeInspectorSkeleton + public override IEnumerable GetProperties(Type type, object container) + { + return _InnerTypeDescriptor + .GetProperties(type, container) + .OrderBy(prop => prop.Name); + } +} + +/// +/// A YAML type inspector to read fields and properties from a type for serialization. +/// +internal sealed class FieldYamlTypeInspector : TypeInspectorSkeleton +{ + private readonly ITypeResolver _TypeResolver; + private readonly INamingConvention _NamingConvention; + + public FieldYamlTypeInspector() { - private readonly ITypeInspector _InnerTypeDescriptor; + _TypeResolver = new StaticTypeResolver(); + _NamingConvention = CamelCaseNamingConvention.Instance; + } + + public override IEnumerable GetProperties(Type type, object container) + { + return GetPropertyDescriptor(type: type); + } - public OrderedPropertiesTypeInspector(ITypeInspector innerTypeDescriptor) + private IEnumerable GetPropertyDescriptor(Type type) + { + foreach (var f in SelectField(type: type)) { - _InnerTypeDescriptor = innerTypeDescriptor; + yield return f; } - public override IEnumerable GetProperties(Type type, object container) + foreach (var p in SelectProperty(type: type)) { - return _InnerTypeDescriptor - .GetProperties(type, container) - .OrderBy(prop => prop.Name); + yield return p; } } - /// - /// A YAML type inspector to read fields and properties from a type for serialization. - /// - internal sealed class FieldYamlTypeInspector : TypeInspectorSkeleton + private IEnumerable SelectField(Type type) { + return type + .GetRuntimeFields() + .Where(f => !f.IsStatic && f.IsPublic && !f.IsDefined(typeof(YamlIgnoreAttribute), true)) + .Select(p => new Field(p, _TypeResolver, _NamingConvention)); + } + + private IEnumerable SelectProperty(Type type) + { + return type + .GetProperties(bindingAttr: BindingFlags.Public | BindingFlags.GetProperty | BindingFlags.Instance) + .Where(p => p.CanRead && IsAllowedProperty(p.Name) && !p.IsDefined(typeof(YamlIgnoreAttribute), true)) + .Select(p => new Property(p, _TypeResolver, _NamingConvention)); + } + + private static bool IsAllowedProperty(string name) + { + return !(name == "TargetObject" || name == "Exception"); + } + + private sealed class Field : IPropertyDescriptor + { + private readonly FieldInfo _FieldInfo; private readonly ITypeResolver _TypeResolver; private readonly INamingConvention _NamingConvention; - public FieldYamlTypeInspector() + public Field(FieldInfo fieldInfo, ITypeResolver typeResolver, INamingConvention namingConvention) { - _TypeResolver = new StaticTypeResolver(); - _NamingConvention = CamelCaseNamingConvention.Instance; + _FieldInfo = fieldInfo; + _TypeResolver = typeResolver; + _NamingConvention = namingConvention; + ScalarStyle = ScalarStyle.Any; } - public override IEnumerable GetProperties(Type type, object container) - { - return GetPropertyDescriptor(type: type); - } + public string Name => _NamingConvention.Apply(_FieldInfo.Name); - private IEnumerable GetPropertyDescriptor(Type type) - { - foreach (var f in SelectField(type: type)) - { - yield return f; - } + public Type Type => _FieldInfo.FieldType; - foreach (var p in SelectProperty(type: type)) - { - yield return p; - } - } + public Type TypeOverride { get; set; } - private IEnumerable SelectField(Type type) - { - return type - .GetRuntimeFields() - .Where(f => !f.IsStatic && f.IsPublic && !f.IsDefined(typeof(YamlIgnoreAttribute), true)) - .Select(p => new Field(p, _TypeResolver, _NamingConvention)); - } + public int Order { get; set; } + + public bool CanWrite => false; - private IEnumerable SelectProperty(Type type) + public ScalarStyle ScalarStyle { get; set; } + + public void Write(object target, object value) { - return type - .GetProperties(bindingAttr: BindingFlags.Public | BindingFlags.GetProperty | BindingFlags.Instance) - .Where(p => p.CanRead && IsAllowedProperty(p.Name) && !p.IsDefined(typeof(YamlIgnoreAttribute), true)) - .Select(p => new Property(p, _TypeResolver, _NamingConvention)); + throw new NotImplementedException(); } - private static bool IsAllowedProperty(string name) + public T GetCustomAttribute() where T : Attribute { - return !(name == "TargetObject" || name == "Exception"); + return _FieldInfo.GetCustomAttributes(typeof(T), true).OfType().FirstOrDefault(); } - private sealed class Field : IPropertyDescriptor + public IObjectDescriptor Read(object target) { - private readonly FieldInfo _FieldInfo; - private readonly ITypeResolver _TypeResolver; - private readonly INamingConvention _NamingConvention; + var propertyValue = _FieldInfo.GetValue(target); + var actualType = TypeOverride ?? _TypeResolver.Resolve(Type, propertyValue); + return new ObjectDescriptor(propertyValue, actualType, Type, ScalarStyle); + } + } - public Field(FieldInfo fieldInfo, ITypeResolver typeResolver, INamingConvention namingConvention) - { - _FieldInfo = fieldInfo; - _TypeResolver = typeResolver; - _NamingConvention = namingConvention; - ScalarStyle = ScalarStyle.Any; - } + private sealed class Property : IPropertyDescriptor + { + private readonly PropertyInfo _PropertyInfo; + private readonly ITypeResolver _TypeResolver; + private readonly INamingConvention _NamingConvention; - public string Name => _NamingConvention.Apply(_FieldInfo.Name); + public Property(PropertyInfo propertyInfo, ITypeResolver typeResolver, INamingConvention namingConvention) + { + _PropertyInfo = propertyInfo; + _TypeResolver = typeResolver; + _NamingConvention = namingConvention; + ScalarStyle = ScalarStyle.Any; + } - public Type Type => _FieldInfo.FieldType; + public string Name => _NamingConvention.Apply(_PropertyInfo.Name); - public Type TypeOverride { get; set; } + public Type Type => _PropertyInfo.PropertyType; - public int Order { get; set; } + public Type TypeOverride { get; set; } - public bool CanWrite => false; + public int Order { get; set; } - public ScalarStyle ScalarStyle { get; set; } + public bool CanWrite => false; - public void Write(object target, object value) - { - throw new NotImplementedException(); - } + public ScalarStyle ScalarStyle { get; set; } - public T GetCustomAttribute() where T : Attribute - { - return _FieldInfo.GetCustomAttributes(typeof(T), true).OfType().FirstOrDefault(); - } - - public IObjectDescriptor Read(object target) - { - var propertyValue = _FieldInfo.GetValue(target); - var actualType = TypeOverride ?? _TypeResolver.Resolve(Type, propertyValue); - return new ObjectDescriptor(propertyValue, actualType, Type, ScalarStyle); - } + public T GetCustomAttribute() where T : Attribute + { + return _PropertyInfo.GetCustomAttributes(typeof(T), true).OfType().FirstOrDefault(); } - private sealed class Property : IPropertyDescriptor + public void Write(object target, object value) { - private readonly PropertyInfo _PropertyInfo; - private readonly ITypeResolver _TypeResolver; - private readonly INamingConvention _NamingConvention; - - public Property(PropertyInfo propertyInfo, ITypeResolver typeResolver, INamingConvention namingConvention) - { - _PropertyInfo = propertyInfo; - _TypeResolver = typeResolver; - _NamingConvention = namingConvention; - ScalarStyle = ScalarStyle.Any; - } - - public string Name => _NamingConvention.Apply(_PropertyInfo.Name); - - public Type Type => _PropertyInfo.PropertyType; - - public Type TypeOverride { get; set; } - - public int Order { get; set; } - - public bool CanWrite => false; + throw new NotImplementedException(); + } - public ScalarStyle ScalarStyle { get; set; } + public IObjectDescriptor Read(object target) + { + var propertyValue = _PropertyInfo.GetValue(target); + var actualType = TypeOverride ?? _TypeResolver.Resolve(Type, propertyValue); + return new ObjectDescriptor(propertyValue, actualType, Type, ScalarStyle); + } + } +} - public T GetCustomAttribute() where T : Attribute - { - return _PropertyInfo.GetCustomAttributes(typeof(T), true).OfType().FirstOrDefault(); - } +/// +/// A custom deserializer to convert YAML into a . +/// +internal sealed class ResourceNodeDeserializer : INodeDeserializer +{ + private const string FIELD_APIVERSION = "apiVersion"; + private const string FIELD_KIND = "kind"; + private const string FIELD_METADATA = "metadata"; + private const string FIELD_SPEC = "spec"; - public void Write(object target, object value) - { - throw new NotImplementedException(); - } + private readonly INodeDeserializer _Next; + private readonly SpecFactory _Factory; - public IObjectDescriptor Read(object target) - { - var propertyValue = _PropertyInfo.GetValue(target); - var actualType = TypeOverride ?? _TypeResolver.Resolve(Type, propertyValue); - return new ObjectDescriptor(propertyValue, actualType, Type, ScalarStyle); - } - } + public ResourceNodeDeserializer(INodeDeserializer next) + { + _Next = next; + _Factory = new SpecFactory(); } - /// - /// A custom deserializer to convert YAML into a . - /// - internal sealed class ResourceNodeDeserializer : INodeDeserializer + bool INodeDeserializer.Deserialize(IParser reader, Type expectedType, Func nestedObjectDeserializer, out object value) { - private const string FIELD_APIVERSION = "apiVersion"; - private const string FIELD_KIND = "kind"; - private const string FIELD_METADATA = "metadata"; - private const string FIELD_SPEC = "spec"; - - private readonly INodeDeserializer _Next; - private readonly SpecFactory _Factory; - - public ResourceNodeDeserializer(INodeDeserializer next) + if (typeof(ResourceObject).IsAssignableFrom(expectedType)) { - _Next = next; - _Factory = new SpecFactory(); + var comment = HostHelper.GetCommentMeta(RunspaceContext.CurrentThread.Source.File.Path, reader.Current.Start.Line - 2, reader.Current.Start.Column); + var resource = MapResource(reader, nestedObjectDeserializer, comment); + value = new ResourceObject(resource); + return true; } - - bool INodeDeserializer.Deserialize(IParser reader, Type expectedType, Func nestedObjectDeserializer, out object value) + else { - if (typeof(ResourceObject).IsAssignableFrom(expectedType)) - { - var comment = HostHelper.GetCommentMeta(RunspaceContext.CurrentThread.Source.File.Path, reader.Current.Start.Line - 2, reader.Current.Start.Column); - var resource = MapResource(reader, nestedObjectDeserializer, comment); - value = new ResourceObject(resource); - return true; - } - else - { - return _Next.Deserialize(reader, expectedType, nestedObjectDeserializer, out value); - } + return _Next.Deserialize(reader, expectedType, nestedObjectDeserializer, out value); } + } - private IResource MapResource(IParser reader, Func nestedObjectDeserializer, CommentMetadata comment) + private IResource MapResource(IParser reader, Func nestedObjectDeserializer, CommentMetadata comment) + { + IResource result = null; + string apiVersion = null; + string kind = null; + ResourceMetadata metadata = null; + if (reader.TryConsume(out var mappingStart)) { - IResource result = null; - string apiVersion = null; - string kind = null; - ResourceMetadata metadata = null; - if (reader.TryConsume(out var mappingStart)) + var extent = new SourceExtent(RunspaceContext.CurrentThread.Source.File.Path, mappingStart.Start.Line, mappingStart.Start.Column); + while (reader.TryConsume(out Scalar scalar)) { - var extent = new SourceExtent(RunspaceContext.CurrentThread.Source.File.Path, mappingStart.Start.Line, mappingStart.Start.Column); - while (reader.TryConsume(out Scalar scalar)) + // Read apiVersion + if (TryApiVersion(reader, scalar, out var apiVersionValue)) { - // Read apiVersion - if (TryApiVersion(reader, scalar, out var apiVersionValue)) - { - apiVersion = apiVersionValue; - } - // Read kind - else if (TryKind(reader, scalar, out var kindValue)) - { - kind = kindValue; - } - // Read metadata - else if (TryMetadata(reader, scalar, nestedObjectDeserializer, out var metadataValue)) - { - metadata = metadataValue; - } - // Read spec - else if (kind != null && TrySpec(reader, scalar, apiVersion, kind, nestedObjectDeserializer, metadata, comment, extent, out var resource)) - { - result = resource; - } - else - { - reader.SkipThisAndNestedEvents(); - } + apiVersion = apiVersionValue; + } + // Read kind + else if (TryKind(reader, scalar, out var kindValue)) + { + kind = kindValue; + } + // Read metadata + else if (TryMetadata(reader, scalar, nestedObjectDeserializer, out var metadataValue)) + { + metadata = metadataValue; + } + // Read spec + else if (kind != null && TrySpec(reader, scalar, apiVersion, kind, nestedObjectDeserializer, metadata, comment, extent, out var resource)) + { + result = resource; + } + else + { + reader.SkipThisAndNestedEvents(); } - reader.Require(); - reader.MoveNext(); } - return result; + reader.Require(); + reader.MoveNext(); } + return result; + } - private static bool TryApiVersion(IParser reader, Scalar scalar, out string apiVersion) + private static bool TryApiVersion(IParser reader, Scalar scalar, out string apiVersion) + { + apiVersion = null; + if (scalar.Value == FIELD_APIVERSION) { - apiVersion = null; - if (scalar.Value == FIELD_APIVERSION) - { - apiVersion = reader.Consume().Value; - return true; - } - return false; + apiVersion = reader.Consume().Value; + return true; } + return false; + } - private static bool TryKind(IParser reader, Scalar scalar, out string kind) + private static bool TryKind(IParser reader, Scalar scalar, out string kind) + { + kind = null; + if (scalar.Value == FIELD_KIND) { - kind = null; - if (scalar.Value == FIELD_KIND) - { - kind = reader.Consume().Value; - return true; - } - return false; + kind = reader.Consume().Value; + return true; } + return false; + } + + private bool TryMetadata(IParser reader, Scalar scalar, Func nestedObjectDeserializer, out ResourceMetadata metadata) + { + metadata = null; + if (scalar.Value != FIELD_METADATA) + return false; - private bool TryMetadata(IParser reader, Scalar scalar, Func nestedObjectDeserializer, out ResourceMetadata metadata) + if (reader.Current is MappingStart) { - metadata = null; - if (scalar.Value != FIELD_METADATA) + if (!_Next.Deserialize(reader, typeof(ResourceMetadata), nestedObjectDeserializer, out var value)) return false; - if (reader.Current is MappingStart) - { - if (!_Next.Deserialize(reader, typeof(ResourceMetadata), nestedObjectDeserializer, out var value)) - return false; - - metadata = (ResourceMetadata)value; - return true; - } - return false; + metadata = (ResourceMetadata)value; + return true; } + return false; + } - private bool TrySpec(IParser reader, Scalar scalar, string apiVersion, string kind, Func nestedObjectDeserializer, ResourceMetadata metadata, CommentMetadata comment, ISourceExtent extent, out IResource spec) - { - spec = null; - return scalar.Value == FIELD_SPEC && TryResource(reader, apiVersion, kind, nestedObjectDeserializer, metadata, comment, extent, out spec); - } + private bool TrySpec(IParser reader, Scalar scalar, string apiVersion, string kind, Func nestedObjectDeserializer, ResourceMetadata metadata, CommentMetadata comment, ISourceExtent extent, out IResource spec) + { + spec = null; + return scalar.Value == FIELD_SPEC && TryResource(reader, apiVersion, kind, nestedObjectDeserializer, metadata, comment, extent, out spec); + } - private bool TryResource(IParser reader, string apiVersion, string kind, Func nestedObjectDeserializer, ResourceMetadata metadata, CommentMetadata comment, ISourceExtent extent, out IResource spec) + private bool TryResource(IParser reader, string apiVersion, string kind, Func nestedObjectDeserializer, ResourceMetadata metadata, CommentMetadata comment, ISourceExtent extent, out IResource spec) + { + spec = null; + if (_Factory.TryDescriptor(apiVersion, kind, out var descriptor) && reader.Current is MappingStart) { - spec = null; - if (_Factory.TryDescriptor(apiVersion, kind, out var descriptor) && reader.Current is MappingStart) - { - if (!_Next.Deserialize(reader, descriptor.SpecType, nestedObjectDeserializer, out var value)) - return false; + if (!_Next.Deserialize(reader, descriptor.SpecType, nestedObjectDeserializer, out var value)) + return false; - spec = descriptor.CreateInstance(RunspaceContext.CurrentThread.Source.File, metadata, comment, extent, value); - return true; - } - return false; + spec = descriptor.CreateInstance(RunspaceContext.CurrentThread.Source.File, metadata, comment, extent, value); + return true; } + return false; } +} - /// - /// A custom deserializer to convert YAML into a language expression. - /// - internal sealed class LanguageExpressionDeserializer : INodeDeserializer - { - private const string OPERATOR_IF = "if"; +/// +/// A custom deserializer to convert YAML into a language expression. +/// +internal sealed class LanguageExpressionDeserializer : INodeDeserializer +{ + private const string OPERATOR_IF = "if"; - private readonly INodeDeserializer _Next; - private readonly LanguageExpressionFactory _Factory; - private readonly FunctionBuilder _FunctionBuilder; + private readonly INodeDeserializer _Next; + private readonly LanguageExpressionFactory _Factory; + private readonly FunctionBuilder _FunctionBuilder; - public LanguageExpressionDeserializer(INodeDeserializer next) + public LanguageExpressionDeserializer(INodeDeserializer next) + { + _Next = next; + _Factory = new LanguageExpressionFactory(); + _FunctionBuilder = new FunctionBuilder(); + } + + bool INodeDeserializer.Deserialize(IParser reader, Type expectedType, Func nestedObjectDeserializer, out object value) + { + if (typeof(LanguageExpression).IsAssignableFrom(expectedType)) { - _Next = next; - _Factory = new LanguageExpressionFactory(); - _FunctionBuilder = new FunctionBuilder(); + var resource = MapOperator(OPERATOR_IF, null, null, reader, nestedObjectDeserializer); + value = new LanguageIf(resource); + return true; } - - bool INodeDeserializer.Deserialize(IParser reader, Type expectedType, Func nestedObjectDeserializer, out object value) + else { - if (typeof(LanguageExpression).IsAssignableFrom(expectedType)) - { - var resource = MapOperator(OPERATOR_IF, null, null, reader, nestedObjectDeserializer); - value = new LanguageIf(resource); - return true; - } - else - { - return _Next.Deserialize(reader, expectedType, nestedObjectDeserializer, out value); - } + return _Next.Deserialize(reader, expectedType, nestedObjectDeserializer, out value); } + } - /// - /// Map an operator. - /// - private LanguageOperator MapOperator(string type, LanguageExpression.PropertyBag properties, LanguageExpression subselector, IParser reader, Func nestedObjectDeserializer) + /// + /// Map an operator. + /// + private LanguageOperator MapOperator(string type, LanguageExpression.PropertyBag properties, LanguageExpression subselector, IParser reader, Func nestedObjectDeserializer) + { + if (TryExpression(reader, type, properties, nestedObjectDeserializer, out LanguageOperator result)) { - if (TryExpression(reader, type, properties, nestedObjectDeserializer, out LanguageOperator result)) + // If and Not + if (reader.TryConsume(out _)) { - // If and Not - if (reader.TryConsume(out _)) + result.Add(MapExpression(reader, nestedObjectDeserializer)); + reader.Require(); + reader.MoveNext(); + } + // AllOf and AnyOf + else if (reader.TryConsume(out _)) + { + while (reader.TryConsume(out _)) { result.Add(MapExpression(reader, nestedObjectDeserializer)); reader.Require(); reader.MoveNext(); } - // AllOf and AnyOf - else if (reader.TryConsume(out _)) - { - while (reader.TryConsume(out _)) - { - result.Add(MapExpression(reader, nestedObjectDeserializer)); - reader.Require(); - reader.MoveNext(); - } - reader.Require(); - reader.MoveNext(); - } - result.Subselector = subselector; + reader.Require(); + reader.MoveNext(); } - return result; + result.Subselector = subselector; } + return result; + } - private LanguageExpression MapCondition(string type, LanguageExpression.PropertyBag properties, IParser reader, Func nestedObjectDeserializer) + private LanguageExpression MapCondition(string type, LanguageExpression.PropertyBag properties, IParser reader, Func nestedObjectDeserializer) + { + if (TryExpression(reader, type, null, nestedObjectDeserializer, out LanguageCondition result)) { - if (TryExpression(reader, type, null, nestedObjectDeserializer, out LanguageCondition result)) + while (!reader.Accept(out MappingEnd end)) { - while (!reader.Accept(out MappingEnd end)) - { - MapProperty(properties, reader, nestedObjectDeserializer, out _, out _); - } - result.Add(properties); + MapProperty(properties, reader, nestedObjectDeserializer, out _, out _); } - return result; + result.Add(properties); } + return result; + } - private LanguageExpression MapExpression(IParser reader, Func nestedObjectDeserializer) + private LanguageExpression MapExpression(IParser reader, Func nestedObjectDeserializer) + { + LanguageExpression result = null; + var properties = new LanguageExpression.PropertyBag(); + MapProperty(properties, reader, nestedObjectDeserializer, out var key, out var subselector); + if (key != null && TryCondition(key)) { - LanguageExpression result = null; - var properties = new LanguageExpression.PropertyBag(); - MapProperty(properties, reader, nestedObjectDeserializer, out var key, out var subselector); - if (key != null && TryCondition(key)) - { - result = MapCondition(key, properties, reader, nestedObjectDeserializer); - } - else if (TryOperator(key) && reader.Accept(out _)) - { - var op = MapOperator(key, properties, subselector, reader, nestedObjectDeserializer); - MapProperty(properties, reader, nestedObjectDeserializer, out _, out _); - result = op; - } - else if (TryOperator(key) && reader.Accept(out _)) - { - var op = MapOperator(key, properties, subselector, reader, nestedObjectDeserializer); - MapProperty(properties, reader, nestedObjectDeserializer, out _, out subselector); - if (subselector != null) - op.Subselector = subselector; + result = MapCondition(key, properties, reader, nestedObjectDeserializer); + } + else if (TryOperator(key) && reader.Accept(out _)) + { + var op = MapOperator(key, properties, subselector, reader, nestedObjectDeserializer); + MapProperty(properties, reader, nestedObjectDeserializer, out _, out _); + result = op; + } + else if (TryOperator(key) && reader.Accept(out _)) + { + var op = MapOperator(key, properties, subselector, reader, nestedObjectDeserializer); + MapProperty(properties, reader, nestedObjectDeserializer, out _, out subselector); + if (subselector != null) + op.Subselector = subselector; - result = op; - } - return result; + result = op; } + return result; + } - private ExpressionFnOuter MapFunction(string type, IParser reader, Func nestedObjectDeserializer) + private ExpressionFnOuter MapFunction(string type, IParser reader, Func nestedObjectDeserializer) + { + _FunctionBuilder.Push(); + string name = null; + while (!(reader.Accept(out _) || reader.Accept(out _))) { - _FunctionBuilder.Push(); - string name = null; - while (!(reader.Accept(out _) || reader.Accept(out _))) + if (reader.TryConsume(out var s)) { - if (reader.TryConsume(out var s)) + if (name != null) { - if (name != null) - { - _FunctionBuilder.Add(name, s.Value); - name = null; - } - else - { - name = s.Value; - } + _FunctionBuilder.Add(name, s.Value); + name = null; } - else if (reader.TryConsume(out _)) + else { - var child = MapFunction(name, reader, nestedObjectDeserializer); - if (name != null) - { - _FunctionBuilder.Add(name, child); - name = null; - } - reader.Consume(); + name = s.Value; } - else if (reader.TryConsume(out _)) + } + else if (reader.TryConsume(out _)) + { + var child = MapFunction(name, reader, nestedObjectDeserializer); + if (name != null) { - var sequence = MapSequence(name, reader, nestedObjectDeserializer); - if (name != null) - { - _FunctionBuilder.Add(name, sequence); - name = null; - } - reader.Consume(); + _FunctionBuilder.Add(name, child); + name = null; } + reader.Consume(); } - var result = _FunctionBuilder.Pop(); - return result; - } - - private object MapSequence(string name, IParser reader, Func nestedObjectDeserializer) - { - var result = new List(); - while (!reader.Accept(out _)) + else if (reader.TryConsume(out _)) { - if (reader.TryConsume(out var s)) - result.Add(s.Value); - - else if (reader.TryConsume(out _)) + var sequence = MapSequence(name, reader, nestedObjectDeserializer); + if (name != null) { - var child = MapFunction(name, reader, nestedObjectDeserializer); - result.Add(child); - reader.Consume(); + _FunctionBuilder.Add(name, sequence); + name = null; } + reader.Consume(); } - return result.ToArray(); } + var result = _FunctionBuilder.Pop(); + return result; + } - private void MapProperty(LanguageExpression.PropertyBag properties, IParser reader, Func nestedObjectDeserializer, out string name, out LanguageExpression subselector) + private object MapSequence(string name, IParser reader, Func nestedObjectDeserializer) + { + var result = new List(); + while (!reader.Accept(out _)) { - name = null; - subselector = null; - while (reader.TryConsume(out Scalar scalar)) + if (reader.TryConsume(out var s)) + result.Add(s.Value); + + else if (reader.TryConsume(out _)) { - var key = scalar.Value; - if (TryCondition(key) || TryOperator(key)) - name = key; + var child = MapFunction(name, reader, nestedObjectDeserializer); + result.Add(child); + reader.Consume(); + } + } + return result.ToArray(); + } - if (reader.TryConsume(out scalar)) - { - properties[key] = scalar.Value; - } - // value: - else if (TryValue(key, reader, nestedObjectDeserializer, out var value)) - { - properties[key] = value; - } - else if (TryCondition(key) && reader.TryConsume(out _)) - { - if (TryFunction(reader, nestedObjectDeserializer, out var fn)) - properties[key] = fn; - } - else if (TryCondition(key) && reader.TryConsume(out _)) + private void MapProperty(LanguageExpression.PropertyBag properties, IParser reader, Func nestedObjectDeserializer, out string name, out LanguageExpression subselector) + { + name = null; + subselector = null; + while (reader.TryConsume(out Scalar scalar)) + { + var key = scalar.Value; + if (TryCondition(key) || TryOperator(key)) + name = key; + + if (reader.TryConsume(out scalar)) + { + properties[key] = scalar.Value; + } + // value: + else if (TryValue(key, reader, nestedObjectDeserializer, out var value)) + { + properties[key] = value; + } + else if (TryCondition(key) && reader.TryConsume(out _)) + { + if (TryFunction(reader, nestedObjectDeserializer, out var fn)) + properties[key] = fn; + } + else if (TryCondition(key) && reader.TryConsume(out _)) + { + var objects = new List(); + while (!reader.TryConsume(out _)) { - var objects = new List(); - while (!reader.TryConsume(out _)) + if (reader.TryConsume(out scalar)) { - if (reader.TryConsume(out scalar)) - { - objects.Add(scalar.Value); - } + objects.Add(scalar.Value); } - properties[key] = objects.ToArray(); - } - // where: - else if (TrySubSelector(key) && reader.TryConsume(out _)) - { - subselector = MapExpression(reader, nestedObjectDeserializer); - reader.Consume(); } + properties[key] = objects.ToArray(); + } + // where: + else if (TrySubSelector(key) && reader.TryConsume(out _)) + { + subselector = MapExpression(reader, nestedObjectDeserializer); + reader.Consume(); } } + } - private bool TrySubSelector(string key) - { - return _Factory.IsSubselector(key); - } - - private bool TryOperator(string key) - { - return _Factory.IsOperator(key); - } + private bool TrySubSelector(string key) + { + return _Factory.IsSubselector(key); + } - private bool TryCondition(string key) - { - return _Factory.IsCondition(key); - } + private bool TryOperator(string key) + { + return _Factory.IsOperator(key); + } - private bool TryValue(string key, IParser reader, Func nestedObjectDeserializer, out object value) - { - value = null; - if (key != "value") - return false; + private bool TryCondition(string key) + { + return _Factory.IsCondition(key); + } - if (reader.TryConsume(out _) && TryFunction(reader, nestedObjectDeserializer, out var fn)) - { - value = fn; - return true; - } - reader.SkipThisAndNestedEvents(); + private bool TryValue(string key, IParser reader, Func nestedObjectDeserializer, out object value) + { + value = null; + if (key != "value") return false; - } - private bool TryFunction(IParser reader, Func nestedObjectDeserializer, out ExpressionFnOuter fn) + if (reader.TryConsume(out _) && TryFunction(reader, nestedObjectDeserializer, out var fn)) { - fn = null; - if (!IsFunction(reader)) - return false; - - reader.Consume(); - reader.Consume(); - fn = MapFunction("$", reader, nestedObjectDeserializer); - reader.Consume(); - reader.Consume(); + value = fn; return true; } + reader.SkipThisAndNestedEvents(); + return false; + } - private static bool IsFunction(IParser reader) - { - return reader.Accept(out var scalar) || scalar.Value == "$"; - } - - private bool TryExpression(IParser reader, string type, LanguageExpression.PropertyBag properties, Func nestedObjectDeserializer, out T expression) where T : LanguageExpression - { - expression = null; - if (_Factory.TryDescriptor(type, out var descriptor)) - { - expression = (T)descriptor.CreateInstance(RunspaceContext.CurrentThread.Source.File, properties); - return expression != null; - } + private bool TryFunction(IParser reader, Func nestedObjectDeserializer, out ExpressionFnOuter fn) + { + fn = null; + if (!IsFunction(reader)) return false; - } + + reader.Consume(); + reader.Consume(); + fn = MapFunction("$", reader, nestedObjectDeserializer); + reader.Consume(); + reader.Consume(); + return true; } - internal sealed class PSObjectYamlDeserializer : INodeDeserializer + private static bool IsFunction(IParser reader) { - private readonly INodeDeserializer _Next; - private readonly PSObjectYamlTypeConverter _Converter; - private readonly IFileInfo _FileInfo; + return reader.Accept(out var scalar) || scalar.Value == "$"; + } - public PSObjectYamlDeserializer(INodeDeserializer next, IFileInfo sourceInfo) + private bool TryExpression(IParser reader, string type, LanguageExpression.PropertyBag properties, Func nestedObjectDeserializer, out T expression) where T : LanguageExpression + { + expression = null; + if (_Factory.TryDescriptor(type, out var descriptor)) { - _Next = next; - _Converter = new PSObjectYamlTypeConverter(); - _FileInfo = sourceInfo; + expression = (T)descriptor.CreateInstance(RunspaceContext.CurrentThread.Source.File, properties); + return expression != null; } + return false; + } +} - bool INodeDeserializer.Deserialize(IParser reader, Type expectedType, Func nestedObjectDeserializer, out object value) +internal sealed class PSObjectYamlDeserializer : INodeDeserializer +{ + private readonly INodeDeserializer _Next; + private readonly PSObjectYamlTypeConverter _Converter; + private readonly IFileInfo _FileInfo; + + public PSObjectYamlDeserializer(INodeDeserializer next, IFileInfo sourceInfo) + { + _Next = next; + _Converter = new PSObjectYamlTypeConverter(); + _FileInfo = sourceInfo; + } + + bool INodeDeserializer.Deserialize(IParser reader, Type expectedType, Func nestedObjectDeserializer, out object value) + { + if (expectedType == typeof(PSObject[]) && reader.Current is MappingStart) { - if (expectedType == typeof(PSObject[]) && reader.Current is MappingStart) + var lineNumber = reader.Current.Start.Line; + var linePosition = reader.Current.Start.Column; + value = _Converter.ReadYaml(reader, typeof(PSObject)); + if (value is PSObject pso) { - var lineNumber = reader.Current.Start.Line; - var linePosition = reader.Current.Start.Column; - value = _Converter.ReadYaml(reader, typeof(PSObject)); - if (value is PSObject pso) - { - pso.UseTargetInfo(out var info); - info.SetSource(_FileInfo?.Path, lineNumber, linePosition); - value = new PSObject[] { pso }; - return true; - } - return false; - } - else - { - return _Next.Deserialize(reader, expectedType, nestedObjectDeserializer, out value); + pso.UseTargetInfo(out var info); + info.SetSource(_FileInfo?.Path, lineNumber, linePosition); + value = new PSObject[] { pso }; + return true; } + return false; + } + else + { + return _Next.Deserialize(reader, expectedType, nestedObjectDeserializer, out value); } } +} - internal sealed class InfoStringYamlTypeConverter : IYamlTypeConverter +internal sealed class InfoStringYamlTypeConverter : IYamlTypeConverter +{ + public bool Accepts(Type type) { - public bool Accepts(Type type) - { - return type == typeof(InfoString); - } + return type == typeof(InfoString); + } - public object ReadYaml(IParser parser, Type type) - { - return parser.TryConsume(out var scalar) && - !string.IsNullOrEmpty(scalar.Value) ? new InfoString(scalar.Value) : new InfoString(); - } + public object ReadYaml(IParser parser, Type type) + { + return parser.TryConsume(out var scalar) && + !string.IsNullOrEmpty(scalar.Value) ? new InfoString(scalar.Value) : new InfoString(); + } - public void WriteYaml(IEmitter emitter, object value, Type type) - { - if (value is InfoString info && info.HasValue) - emitter.Emit(new Scalar(info.Text)); - } + public void WriteYaml(IEmitter emitter, object value, Type type) + { + if (value is InfoString info && info.HasValue) + emitter.Emit(new Scalar(info.Text)); } } diff --git a/src/PSRule/Configuration/BannerFormat.cs b/src/PSRule/Configuration/BannerFormat.cs index dca343bb95..b16b1dbc00 100644 --- a/src/PSRule/Configuration/BannerFormat.cs +++ b/src/PSRule/Configuration/BannerFormat.cs @@ -4,49 +4,48 @@ using Newtonsoft.Json; using Newtonsoft.Json.Converters; -namespace PSRule.Configuration +namespace PSRule.Configuration; + +/// +/// The information displayed for Assert-PSRule banner. +/// See help. +/// +[Flags] +[JsonConverter(typeof(StringEnumConverter))] +public enum BannerFormat { /// - /// The information displayed for Assert-PSRule banner. - /// See help. + /// No banner is shown. /// - [Flags] - [JsonConverter(typeof(StringEnumConverter))] - public enum BannerFormat - { - /// - /// No banner is shown. - /// - None = 0, - - /// - /// The PSRule title ASCII text is shown. - /// - Title = 1, - - /// - /// The rules module versions used in this run are shown. - /// - Source = 2, - - /// - /// Supporting links for PSRule and rules modules are shown. - /// - SupportLinks = 4, - - /// - /// Information about the repository where PSRule is being run from. - /// - RepositoryInfo = 8, - - /// - /// The default information shown in the assert banner. - /// - Default = Title | Source | SupportLinks | RepositoryInfo, - - /// - /// A minimal set of information shown in the assert banner. - /// - Minimal = Source - } + None = 0, + + /// + /// The PSRule title ASCII text is shown. + /// + Title = 1, + + /// + /// The rules module versions used in this run are shown. + /// + Source = 2, + + /// + /// Supporting links for PSRule and rules modules are shown. + /// + SupportLinks = 4, + + /// + /// Information about the repository where PSRule is being run from. + /// + RepositoryInfo = 8, + + /// + /// The default information shown in the assert banner. + /// + Default = Title | Source | SupportLinks | RepositoryInfo, + + /// + /// A minimal set of information shown in the assert banner. + /// + Minimal = Source } diff --git a/src/PSRule/Configuration/BaselineOption.cs b/src/PSRule/Configuration/BaselineOption.cs index 0636b94454..786cee2cc9 100644 --- a/src/PSRule/Configuration/BaselineOption.cs +++ b/src/PSRule/Configuration/BaselineOption.cs @@ -4,174 +4,173 @@ using System.Collections; using PSRule.Definitions.Baselines; -namespace PSRule.Configuration +namespace PSRule.Configuration; + +/// +/// A subset of options that can be defined within a baseline. +/// These options can be passes as a baseline for use within a pipeline. +/// +public class BaselineOption { - /// - /// A subset of options that can be defined within a baseline. - /// These options can be passes as a baseline for use within a pipeline. - /// - public class BaselineOption + internal sealed class BaselineRef : BaselineOption { - internal sealed class BaselineRef : BaselineOption - { - public readonly string Name; + public readonly string Name; - public BaselineRef(string name) - { - Name = name; - } + public BaselineRef(string name) + { + Name = name; } + } - internal sealed class BaselineInline : BaselineOption, IBaselineV1Spec + internal sealed class BaselineInline : BaselineOption, IBaselineV1Spec + { + public BaselineInline() { - public BaselineInline() - { - Binding = new BindingOption(); - Configuration = new ConfigurationOption(); - Rule = new RuleOption(); - } + Binding = new BindingOption(); + Configuration = new ConfigurationOption(); + Rule = new RuleOption(); + } - public BindingOption Binding { get; set; } + public BindingOption Binding { get; set; } - public ConfigurationOption Configuration { get; set; } + public ConfigurationOption Configuration { get; set; } - public ConventionOption Convention { get; set; } + public ConventionOption Convention { get; set; } - public RuleOption Rule { get; set; } - } + public RuleOption Rule { get; set; } + } - /// - /// Creates a baseline option from a hashtable of key/ values. - /// - /// A hashtable of key/ values. - /// A baseline option composed of provided key/ values. - public static implicit operator BaselineOption(Hashtable hashtable) - { - return FromHashtable(hashtable); - } + /// + /// Creates a baseline option from a hashtable of key/ values. + /// + /// A hashtable of key/ values. + /// A baseline option composed of provided key/ values. + public static implicit operator BaselineOption(Hashtable hashtable) + { + return FromHashtable(hashtable); + } - /// - /// Creates a reference to a baseline by name which is resolved at runtime. - /// - /// The name of the baseline. - /// A reference to a baseline option. - public static implicit operator BaselineOption(string value) - { - return FromString(value); - } + /// + /// Creates a reference to a baseline by name which is resolved at runtime. + /// + /// The name of the baseline. + /// A reference to a baseline option. + public static implicit operator BaselineOption(string value) + { + return FromString(value); + } - /// - /// Creates a baseline option from a hashtable of key/ values. - /// - /// A hashtable of key/ values. - /// A baseline option composed of provided key/ values. - public static BaselineOption FromHashtable(Hashtable hashtable) + /// + /// Creates a baseline option from a hashtable of key/ values. + /// + /// A hashtable of key/ values. + /// A baseline option composed of provided key/ values. + public static BaselineOption FromHashtable(Hashtable hashtable) + { + var option = new BaselineInline(); + if (hashtable != null) { - var option = new BaselineInline(); - if (hashtable != null) - { - // Build index to allow mapping - var index = PSRuleOption.BuildIndex(hashtable); - Load(option, index); - } - return option; + // Build index to allow mapping + var index = PSRuleOption.BuildIndex(hashtable); + Load(option, index); } + return option; + } - /// - /// Creates a reference to a baseline by name which is resolved at runtime. - /// - /// The name of the baseline. - /// A reference to a baseline option. - public static BaselineOption FromString(string value) - { - return string.IsNullOrEmpty(value) ? null : new BaselineRef(value); - } + /// + /// Creates a reference to a baseline by name which is resolved at runtime. + /// + /// The name of the baseline. + /// A reference to a baseline option. + public static BaselineOption FromString(string value) + { + return string.IsNullOrEmpty(value) ? null : new BaselineRef(value); + } - internal static void Load(IBaselineV1Spec option) - { - // Binding.Field - currently not supported + internal static void Load(IBaselineV1Spec option) + { + // Binding.Field - currently not supported - if (Environment.TryBool("PSRULE_BINDING_IGNORECASE", out var ignoreCase)) - option.Binding.IgnoreCase = ignoreCase; + if (Environment.TryBool("PSRULE_BINDING_IGNORECASE", out var ignoreCase)) + option.Binding.IgnoreCase = ignoreCase; - if (Environment.TryString("PSRULE_BINDING_NAMESEPARATOR", out var nameSeparator)) - option.Binding.NameSeparator = nameSeparator; + if (Environment.TryString("PSRULE_BINDING_NAMESEPARATOR", out var nameSeparator)) + option.Binding.NameSeparator = nameSeparator; - if (Environment.TryBool("PSRULE_BINDING_PREFERTARGETINFO", out var preferTargetInfo)) - option.Binding.PreferTargetInfo = preferTargetInfo; + if (Environment.TryBool("PSRULE_BINDING_PREFERTARGETINFO", out var preferTargetInfo)) + option.Binding.PreferTargetInfo = preferTargetInfo; - if (Environment.TryStringArray("PSRULE_BINDING_TARGETNAME", out var targetName)) - option.Binding.TargetName = targetName; + if (Environment.TryStringArray("PSRULE_BINDING_TARGETNAME", out var targetName)) + option.Binding.TargetName = targetName; - if (Environment.TryStringArray("PSRULE_BINDING_TARGETTYPE", out var targetType)) - option.Binding.TargetType = targetType; + if (Environment.TryStringArray("PSRULE_BINDING_TARGETTYPE", out var targetType)) + option.Binding.TargetType = targetType; - if (Environment.TryBool("PSRULE_BINDING_USEQUALIFIEDNAME", out var useQualifiedName)) - option.Binding.UseQualifiedName = useQualifiedName; + if (Environment.TryBool("PSRULE_BINDING_USEQUALIFIEDNAME", out var useQualifiedName)) + option.Binding.UseQualifiedName = useQualifiedName; - if (Environment.TryString("PSRULE_RULE_BASELINE", out var baseline)) - option.Rule.Baseline = baseline; + if (Environment.TryString("PSRULE_RULE_BASELINE", out var baseline)) + option.Rule.Baseline = baseline; - if (Environment.TryStringArray("PSRULE_RULE_EXCLUDE", out var exclude)) - option.Rule.Exclude = exclude; + if (Environment.TryStringArray("PSRULE_RULE_EXCLUDE", out var exclude)) + option.Rule.Exclude = exclude; - if (Environment.TryBool("PSRULE_RULE_INCLUDELOCAL", out var includeLocal)) - option.Rule.IncludeLocal = includeLocal; + if (Environment.TryBool("PSRULE_RULE_INCLUDELOCAL", out var includeLocal)) + option.Rule.IncludeLocal = includeLocal; - if (Environment.TryStringArray("PSRULE_RULE_INCLUDE", out var include)) - option.Rule.Include = include; + if (Environment.TryStringArray("PSRULE_RULE_INCLUDE", out var include)) + option.Rule.Include = include; - // Rule.Tag - currently not supported + // Rule.Tag - currently not supported - // Process configuration values - option.Configuration.Load(); - } + // Process configuration values + option.Configuration.Load(); + } - /// - /// Load matching values - /// - /// A baseline options object to load. - /// One or more indexed properties. - internal static void Load(IBaselineV1Spec option, Dictionary properties) - { - if (properties.TryPopValue("Binding.Field", out Hashtable map)) - option.Binding.Field = new FieldMap(map); + /// + /// Load matching values + /// + /// A baseline options object to load. + /// One or more indexed properties. + internal static void Load(IBaselineV1Spec option, Dictionary properties) + { + if (properties.TryPopValue("Binding.Field", out Hashtable map)) + option.Binding.Field = new FieldMap(map); - if (properties.TryPopBool("Binding.IgnoreCase", out var ignoreCase)) - option.Binding.IgnoreCase = ignoreCase; + if (properties.TryPopBool("Binding.IgnoreCase", out var ignoreCase)) + option.Binding.IgnoreCase = ignoreCase; - if (properties.TryPopString("Binding.NameSeparator", out var nameSeparator)) - option.Binding.NameSeparator = nameSeparator; + if (properties.TryPopString("Binding.NameSeparator", out var nameSeparator)) + option.Binding.NameSeparator = nameSeparator; - if (properties.TryPopBool("Binding.PreferTargetInfo", out var preferTargetInfo)) - option.Binding.PreferTargetInfo = preferTargetInfo; + if (properties.TryPopBool("Binding.PreferTargetInfo", out var preferTargetInfo)) + option.Binding.PreferTargetInfo = preferTargetInfo; - if (properties.TryPopStringArray("Binding.TargetName", out var targetName)) - option.Binding.TargetName = targetName; + if (properties.TryPopStringArray("Binding.TargetName", out var targetName)) + option.Binding.TargetName = targetName; - if (properties.TryPopStringArray("Binding.TargetType", out var targetType)) - option.Binding.TargetType = targetType; + if (properties.TryPopStringArray("Binding.TargetType", out var targetType)) + option.Binding.TargetType = targetType; - if (properties.TryPopValue("Binding.UseQualifiedName", out bool useQualifiedName)) - option.Binding.UseQualifiedName = useQualifiedName; + if (properties.TryPopValue("Binding.UseQualifiedName", out bool useQualifiedName)) + option.Binding.UseQualifiedName = useQualifiedName; - if (properties.TryPopString("Rule.Baseline", out var baseline)) - option.Rule.Baseline = baseline; + if (properties.TryPopString("Rule.Baseline", out var baseline)) + option.Rule.Baseline = baseline; - if (properties.TryPopStringArray("Rule.Exclude", out var exclude)) - option.Rule.Exclude = exclude; + if (properties.TryPopStringArray("Rule.Exclude", out var exclude)) + option.Rule.Exclude = exclude; - if (properties.TryPopBool("Rule.IncludeLocal", out var includeLocal)) - option.Rule.IncludeLocal = includeLocal; + if (properties.TryPopBool("Rule.IncludeLocal", out var includeLocal)) + option.Rule.IncludeLocal = includeLocal; - if (properties.TryPopStringArray("Rule.Include", out var include)) - option.Rule.Include = include; + if (properties.TryPopStringArray("Rule.Include", out var include)) + option.Rule.Include = include; - if (properties.TryPopValue("Rule.Tag", out Hashtable tag)) - option.Rule.Tag = tag; + if (properties.TryPopValue("Rule.Tag", out Hashtable tag)) + option.Rule.Tag = tag; - // Process configuration values - option.Configuration.Load(properties); - } + // Process configuration values + option.Configuration.Load(properties); } } diff --git a/src/PSRule/Configuration/BindingOption.cs b/src/PSRule/Configuration/BindingOption.cs index 9aa9a3d2aa..8f29b03950 100644 --- a/src/PSRule/Configuration/BindingOption.cs +++ b/src/PSRule/Configuration/BindingOption.cs @@ -4,183 +4,182 @@ using System.ComponentModel; using System.Diagnostics; -namespace PSRule.Configuration +namespace PSRule.Configuration; + +internal static class BindingOptionExtensions { - internal static class BindingOptionExtensions + [DebuggerStepThrough] + public static StringComparer GetComparer(this BindingOption option) { - [DebuggerStepThrough] - public static StringComparer GetComparer(this BindingOption option) - { - return option.IgnoreCase.GetValueOrDefault(BindingOption.Default.IgnoreCase.Value) ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal; - } + return option.IgnoreCase.GetValueOrDefault(BindingOption.Default.IgnoreCase.Value) ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal; } +} + +/// +/// Options that affect property binding of TargetName and TargetType. +/// +public sealed class BindingOption : IEquatable +{ + private const bool DEFAULT_IGNORECASE = true; + private const bool DEFAULT_PREFERTARGETINFO = false; + private const string DEFAULT_NAMESEPARATOR = "/"; + private const bool DEFAULT_USEQUALIFIEDNAME = false; + + internal static readonly BindingOption Default = new() + { + IgnoreCase = DEFAULT_IGNORECASE, + NameSeparator = DEFAULT_NAMESEPARATOR, + PreferTargetInfo = DEFAULT_PREFERTARGETINFO, + UseQualifiedName = DEFAULT_USEQUALIFIEDNAME + }; /// - /// Options that affect property binding of TargetName and TargetType. + /// Creates an empty binding option. /// - public sealed class BindingOption : IEquatable + public BindingOption() { - private const bool DEFAULT_IGNORECASE = true; - private const bool DEFAULT_PREFERTARGETINFO = false; - private const string DEFAULT_NAMESEPARATOR = "/"; - private const bool DEFAULT_USEQUALIFIEDNAME = false; + Field = null; + IgnoreCase = null; + NameSeparator = null; + PreferTargetInfo = null; + TargetName = null; + TargetType = null; + UseQualifiedName = null; + } - internal static readonly BindingOption Default = new() - { - IgnoreCase = DEFAULT_IGNORECASE, - NameSeparator = DEFAULT_NAMESEPARATOR, - PreferTargetInfo = DEFAULT_PREFERTARGETINFO, - UseQualifiedName = DEFAULT_USEQUALIFIEDNAME - }; + /// + /// Creates a binding option by copying an existing instance. + /// + /// The option instance to copy. + public BindingOption(BindingOption option) + { + if (option == null) + return; + + Field = option.Field; + IgnoreCase = option.IgnoreCase; + NameSeparator = option.NameSeparator; + PreferTargetInfo = option.PreferTargetInfo; + TargetName = option.TargetName; + TargetType = option.TargetType; + UseQualifiedName = option.UseQualifiedName; + } - /// - /// Creates an empty binding option. - /// - public BindingOption() - { - Field = null; - IgnoreCase = null; - NameSeparator = null; - PreferTargetInfo = null; - TargetName = null; - TargetType = null; - UseQualifiedName = null; - } + /// + public override bool Equals(object obj) + { + return obj is BindingOption option && Equals(option); + } - /// - /// Creates a binding option by copying an existing instance. - /// - /// The option instance to copy. - public BindingOption(BindingOption option) - { - if (option == null) - return; - - Field = option.Field; - IgnoreCase = option.IgnoreCase; - NameSeparator = option.NameSeparator; - PreferTargetInfo = option.PreferTargetInfo; - TargetName = option.TargetName; - TargetType = option.TargetType; - UseQualifiedName = option.UseQualifiedName; - } + /// + public bool Equals(BindingOption other) + { + return other != null && + Field == other.Field && + IgnoreCase == other.IgnoreCase && + NameSeparator == other.NameSeparator && + PreferTargetInfo == other.PreferTargetInfo && + TargetName == other.TargetName && + TargetType == other.TargetType && + UseQualifiedName == other.UseQualifiedName; + } - /// - public override bool Equals(object obj) + /// + public override int GetHashCode() + { + unchecked // Overflow is fine { - return obj is BindingOption option && Equals(option); + var hash = 17; + hash = hash * 23 + (Field != null ? Field.GetHashCode() : 0); + hash = hash * 23 + (IgnoreCase.HasValue ? IgnoreCase.Value.GetHashCode() : 0); + hash = hash * 23 + (NameSeparator != null ? NameSeparator.GetHashCode() : 0); + hash = hash * 23 + (PreferTargetInfo.HasValue ? PreferTargetInfo.Value.GetHashCode() : 0); + hash = hash * 23 + (TargetName != null ? TargetName.GetHashCode() : 0); + hash = hash * 23 + (TargetType != null ? TargetType.GetHashCode() : 0); + hash = hash * 23 + (UseQualifiedName.HasValue ? UseQualifiedName.Value.GetHashCode() : 0); + return hash; } + } - /// - public bool Equals(BindingOption other) + /// + /// 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) { - return other != null && - Field == other.Field && - IgnoreCase == other.IgnoreCase && - NameSeparator == other.NameSeparator && - PreferTargetInfo == other.PreferTargetInfo && - TargetName == other.TargetName && - TargetType == other.TargetType && - UseQualifiedName == other.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; + } - /// - public override int GetHashCode() - { - unchecked // Overflow is fine - { - var hash = 17; - hash = hash * 23 + (Field != null ? Field.GetHashCode() : 0); - hash = hash * 23 + (IgnoreCase.HasValue ? IgnoreCase.Value.GetHashCode() : 0); - hash = hash * 23 + (NameSeparator != null ? NameSeparator.GetHashCode() : 0); - hash = hash * 23 + (PreferTargetInfo.HasValue ? PreferTargetInfo.Value.GetHashCode() : 0); - hash = hash * 23 + (TargetName != null ? TargetName.GetHashCode() : 0); - hash = hash * 23 + (TargetType != null ? TargetType.GetHashCode() : 0); - hash = hash * 23 + (UseQualifiedName.HasValue ? UseQualifiedName.Value.GetHashCode() : 0); - return hash; - } - } + /// + /// One or more custom fields to bind. + /// + /// + /// See . + /// + [DefaultValue(null)] + public FieldMap Field { get; set; } - /// - /// 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 = 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; - } + /// + /// Determines if custom binding uses ignores case when matching properties. + /// + /// + /// See . + /// + [DefaultValue(null)] + public bool? IgnoreCase { get; set; } - /// - /// One or more custom fields to bind. - /// - /// - /// See . - /// - [DefaultValue(null)] - public FieldMap Field { get; set; } - - /// - /// Determines if custom binding uses ignores case when matching properties. - /// - /// - /// See . - /// - [DefaultValue(null)] - public bool? IgnoreCase { get; set; } - - /// - /// Configures the separator to use for building a qualified name. - /// - /// - /// See . - /// - [DefaultValue(null)] - public string NameSeparator { get; set; } - - /// - /// Determines if binding prefers target info provided by the object over custom configuration. - /// - /// - /// See . - /// - [DefaultValue(null)] - public bool? PreferTargetInfo { get; set; } - - /// - /// Property names to use to bind TargetName. - /// - /// - /// See . - /// - [DefaultValue(null)] - public string[] TargetName { get; set; } - - /// - /// Property names to use to bind TargetType. - /// - /// - /// See . - /// - [DefaultValue(null)] - public string[] TargetType { get; set; } - - /// - /// Determines if a qualified TargetName is used. - /// - /// - /// See . - /// - [DefaultValue(null)] - public bool? UseQualifiedName { get; set; } - } + /// + /// Configures the separator to use for building a qualified name. + /// + /// + /// See . + /// + [DefaultValue(null)] + public string NameSeparator { get; set; } + + /// + /// Determines if binding prefers target info provided by the object over custom configuration. + /// + /// + /// See . + /// + [DefaultValue(null)] + public bool? PreferTargetInfo { get; set; } + + /// + /// Property names to use to bind TargetName. + /// + /// + /// See . + /// + [DefaultValue(null)] + public string[] TargetName { get; set; } + + /// + /// Property names to use to bind TargetType. + /// + /// + /// See . + /// + [DefaultValue(null)] + public string[] TargetType { get; set; } + + /// + /// Determines if a qualified TargetName is used. + /// + /// + /// See . + /// + [DefaultValue(null)] + public bool? UseQualifiedName { get; set; } } diff --git a/src/PSRule/Configuration/ConfigurationOption.cs b/src/PSRule/Configuration/ConfigurationOption.cs index 7a50d91bb1..d20f2f4f16 100644 --- a/src/PSRule/Configuration/ConfigurationOption.cs +++ b/src/PSRule/Configuration/ConfigurationOption.cs @@ -3,71 +3,70 @@ using System.Collections; -namespace PSRule.Configuration +namespace PSRule.Configuration; + +/// +/// A set of configuration values that can be used within rule definitions. +/// +public sealed class ConfigurationOption : KeyMapDictionary { + private const string ENVIRONMENT_PREFIX = "PSRULE_CONFIGURATION_"; + private const string DICTIONARY_PREFIX = "Configuration."; + /// - /// A set of configuration values that can be used within rule definitions. + /// Creates an empty configuration option. /// - public sealed class ConfigurationOption : KeyMapDictionary - { - private const string ENVIRONMENT_PREFIX = "PSRULE_CONFIGURATION_"; - private const string DICTIONARY_PREFIX = "Configuration."; + public ConfigurationOption() + : base() { } - /// - /// Creates an empty configuration option. - /// - public ConfigurationOption() - : base() { } - - /// - /// Creates a configuration option by copying an existing instance. - /// - /// The option instance to copy. - public ConfigurationOption(ConfigurationOption option) - : base(option) { } + /// + /// Creates a configuration option by copying an existing instance. + /// + /// The option instance to copy. + public ConfigurationOption(ConfigurationOption option) + : base(option) { } - /// - /// Creates a configuration option from a hashtable. - /// - private ConfigurationOption(Hashtable hashtable) - : base(hashtable) { } + /// + /// Creates a configuration option from a hashtable. + /// + private ConfigurationOption(Hashtable hashtable) + : base(hashtable) { } - /// - /// Convert a hashtable (commonly used in PowerShell) to a configuration option. - /// - /// The hashtable to convert. - public static implicit operator ConfigurationOption(Hashtable hashtable) - { - return new ConfigurationOption(hashtable); - } + /// + /// Convert a hashtable (commonly used in PowerShell) to a configuration option. + /// + /// The hashtable to convert. + public static implicit operator ConfigurationOption(Hashtable hashtable) + { + return new ConfigurationOption(hashtable); + } - /// - /// Merge two option instances by repacing any unset properties from with values. - /// Values from that are set are not overridden. - /// - internal static ConfigurationOption Combine(ConfigurationOption o1, ConfigurationOption o2) - { - var result = new ConfigurationOption(o1); - result.AddUnique(o2); - return result; - } + /// + /// Merge two option instances by repacing any unset properties from with values. + /// Values from that are set are not overridden. + /// + internal static ConfigurationOption Combine(ConfigurationOption o1, ConfigurationOption o2) + { + var result = new ConfigurationOption(o1); + result.AddUnique(o2); + return result; + } - /// - /// Load values from environment variables into the configuration option. - /// Keys that appear in both will replaced by environment variable values. - /// - internal void Load() - { - Load(ENVIRONMENT_PREFIX); - } + /// + /// Load values from environment variables into the configuration option. + /// Keys that appear in both will replaced by environment variable values. + /// + internal void Load() + { + Load(ENVIRONMENT_PREFIX); + } - /// - /// Load values from a key/ value dictionary into the configuration option. - /// Keys that appear in both will replaced by dictionary values. - /// - internal void Load(IDictionary dictionary) - { - Load(DICTIONARY_PREFIX, dictionary); - } + /// + /// Load values from a key/ value dictionary into the configuration option. + /// Keys that appear in both will replaced by dictionary values. + /// + internal void Load(IDictionary dictionary) + { + Load(DICTIONARY_PREFIX, dictionary); } } diff --git a/src/PSRule/Configuration/ConventionOption.cs b/src/PSRule/Configuration/ConventionOption.cs index 9c519e33bb..824bf8ef9c 100644 --- a/src/PSRule/Configuration/ConventionOption.cs +++ b/src/PSRule/Configuration/ConventionOption.cs @@ -3,87 +3,86 @@ using System.ComponentModel; -namespace PSRule.Configuration +namespace PSRule.Configuration; + +/// +/// Options that configure conventions. +/// +public sealed class ConventionOption : IEquatable { - /// - /// Options that configure conventions. - /// - public sealed class ConventionOption : IEquatable + internal static readonly ConventionOption Default = new() { - internal static readonly ConventionOption Default = new() - { - }; + }; - /// - /// Creates an empty convention option. - /// - public ConventionOption() - { - Include = null; - } + /// + /// Creates an empty convention option. + /// + public ConventionOption() + { + Include = null; + } - /// - /// Creates a convertion option by copying an existing instance. - /// - /// The option instance to copy. - public ConventionOption(ConventionOption option) - { - if (option == null) - return; + /// + /// Creates a convertion option by copying an existing instance. + /// + /// The option instance to copy. + public ConventionOption(ConventionOption option) + { + if (option == null) + return; - Include = option.Include; - } + Include = option.Include; + } - /// - public override bool Equals(object obj) - { - return obj is ConventionOption option && Equals(option); - } + /// + public override bool Equals(object obj) + { + return obj is ConventionOption option && Equals(option); + } - /// - public bool Equals(ConventionOption other) - { - return other != null && - Include == other.Include; - } + /// + public bool Equals(ConventionOption other) + { + return other != null && + Include == other.Include; + } - /// - public override int GetHashCode() + /// + public override int GetHashCode() + { + unchecked // Overflow is fine { - unchecked // Overflow is fine - { - var hash = 17; - hash = hash * 23 + (Include != null ? Include.GetHashCode() : 0); - return hash; - } + var hash = 17; + hash = hash * 23 + (Include != null ? Include.GetHashCode() : 0); + return hash; } + } - internal static ConventionOption Combine(ConventionOption o1, ConventionOption o2) + internal static ConventionOption Combine(ConventionOption o1, ConventionOption o2) + { + var result = new ConventionOption(o1) { - var result = new ConventionOption(o1) - { - Include = o1?.Include ?? o2?.Include - }; - return result; - } + Include = o1?.Include ?? o2?.Include + }; + return result; + } - /// - /// Conventions by name to use when executing PSRule. - /// - [DefaultValue(null)] - public string[] Include { get; set; } + /// + /// Conventions by name to use when executing PSRule. + /// + [DefaultValue(null)] + public string[] Include { get; set; } - internal void Load() - { - if (Environment.TryStringArray("PSRULE_CONVENTION_INCLUDE", out var include)) - Include = include; - } + internal void Load() + { + if (Environment.TryStringArray("PSRULE_CONVENTION_INCLUDE", out var include)) + Include = include; + } - internal void Load(Dictionary index) - { - if (index.TryPopStringArray("Convention.Include", out var include)) - Include = include; - } + internal void Load(Dictionary index) + { + if (index.TryPopStringArray("Convention.Include", out var include)) + Include = include; } } diff --git a/src/PSRule/Configuration/FieldMap.cs b/src/PSRule/Configuration/FieldMap.cs index 3d4adbd5fb..0501720f92 100644 --- a/src/PSRule/Configuration/FieldMap.cs +++ b/src/PSRule/Configuration/FieldMap.cs @@ -4,130 +4,129 @@ using System.Collections; using System.Dynamic; -namespace PSRule.Configuration +namespace PSRule.Configuration; + +/// +/// A mapping of fields to property names. +/// +public sealed class FieldMap : DynamicObject, IEnumerable> { + private readonly Dictionary _Map; + /// - /// A mapping of fields to property names. + /// Create an empty instance. /// - public sealed class FieldMap : DynamicObject, IEnumerable> + public FieldMap() { - private readonly Dictionary _Map; - - /// - /// Create an empty instance. - /// - public FieldMap() - { - _Map = new Dictionary(StringComparer.OrdinalIgnoreCase); - } + _Map = new Dictionary(StringComparer.OrdinalIgnoreCase); + } - /// - /// Create an instance by copying an existing . - /// - internal FieldMap(FieldMap map) - { - _Map = new Dictionary(map._Map, StringComparer.OrdinalIgnoreCase); - } + /// + /// Create an instance by copying an existing . + /// + internal FieldMap(FieldMap map) + { + _Map = new Dictionary(map._Map, StringComparer.OrdinalIgnoreCase); + } - /// - /// Create an instance by copying mapped fields from a string dictionary. - /// - internal FieldMap(Dictionary map) - { - _Map = new Dictionary(map, StringComparer.OrdinalIgnoreCase); - } + /// + /// Create an instance by copying mapped fields from a string dictionary. + /// + internal FieldMap(Dictionary map) + { + _Map = new Dictionary(map, StringComparer.OrdinalIgnoreCase); + } - /// - /// Create an instance by copying mapped fields from a . - /// - /// - internal FieldMap(Hashtable map) - : this() - { - var index = PSRuleOption.BuildIndex(map); - Load(this, index); - } + /// + /// Create an instance by copying mapped fields from a . + /// + /// + internal FieldMap(Hashtable map) + : this() + { + var index = PSRuleOption.BuildIndex(map); + Load(this, index); + } - /// - /// The number of mapped fields. - /// - public int Count => _Map.Count; + /// + /// The number of mapped fields. + /// + public int Count => _Map.Count; - /// - /// - /// - /// - public static implicit operator FieldMap(Hashtable hashtable) - { - return new FieldMap(hashtable); - } + /// + /// + /// + /// + public static implicit operator FieldMap(Hashtable hashtable) + { + return new FieldMap(hashtable); + } - /// - /// Try to get a field mapping by name. - /// - /// The name of the field. - /// Returns an array of mapped fields when the field name was found. - /// Returns true if the field name was found. Otherwise false is returned. - public bool TryField(string fieldName, out string[] fields) - { - return _Map.TryGetValue(fieldName, out fields); - } + /// + /// Try to get a field mapping by name. + /// + /// The name of the field. + /// Returns an array of mapped fields when the field name was found. + /// Returns true if the field name was found. Otherwise false is returned. + public bool TryField(string fieldName, out string[] fields) + { + return _Map.TryGetValue(fieldName, out fields); + } - /// - /// Set a field mapping. - /// - internal void Set(string fieldName, string[] fields) - { - _Map[fieldName] = fields; - } + /// + /// Set a field mapping. + /// + internal void Set(string fieldName, string[] fields) + { + _Map[fieldName] = fields; + } - /// - /// Load a field map from an existing dictionary. - /// - internal static void Load(FieldMap map, Dictionary properties) + /// + /// Load a field map from an existing dictionary. + /// + internal static void Load(FieldMap map, Dictionary properties) + { + foreach (var property in properties) { - foreach (var property in properties) - { - if (property.Value is string value && !string.IsNullOrEmpty(value)) - map.Set(property.Key, new string[] { value }); - else if (property.Value is string[] array && array.Length > 0) - map.Set(property.Key, array); - } + if (property.Value is string value && !string.IsNullOrEmpty(value)) + map.Set(property.Key, new string[] { value }); + else if (property.Value is string[] array && array.Length > 0) + map.Set(property.Key, array); } + } - /// - public IEnumerator> GetEnumerator() - { - return ((IEnumerable>)_Map).GetEnumerator(); - } + /// + public IEnumerator> GetEnumerator() + { + return ((IEnumerable>)_Map).GetEnumerator(); + } - /// - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } + /// + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } - /// - public override bool TryGetMember(GetMemberBinder binder, out object result) - { - var found = TryField(binder.Name, out var value); - result = value; - return found; - } + /// + public override bool TryGetMember(GetMemberBinder binder, out object result) + { + var found = TryField(binder.Name, out var value); + result = value; + return found; + } - internal IDictionary GetFieldMap - { - get => _Map; - } + internal IDictionary GetFieldMap + { + get => _Map; + } - internal static FieldMap Combine(FieldMap m1, FieldMap m2) - { - if (m1 == null) return m2; - if (m2 == null) return m1; + 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; - } + var result = new FieldMap(m1); + result._Map.AddUnique(m2._Map); + return result; } } diff --git a/src/PSRule/Configuration/FooterFormat.cs b/src/PSRule/Configuration/FooterFormat.cs index f91a7bcae7..2c1318bbbe 100644 --- a/src/PSRule/Configuration/FooterFormat.cs +++ b/src/PSRule/Configuration/FooterFormat.cs @@ -4,38 +4,37 @@ using Newtonsoft.Json; using Newtonsoft.Json.Converters; -namespace PSRule.Configuration +namespace PSRule.Configuration; + +/// +/// The information displayed for Assert-PSRule footer. +/// +[Flags] +[JsonConverter(typeof(StringEnumConverter))] +public enum FooterFormat { /// - /// The information displayed for Assert-PSRule footer. + /// No footer is shown. /// - [Flags] - [JsonConverter(typeof(StringEnumConverter))] - public enum FooterFormat - { - /// - /// No footer is shown. - /// - None = 0, + None = 0, - /// - /// A summary of rules processed. - /// - RuleCount = 1, + /// + /// A summary of rules processed. + /// + RuleCount = 1, - /// - /// Information about the run. - /// - RunInfo = 2, + /// + /// Information about the run. + /// + RunInfo = 2, - /// - /// Information about the output file if an output path is set. - /// - OutputFile = 4, + /// + /// Information about the output file if an output path is set. + /// + OutputFile = 4, - /// - /// The default footer output. - /// - Default = RuleCount | RunInfo | OutputFile - } + /// + /// The default footer output. + /// + Default = RuleCount | RunInfo | OutputFile } diff --git a/src/PSRule/Configuration/IncludeOption.cs b/src/PSRule/Configuration/IncludeOption.cs index 3146f36b57..bcc6e194ca 100644 --- a/src/PSRule/Configuration/IncludeOption.cs +++ b/src/PSRule/Configuration/IncludeOption.cs @@ -3,109 +3,107 @@ using System.ComponentModel; -namespace PSRule.Configuration +namespace PSRule.Configuration; + +/// +/// Options that affect source locations imported for execution. +/// +public sealed class IncludeOption : IEquatable { + private const string[] DEFAULT_MODULE = null; + private const string[] DEFAULT_PATH = null; + + internal static readonly IncludeOption Default = new() + { + Path = DEFAULT_PATH, + Module = DEFAULT_MODULE, + }; + /// - /// Options that affect source locations imported for execution. + /// Create an empty include option. /// - public sealed class IncludeOption : IEquatable + public IncludeOption() { - private const string[] DEFAULT_MODULE = null; - private const string[] DEFAULT_PATH = null; - - internal static readonly IncludeOption Default = new() - { - Path = DEFAULT_PATH, - Module = DEFAULT_MODULE, - }; - - /// - /// Create an empty include option. - /// - public IncludeOption() - { - Path = null; - Module = null; - } + Path = null; + Module = null; + } - /// - /// Create an include option by copying an existing instance. - /// - /// The option instance to copy. - public IncludeOption(IncludeOption option) - { - if (option == null) - return; + /// + /// Create an include option by copying an existing instance. + /// + /// The option instance to copy. + public IncludeOption(IncludeOption option) + { + if (option == null) + return; - Path = option.Path; - Module = option.Module; - } + Path = option.Path; + Module = option.Module; + } - /// - public override bool Equals(object obj) - { - return obj is IncludeOption option && Equals(option); - } + /// + public override bool Equals(object obj) + { + return obj is IncludeOption option && Equals(option); + } - /// - public bool Equals(IncludeOption other) - { - return other != null && - Path == other.Path && - Module == other.Module; - } + /// + public bool Equals(IncludeOption other) + { + return other != null && + Path == other.Path && + Module == other.Module; + } - /// - public override int GetHashCode() + /// + public override int GetHashCode() + { + unchecked // Overflow is fine { - unchecked // Overflow is fine - { - var hash = 17; - hash = hash * 23 + (Path != null ? Path.GetHashCode() : 0); - hash = hash * 23 + (Module != null ? Module.GetHashCode() : 0); - return hash; - } + var hash = 17; + hash = hash * 23 + (Path != null ? Path.GetHashCode() : 0); + hash = hash * 23 + (Module != null ? Module.GetHashCode() : 0); + return hash; } + } - internal static IncludeOption Combine(IncludeOption o1, IncludeOption o2) + internal static IncludeOption Combine(IncludeOption o1, IncludeOption o2) + { + var result = new IncludeOption(o1) { - var result = new IncludeOption(o1) - { - Path = o1?.Path ?? o2?.Path, - Module = o1?.Module ?? o2?.Module - }; - return result; - } + Path = o1?.Path ?? o2?.Path, + Module = o1?.Module ?? o2?.Module + }; + return result; + } - /// - /// Include additional module sources. - /// - [DefaultValue(null)] - public string[] Path { get; set; } + /// + /// Include additional module sources. + /// + [DefaultValue(null)] + public string[] Path { get; set; } - /// - /// Include additional standalone sources. - /// - [DefaultValue(null)] - public string[] Module { get; set; } + /// + /// Include additional standalone sources. + /// + [DefaultValue(null)] + public string[] Module { get; set; } - internal void Load() - { - if (Environment.TryStringArray("PSRULE_INCLUDE_PATH", out var path)) - Path = path; + internal void Load() + { + if (Environment.TryStringArray("PSRULE_INCLUDE_PATH", out var path)) + Path = path; - if (Environment.TryStringArray("PSRULE_INCLUDE_MODULE", out var module)) - Module = module; - } + if (Environment.TryStringArray("PSRULE_INCLUDE_MODULE", out var module)) + Module = module; + } - internal void Load(Dictionary index) - { - if (index.TryPopStringArray("Include.Path", out var path)) - Path = path; + internal void Load(Dictionary index) + { + if (index.TryPopStringArray("Include.Path", out var path)) + Path = path; - if (index.TryPopStringArray("Include.Module", out var module)) - Module = module; - } + if (index.TryPopStringArray("Include.Module", out var module)) + Module = module; } - } diff --git a/src/PSRule/Configuration/InputFormat.cs b/src/PSRule/Configuration/InputFormat.cs index dc9f8e4006..aea3aca102 100644 --- a/src/PSRule/Configuration/InputFormat.cs +++ b/src/PSRule/Configuration/InputFormat.cs @@ -4,48 +4,47 @@ using Newtonsoft.Json; using Newtonsoft.Json.Converters; -namespace PSRule.Configuration +namespace PSRule.Configuration; + +/// +/// The formats to convert input from. +/// +/// +[JsonConverter(typeof(StringEnumConverter))] +public enum InputFormat { /// - /// The formats to convert input from. - /// + /// Treat strings as plain text and do not deserialize files. /// - [JsonConverter(typeof(StringEnumConverter))] - public enum InputFormat - { - /// - /// Treat strings as plain text and do not deserialize files. - /// - None = 0, - - /// - /// Deserialize as one or more YAML objects. - /// - Yaml = 1, - - /// - /// Deserialize as one or more JSON objects. - /// - Json = 2, - - /// - /// Deserialize as a markdown object. - /// - Markdown = 3, - - /// - /// Deserialize as a PowerShell data object. - /// - PowerShellData = 4, - - /// - /// Files are treated as objects and not deserialized. - /// - File = 5, - - /// - /// Detect format based on file extension. This is the default. - /// - Detect = 255 - } + None = 0, + + /// + /// Deserialize as one or more YAML objects. + /// + Yaml = 1, + + /// + /// Deserialize as one or more JSON objects. + /// + Json = 2, + + /// + /// Deserialize as a markdown object. + /// + Markdown = 3, + + /// + /// Deserialize as a PowerShell data object. + /// + PowerShellData = 4, + + /// + /// Files are treated as objects and not deserialized. + /// + File = 5, + + /// + /// Detect format based on file extension. This is the default. + /// + Detect = 255 } diff --git a/src/PSRule/Configuration/InputOption.cs b/src/PSRule/Configuration/InputOption.cs index 26d3c9f4cd..92f55c9563 100644 --- a/src/PSRule/Configuration/InputOption.cs +++ b/src/PSRule/Configuration/InputOption.cs @@ -3,226 +3,225 @@ using System.ComponentModel; -namespace PSRule.Configuration +namespace PSRule.Configuration; + +/// +/// Options that affect how input types are processed. +/// +public sealed class InputOption : IEquatable { + private const InputFormat DEFAULT_FORMAT = InputFormat.Detect; + private const bool DEFAULT_IGNOREGITPATH = true; + private const bool DEFAULT_IGNOREOBJECTSOURCE = false; + private const bool DEFAULT_IGNOREREPOSITORYCOMMON = true; + private const bool DEFAULT_IGNOREUNCHANGEDPATH = false; + private const string DEFAULT_OBJECTPATH = null; + private const string[] DEFAULT_PATHIGNORE = null; + private const string[] DEFAULT_TARGETTYPE = null; + + internal static readonly InputOption Default = new() + { + Format = DEFAULT_FORMAT, + IgnoreGitPath = DEFAULT_IGNOREGITPATH, + IgnoreObjectSource = DEFAULT_IGNOREOBJECTSOURCE, + IgnoreRepositoryCommon = DEFAULT_IGNOREREPOSITORYCOMMON, + IgnoreUnchangedPath = DEFAULT_IGNOREUNCHANGEDPATH, + ObjectPath = DEFAULT_OBJECTPATH, + PathIgnore = DEFAULT_PATHIGNORE, + TargetType = DEFAULT_TARGETTYPE, + }; + /// - /// Options that affect how input types are processed. + /// Creates an empty input option. /// - public sealed class InputOption : IEquatable + public InputOption() { - private const InputFormat DEFAULT_FORMAT = InputFormat.Detect; - private const bool DEFAULT_IGNOREGITPATH = true; - private const bool DEFAULT_IGNOREOBJECTSOURCE = false; - private const bool DEFAULT_IGNOREREPOSITORYCOMMON = true; - private const bool DEFAULT_IGNOREUNCHANGEDPATH = false; - private const string DEFAULT_OBJECTPATH = null; - private const string[] DEFAULT_PATHIGNORE = null; - private const string[] DEFAULT_TARGETTYPE = null; - - internal static readonly InputOption Default = new() - { - Format = DEFAULT_FORMAT, - IgnoreGitPath = DEFAULT_IGNOREGITPATH, - IgnoreObjectSource = DEFAULT_IGNOREOBJECTSOURCE, - IgnoreRepositoryCommon = DEFAULT_IGNOREREPOSITORYCOMMON, - IgnoreUnchangedPath = DEFAULT_IGNOREUNCHANGEDPATH, - ObjectPath = DEFAULT_OBJECTPATH, - PathIgnore = DEFAULT_PATHIGNORE, - TargetType = DEFAULT_TARGETTYPE, - }; + Format = null; + IgnoreGitPath = null; + IgnoreObjectSource = null; + IgnoreRepositoryCommon = null; + IgnoreUnchangedPath = null; + ObjectPath = null; + PathIgnore = null; + TargetType = null; + } - /// - /// Creates an empty input option. - /// - public InputOption() - { - Format = null; - IgnoreGitPath = null; - IgnoreObjectSource = null; - IgnoreRepositoryCommon = null; - IgnoreUnchangedPath = null; - ObjectPath = null; - PathIgnore = null; - TargetType = null; - } + /// + /// Creates a input option by copying an existing instance. + /// + /// The option instance to copy. + public InputOption(InputOption option) + { + if (option == null) + return; + + Format = option.Format; + IgnoreGitPath = option.IgnoreGitPath; + IgnoreObjectSource = option.IgnoreObjectSource; + IgnoreRepositoryCommon = option.IgnoreRepositoryCommon; + IgnoreUnchangedPath = option.IgnoreUnchangedPath; + ObjectPath = option.ObjectPath; + PathIgnore = option.PathIgnore; + TargetType = option.TargetType; + } - /// - /// Creates a input option by copying an existing instance. - /// - /// The option instance to copy. - public InputOption(InputOption option) - { - if (option == null) - return; - - Format = option.Format; - IgnoreGitPath = option.IgnoreGitPath; - IgnoreObjectSource = option.IgnoreObjectSource; - IgnoreRepositoryCommon = option.IgnoreRepositoryCommon; - IgnoreUnchangedPath = option.IgnoreUnchangedPath; - ObjectPath = option.ObjectPath; - PathIgnore = option.PathIgnore; - TargetType = option.TargetType; - } + /// + public override bool Equals(object obj) + { + return obj is InputOption option && Equals(option); + } - /// - public override bool Equals(object obj) - { - return obj is InputOption option && Equals(option); - } + /// + public bool Equals(InputOption other) + { + return other != null && + Format == other.Format && + IgnoreGitPath == other.IgnoreGitPath && + IgnoreObjectSource == other.IgnoreObjectSource && + IgnoreRepositoryCommon == other.IgnoreRepositoryCommon && + IgnoreUnchangedPath == other.IgnoreUnchangedPath && + ObjectPath == other.ObjectPath && + PathIgnore == other.PathIgnore && + TargetType == other.TargetType; + } - /// - public bool Equals(InputOption other) + /// + public override int GetHashCode() + { + unchecked // Overflow is fine { - return other != null && - Format == other.Format && - IgnoreGitPath == other.IgnoreGitPath && - IgnoreObjectSource == other.IgnoreObjectSource && - IgnoreRepositoryCommon == other.IgnoreRepositoryCommon && - IgnoreUnchangedPath == other.IgnoreUnchangedPath && - ObjectPath == other.ObjectPath && - PathIgnore == other.PathIgnore && - TargetType == other.TargetType; + var hash = 17; + hash = hash * 23 + (Format.HasValue ? Format.Value.GetHashCode() : 0); + hash = hash * 23 + (IgnoreGitPath.HasValue ? IgnoreGitPath.Value.GetHashCode() : 0); + hash = hash * 23 + (IgnoreObjectSource.HasValue ? IgnoreObjectSource.Value.GetHashCode() : 0); + hash = hash * 23 + (IgnoreRepositoryCommon.HasValue ? IgnoreRepositoryCommon.Value.GetHashCode() : 0); + hash = hash * 23 + (IgnoreUnchangedPath.HasValue ? IgnoreUnchangedPath.Value.GetHashCode() : 0); + hash = hash * 23 + (ObjectPath != null ? ObjectPath.GetHashCode() : 0); + hash = hash * 23 + (PathIgnore != null ? PathIgnore.GetHashCode() : 0); + hash = hash * 23 + (TargetType != null ? TargetType.GetHashCode() : 0); + return hash; } + } - /// - public override int GetHashCode() + /// + /// Merge two option instances by repacing any unset properties from with values. + /// Values from that are set are not overridden. + /// + internal static InputOption Combine(InputOption o1, InputOption o2) + { + var result = new InputOption(o1) { - unchecked // Overflow is fine - { - var hash = 17; - hash = hash * 23 + (Format.HasValue ? Format.Value.GetHashCode() : 0); - hash = hash * 23 + (IgnoreGitPath.HasValue ? IgnoreGitPath.Value.GetHashCode() : 0); - hash = hash * 23 + (IgnoreObjectSource.HasValue ? IgnoreObjectSource.Value.GetHashCode() : 0); - hash = hash * 23 + (IgnoreRepositoryCommon.HasValue ? IgnoreRepositoryCommon.Value.GetHashCode() : 0); - hash = hash * 23 + (IgnoreUnchangedPath.HasValue ? IgnoreUnchangedPath.Value.GetHashCode() : 0); - hash = hash * 23 + (ObjectPath != null ? ObjectPath.GetHashCode() : 0); - hash = hash * 23 + (PathIgnore != null ? PathIgnore.GetHashCode() : 0); - hash = hash * 23 + (TargetType != null ? TargetType.GetHashCode() : 0); - return hash; - } - } + 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; + } - /// - /// Merge two option instances by repacing any unset properties from with values. - /// Values from that are set are not overridden. - /// - 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 - }; - return result; - } + /// + /// The input string format. + /// + [DefaultValue(null)] + public InputFormat? Format { get; set; } - /// - /// The input string format. - /// - [DefaultValue(null)] - public InputFormat? Format { get; set; } - - /// - /// Determine if files within the .git path are ignored. - /// - [DefaultValue(null)] - public bool? IgnoreGitPath { get; set; } - - /// - /// Determines if objects are ignored based on their file source path. - /// - [DefaultValue(null)] - public bool? IgnoreObjectSource { get; set; } - - /// - /// Determine if common repository files are ignored. - /// - [DefaultValue(null)] - public bool? IgnoreRepositoryCommon { get; set; } - - /// - /// Determine if unchanged files are ignored. - /// - [DefaultValue(null)] - public bool? IgnoreUnchangedPath { get; set; } - - /// - /// The object path to a property to use instead of the pipeline object. - /// - [DefaultValue(null)] - public string ObjectPath { get; set; } - - /// - /// Ignores input files that match the path spec. - /// - [DefaultValue(null)] - public string[] PathIgnore { get; set; } - - /// - /// Only process objects that match one of the included types. - /// - [DefaultValue(null)] - public string[] TargetType { get; set; } - - internal void Load() - { - if (Environment.TryEnum("PSRULE_INPUT_FORMAT", out InputFormat format)) - Format = format; + /// + /// Determine if files within the .git path are ignored. + /// + [DefaultValue(null)] + public bool? IgnoreGitPath { get; set; } + + /// + /// Determines if objects are ignored based on their file source path. + /// + [DefaultValue(null)] + public bool? IgnoreObjectSource { get; set; } + + /// + /// Determine if common repository files are ignored. + /// + [DefaultValue(null)] + public bool? IgnoreRepositoryCommon { get; set; } + + /// + /// Determine if unchanged files are ignored. + /// + [DefaultValue(null)] + public bool? IgnoreUnchangedPath { get; set; } + + /// + /// The object path to a property to use instead of the pipeline object. + /// + [DefaultValue(null)] + public string ObjectPath { get; set; } - if (Environment.TryBool("PSRULE_INPUT_IGNOREGITPATH", out var ignoreGitPath)) - IgnoreGitPath = ignoreGitPath; + /// + /// Ignores input files that match the path spec. + /// + [DefaultValue(null)] + public string[] PathIgnore { get; set; } - if (Environment.TryBool("PSRULE_INPUT_IGNOREOBJECTSOURCE", out var ignoreObjectSource)) - IgnoreObjectSource = ignoreObjectSource; + /// + /// Only process objects that match one of the included types. + /// + [DefaultValue(null)] + public string[] TargetType { get; set; } - if (Environment.TryBool("PSRULE_INPUT_IGNOREREPOSITORYCOMMON", out var ignoreRepositoryCommon)) - IgnoreRepositoryCommon = ignoreRepositoryCommon; + internal void Load() + { + if (Environment.TryEnum("PSRULE_INPUT_FORMAT", out InputFormat format)) + Format = format; - if (Environment.TryBool("PSRULE_INPUT_IGNOREUNCHANGEDPATH", out var ignoreUnchangedPath)) - IgnoreUnchangedPath = ignoreUnchangedPath; + if (Environment.TryBool("PSRULE_INPUT_IGNOREGITPATH", out var ignoreGitPath)) + IgnoreGitPath = ignoreGitPath; - if (Environment.TryString("PSRULE_INPUT_OBJECTPATH", out var objectPath)) - ObjectPath = objectPath; + if (Environment.TryBool("PSRULE_INPUT_IGNOREOBJECTSOURCE", out var ignoreObjectSource)) + IgnoreObjectSource = ignoreObjectSource; - if (Environment.TryStringArray("PSRULE_INPUT_PATHIGNORE", out var pathIgnore)) - PathIgnore = pathIgnore; + if (Environment.TryBool("PSRULE_INPUT_IGNOREREPOSITORYCOMMON", out var ignoreRepositoryCommon)) + IgnoreRepositoryCommon = ignoreRepositoryCommon; - if (Environment.TryStringArray("PSRULE_INPUT_TARGETTYPE", out var targetType)) - TargetType = targetType; - } + if (Environment.TryBool("PSRULE_INPUT_IGNOREUNCHANGEDPATH", out var ignoreUnchangedPath)) + IgnoreUnchangedPath = ignoreUnchangedPath; - internal void Load(Dictionary index) - { - if (index.TryPopEnum("Input.Format", out InputFormat format)) - Format = format; + if (Environment.TryString("PSRULE_INPUT_OBJECTPATH", out var objectPath)) + ObjectPath = objectPath; - if (index.TryPopBool("Input.IgnoreGitPath", out var ignoreGitPath)) - IgnoreGitPath = ignoreGitPath; + if (Environment.TryStringArray("PSRULE_INPUT_PATHIGNORE", out var pathIgnore)) + PathIgnore = pathIgnore; - if (index.TryPopBool("Input.IgnoreObjectSource", out var ignoreObjectSource)) - IgnoreObjectSource = ignoreObjectSource; + if (Environment.TryStringArray("PSRULE_INPUT_TARGETTYPE", out var targetType)) + TargetType = targetType; + } - if (index.TryPopBool("Input.IgnoreRepositoryCommon", out var ignoreRepositoryCommon)) - IgnoreRepositoryCommon = ignoreRepositoryCommon; + internal void Load(Dictionary index) + { + if (index.TryPopEnum("Input.Format", out InputFormat format)) + Format = format; - if (index.TryPopBool("Input.IgnoreUnchangedPath", out var ignoreUnchangedPath)) - IgnoreUnchangedPath = ignoreUnchangedPath; + if (index.TryPopBool("Input.IgnoreGitPath", out var ignoreGitPath)) + IgnoreGitPath = ignoreGitPath; - if (index.TryPopString("Input.ObjectPath", out var objectPath)) - ObjectPath = objectPath; + if (index.TryPopBool("Input.IgnoreObjectSource", out var ignoreObjectSource)) + IgnoreObjectSource = ignoreObjectSource; - if (index.TryPopStringArray("Input.PathIgnore", out var pathIgnore)) - PathIgnore = pathIgnore; + if (index.TryPopBool("Input.IgnoreRepositoryCommon", out var ignoreRepositoryCommon)) + IgnoreRepositoryCommon = ignoreRepositoryCommon; - if (index.TryPopStringArray("Input.TargetType", out var targetType)) - TargetType = targetType; - } + if (index.TryPopBool("Input.IgnoreUnchangedPath", out var ignoreUnchangedPath)) + IgnoreUnchangedPath = ignoreUnchangedPath; + + if (index.TryPopString("Input.ObjectPath", out var objectPath)) + ObjectPath = objectPath; + + if (index.TryPopStringArray("Input.PathIgnore", out var pathIgnore)) + PathIgnore = pathIgnore; + + if (index.TryPopStringArray("Input.TargetType", out var targetType)) + TargetType = targetType; } } diff --git a/src/PSRule/Configuration/JobSummaryFormat.cs b/src/PSRule/Configuration/JobSummaryFormat.cs index 6ce30f3ef4..6084d99065 100644 --- a/src/PSRule/Configuration/JobSummaryFormat.cs +++ b/src/PSRule/Configuration/JobSummaryFormat.cs @@ -4,34 +4,33 @@ using Newtonsoft.Json; using Newtonsoft.Json.Converters; -namespace PSRule.Configuration +namespace PSRule.Configuration; + +/// +/// The information displayed for job summaries. +/// Currently this is not exposed as a configuration option. +/// +[Flags] +[JsonConverter(typeof(StringEnumConverter))] +internal enum JobSummaryFormat { /// - /// The information displayed for job summaries. - /// Currently this is not exposed as a configuration option. + /// No job summary is outputted. /// - [Flags] - [JsonConverter(typeof(StringEnumConverter))] - internal enum JobSummaryFormat - { - /// - /// No job summary is outputted. - /// - None = 0, + None = 0, - /// - /// Include rule analysis within job summary. - /// - Analysis = 1, + /// + /// Include rule analysis within job summary. + /// + Analysis = 1, - /// - /// The rules module versions used in this run are shown. - /// - Source = 2, + /// + /// The rules module versions used in this run are shown. + /// + Source = 2, - /// - /// The default information shown in job summaries. - /// - Default = Analysis | Source, - } + /// + /// The default information shown in job summaries. + /// + Default = Analysis | Source, } diff --git a/src/PSRule/Configuration/LoggingOption.cs b/src/PSRule/Configuration/LoggingOption.cs index 1ecc04e084..09f93720f0 100644 --- a/src/PSRule/Configuration/LoggingOption.cs +++ b/src/PSRule/Configuration/LoggingOption.cs @@ -3,144 +3,143 @@ using System.ComponentModel; -namespace PSRule.Configuration +namespace PSRule.Configuration; + +/// +/// Options for logging outcomes to a informational streams. +/// +public sealed class LoggingOption : IEquatable { + private const OutcomeLogStream DEFAULT_RULEFAIL = OutcomeLogStream.None; + private const OutcomeLogStream DEFAULT_RULEPASS = OutcomeLogStream.None; + + internal static readonly LoggingOption Default = new() + { + RuleFail = DEFAULT_RULEFAIL, + RulePass = DEFAULT_RULEPASS, + LimitVerbose = null, + LimitDebug = null + }; + /// - /// Options for logging outcomes to a informational streams. + /// Create an empty logging option. /// - public sealed class LoggingOption : IEquatable + public LoggingOption() { - private const OutcomeLogStream DEFAULT_RULEFAIL = OutcomeLogStream.None; - private const OutcomeLogStream DEFAULT_RULEPASS = OutcomeLogStream.None; + LimitDebug = null; + LimitVerbose = null; + RuleFail = null; + RulePass = null; + } - internal static readonly LoggingOption Default = new() - { - RuleFail = DEFAULT_RULEFAIL, - RulePass = DEFAULT_RULEPASS, - LimitVerbose = null, - LimitDebug = null - }; + /// + /// Create an logging option by copying an existing instance. + /// + /// The option instance to copy. + public LoggingOption(LoggingOption option) + { + if (option == null) + return; - /// - /// Create an empty logging option. - /// - public LoggingOption() - { - LimitDebug = null; - LimitVerbose = null; - RuleFail = null; - RulePass = null; - } + LimitDebug = option.LimitDebug; + LimitVerbose = option.LimitVerbose; + RuleFail = option.RuleFail; + RulePass = option.RulePass; + } - /// - /// Create an logging option by copying an existing instance. - /// - /// The option instance to copy. - public LoggingOption(LoggingOption option) - { - if (option == null) - return; + /// + public override bool Equals(object obj) + { + return obj is LoggingOption option && Equals(option); + } - LimitDebug = option.LimitDebug; - LimitVerbose = option.LimitVerbose; - RuleFail = option.RuleFail; - RulePass = option.RulePass; - } + /// + public bool Equals(LoggingOption other) + { + return other != null && + LimitDebug == other.LimitDebug && + LimitVerbose == other.LimitVerbose && + RuleFail == other.RuleFail && + RulePass == other.RulePass; + } - /// - public override bool Equals(object obj) + /// + public override int GetHashCode() + { + unchecked // Overflow is fine { - return obj is LoggingOption option && Equals(option); + var hash = 17; + hash = hash * 23 + (LimitDebug != null ? LimitDebug.GetHashCode() : 0); + hash = hash * 23 + (LimitVerbose != null ? LimitVerbose.GetHashCode() : 0); + hash = hash * 23 + (RuleFail.HasValue ? RuleFail.Value.GetHashCode() : 0); + hash = hash * 23 + (RulePass.HasValue ? RulePass.Value.GetHashCode() : 0); + return hash; } + } - /// - public bool Equals(LoggingOption other) + internal static LoggingOption Combine(LoggingOption o1, LoggingOption o2) + { + var result = new LoggingOption(o1) { - return other != null && - LimitDebug == other.LimitDebug && - LimitVerbose == other.LimitVerbose && - RuleFail == other.RuleFail && - RulePass == other.RulePass; - } + LimitDebug = o1?.LimitDebug ?? o2?.LimitDebug, + LimitVerbose = o1?.LimitVerbose ?? o2?.LimitVerbose, + RuleFail = o1?.RuleFail ?? o2?.RuleFail, + RulePass = o1?.RulePass ?? o2?.RulePass + }; + return result; + } - /// - public override int GetHashCode() - { - unchecked // Overflow is fine - { - var hash = 17; - hash = hash * 23 + (LimitDebug != null ? LimitDebug.GetHashCode() : 0); - hash = hash * 23 + (LimitVerbose != null ? LimitVerbose.GetHashCode() : 0); - hash = hash * 23 + (RuleFail.HasValue ? RuleFail.Value.GetHashCode() : 0); - hash = hash * 23 + (RulePass.HasValue ? RulePass.Value.GetHashCode() : 0); - return hash; - } - } + /// + /// Limits debug messages to a list of named debug scopes. + /// + [DefaultValue(null)] + public string[] LimitDebug { get; set; } - 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 - }; - return result; - } + /// + /// Limits verbose messages to a list of named verbose scopes. + /// + [DefaultValue(null)] + public string[] LimitVerbose { get; set; } - /// - /// Limits debug messages to a list of named debug scopes. - /// - [DefaultValue(null)] - public string[] LimitDebug { get; set; } - - /// - /// Limits verbose messages to a list of named verbose scopes. - /// - [DefaultValue(null)] - public string[] LimitVerbose { get; set; } - - /// - /// Log fail outcomes for each rule to a specific informational stream. - /// - [DefaultValue(null)] - public OutcomeLogStream? RuleFail { get; set; } - - /// - /// Log pass outcomes for each rule to a specific informational stream. - /// - [DefaultValue(null)] - public OutcomeLogStream? RulePass { get; set; } - - internal void Load() - { - if (Environment.TryStringArray("PSRULE_LOGGING_LIMITDEBUG", out var limitDebug)) - LimitDebug = limitDebug; + /// + /// Log fail outcomes for each rule to a specific informational stream. + /// + [DefaultValue(null)] + public OutcomeLogStream? RuleFail { get; set; } - if (Environment.TryStringArray("PSRULE_LOGGING_LIMITVERBOSE", out var limitVerbose)) - LimitVerbose = limitVerbose; + /// + /// Log pass outcomes for each rule to a specific informational stream. + /// + [DefaultValue(null)] + public OutcomeLogStream? RulePass { get; set; } - if (Environment.TryEnum("PSRULE_LOGGING_RULEFAIL", out OutcomeLogStream ruleFail)) - RuleFail = ruleFail; + internal void Load() + { + if (Environment.TryStringArray("PSRULE_LOGGING_LIMITDEBUG", out var limitDebug)) + LimitDebug = limitDebug; - if (Environment.TryEnum("PSRULE_LOGGING_RULEPASS", out OutcomeLogStream rulePass)) - RulePass = rulePass; - } + if (Environment.TryStringArray("PSRULE_LOGGING_LIMITVERBOSE", out var limitVerbose)) + LimitVerbose = limitVerbose; - internal void Load(Dictionary index) - { - if (index.TryPopStringArray("Logging.LimitDebug", out var limitDebug)) - LimitDebug = limitDebug; + if (Environment.TryEnum("PSRULE_LOGGING_RULEFAIL", out OutcomeLogStream ruleFail)) + RuleFail = ruleFail; - if (index.TryPopStringArray("Logging.LimitVerbose", out var limitVerbose)) - LimitVerbose = limitVerbose; + if (Environment.TryEnum("PSRULE_LOGGING_RULEPASS", out OutcomeLogStream rulePass)) + RulePass = rulePass; + } - if (index.TryPopEnum("Logging.RuleFail", out OutcomeLogStream ruleFail)) - RuleFail = ruleFail; + internal void Load(Dictionary index) + { + if (index.TryPopStringArray("Logging.LimitDebug", out var limitDebug)) + LimitDebug = limitDebug; - if (index.TryPopEnum("Logging.RulePass", out OutcomeLogStream rulePass)) - RulePass = rulePass; - } + if (index.TryPopStringArray("Logging.LimitVerbose", out var limitVerbose)) + LimitVerbose = limitVerbose; + + if (index.TryPopEnum("Logging.RuleFail", out OutcomeLogStream ruleFail)) + RuleFail = ruleFail; + + if (index.TryPopEnum("Logging.RulePass", out OutcomeLogStream rulePass)) + RulePass = rulePass; } } diff --git a/src/PSRule/Configuration/OutcomeLogStream.cs b/src/PSRule/Configuration/OutcomeLogStream.cs index 99b1abd3f1..ce027a8720 100644 --- a/src/PSRule/Configuration/OutcomeLogStream.cs +++ b/src/PSRule/Configuration/OutcomeLogStream.cs @@ -4,32 +4,31 @@ using Newtonsoft.Json; using Newtonsoft.Json.Converters; -namespace PSRule.Configuration +namespace PSRule.Configuration; + +/// +/// The PowerShell informational stream to log specific outcomes to. +/// +[JsonConverter(typeof(StringEnumConverter))] +public enum OutcomeLogStream { /// - /// The PowerShell informational stream to log specific outcomes to. + /// Outcomes will not be logged to an informational stream. /// - [JsonConverter(typeof(StringEnumConverter))] - public enum OutcomeLogStream - { - /// - /// Outcomes will not be logged to an informational stream. - /// - None = 0, + None = 0, - /// - /// Log to Error stream. - /// - Error = 1, + /// + /// Log to Error stream. + /// + Error = 1, - /// - /// Log to Warning stream. - /// - Warning = 2, + /// + /// Log to Warning stream. + /// + Warning = 2, - /// - /// Log to Information stream. - /// - Information = 3 - } + /// + /// Log to Information stream. + /// + Information = 3 } diff --git a/src/PSRule/Configuration/OutputEncoding.cs b/src/PSRule/Configuration/OutputEncoding.cs index a24e9a3a99..b04f7622dd 100644 --- a/src/PSRule/Configuration/OutputEncoding.cs +++ b/src/PSRule/Configuration/OutputEncoding.cs @@ -4,43 +4,42 @@ using Newtonsoft.Json; using Newtonsoft.Json.Converters; -namespace PSRule.Configuration +namespace PSRule.Configuration; + +/// +/// The encoding format to convert output to. +/// +/// +[JsonConverter(typeof(StringEnumConverter))] +public enum OutputEncoding { /// - /// The encoding format to convert output to. - /// + /// UTF-8 with Byte Order Mark (BOM). This is the default. + /// + Default = 0, + + /// + /// UTF-8 without Byte Order Mark (BOM). + /// + UTF8, + + /// + /// UTF-7. + /// + UTF7, + + /// + /// Unicode. Same as UTF-16. + /// + Unicode, + + /// + /// UTF-32. + /// + UTF32, + + /// + /// ASCII. /// - [JsonConverter(typeof(StringEnumConverter))] - public enum OutputEncoding - { - /// - /// UTF-8 with Byte Order Mark (BOM). This is the default. - /// - Default = 0, - - /// - /// UTF-8 without Byte Order Mark (BOM). - /// - UTF8, - - /// - /// UTF-7. - /// - UTF7, - - /// - /// Unicode. Same as UTF-16. - /// - Unicode, - - /// - /// UTF-32. - /// - UTF32, - - /// - /// ASCII. - /// - ASCII - } + ASCII } diff --git a/src/PSRule/Configuration/OutputFormat.cs b/src/PSRule/Configuration/OutputFormat.cs index e6a0a736c9..0cb0997f65 100644 --- a/src/PSRule/Configuration/OutputFormat.cs +++ b/src/PSRule/Configuration/OutputFormat.cs @@ -4,53 +4,52 @@ using Newtonsoft.Json; using Newtonsoft.Json.Converters; -namespace PSRule.Configuration +namespace PSRule.Configuration; + +/// +/// The formats to return results in. +/// +/// +[JsonConverter(typeof(StringEnumConverter))] +public enum OutputFormat { /// - /// The formats to return results in. - /// - /// - [JsonConverter(typeof(StringEnumConverter))] - public enum OutputFormat - { - /// - /// Output is presented as an object using PowerShell defaults. This is the default. - /// - None = 0, - - /// - /// Output is serialized as YAML. - /// - Yaml = 1, - - /// - /// Output is serialized as JSON. - /// - Json = 2, - - /// - /// Output is serialized as NUnit3 (XML). - /// - NUnit3 = 3, - - /// - /// Output is serialized as a comma-separated values (CSV). - /// - Csv = 4, - - /// - /// Output is presented using the wide table format, which includes reason and wraps columns. - /// - Wide = 5, - - /// - /// Output is serialized as Markdown. - /// - Markdown = 6, - - /// - /// Output is serialized as SARIF. - /// - Sarif = 7 - } + /// Output is presented as an object using PowerShell defaults. This is the default. + /// + None = 0, + + /// + /// Output is serialized as YAML. + /// + Yaml = 1, + + /// + /// Output is serialized as JSON. + /// + Json = 2, + + /// + /// Output is serialized as NUnit3 (XML). + /// + NUnit3 = 3, + + /// + /// Output is serialized as a comma-separated values (CSV). + /// + Csv = 4, + + /// + /// Output is presented using the wide table format, which includes reason and wraps columns. + /// + Wide = 5, + + /// + /// Output is serialized as Markdown. + /// + Markdown = 6, + + /// + /// Output is serialized as SARIF. + /// + Sarif = 7 } diff --git a/src/PSRule/Configuration/OutputOption.cs b/src/PSRule/Configuration/OutputOption.cs index 71a801f11d..b147f97804 100644 --- a/src/PSRule/Configuration/OutputOption.cs +++ b/src/PSRule/Configuration/OutputOption.cs @@ -4,291 +4,290 @@ using System.ComponentModel; using PSRule.Rules; -namespace PSRule.Configuration +namespace PSRule.Configuration; + +/// +/// Options for generating and formatting output. +/// +public sealed class OutputOption : IEquatable { + private const ResultFormat DEFAULT_AS = ResultFormat.Detail; + private const BannerFormat DEFAULT_BANNER = BannerFormat.Default; + private const OutputEncoding DEFAULT_ENCODING = OutputEncoding.Default; + private const FooterFormat DEFAULT_FOOTER = FooterFormat.Default; + private const OutputFormat DEFAULT_FORMAT = OutputFormat.None; + private const int DEFAULT_JSON_INDENT = 0; + private const RuleOutcome DEFAULT_OUTCOME = RuleOutcome.Processed; + private const bool DEFAULT_SARIF_PROBLEMS_ONLY = true; + private const OutputStyle DEFAULT_STYLE = OutputStyle.Detect; + + internal static readonly OutputOption Default = new() + { + As = DEFAULT_AS, + Banner = DEFAULT_BANNER, + Encoding = DEFAULT_ENCODING, + Footer = DEFAULT_FOOTER, + Format = DEFAULT_FORMAT, + JsonIndent = DEFAULT_JSON_INDENT, + Outcome = DEFAULT_OUTCOME, + SarifProblemsOnly = DEFAULT_SARIF_PROBLEMS_ONLY, + Style = DEFAULT_STYLE, + }; + /// - /// Options for generating and formatting output. + /// Creates an empty output option. /// - public sealed class OutputOption : IEquatable + public OutputOption() { - private const ResultFormat DEFAULT_AS = ResultFormat.Detail; - private const BannerFormat DEFAULT_BANNER = BannerFormat.Default; - private const OutputEncoding DEFAULT_ENCODING = OutputEncoding.Default; - private const FooterFormat DEFAULT_FOOTER = FooterFormat.Default; - private const OutputFormat DEFAULT_FORMAT = OutputFormat.None; - private const int DEFAULT_JSON_INDENT = 0; - private const RuleOutcome DEFAULT_OUTCOME = RuleOutcome.Processed; - private const bool DEFAULT_SARIF_PROBLEMS_ONLY = true; - private const OutputStyle DEFAULT_STYLE = OutputStyle.Detect; - - internal static readonly OutputOption Default = new() - { - As = DEFAULT_AS, - Banner = DEFAULT_BANNER, - Encoding = DEFAULT_ENCODING, - Footer = DEFAULT_FOOTER, - Format = DEFAULT_FORMAT, - JsonIndent = DEFAULT_JSON_INDENT, - Outcome = DEFAULT_OUTCOME, - SarifProblemsOnly = DEFAULT_SARIF_PROBLEMS_ONLY, - Style = DEFAULT_STYLE, - }; + As = null; + Banner = null; + Culture = null; + Encoding = null; + Footer = null; + Format = null; + JobSummaryPath = null; + JsonIndent = null; + Path = null; + SarifProblemsOnly = null; + Style = null; + } - /// - /// Creates an empty output option. - /// - public OutputOption() - { - As = null; - Banner = null; - Culture = null; - Encoding = null; - Footer = null; - Format = null; - JobSummaryPath = null; - JsonIndent = null; - Path = null; - SarifProblemsOnly = null; - Style = null; - } + /// + /// Creates a output option by copying an existing instance. + /// + /// The option instance to copy. + public OutputOption(OutputOption option) + { + if (option == null) + return; + + As = option.As; + Banner = option.Banner; + Culture = option.Culture; + Encoding = option.Encoding; + Footer = option.Footer; + Format = option.Format; + JobSummaryPath = option.JobSummaryPath; + JsonIndent = option.JsonIndent; + Outcome = option.Outcome; + Path = option.Path; + SarifProblemsOnly = option.SarifProblemsOnly; + Style = option.Style; + } - /// - /// Creates a output option by copying an existing instance. - /// - /// The option instance to copy. - public OutputOption(OutputOption option) - { - if (option == null) - return; - - As = option.As; - Banner = option.Banner; - Culture = option.Culture; - Encoding = option.Encoding; - Footer = option.Footer; - Format = option.Format; - JobSummaryPath = option.JobSummaryPath; - JsonIndent = option.JsonIndent; - Outcome = option.Outcome; - Path = option.Path; - SarifProblemsOnly = option.SarifProblemsOnly; - Style = option.Style; - } + /// + public override bool Equals(object obj) + { + return obj is OutputOption option && Equals(option); + } - /// - public override bool Equals(object obj) - { - return obj is OutputOption option && Equals(option); - } + /// + public bool Equals(OutputOption other) + { + return other != null && + As == other.As && + Banner == other.Banner && + Culture == other.Culture && + Encoding == other.Encoding && + Footer == other.Footer && + Format == other.Format && + JobSummaryPath == other.JobSummaryPath && + JsonIndent == other.JsonIndent && + Outcome == other.Outcome && + Path == other.Path && + SarifProblemsOnly == other.SarifProblemsOnly && + Style == other.Style; + } - /// - public bool Equals(OutputOption other) + /// + public override int GetHashCode() + { + unchecked // Overflow is fine { - return other != null && - As == other.As && - Banner == other.Banner && - Culture == other.Culture && - Encoding == other.Encoding && - Footer == other.Footer && - Format == other.Format && - JobSummaryPath == other.JobSummaryPath && - JsonIndent == other.JsonIndent && - Outcome == other.Outcome && - Path == other.Path && - SarifProblemsOnly == other.SarifProblemsOnly && - Style == other.Style; + var hash = 17; + hash = hash * 23 + (As.HasValue ? As.Value.GetHashCode() : 0); + hash = hash * 23 + (Banner.HasValue ? Banner.Value.GetHashCode() : 0); + hash = hash * 23 + (Culture != null ? Culture.GetHashCode() : 0); + hash = hash * 23 + (Encoding.HasValue ? Encoding.Value.GetHashCode() : 0); + hash = hash * 23 + (Footer.HasValue ? Footer.Value.GetHashCode() : 0); + hash = hash * 23 + (Format.HasValue ? Format.Value.GetHashCode() : 0); + hash = hash * 23 + (JobSummaryPath != null ? JobSummaryPath.GetHashCode() : 0); + hash = hash * 23 + (JsonIndent.HasValue ? JsonIndent.Value.GetHashCode() : 0); + hash = hash * 23 + (Outcome.HasValue ? Outcome.Value.GetHashCode() : 0); + hash = hash * 23 + (Path != null ? Path.GetHashCode() : 0); + hash = hash * 23 + (SarifProblemsOnly.HasValue ? SarifProblemsOnly.Value.GetHashCode() : 0); + hash = hash * 23 + (Style.HasValue ? Style.Value.GetHashCode() : 0); + return hash; } + } - /// - public override int GetHashCode() + internal static OutputOption Combine(OutputOption o1, OutputOption o2) + { + var result = new OutputOption(o1) { - unchecked // Overflow is fine - { - var hash = 17; - hash = hash * 23 + (As.HasValue ? As.Value.GetHashCode() : 0); - hash = hash * 23 + (Banner.HasValue ? Banner.Value.GetHashCode() : 0); - hash = hash * 23 + (Culture != null ? Culture.GetHashCode() : 0); - hash = hash * 23 + (Encoding.HasValue ? Encoding.Value.GetHashCode() : 0); - hash = hash * 23 + (Footer.HasValue ? Footer.Value.GetHashCode() : 0); - hash = hash * 23 + (Format.HasValue ? Format.Value.GetHashCode() : 0); - hash = hash * 23 + (JobSummaryPath != null ? JobSummaryPath.GetHashCode() : 0); - hash = hash * 23 + (JsonIndent.HasValue ? JsonIndent.Value.GetHashCode() : 0); - hash = hash * 23 + (Outcome.HasValue ? Outcome.Value.GetHashCode() : 0); - hash = hash * 23 + (Path != null ? Path.GetHashCode() : 0); - hash = hash * 23 + (SarifProblemsOnly.HasValue ? SarifProblemsOnly.Value.GetHashCode() : 0); - hash = hash * 23 + (Style.HasValue ? Style.Value.GetHashCode() : 0); - return hash; - } - } + 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; + } - 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, - }; - return result; - } + /// + /// The type of result to produce. + /// + [DefaultValue(null)] + public ResultFormat? As { get; set; } - /// - /// The type of result to produce. - /// - [DefaultValue(null)] - public ResultFormat? As { get; set; } - - /// - /// The information displayed for Assert-PSRule banner. - /// - [DefaultValue(null)] - public BannerFormat? Banner { get; set; } - - /// - /// One or more cultures to use for generating output. - /// - [DefaultValue(null)] - public string[] Culture { get; set; } - - /// - /// The encoding to use when writing results to file. - /// - [DefaultValue(null)] - public OutputEncoding? Encoding { get; set; } - - /// - /// The information displayed for Assert-PSRule footer. - /// - [DefaultValue(null)] - public FooterFormat? Footer { get; set; } - - /// - /// The output format. - /// - [DefaultValue(null)] - public OutputFormat? Format { get; set; } - - /// - /// The path to a job summary output file. - /// - [DefaultValue(null)] - public string JobSummaryPath { get; set; } - - /// - /// The indentation for JSON output. - /// - [DefaultValue(null)] - public int? JsonIndent { get; set; } - - /// - /// The outcome of rule results to return. - /// - [DefaultValue(null)] - public RuleOutcome? Outcome { get; set; } - - /// - /// The file path location to save results. - /// - [DefaultValue(null)] - public string Path { get; set; } - - /// - /// The style that results will be presented in. - /// - [DefaultValue(null)] - public OutputStyle? Style { get; set; } - - /// - /// Determines if SARIF output only includes rules with fail or error outcomes. - /// - [DefaultValue(null)] - public bool? SarifProblemsOnly { get; set; } - - internal void Load() - { - if (Environment.TryEnum("PSRULE_OUTPUT_AS", out ResultFormat value)) - As = value; + /// + /// The information displayed for Assert-PSRule banner. + /// + [DefaultValue(null)] + public BannerFormat? Banner { get; set; } + + /// + /// One or more cultures to use for generating output. + /// + [DefaultValue(null)] + public string[] Culture { get; set; } - if (Environment.TryEnum("PSRULE_OUTPUT_BANNER", out BannerFormat banner)) - Banner = banner; + /// + /// The encoding to use when writing results to file. + /// + [DefaultValue(null)] + public OutputEncoding? Encoding { get; set; } - if (Environment.TryStringArray("PSRULE_OUTPUT_CULTURE", out var culture)) - Culture = culture; + /// + /// The information displayed for Assert-PSRule footer. + /// + [DefaultValue(null)] + public FooterFormat? Footer { get; set; } - if (Environment.TryEnum("PSRULE_OUTPUT_ENCODING", out OutputEncoding encoding)) - Encoding = encoding; + /// + /// The output format. + /// + [DefaultValue(null)] + public OutputFormat? Format { get; set; } - if (Environment.TryEnum("PSRULE_OUTPUT_FOOTER", out FooterFormat footer)) - Footer = footer; + /// + /// The path to a job summary output file. + /// + [DefaultValue(null)] + public string JobSummaryPath { get; set; } - if (Environment.TryEnum("PSRULE_OUTPUT_FORMAT", out OutputFormat format)) - Format = format; + /// + /// The indentation for JSON output. + /// + [DefaultValue(null)] + public int? JsonIndent { get; set; } - if (Environment.TryString("PSRULE_OUTPUT_JOBSUMMARYPATH", out var jobSummaryPath)) - JobSummaryPath = jobSummaryPath; + /// + /// The outcome of rule results to return. + /// + [DefaultValue(null)] + public RuleOutcome? Outcome { get; set; } - if (Environment.TryInt("PSRULE_OUTPUT_JSONINDENT", out var jsonIndent)) - JsonIndent = jsonIndent; + /// + /// The file path location to save results. + /// + [DefaultValue(null)] + public string Path { get; set; } - if (Environment.TryEnum("PSRULE_OUTPUT_OUTCOME", out RuleOutcome outcome)) - Outcome = outcome; + /// + /// The style that results will be presented in. + /// + [DefaultValue(null)] + public OutputStyle? Style { get; set; } - if (Environment.TryString("PSRULE_OUTPUT_PATH", out var path)) - Path = path; + /// + /// Determines if SARIF output only includes rules with fail or error outcomes. + /// + [DefaultValue(null)] + public bool? SarifProblemsOnly { get; set; } - if (Environment.TryEnum("PSRULE_OUTPUT_STYLE", out OutputStyle style)) - Style = style; + internal void Load() + { + if (Environment.TryEnum("PSRULE_OUTPUT_AS", out ResultFormat value)) + As = value; - if (Environment.TryBool("PSRULE_OUTPUT_SARIFPROBLEMSONLY", out var sarifProblemsOnly)) - SarifProblemsOnly = sarifProblemsOnly; - } + if (Environment.TryEnum("PSRULE_OUTPUT_BANNER", out BannerFormat banner)) + Banner = banner; - internal void Load(Dictionary index) - { - if (index.TryPopEnum("Output.As", out ResultFormat value)) - As = value; + if (Environment.TryStringArray("PSRULE_OUTPUT_CULTURE", out var culture)) + Culture = culture; - if (index.TryPopEnum("Output.Banner", out BannerFormat banner)) - Banner = banner; + if (Environment.TryEnum("PSRULE_OUTPUT_ENCODING", out OutputEncoding encoding)) + Encoding = encoding; - if (index.TryPopStringArray("Output.Culture", out var culture)) - Culture = culture; + if (Environment.TryEnum("PSRULE_OUTPUT_FOOTER", out FooterFormat footer)) + Footer = footer; - if (index.TryPopEnum("Output.Encoding", out OutputEncoding encoding)) - Encoding = encoding; + if (Environment.TryEnum("PSRULE_OUTPUT_FORMAT", out OutputFormat format)) + Format = format; - if (index.TryPopEnum("Output.Footer", out FooterFormat footer)) - Footer = footer; + if (Environment.TryString("PSRULE_OUTPUT_JOBSUMMARYPATH", out var jobSummaryPath)) + JobSummaryPath = jobSummaryPath; - if (index.TryPopEnum("Output.Format", out OutputFormat format)) - Format = format; + if (Environment.TryInt("PSRULE_OUTPUT_JSONINDENT", out var jsonIndent)) + JsonIndent = jsonIndent; - if (index.TryPopString("Output.JobSummaryPath", out var jobSummaryPath)) - JobSummaryPath = jobSummaryPath; + if (Environment.TryEnum("PSRULE_OUTPUT_OUTCOME", out RuleOutcome outcome)) + Outcome = outcome; - if (index.TryPopValue("Output.JsonIndent", out var jsonIndent)) - JsonIndent = jsonIndent; + if (Environment.TryString("PSRULE_OUTPUT_PATH", out var path)) + Path = path; - if (index.TryPopEnum("Output.Outcome", out RuleOutcome outcome)) - Outcome = outcome; + if (Environment.TryEnum("PSRULE_OUTPUT_STYLE", out OutputStyle style)) + Style = style; - if (index.TryPopString("Output.Path", out var path)) - Path = path; + if (Environment.TryBool("PSRULE_OUTPUT_SARIFPROBLEMSONLY", out var sarifProblemsOnly)) + SarifProblemsOnly = sarifProblemsOnly; + } - if (index.TryPopEnum("Output.Style", out OutputStyle style)) - Style = style; + internal void Load(Dictionary index) + { + if (index.TryPopEnum("Output.As", out ResultFormat value)) + As = value; - if (index.TryPopBool("Output.SarifProblemsOnly", out var sarifProblemsOnly)) - SarifProblemsOnly = sarifProblemsOnly; - } + if (index.TryPopEnum("Output.Banner", out BannerFormat banner)) + Banner = banner; + + if (index.TryPopStringArray("Output.Culture", out var culture)) + Culture = culture; + + if (index.TryPopEnum("Output.Encoding", out OutputEncoding encoding)) + Encoding = encoding; + + if (index.TryPopEnum("Output.Footer", out FooterFormat footer)) + Footer = footer; + + if (index.TryPopEnum("Output.Format", out OutputFormat format)) + Format = format; + + if (index.TryPopString("Output.JobSummaryPath", out var jobSummaryPath)) + JobSummaryPath = jobSummaryPath; + + if (index.TryPopValue("Output.JsonIndent", out var jsonIndent)) + JsonIndent = jsonIndent; + + if (index.TryPopEnum("Output.Outcome", out RuleOutcome outcome)) + Outcome = outcome; + + if (index.TryPopString("Output.Path", out var path)) + Path = path; + + if (index.TryPopEnum("Output.Style", out OutputStyle style)) + Style = style; + + if (index.TryPopBool("Output.SarifProblemsOnly", out var sarifProblemsOnly)) + SarifProblemsOnly = sarifProblemsOnly; } } diff --git a/src/PSRule/Configuration/OutputStyle.cs b/src/PSRule/Configuration/OutputStyle.cs index c8d26eecbc..5ba566aa57 100644 --- a/src/PSRule/Configuration/OutputStyle.cs +++ b/src/PSRule/Configuration/OutputStyle.cs @@ -4,42 +4,41 @@ using Newtonsoft.Json; using Newtonsoft.Json.Converters; -namespace PSRule.Configuration +namespace PSRule.Configuration; + +/// +/// The style to present assert output in. +/// +[JsonConverter(typeof(StringEnumConverter))] +public enum OutputStyle { /// - /// The style to present assert output in. + /// Formatted text written to host. + /// + Client = 0, + + /// + /// Plain text written to output stream. + /// + Plain = 1, + + /// + /// Text written to output stream, with fails marked for Azure Pipelines. + /// + AzurePipelines = 2, + + /// + /// Text written to output stream, with fails marked for GitHub Actions. + /// + GitHubActions = 3, + + /// + /// Text is written to output stream formatted for Visual Studio Code. + /// + VisualStudioCode = 4, + + /// + /// Automatically detect the style to use. /// - [JsonConverter(typeof(StringEnumConverter))] - public enum OutputStyle - { - /// - /// Formatted text written to host. - /// - Client = 0, - - /// - /// Plain text written to output stream. - /// - Plain = 1, - - /// - /// Text written to output stream, with fails marked for Azure Pipelines. - /// - AzurePipelines = 2, - - /// - /// Text written to output stream, with fails marked for GitHub Actions. - /// - GitHubActions = 3, - - /// - /// Text is written to output stream formatted for Visual Studio Code. - /// - VisualStudioCode = 4, - - /// - /// Automatically detect the style to use. - /// - Detect = 255 - } + Detect = 255 } diff --git a/src/PSRule/Configuration/PSRuleOption.cs b/src/PSRule/Configuration/PSRuleOption.cs index 434bd68873..08fd150923 100644 --- a/src/PSRule/Configuration/PSRuleOption.cs +++ b/src/PSRule/Configuration/PSRuleOption.cs @@ -13,530 +13,529 @@ using YamlDotNet.Serialization; using YamlDotNet.Serialization.NamingConventions; -namespace PSRule.Configuration +namespace PSRule.Configuration; + +/// +/// A structure that stores PSRule configuration options. +/// +/// +/// See . +/// +public sealed class PSRuleOption : IEquatable, IBaselineV1Spec { + private const string DEFAULT_FILENAME = "ps-rule.yaml"; + /// - /// A structure that stores PSRule configuration options. + /// The original source path the options were loaded from if applicable. /// - /// - /// See . - /// - public sealed class PSRuleOption : IEquatable, IBaselineV1Spec + private string _SourcePath; + + private static readonly PSRuleOption Default = new() { - private const string DEFAULT_FILENAME = "ps-rule.yaml"; + Baseline = Options.BaselineOption.Default, + Binding = BindingOption.Default, + Convention = ConventionOption.Default, + Execution = Options.ExecutionOption.Default, + Include = IncludeOption.Default, + Input = InputOption.Default, + Logging = LoggingOption.Default, + Output = OutputOption.Default, + Rule = RuleOption.Default, + }; - /// - /// The original source path the options were loaded from if applicable. - /// - private string _SourcePath; + /// + /// Create an empty PSRule options object. + /// + public PSRuleOption() + { + // Set defaults + Baseline = new Options.BaselineOption(); + Binding = new BindingOption(); + Configuration = new ConfigurationOption(); + Convention = new ConventionOption(); + Execution = new Options.ExecutionOption(); + Include = new IncludeOption(); + Input = new InputOption(); + Logging = new LoggingOption(); + Output = new OutputOption(); + Pipeline = new PipelineHook(); + Repository = new RepositoryOption(); + Requires = new RequiresOption(); + Rule = new RuleOption(); + Suppression = new SuppressionOption(); + } - private static readonly PSRuleOption Default = new() - { - Baseline = Options.BaselineOption.Default, - Binding = BindingOption.Default, - Convention = ConventionOption.Default, - Execution = Options.ExecutionOption.Default, - Include = IncludeOption.Default, - Input = InputOption.Default, - Logging = LoggingOption.Default, - Output = OutputOption.Default, - Rule = RuleOption.Default, - }; - - /// - /// Create an empty PSRule options object. - /// - public PSRuleOption() - { - // Set defaults - Baseline = new Options.BaselineOption(); - Binding = new BindingOption(); - Configuration = new ConfigurationOption(); - Convention = new ConventionOption(); - Execution = new Options.ExecutionOption(); - Include = new IncludeOption(); - Input = new InputOption(); - Logging = new LoggingOption(); - Output = new OutputOption(); - Pipeline = new PipelineHook(); - Repository = new RepositoryOption(); - Requires = new RequiresOption(); - Rule = new RuleOption(); - Suppression = new SuppressionOption(); - } + private PSRuleOption(string sourcePath, PSRuleOption option) + { + _SourcePath = sourcePath; + + // Set from existing option instance + Baseline = new Options.BaselineOption(option?.Baseline); + Binding = new BindingOption(option?.Binding); + Configuration = new ConfigurationOption(option?.Configuration); + Convention = new ConventionOption(option?.Convention); + Execution = new Options.ExecutionOption(option?.Execution); + Include = new IncludeOption(option?.Include); + Input = new InputOption(option?.Input); + Logging = new LoggingOption(option?.Logging); + Output = new OutputOption(option?.Output); + Pipeline = new PipelineHook(option?.Pipeline); + Repository = new RepositoryOption(option?.Repository); + Requires = new RequiresOption(option?.Requires); + Rule = new RuleOption(option?.Rule); + Suppression = new SuppressionOption(option?.Suppression); + } - private PSRuleOption(string sourcePath, PSRuleOption option) - { - _SourcePath = sourcePath; - - // Set from existing option instance - Baseline = new Options.BaselineOption(option?.Baseline); - Binding = new BindingOption(option?.Binding); - Configuration = new ConfigurationOption(option?.Configuration); - Convention = new ConventionOption(option?.Convention); - Execution = new Options.ExecutionOption(option?.Execution); - Include = new IncludeOption(option?.Include); - Input = new InputOption(option?.Input); - Logging = new LoggingOption(option?.Logging); - Output = new OutputOption(option?.Output); - Pipeline = new PipelineHook(option?.Pipeline); - Repository = new RepositoryOption(option?.Repository); - Requires = new RequiresOption(option?.Requires); - Rule = new RuleOption(option?.Rule); - Suppression = new SuppressionOption(option?.Suppression); - } + /// + /// Options that configure baselines. + /// + public Options.BaselineOption Baseline { get; set; } - /// - /// Options that configure baselines. - /// - public Options.BaselineOption Baseline { get; set; } - - /// - /// Options that affect property binding. - /// - public BindingOption Binding { get; set; } - - /// - /// Allows configuration key/ values to be specified that can be used within rule definitions. - /// - public ConfigurationOption Configuration { get; set; } - - /// - /// Options that configure conventions. - /// - public ConventionOption Convention { get; set; } - - /// - /// Options that configure the execution sandbox. - /// - public Options.ExecutionOption Execution { get; set; } - - /// - /// Options that affect source locations imported for execution. - /// - public IncludeOption Include { get; set; } - - /// - /// Options that affect how input types are processed. - /// - public InputOption Input { get; set; } - - /// - /// Options for logging outcomes to a informational streams. - /// - public LoggingOption Logging { get; set; } - - /// - /// Options that affect how output is generated. - /// - public OutputOption Output { get; set; } - - /// - /// Configures pipeline hooks. - /// - [YamlIgnore] - [JsonIgnore] - public PipelineHook Pipeline { get; set; } - - /// - /// Options for repository properties that are used by PSRule. - /// - public RepositoryOption Repository { get; set; } - - /// - /// Specifies the required version of a module to use. - /// - public RequiresOption Requires { get; set; } - - /// - /// Options for that affect which rules are executed by including and filtering discovered rules. - /// - public RuleOption Rule { get; set; } - - /// - /// A set of suppression rules. - /// - public SuppressionOption Suppression { get; set; } - - /// - /// Return options as YAML. - /// - /// PSRule options serialized as YAML. - /// - /// Called from PowerShell. - /// - public string ToYaml() - { - var yaml = GetYaml(); - return string.IsNullOrEmpty(_SourcePath) - ? yaml - : string.Concat( - string.Format( - Thread.CurrentThread.CurrentCulture, - PSRuleResources.OptionsSourceComment, - _SourcePath), - System.Environment.NewLine, - yaml); - } + /// + /// Options that affect property binding. + /// + public BindingOption Binding { get; set; } - /// - /// Create a new object instance with the same options set. - /// - /// A new instance. - public PSRuleOption Clone() - { - return new PSRuleOption(sourcePath: _SourcePath, option: this); - } + /// + /// Allows configuration key/ values to be specified that can be used within rule definitions. + /// + public ConfigurationOption Configuration { get; set; } - /// - /// Create a instance from PSRule defaults. - /// - /// A new instance. - public static PSRuleOption FromDefault() - { - return Default.Clone(); - } + /// + /// Options that configure conventions. + /// + public ConventionOption Convention { get; set; } - /// - /// Merge two option instances by repacing any unset properties from with values. - /// Values from that are set are not overridden. - /// - /// A new instance combining options from both instances. - private static PSRuleOption Combine(PSRuleOption o1, PSRuleOption o2) - { - var result = new PSRuleOption(o1?._SourcePath ?? o2?._SourcePath, o1); - result.Baseline = Options.BaselineOption.Combine(result.Baseline, o2?.Baseline); - result.Binding = BindingOption.Combine(result.Binding, o2?.Binding); - result.Configuration = ConfigurationOption.Combine(result.Configuration, o2?.Configuration); - result.Convention = ConventionOption.Combine(result.Convention, o2?.Convention); - result.Execution = Options.ExecutionOption.Combine(result.Execution, o2?.Execution); - result.Include = IncludeOption.Combine(result.Include, o2?.Include); - result.Input = InputOption.Combine(result.Input, o2?.Input); - result.Logging = LoggingOption.Combine(result.Logging, o2?.Logging); - result.Repository = RepositoryOption.Combine(result.Repository, o2?.Repository); - result.Output = OutputOption.Combine(result.Output, o2?.Output); - return result; - } + /// + /// Options that configure the execution sandbox. + /// + public Options.ExecutionOption Execution { get; set; } - /// - /// Save the PSRuleOption to disk as YAML. - /// - /// The file or directory path to save the YAML file. - public void ToFile(string path) - { - // Get a rooted file path instead of directory or relative path - var filePath = GetFilePath(path: path); - File.WriteAllText(path: filePath, contents: GetYaml()); - } + /// + /// Options that affect source locations imported for execution. + /// + public IncludeOption Include { get; set; } - /// - /// Load a YAML formatted PSRuleOption object from disk. - /// - /// A file or directory to read options from. - /// An options object. - /// - /// This method is called from PowerShell. - /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0046:Convert to conditional expression", Justification = "Avoid nested conditional expressions that increase complexity.")] - public static PSRuleOption FromFile(string path) - { - // Get a rooted file path instead of directory or relative path - var filePath = GetFilePath(path); + /// + /// Options that affect how input types are processed. + /// + public InputOption Input { get; set; } - // Fallback to defaults even if file does not exist when silentlyContinue is true - if (!File.Exists(filePath)) - throw new FileNotFoundException(PSRuleResources.OptionsNotFound, filePath); + /// + /// Options for logging outcomes to a informational streams. + /// + public LoggingOption Logging { get; set; } - return FromEnvironment(FromYaml(path: filePath, yaml: File.ReadAllText(filePath))); - } + /// + /// Options that affect how output is generated. + /// + public OutputOption Output { get; set; } - /// - /// Load a YAML formatted PSRuleOption object from disk. - /// - /// A file or directory to read options from. - /// An options object. - /// - /// This method is called from PowerShell. - /// - public static PSRuleOption FromFileOrEmpty(string path) - { - // Get a rooted file path instead of directory or relative path - var filePath = GetFilePath(path); + /// + /// Configures pipeline hooks. + /// + [YamlIgnore] + [JsonIgnore] + public PipelineHook Pipeline { get; set; } - // Return empty options if file does not exist - return !File.Exists(filePath) ? new PSRuleOption() : FromEnvironment(FromYaml(path: filePath, yaml: File.ReadAllText(filePath))); - } + /// + /// Options for repository properties that are used by PSRule. + /// + public RepositoryOption Repository { get; set; } - /// - /// Load a YAML formatted PSRuleOption object from disk. - /// - /// An options object. - /// - /// This method is called from PowerShell. - /// - public static PSRuleOption FromFileOrEmpty() - { - return FromFileOrEmpty(Environment.GetWorkingPath()); - } + /// + /// Specifies the required version of a module to use. + /// + public RequiresOption Requires { get; set; } - /// - /// Load a YAML formatted PSRuleOption object from disk. - /// - /// - /// A file or directory to read options from. - /// An options object. - /// - /// This method is called from PowerShell. - /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0046:Convert to conditional expression", Justification = "Avoid nested conditional expressions that increase complexity.")] - public static PSRuleOption FromFileOrEmpty(PSRuleOption option, string path) - { - if (option == null) - return FromFileOrEmpty(path); + /// + /// Options for that affect which rules are executed by including and filtering discovered rules. + /// + public RuleOption Rule { get; set; } - return string.IsNullOrEmpty(option._SourcePath) ? Combine(option, FromFileOrEmpty(path)) : option; - } + /// + /// A set of suppression rules. + /// + public SuppressionOption Suppression { get; set; } - private static PSRuleOption FromYaml(string path, string yaml) - { - try - { - var d = new DeserializerBuilder() - .IgnoreUnmatchedProperties() - .WithNamingConvention(CamelCaseNamingConvention.Instance) - .WithTypeConverter(new FieldMapYamlTypeConverter()) - .WithTypeConverter(new StringArrayMapConverter()) - .WithTypeConverter(new SuppressionRuleYamlTypeConverter()) - .WithTypeConverter(new PSObjectYamlTypeConverter()) - .WithNodeTypeResolver(new PSOptionYamlTypeResolver()) - .Build(); - - var option = d.Deserialize(yaml) ?? new PSRuleOption(); - option._SourcePath = path; - return option; - } - catch (SemanticErrorException ex) - { - throw new ConfigurationParseException(path, string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.PSR0001, path, ex.Message), ex); - } - } + /// + /// Return options as YAML. + /// + /// PSRule options serialized as YAML. + /// + /// Called from PowerShell. + /// + public string ToYaml() + { + var yaml = GetYaml(); + return string.IsNullOrEmpty(_SourcePath) + ? yaml + : string.Concat( + string.Format( + Thread.CurrentThread.CurrentCulture, + PSRuleResources.OptionsSourceComment, + _SourcePath), + System.Environment.NewLine, + yaml); + } - /// - /// Read PSRule options from environment variables. - /// - /// An existing options object to set. If null an empty options object is used. - /// An options object. - /// - /// Any environment variables that are set will override options set in the specified object. - /// - private static PSRuleOption FromEnvironment(PSRuleOption option) - { - option ??= new PSRuleOption(); - - // Start loading matching values - option.Baseline.Load(); - option.Convention.Load(); - option.Execution.Load(); - option.Include.Load(); - option.Input.Load(); - option.Logging.Load(); - option.Output.Load(); - option.Repository.Load(); - option.Requires.Load(); - BaselineOption.Load(option); - return option; - } + /// + /// Create a new object instance with the same options set. + /// + /// A new instance. + public PSRuleOption Clone() + { + return new PSRuleOption(sourcePath: _SourcePath, option: this); + } + + /// + /// Create a instance from PSRule defaults. + /// + /// A new instance. + public static PSRuleOption FromDefault() + { + return Default.Clone(); + } + + /// + /// Merge two option instances by repacing any unset properties from with values. + /// Values from that are set are not overridden. + /// + /// A new instance combining options from both instances. + private static PSRuleOption Combine(PSRuleOption o1, PSRuleOption o2) + { + var result = new PSRuleOption(o1?._SourcePath ?? o2?._SourcePath, o1); + result.Baseline = Options.BaselineOption.Combine(result.Baseline, o2?.Baseline); + result.Binding = BindingOption.Combine(result.Binding, o2?.Binding); + result.Configuration = ConfigurationOption.Combine(result.Configuration, o2?.Configuration); + result.Convention = ConventionOption.Combine(result.Convention, o2?.Convention); + result.Execution = Options.ExecutionOption.Combine(result.Execution, o2?.Execution); + result.Include = IncludeOption.Combine(result.Include, o2?.Include); + result.Input = InputOption.Combine(result.Input, o2?.Input); + result.Logging = LoggingOption.Combine(result.Logging, o2?.Logging); + result.Repository = RepositoryOption.Combine(result.Repository, o2?.Repository); + result.Output = OutputOption.Combine(result.Output, o2?.Output); + return result; + } + + /// + /// Save the PSRuleOption to disk as YAML. + /// + /// The file or directory path to save the YAML file. + public void ToFile(string path) + { + // Get a rooted file path instead of directory or relative path + var filePath = GetFilePath(path: path); + File.WriteAllText(path: filePath, contents: GetYaml()); + } + + /// + /// Load a YAML formatted PSRuleOption object from disk. + /// + /// A file or directory to read options from. + /// An options object. + /// + /// This method is called from PowerShell. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0046:Convert to conditional expression", Justification = "Avoid nested conditional expressions that increase complexity.")] + public static PSRuleOption FromFile(string path) + { + // Get a rooted file path instead of directory or relative path + var filePath = GetFilePath(path); + + // Fallback to defaults even if file does not exist when silentlyContinue is true + if (!File.Exists(filePath)) + throw new FileNotFoundException(PSRuleResources.OptionsNotFound, filePath); + + return FromEnvironment(FromYaml(path: filePath, yaml: File.ReadAllText(filePath))); + } + + /// + /// Load a YAML formatted PSRuleOption object from disk. + /// + /// A file or directory to read options from. + /// An options object. + /// + /// This method is called from PowerShell. + /// + public static PSRuleOption FromFileOrEmpty(string path) + { + // Get a rooted file path instead of directory or relative path + var filePath = GetFilePath(path); - /// - /// Read PSRule options from a hashtable. - /// - /// A hashtable to read options from. - /// An options object. - /// - /// A null or empty hashtable will return an empty options object. - /// - public static PSRuleOption FromHashtable(Hashtable hashtable) + // Return empty options if file does not exist + return !File.Exists(filePath) ? new PSRuleOption() : FromEnvironment(FromYaml(path: filePath, yaml: File.ReadAllText(filePath))); + } + + /// + /// Load a YAML formatted PSRuleOption object from disk. + /// + /// An options object. + /// + /// This method is called from PowerShell. + /// + public static PSRuleOption FromFileOrEmpty() + { + return FromFileOrEmpty(Environment.GetWorkingPath()); + } + + /// + /// Load a YAML formatted PSRuleOption object from disk. + /// + /// + /// A file or directory to read options from. + /// An options object. + /// + /// This method is called from PowerShell. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0046:Convert to conditional expression", Justification = "Avoid nested conditional expressions that increase complexity.")] + public static PSRuleOption FromFileOrEmpty(PSRuleOption option, string path) + { + if (option == null) + return FromFileOrEmpty(path); + + return string.IsNullOrEmpty(option._SourcePath) ? Combine(option, FromFileOrEmpty(path)) : option; + } + + private static PSRuleOption FromYaml(string path, string yaml) + { + try { - var option = new PSRuleOption(); - if (hashtable == null || hashtable.Count == 0) - return option; - - // Start loading matching values - var index = BuildIndex(hashtable); - option.Baseline.Load(index); - option.Convention.Load(index); - option.Execution.Load(index); - option.Include.Load(index); - option.Input.Load(index); - option.Logging.Load(index); - option.Output.Load(index); - option.Repository.Load(index); - option.Requires.Load(index); - BaselineOption.Load(option, index); + var d = new DeserializerBuilder() + .IgnoreUnmatchedProperties() + .WithNamingConvention(CamelCaseNamingConvention.Instance) + .WithTypeConverter(new FieldMapYamlTypeConverter()) + .WithTypeConverter(new StringArrayMapConverter()) + .WithTypeConverter(new SuppressionRuleYamlTypeConverter()) + .WithTypeConverter(new PSObjectYamlTypeConverter()) + .WithNodeTypeResolver(new PSOptionYamlTypeResolver()) + .Build(); + + var option = d.Deserialize(yaml) ?? new PSRuleOption(); + option._SourcePath = path; return option; } - - /// - /// Set working path from PowerShell host environment. - /// - /// An $ExecutionContext object. - /// - /// Called from PowerShell. - /// - public static void UseExecutionContext(EngineIntrinsics executionContext) + catch (SemanticErrorException ex) { - Environment.UseWorkingPathResolver(executionContext == null ? () => Directory.GetCurrentDirectory() : () => executionContext.SessionState.Path.CurrentFileSystemLocation.Path); + throw new ConfigurationParseException(path, string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.PSR0001, path, ex.Message), ex); } + } - /// - /// Set working path from a command-line host environment. - /// - public static void UseHostContext(IHostContext hostContext) - { - Environment.UseWorkingPathResolver(hostContext == null ? () => Directory.GetCurrentDirectory() : () => hostContext.GetWorkingPath()); - } + /// + /// Read PSRule options from environment variables. + /// + /// An existing options object to set. If null an empty options object is used. + /// An options object. + /// + /// Any environment variables that are set will override options set in the specified object. + /// + private static PSRuleOption FromEnvironment(PSRuleOption option) + { + option ??= new PSRuleOption(); + + // Start loading matching values + option.Baseline.Load(); + option.Convention.Load(); + option.Execution.Load(); + option.Include.Load(); + option.Input.Load(); + option.Logging.Load(); + option.Output.Load(); + option.Repository.Load(); + option.Requires.Load(); + BaselineOption.Load(option); + return option; + } - /// - /// Convert from hashtable to options by processing key values. This enables -Option @{ } from PowerShell. - /// - /// A hashtable to read options from. - /// An options object. - public static implicit operator PSRuleOption(Hashtable hashtable) - { - return FromHashtable(hashtable); - } + /// + /// Read PSRule options from a hashtable. + /// + /// A hashtable to read options from. + /// An options object. + /// + /// A null or empty hashtable will return an empty options object. + /// + public static PSRuleOption FromHashtable(Hashtable hashtable) + { + var option = new PSRuleOption(); + if (hashtable == null || hashtable.Count == 0) + return option; - /// - /// Convert from string to options by loading the yaml file from disk. This enables -Option '.\ps-rule.yaml' from PowerShell. - /// - /// A file or directory to read options from. - /// An options object. - public static implicit operator PSRuleOption(string path) - { - return FromFile(path); - } + // Start loading matching values + var index = BuildIndex(hashtable); + option.Baseline.Load(index); + option.Convention.Load(index); + option.Execution.Load(index); + option.Include.Load(index); + option.Input.Load(index); + option.Logging.Load(index); + option.Output.Load(index); + option.Repository.Load(index); + option.Requires.Load(index); + BaselineOption.Load(option, index); + return option; + } - /// - public override bool Equals(object obj) - { - return obj is PSRuleOption option && Equals(option); - } + /// + /// Set working path from PowerShell host environment. + /// + /// An $ExecutionContext object. + /// + /// Called from PowerShell. + /// + public static void UseExecutionContext(EngineIntrinsics executionContext) + { + Environment.UseWorkingPathResolver(executionContext == null ? () => Directory.GetCurrentDirectory() : () => executionContext.SessionState.Path.CurrentFileSystemLocation.Path); + } - /// - public bool Equals(PSRuleOption other) - { - return other != null && - Baseline == other.Baseline && - Binding == other.Binding && - Configuration == other.Configuration && - Convention == other.Convention && - Execution == other.Execution && - Include == other.Include && - Input == other.Input && - Logging == other.Logging && - Output == other.Output && - Suppression == other.Suppression && - Pipeline == other.Pipeline && - Repository == other.Repository && - Rule == other.Rule; - } + /// + /// Set working path from a command-line host environment. + /// + public static void UseHostContext(IHostContext hostContext) + { + Environment.UseWorkingPathResolver(hostContext == null ? () => Directory.GetCurrentDirectory() : () => hostContext.GetWorkingPath()); + } + + /// + /// Convert from hashtable to options by processing key values. This enables -Option @{ } from PowerShell. + /// + /// A hashtable to read options from. + /// An options object. + public static implicit operator PSRuleOption(Hashtable hashtable) + { + return FromHashtable(hashtable); + } - /// - public override int GetHashCode() + /// + /// Convert from string to options by loading the yaml file from disk. This enables -Option '.\ps-rule.yaml' from PowerShell. + /// + /// A file or directory to read options from. + /// An options object. + public static implicit operator PSRuleOption(string path) + { + return FromFile(path); + } + + /// + public override bool Equals(object obj) + { + return obj is PSRuleOption option && Equals(option); + } + + /// + public bool Equals(PSRuleOption other) + { + return other != null && + Baseline == other.Baseline && + Binding == other.Binding && + Configuration == other.Configuration && + Convention == other.Convention && + Execution == other.Execution && + Include == other.Include && + Input == other.Input && + Logging == other.Logging && + Output == other.Output && + Suppression == other.Suppression && + Pipeline == other.Pipeline && + Repository == other.Repository && + Rule == other.Rule; + } + + /// + public override int GetHashCode() + { + unchecked // Overflow is fine { - unchecked // Overflow is fine - { - var hash = 17; - hash = hash * 23 + (Baseline != null ? Baseline.GetHashCode() : 0); - hash = hash * 23 + (Binding != null ? Binding.GetHashCode() : 0); - hash = hash * 23 + (Configuration != null ? Configuration.GetHashCode() : 0); - hash = hash * 23 + (Convention != null ? Convention.GetHashCode() : 0); - hash = hash * 23 + (Execution != null ? Execution.GetHashCode() : 0); - hash = hash * 23 + (Include != null ? Include.GetHashCode() : 0); - hash = hash * 23 + (Input != null ? Input.GetHashCode() : 0); - hash = hash * 23 + (Logging != null ? Logging.GetHashCode() : 0); - hash = hash * 23 + (Output != null ? Output.GetHashCode() : 0); - hash = hash * 23 + (Suppression != null ? Suppression.GetHashCode() : 0); - hash = hash * 23 + (Pipeline != null ? Pipeline.GetHashCode() : 0); - hash = hash * 23 + (Repository != null ? Repository.GetHashCode() : 0); - hash = hash * 23 + (Rule != null ? Rule.GetHashCode() : 0); - return hash; - } + var hash = 17; + hash = hash * 23 + (Baseline != null ? Baseline.GetHashCode() : 0); + hash = hash * 23 + (Binding != null ? Binding.GetHashCode() : 0); + hash = hash * 23 + (Configuration != null ? Configuration.GetHashCode() : 0); + hash = hash * 23 + (Convention != null ? Convention.GetHashCode() : 0); + hash = hash * 23 + (Execution != null ? Execution.GetHashCode() : 0); + hash = hash * 23 + (Include != null ? Include.GetHashCode() : 0); + hash = hash * 23 + (Input != null ? Input.GetHashCode() : 0); + hash = hash * 23 + (Logging != null ? Logging.GetHashCode() : 0); + hash = hash * 23 + (Output != null ? Output.GetHashCode() : 0); + hash = hash * 23 + (Suppression != null ? Suppression.GetHashCode() : 0); + hash = hash * 23 + (Pipeline != null ? Pipeline.GetHashCode() : 0); + hash = hash * 23 + (Repository != null ? Repository.GetHashCode() : 0); + hash = hash * 23 + (Rule != null ? Rule.GetHashCode() : 0); + return hash; } + } - /// - /// Get a fully qualified file path. - /// - /// A file or directory path. - /// - public static string GetFilePath(string path) + /// + /// Get a fully qualified file path. + /// + /// A file or directory path. + /// + public static string GetFilePath(string path) + { + var rootedPath = Environment.GetRootedPath(path); + if (Path.HasExtension(rootedPath)) { - var rootedPath = Environment.GetRootedPath(path); - if (Path.HasExtension(rootedPath)) + var ext = Path.GetExtension(rootedPath); + if (string.Equals(ext, ".yaml", StringComparison.OrdinalIgnoreCase) || + string.Equals(ext, ".yml", StringComparison.OrdinalIgnoreCase)) { - var ext = Path.GetExtension(rootedPath); - if (string.Equals(ext, ".yaml", StringComparison.OrdinalIgnoreCase) || - string.Equals(ext, ".yml", StringComparison.OrdinalIgnoreCase)) - { - return rootedPath; - } + return rootedPath; } - - // Check if default files exist and - return UseFilePath(path: rootedPath, name: "ps-rule.yaml") ?? - UseFilePath(path: rootedPath, name: "ps-rule.yml") ?? - UseFilePath(path: rootedPath, name: "psrule.yaml") ?? - UseFilePath(path: rootedPath, name: "psrule.yml") ?? - Path.Combine(rootedPath, DEFAULT_FILENAME); } - /// - /// Build index to allow mapping values. - /// - [DebuggerStepThrough] - internal static Dictionary BuildIndex(Hashtable hashtable) - { - var index = new Dictionary(StringComparer.OrdinalIgnoreCase); - foreach (DictionaryEntry entry in hashtable) - index.Add(entry.Key.ToString(), entry.Value); + // Check if default files exist and + return UseFilePath(path: rootedPath, name: "ps-rule.yaml") ?? + UseFilePath(path: rootedPath, name: "ps-rule.yml") ?? + UseFilePath(path: rootedPath, name: "psrule.yaml") ?? + UseFilePath(path: rootedPath, name: "psrule.yml") ?? + Path.Combine(rootedPath, DEFAULT_FILENAME); + } - return index; - } + /// + /// Build index to allow mapping values. + /// + [DebuggerStepThrough] + internal static Dictionary BuildIndex(Hashtable hashtable) + { + var index = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (DictionaryEntry entry in hashtable) + index.Add(entry.Key.ToString(), entry.Value); - /// - /// Determines if the working path file system is case sensitive. - /// - [DebuggerStepThrough] - internal static bool IsCaseSensitive() - { - var lower = Environment.GetWorkingPath().ToLower(Thread.CurrentThread.CurrentCulture); - if (!Directory.Exists(lower)) - return true; + return index; + } - var upper = Environment.GetWorkingPath().ToUpper(Thread.CurrentThread.CurrentCulture); - return !Directory.Exists(upper); - } + /// + /// Determines if the working path file system is case sensitive. + /// + [DebuggerStepThrough] + internal static bool IsCaseSensitive() + { + var lower = Environment.GetWorkingPath().ToLower(Thread.CurrentThread.CurrentCulture); + if (!Directory.Exists(lower)) + return true; - /// - /// Determine if the combined file path is exists. - /// - /// A directory path where a options file may be stored. - /// A file name of an options file. - /// Returns a file path if the file exists or null if the file does not exist. - private static string UseFilePath(string path, string name) - { - var filePath = Path.Combine(path, name); - return File.Exists(filePath) ? filePath : null; - } + var upper = Environment.GetWorkingPath().ToUpper(Thread.CurrentThread.CurrentCulture); + return !Directory.Exists(upper); + } - private string GetYaml() - { - var s = new SerializerBuilder() - .WithNamingConvention(CamelCaseNamingConvention.Instance) - .WithTypeConverter(new FieldMapYamlTypeConverter()) - .WithTypeConverter(new StringArrayMapConverter()) - .Build(); - return s.Serialize(this); - } + /// + /// Determine if the combined file path is exists. + /// + /// A directory path where a options file may be stored. + /// A file name of an options file. + /// Returns a file path if the file exists or null if the file does not exist. + private static string UseFilePath(string path, string name) + { + var filePath = Path.Combine(path, name); + return File.Exists(filePath) ? filePath : null; + } + + private string GetYaml() + { + var s = new SerializerBuilder() + .WithNamingConvention(CamelCaseNamingConvention.Instance) + .WithTypeConverter(new FieldMapYamlTypeConverter()) + .WithTypeConverter(new StringArrayMapConverter()) + .Build(); + return s.Serialize(this); } } diff --git a/src/PSRule/Configuration/PipelineHook.cs b/src/PSRule/Configuration/PipelineHook.cs index b10b1b9945..249166458b 100644 --- a/src/PSRule/Configuration/PipelineHook.cs +++ b/src/PSRule/Configuration/PipelineHook.cs @@ -1,48 +1,47 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -namespace PSRule.Configuration +namespace PSRule.Configuration; + +/// +/// Used by custom binding functions. +/// +public delegate string BindTargetName(object targetObject); + +internal delegate string BindTargetMethod(string[] propertyNames, bool caseSensitive, bool preferTargetInfo, object targetObject, out string path); +internal delegate string BindTargetFunc(string[] propertyNames, bool caseSensitive, bool preferTargetInfo, object targetObject, BindTargetMethod next, out string path); + +/// +/// Hooks that provide customize pipeline execution. +/// +public sealed class PipelineHook { /// - /// Used by custom binding functions. + /// Create an empty set of pipeline hooks. /// - public delegate string BindTargetName(object targetObject); - - internal delegate string BindTargetMethod(string[] propertyNames, bool caseSensitive, bool preferTargetInfo, object targetObject, out string path); - internal delegate string BindTargetFunc(string[] propertyNames, bool caseSensitive, bool preferTargetInfo, object targetObject, BindTargetMethod next, out string path); + public PipelineHook() + { + BindTargetName = new List(); + BindTargetType = new List(); + } /// - /// Hooks that provide customize pipeline execution. + /// Create pipeline hooks based on an existing option instance. /// - public sealed class PipelineHook + /// An existing pipeline hook option. + public PipelineHook(PipelineHook option) { - /// - /// Create an empty set of pipeline hooks. - /// - public PipelineHook() - { - BindTargetName = new List(); - BindTargetType = new List(); - } - - /// - /// Create pipeline hooks based on an existing option instance. - /// - /// An existing pipeline hook option. - public PipelineHook(PipelineHook option) - { - BindTargetName = option?.BindTargetName ?? new List(); - BindTargetType = option?.BindTargetType ?? new List(); - } + BindTargetName = option?.BindTargetName ?? new List(); + BindTargetType = option?.BindTargetType ?? new List(); + } - /// - /// One or more custom functions to use to bind TargetName of a pipeline object. - /// - public List BindTargetName { get; set; } + /// + /// One or more custom functions to use to bind TargetName of a pipeline object. + /// + public List BindTargetName { get; set; } - /// - /// One or more custom functions to use to bind TargetType of a pipeline object. - /// - public List BindTargetType { get; set; } - } + /// + /// One or more custom functions to use to bind TargetType of a pipeline object. + /// + public List BindTargetType { get; set; } } diff --git a/src/PSRule/Configuration/RepositoryOption.cs b/src/PSRule/Configuration/RepositoryOption.cs index 73ed331e7e..ff97c040e4 100644 --- a/src/PSRule/Configuration/RepositoryOption.cs +++ b/src/PSRule/Configuration/RepositoryOption.cs @@ -3,117 +3,116 @@ using System.ComponentModel; -namespace PSRule.Configuration +namespace PSRule.Configuration; + +/// +/// Options for repository properties that are used by PSRule. +/// +public sealed class RepositoryOption : IEquatable { + internal static readonly RepositoryOption Default = new() + { + + }; + /// - /// Options for repository properties that are used by PSRule. + /// Create an empty repository option. /// - public sealed class RepositoryOption : IEquatable + public RepositoryOption() { - internal static readonly RepositoryOption Default = new() - { + BaseRef = null; + Url = null; + } - }; + /// + /// Create a repository option by copying an existing instance. + /// + /// The option instance to copy. + public RepositoryOption(RepositoryOption option) + { + if (option == null) + return; - /// - /// Create an empty repository option. - /// - public RepositoryOption() - { - BaseRef = null; - Url = null; - } + BaseRef = option.BaseRef; + Url = option.Url; + } - /// - /// Create a repository option by copying an existing instance. - /// - /// The option instance to copy. - public RepositoryOption(RepositoryOption option) - { - if (option == null) - return; + /// + public override bool Equals(object obj) + { + return obj is RepositoryOption option && + Equals(option); + } - BaseRef = option.BaseRef; - Url = option.Url; - } + /// + public bool Equals(RepositoryOption other) + { + return other != null && + BaseRef == other.BaseRef && + Url == other.Url; + } - /// - public override bool Equals(object obj) + /// + public override int GetHashCode() + { + unchecked // Overflow is fine { - return obj is RepositoryOption option && - Equals(option); + var hash = 17; + hash = hash * 23 + (BaseRef != null ? BaseRef.GetHashCode() : 0); + hash = hash * 23 + (Url != null ? Url.GetHashCode() : 0); + return hash; } + } - /// - public bool Equals(RepositoryOption other) + /// + /// Merge two option instances by repacing any unset properties from with values. + /// Values from that are set are not overridden. + /// + internal static RepositoryOption Combine(RepositoryOption o1, RepositoryOption o2) + { + var result = new RepositoryOption(o1) { - return other != null && - BaseRef == other.BaseRef && - Url == other.Url; - } + BaseRef = o1?.BaseRef ?? o2?.BaseRef, + Url = o1?.Url ?? o2?.Url, + }; + return result; + } - /// - public override int GetHashCode() - { - unchecked // Overflow is fine - { - var hash = 17; - hash = hash * 23 + (BaseRef != null ? BaseRef.GetHashCode() : 0); - hash = hash * 23 + (Url != null ? Url.GetHashCode() : 0); - return hash; - } - } + /// + /// Sets the repository base ref used for comparisons of changed files. + /// + [DefaultValue(null)] + public string BaseRef { get; set; } - /// - /// Merge two option instances by repacing any unset properties from with values. - /// Values from that are set are not overridden. - /// - internal static RepositoryOption Combine(RepositoryOption o1, RepositoryOption o2) - { - var result = new RepositoryOption(o1) - { - BaseRef = o1?.BaseRef ?? o2?.BaseRef, - Url = o1?.Url ?? o2?.Url, - }; - return result; - } + /// + /// Configures the repository URL to report in output. + /// + [DefaultValue(null)] + public string Url { get; set; } - /// - /// Sets the repository base ref used for comparisons of changed files. - /// - [DefaultValue(null)] - public string BaseRef { get; set; } - - /// - /// Configures the repository URL to report in output. - /// - [DefaultValue(null)] - public string Url { get; set; } - - /// - /// Load options from environment variables into repository option. - /// Options that appear in both will replaced by environment variable values. - /// - internal void Load() - { - if (Environment.TryString("PSRULE_REPOSITORY_BASEREF", out var baseRef)) - BaseRef = baseRef; + /// + /// Load options from environment variables into repository option. + /// Options that appear in both will replaced by environment variable values. + /// + internal void Load() + { + if (Environment.TryString("PSRULE_REPOSITORY_BASEREF", out var baseRef)) + BaseRef = baseRef; - if (Environment.TryString("PSRULE_REPOSITORY_URL", out var url)) - Url = url; - } + if (Environment.TryString("PSRULE_REPOSITORY_URL", out var url)) + Url = url; + } - /// - /// Load options from a key/ value dictionary into the repository options. - /// Options that appear in both will replaced by dictionary values. - /// - internal void Load(Dictionary index) - { - if (index.TryPopString("Repository.BaseRef", out var baseRef)) - BaseRef = baseRef; + /// + /// Load options from a key/ value dictionary into the repository options. + /// Options that appear in both will replaced by dictionary values. + /// + internal void Load(Dictionary index) + { + if (index.TryPopString("Repository.BaseRef", out var baseRef)) + BaseRef = baseRef; - if (index.TryPopString("Repository.Url", out var url)) - Url = url; - } + if (index.TryPopString("Repository.Url", out var url)) + Url = url; } } diff --git a/src/PSRule/Configuration/RequiresOption.cs b/src/PSRule/Configuration/RequiresOption.cs index ac5dfb8de5..d92fbff8ec 100644 --- a/src/PSRule/Configuration/RequiresOption.cs +++ b/src/PSRule/Configuration/RequiresOption.cs @@ -3,94 +3,93 @@ using PSRule.Data; -namespace PSRule.Configuration +namespace PSRule.Configuration; + +/// +/// Specifies module version constraints for running PSRule. +/// When set, PSRule will error if a module version is used that does not satisfy the requirements. +/// +/// +public sealed class RequiresOption : KeyMapDictionary { + private const string ENVIRONMENT_PREFIX = "PSRULE_REQUIRES_"; + private const string DICTIONARY_PREFIX = "Requires."; + private const char UNDERSCORE = '_'; + private const char DOT = '.'; + /// - /// Specifies module version constraints for running PSRule. - /// When set, PSRule will error if a module version is used that does not satisfy the requirements. - /// + /// Creates an empty requires option. /// - public sealed class RequiresOption : KeyMapDictionary - { - private const string ENVIRONMENT_PREFIX = "PSRULE_REQUIRES_"; - private const string DICTIONARY_PREFIX = "Requires."; - private const char UNDERSCORE = '_'; - private const char DOT = '.'; + public RequiresOption() + : base() { } - /// - /// Creates an empty requires option. - /// - public RequiresOption() - : base() { } - - /// - /// Creates a requires option by copying an existing instance. - /// - /// The option instance to copy. - internal RequiresOption(RequiresOption option) - : base(option) { } + /// + /// Creates a requires option by copying an existing instance. + /// + /// The option instance to copy. + internal RequiresOption(RequiresOption option) + : base(option) { } - /// - /// Returns an array of Key/Values. - /// - public ModuleConstraint[] ToArray() + /// + /// Returns an array of Key/Values. + /// + public ModuleConstraint[] ToArray() + { + var result = new List(); + foreach (var kv in this) { - var result = new List(); - foreach (var kv in this) - { - if (SemanticVersion.TryParseConstraint(kv.Value, out var constraint)) - result.Add(new ModuleConstraint(kv.Key, constraint)); - } - return result.ToArray(); + if (SemanticVersion.TryParseConstraint(kv.Value, out var constraint)) + result.Add(new ModuleConstraint(kv.Key, constraint)); } + return result.ToArray(); + } - /// - /// Return the module constaints as a dictionary indexed by module name. - /// - public IDictionary ToDictionary() + /// + /// Return the module constaints as a dictionary indexed by module name. + /// + public IDictionary ToDictionary() + { + var result = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (var kv in this) { - var result = new Dictionary(StringComparer.OrdinalIgnoreCase); - foreach (var kv in this) - { - if (SemanticVersion.TryParseConstraint(kv.Value, out var constraint)) - result.Add(kv.Key, new ModuleConstraint(kv.Key, constraint)); - } - return result; + if (SemanticVersion.TryParseConstraint(kv.Value, out var constraint)) + result.Add(kv.Key, new ModuleConstraint(kv.Key, constraint)); } + return result; + } - /// - /// 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; - } + /// + /// 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. - /// - internal void Load() - { - Load(ENVIRONMENT_PREFIX, ConvertUnderscore); - } + /// + /// Load Requires option from environment variables. + /// + internal void Load() + { + Load(ENVIRONMENT_PREFIX, ConvertUnderscore); + } - /// - /// Load Requires option from a dictionary. - /// - internal void Load(IDictionary dictionary) - { - Load(DICTIONARY_PREFIX, dictionary); - } + /// + /// Load Requires option from a dictionary. + /// + internal void Load(IDictionary dictionary) + { + Load(DICTIONARY_PREFIX, dictionary); + } - /// - /// Convert module names with underscores to dots. - /// - private string ConvertUnderscore(string key) - { - return key.Replace(UNDERSCORE, DOT); - } + /// + /// Convert module names with underscores to dots. + /// + private string ConvertUnderscore(string key) + { + return key.Replace(UNDERSCORE, DOT); } } diff --git a/src/PSRule/Configuration/ResultFormat.cs b/src/PSRule/Configuration/ResultFormat.cs index bd70c9970e..42a5545db6 100644 --- a/src/PSRule/Configuration/ResultFormat.cs +++ b/src/PSRule/Configuration/ResultFormat.cs @@ -4,23 +4,22 @@ using Newtonsoft.Json; using Newtonsoft.Json.Converters; -namespace PSRule.Configuration +namespace PSRule.Configuration; + +/// +/// The format to return to the pipeline after executing rules. +/// See help. +/// +[JsonConverter(typeof(StringEnumConverter))] +public enum ResultFormat { /// - /// The format to return to the pipeline after executing rules. - /// See help. + /// Return a record per rule per object. /// - [JsonConverter(typeof(StringEnumConverter))] - public enum ResultFormat - { - /// - /// Return a record per rule per object. - /// - Detail = 1, + Detail = 1, - /// - /// Return summary results. - /// - Summary = 2 - } + /// + /// Return summary results. + /// + Summary = 2 } diff --git a/src/PSRule/Configuration/RuleOption.cs b/src/PSRule/Configuration/RuleOption.cs index 1e91459b62..d5fe2df64e 100644 --- a/src/PSRule/Configuration/RuleOption.cs +++ b/src/PSRule/Configuration/RuleOption.cs @@ -5,136 +5,135 @@ using System.ComponentModel; using PSRule.Definitions; -namespace PSRule.Configuration +namespace PSRule.Configuration; + +/// +/// Options for that affect which rules are executed by including and filtering discovered rules. +/// +public sealed class RuleOption : IEquatable { + private const bool DEFAULT_INCLUDELOCAL = false; + + internal static readonly RuleOption Default = new() + { + IncludeLocal = DEFAULT_INCLUDELOCAL + }; + /// - /// Options for that affect which rules are executed by including and filtering discovered rules. + /// Create an empty rule option. /// - public sealed class RuleOption : IEquatable + public RuleOption() { - private const bool DEFAULT_INCLUDELOCAL = false; + Baseline = null; + Exclude = null; + IncludeLocal = null; + Include = null; + Tag = null; + Labels = null; + } - internal static readonly RuleOption Default = new() - { - IncludeLocal = DEFAULT_INCLUDELOCAL - }; + /// + /// Create a rule option by copying an existing instance. + /// + /// The option instance to copy. + public RuleOption(RuleOption option) + { + if (option == null) + return; + + Baseline = option.Baseline; + Exclude = option.Exclude; + IncludeLocal = option.IncludeLocal; + Include = option.Include; + Tag = option.Tag; + Labels = option.Labels; + } - /// - /// Create an empty rule option. - /// - public RuleOption() - { - Baseline = null; - Exclude = null; - IncludeLocal = null; - Include = null; - Tag = null; - Labels = null; - } + /// + public override bool Equals(object obj) + { + return obj is RuleOption option && Equals(option); + } - /// - /// Create a rule option by copying an existing instance. - /// - /// The option instance to copy. - public RuleOption(RuleOption option) - { - if (option == null) - return; - - Baseline = option.Baseline; - Exclude = option.Exclude; - IncludeLocal = option.IncludeLocal; - Include = option.Include; - Tag = option.Tag; - Labels = option.Labels; - } + /// + public bool Equals(RuleOption other) + { + return other != null && + Baseline == other.Baseline && + Exclude == other.Exclude && + IncludeLocal == other.IncludeLocal && + Include == other.Include && + Tag == other.Tag && + Labels == other.Labels; + } - /// - public override bool Equals(object obj) + /// + public override int GetHashCode() + { + unchecked // Overflow is fine { - return obj is RuleOption option && Equals(option); + var hash = 17; + hash = hash * 23 + (Baseline != null ? Baseline.GetHashCode() : 0); + hash = hash * 23 + (Exclude != null ? Exclude.GetHashCode() : 0); + hash = hash * 23 + (IncludeLocal.HasValue ? IncludeLocal.Value.GetHashCode() : 0); + hash = hash * 23 + (Include != null ? Include.GetHashCode() : 0); + hash = hash * 23 + (Tag != null ? Tag.GetHashCode() : 0); + hash = hash * 23 + (Labels != null ? Labels.GetHashCode() : 0); + return hash; } + } - /// - public bool Equals(RuleOption other) + /// + /// Merge two option instances by repacing any unset properties from with values. + /// Values from that are set are not overridden. + /// + internal static RuleOption Combine(RuleOption o1, RuleOption o2) + { + var result = new RuleOption(o1) { - return other != null && - Baseline == other.Baseline && - Exclude == other.Exclude && - IncludeLocal == other.IncludeLocal && - Include == other.Include && - Tag == other.Tag && - Labels == other.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; + } - /// - public override int GetHashCode() - { - unchecked // Overflow is fine - { - var hash = 17; - hash = hash * 23 + (Baseline != null ? Baseline.GetHashCode() : 0); - hash = hash * 23 + (Exclude != null ? Exclude.GetHashCode() : 0); - hash = hash * 23 + (IncludeLocal.HasValue ? IncludeLocal.Value.GetHashCode() : 0); - hash = hash * 23 + (Include != null ? Include.GetHashCode() : 0); - hash = hash * 23 + (Tag != null ? Tag.GetHashCode() : 0); - hash = hash * 23 + (Labels != null ? Labels.GetHashCode() : 0); - return hash; - } - } + /// + /// The name of a baseline to use. + /// + [DefaultValue(null)] + public string Baseline { get; set; } - /// - /// Merge two option instances by repacing any unset properties from with values. - /// Values from that are set are not overridden. - /// - 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, - }; - return result; - } + /// + /// A set of rules to exclude for execution. + /// + [DefaultValue(null)] + public string[] Exclude { get; set; } - /// - /// The name of a baseline to use. - /// - [DefaultValue(null)] - public string Baseline { get; set; } - - /// - /// A set of rules to exclude for execution. - /// - [DefaultValue(null)] - public string[] Exclude { get; set; } - - /// - /// Automatically include all local rules in the search path unless they have been explicitly excluded. - /// - [DefaultValue(null)] - public bool? IncludeLocal { get; set; } - - /// - /// A set of rules to include for execution. - /// - [DefaultValue(null)] - public string[] Include { get; set; } - - /// - /// A set of rule tags to include for execution. - /// - [DefaultValue(null)] - public Hashtable Tag { get; set; } - - /// - /// A set of taxonomy references. - /// - [DefaultValue(null)] - public ResourceLabels Labels { get; set; } - } + /// + /// Automatically include all local rules in the search path unless they have been explicitly excluded. + /// + [DefaultValue(null)] + public bool? IncludeLocal { get; set; } + + /// + /// A set of rules to include for execution. + /// + [DefaultValue(null)] + public string[] Include { get; set; } + + /// + /// A set of rule tags to include for execution. + /// + [DefaultValue(null)] + public Hashtable Tag { get; set; } + + /// + /// A set of taxonomy references. + /// + [DefaultValue(null)] + public ResourceLabels Labels { get; set; } } diff --git a/src/PSRule/Configuration/SuppressionOption.cs b/src/PSRule/Configuration/SuppressionOption.cs index 68dc9a7701..3ba1be0c46 100644 --- a/src/PSRule/Configuration/SuppressionOption.cs +++ b/src/PSRule/Configuration/SuppressionOption.cs @@ -3,162 +3,161 @@ using System.Collections; -namespace PSRule.Configuration +namespace PSRule.Configuration; + +/// +/// Options that affect rule suppression during execution. +/// +public sealed class SuppressionOption : IDictionary { + private readonly Dictionary _Rules; + /// - /// Options that affect rule suppression during execution. + /// Creates an empty suppression option. /// - public sealed class SuppressionOption : IDictionary + public SuppressionOption() { - private readonly Dictionary _Rules; - - /// - /// Creates an empty suppression option. - /// - public SuppressionOption() - { - _Rules = new Dictionary(StringComparer.OrdinalIgnoreCase); - } + _Rules = new Dictionary(StringComparer.OrdinalIgnoreCase); + } - /// - /// Creates a suppression option by loading from a dictionary. - /// - /// A dictionary of . - internal SuppressionOption(IDictionary rules) - { - _Rules = rules == null ? - new Dictionary(StringComparer.OrdinalIgnoreCase) : - new Dictionary(rules, StringComparer.OrdinalIgnoreCase); - } + /// + /// Creates a suppression option by loading from a dictionary. + /// + /// A dictionary of . + internal SuppressionOption(IDictionary rules) + { + _Rules = rules == null ? + new Dictionary(StringComparer.OrdinalIgnoreCase) : + new Dictionary(rules, StringComparer.OrdinalIgnoreCase); + } - /// - /// Get a indexed by rule name. - /// - /// The name of the rule. - /// A matching . - public SuppressionRule this[string key] - { - get => _Rules[key]; - set => _Rules[key] = value; - } + /// + /// Get a indexed by rule name. + /// + /// The name of the rule. + /// A matching . + public SuppressionRule this[string key] + { + get => _Rules[key]; + set => _Rules[key] = value; + } - /// - public ICollection Keys => _Rules.Keys; + /// + public ICollection Keys => _Rules.Keys; - /// - public ICollection Values => _Rules.Values; + /// + public ICollection Values => _Rules.Values; - /// - public int Count => _Rules.Count; + /// + public int Count => _Rules.Count; - /// - public bool IsReadOnly => false; + /// + public bool IsReadOnly => false; - /// - /// Add a suppression rule to the option by rule name. - /// - /// The name of the rule to apply the suppression rule to. - /// A to map to the rule. - public void Add(string key, SuppressionRule value) - { - _Rules.Add(key, value); - } + /// + /// Add a suppression rule to the option by rule name. + /// + /// The name of the rule to apply the suppression rule to. + /// A to map to the rule. + public void Add(string key, SuppressionRule value) + { + _Rules.Add(key, value); + } - /// - public void Add(KeyValuePair item) - { - Add(item.Key, item.Value); - } + /// + public void Add(KeyValuePair item) + { + Add(item.Key, item.Value); + } - /// - /// Clear all suppression rules. - /// - public void Clear() - { - _Rules.Clear(); - } + /// + /// Clear all suppression rules. + /// + public void Clear() + { + _Rules.Clear(); + } - /// - public bool Contains(KeyValuePair item) - { - return ((IDictionary)_Rules).Contains(item); - } + /// + public bool Contains(KeyValuePair item) + { + return ((IDictionary)_Rules).Contains(item); + } - /// - public bool ContainsKey(string key) - { - return _Rules.ContainsKey(key); - } + /// + public bool ContainsKey(string key) + { + return _Rules.ContainsKey(key); + } - /// - public void CopyTo(KeyValuePair[] array, int arrayIndex) - { - ((IDictionary)_Rules).CopyTo(array, arrayIndex); - } + /// + public void CopyTo(KeyValuePair[] array, int arrayIndex) + { + ((IDictionary)_Rules).CopyTo(array, arrayIndex); + } - /// - public IEnumerator> GetEnumerator() - { - return _Rules.GetEnumerator(); - } + /// + 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; - } + /// + /// 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. - /// - /// The name of the rule. - /// Returns true if the element was found and removed. - public bool Remove(string key) - { - return _Rules.Remove(key); - } + /// + /// Remove a specific by rule name. + /// + /// The name of the rule. + /// Returns true if the element was found and removed. + public bool Remove(string key) + { + return _Rules.Remove(key); + } - /// - public bool Remove(KeyValuePair item) - { - return ((IDictionary)_Rules).Remove(item); - } + /// + public bool Remove(KeyValuePair item) + { + return ((IDictionary)_Rules).Remove(item); + } - /// - /// Try to get a from the specified rule name. - /// - /// The name of the rule. - /// A if any match the specified rule name. - /// Returns true if the key was found and returned. - public bool TryGetValue(string key, out SuppressionRule value) - { - return _Rules.TryGetValue(key, out value); - } + /// + /// Try to get a from the specified rule name. + /// + /// The name of the rule. + /// A if any match the specified rule name. + /// Returns true if the key was found and returned. + public bool TryGetValue(string key, out SuppressionRule value) + { + return _Rules.TryGetValue(key, out value); + } - /// - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } + /// + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } - /// - /// Convert a hashtable to suppression options. - /// - /// A hashtable of indexed by rule name. - public static implicit operator SuppressionOption(Hashtable hashtable) + /// + /// Convert a hashtable to suppression options. + /// + /// A hashtable of indexed by rule name. + public static implicit operator SuppressionOption(Hashtable hashtable) + { + var option = new SuppressionOption(); + foreach (DictionaryEntry entry in hashtable) { - var option = new SuppressionOption(); - foreach (DictionaryEntry entry in hashtable) - { - var rule = SuppressionRule.FromObject(entry.Value); - option._Rules.Add(entry.Key.ToString(), rule); - } - return option; + var rule = SuppressionRule.FromObject(entry.Value); + option._Rules.Add(entry.Key.ToString(), rule); } + return option; } } diff --git a/src/PSRule/Configuration/SuppressionRule.cs b/src/PSRule/Configuration/SuppressionRule.cs index a7c5a66261..9aa37aa88e 100644 --- a/src/PSRule/Configuration/SuppressionRule.cs +++ b/src/PSRule/Configuration/SuppressionRule.cs @@ -1,61 +1,60 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -namespace PSRule.Configuration +namespace PSRule.Configuration; + +/// +/// A suppression rule, that specifies TargetNames that will not be processed by individual rules. +/// +public sealed class SuppressionRule { /// - /// A suppression rule, that specifies TargetNames that will not be processed by individual rules. + /// Create an empty suppression rule. + /// + public SuppressionRule() + { + + } + + /// + /// Create an instance with specified targets. /// - public sealed class SuppressionRule + private SuppressionRule(string[] targetNames) { - /// - /// Create an empty suppression rule. - /// - public SuppressionRule() - { - - } - - /// - /// Create an instance with specified targets. - /// - private SuppressionRule(string[] targetNames) - { - TargetName = targetNames; - } - - /// - /// One of more target names to suppress. - /// - public string[] TargetName { get; set; } - - /// - /// Create a suppression rule from a string. - /// - public static implicit operator SuppressionRule(string value) - { - return FromString(value); - } - - /// - /// Create a suppresion rule from a string array. - /// - public static implicit operator SuppressionRule(string[] value) - { - return FromString(value); - } - - internal static SuppressionRule FromString(params string[] value) - { - return new SuppressionRule(value); - } - - internal static SuppressionRule FromObject(object value) - { - if (value is string) - return FromString(value.ToString()); - - return value.GetType().IsArray ? FromString(((object[])value).OfType().ToArray()) : null; - } + TargetName = targetNames; + } + + /// + /// One of more target names to suppress. + /// + public string[] TargetName { get; set; } + + /// + /// Create a suppression rule from a string. + /// + public static implicit operator SuppressionRule(string value) + { + return FromString(value); + } + + /// + /// Create a suppresion rule from a string array. + /// + public static implicit operator SuppressionRule(string[] value) + { + return FromString(value); + } + + internal static SuppressionRule FromString(params string[] value) + { + return new SuppressionRule(value); + } + + internal static SuppressionRule FromObject(object value) + { + if (value is string) + return FromString(value.ToString()); + + return value.GetType().IsArray ? FromString(((object[])value).OfType().ToArray()) : null; } } diff --git a/src/PSRule/Data/ITargetIssueCollection.cs b/src/PSRule/Data/ITargetIssueCollection.cs index ccdf59f3b2..6c928219cf 100644 --- a/src/PSRule/Data/ITargetIssueCollection.cs +++ b/src/PSRule/Data/ITargetIssueCollection.cs @@ -1,70 +1,69 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -namespace PSRule.Data +namespace PSRule.Data; + +/// +/// A collection of issues reported by a downstream tool. +/// +public interface ITargetIssueCollection { /// - /// A collection of issues reported by a downstream tool. + /// Get any issues from the collection that match the specified type. /// - public interface ITargetIssueCollection - { - /// - /// Get any issues from the collection that match the specified type. - /// - /// The type of the issue. - /// Returns issues that match the specified . - TargetIssueInfo[] Get(string type = null); - - /// - /// Check if the collection contains any of the specified issue type. - /// - /// The type of the issue. - /// Returns true if any the collection contains any issues matching the specified . - bool Any(string type = null); - } + /// The type of the issue. + /// Returns issues that match the specified . + TargetIssueInfo[] Get(string type = null); /// - /// A collection of issues reported by a downstream tool. + /// Check if the collection contains any of the specified issue type. /// - internal sealed class TargetIssueCollection : ITargetIssueCollection - { - private List _Items; + /// The type of the issue. + /// Returns true if any the collection contains any issues matching the specified . + bool Any(string type = null); +} - internal TargetIssueCollection() { } +/// +/// A collection of issues reported by a downstream tool. +/// +internal sealed class TargetIssueCollection : ITargetIssueCollection +{ + private List _Items; - /// - public bool Any(string type = null) - { - return Get(type).Length > 0; - } + internal TargetIssueCollection() { } - /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0046:Convert to conditional expression", Justification = "Avoid nested conditional expressions that increase complexity.")] - public TargetIssueInfo[] Get(string type = null) - { - if (_Items == null) - return Array.Empty(); + /// + public bool Any(string type = null) + { + return Get(type).Length > 0; + } - return type == null ? _Items.ToArray() : _Items.Where(i => StringComparer.OrdinalIgnoreCase.Equals(i.Type, type)).ToArray(); - } + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0046:Convert to conditional expression", Justification = "Avoid nested conditional expressions that increase complexity.")] + public TargetIssueInfo[] Get(string type = null) + { + if (_Items == null) + return Array.Empty(); + + return type == null ? _Items.ToArray() : _Items.Where(i => StringComparer.OrdinalIgnoreCase.Equals(i.Type, type)).ToArray(); + } - /// - /// Add one or more issues into the collection. - /// - /// An array of instance to add to the collection. - internal void AddRange(TargetIssueInfo[] issueInfo) - { - for (var i = 0; issueInfo != null && i < issueInfo.Length; i++) - Add(issueInfo[i]); - } + /// + /// Add one or more issues into the collection. + /// + /// An array of instance to add to the collection. + internal void AddRange(TargetIssueInfo[] issueInfo) + { + for (var i = 0; issueInfo != null && i < issueInfo.Length; i++) + Add(issueInfo[i]); + } - private void Add(TargetIssueInfo issueInfo) - { - if (issueInfo == null || string.IsNullOrEmpty(issueInfo.Type)) - return; + private void Add(TargetIssueInfo issueInfo) + { + if (issueInfo == null || string.IsNullOrEmpty(issueInfo.Type)) + return; - _Items ??= new List(); - _Items.Add(issueInfo); - } + _Items ??= new List(); + _Items.Add(issueInfo); } } diff --git a/src/PSRule/Data/ITargetSourceCollection.cs b/src/PSRule/Data/ITargetSourceCollection.cs index 3ba6a4eceb..d3f4bfe249 100644 --- a/src/PSRule/Data/ITargetSourceCollection.cs +++ b/src/PSRule/Data/ITargetSourceCollection.cs @@ -1,59 +1,58 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -namespace PSRule.Data +namespace PSRule.Data; + +/// +/// A collection of sources for a target object. +/// +public interface ITargetSourceCollection { /// - /// A collection of sources for a target object. + /// Get the source details by source type. /// - public interface ITargetSourceCollection - { - /// - /// Get the source details by source type. - /// - TargetSourceInfo this[string type] { get; } - } + TargetSourceInfo this[string type] { get; } +} - internal sealed class TargetSourceCollection : ITargetSourceCollection - { - private List _Items; - private Dictionary _Index; +internal sealed class TargetSourceCollection : ITargetSourceCollection +{ + private List _Items; + private Dictionary _Index; - internal TargetSourceCollection() { } + internal TargetSourceCollection() { } - public int Count => _Items == null ? 0 : _Items.Count; + public int Count => _Items == null ? 0 : _Items.Count; - public TargetSourceInfo this[string type] + public TargetSourceInfo this[string type] + { + get { - get - { - return _Index == null || _Index.Count == 0 || !_Index.TryGetValue(type, out var value) ? null : value; - } + return _Index == null || _Index.Count == 0 || !_Index.TryGetValue(type, out var value) ? null : value; } + } - internal TargetSourceInfo[] GetSourceInfo() - { - return _Items == null || _Items.Count == 0 ? Array.Empty() : _Items.ToArray(); - } + internal TargetSourceInfo[] GetSourceInfo() + { + return _Items == null || _Items.Count == 0 ? Array.Empty() : _Items.ToArray(); + } - internal void AddRange(TargetSourceInfo[] sourceInfo) - { - for (var i = 0; sourceInfo != null && i < sourceInfo.Length; i++) - Add(sourceInfo[i]); - } + internal void AddRange(TargetSourceInfo[] sourceInfo) + { + for (var i = 0; sourceInfo != null && i < sourceInfo.Length; i++) + Add(sourceInfo[i]); + } - internal void Add(TargetSourceInfo sourceInfo) - { - if (sourceInfo == null || string.IsNullOrEmpty(sourceInfo.Type)) - return; + internal void Add(TargetSourceInfo sourceInfo) + { + if (sourceInfo == null || string.IsNullOrEmpty(sourceInfo.Type)) + return; - _Index ??= new Dictionary(StringComparer.OrdinalIgnoreCase); - if (_Index.ContainsKey(sourceInfo.Type)) - return; + _Index ??= new Dictionary(StringComparer.OrdinalIgnoreCase); + if (_Index.ContainsKey(sourceInfo.Type)) + return; - _Items ??= new List(); - _Items.Add(sourceInfo); - _Index.Add(sourceInfo.Type, sourceInfo); - } + _Items ??= new List(); + _Items.Add(sourceInfo); + _Index.Add(sourceInfo.Type, sourceInfo); } } diff --git a/src/PSRule/Data/InputFileInfoCollection.cs b/src/PSRule/Data/InputFileInfoCollection.cs index 52b9363609..b5370004ed 100644 --- a/src/PSRule/Data/InputFileInfoCollection.cs +++ b/src/PSRule/Data/InputFileInfoCollection.cs @@ -3,59 +3,58 @@ using System.Collections; -namespace PSRule.Data +namespace PSRule.Data; + +/// +/// A collection of . +/// +public interface IInputFileInfoCollection : IEnumerable { /// - /// A collection of . + /// Filters the collection to only include with a specific file extension. /// - public interface IInputFileInfoCollection : IEnumerable + /// A file extension to filter the collection to. + /// A filtered collection. + IInputFileInfoCollection WithExtension(string extension); +} + +/// +/// A collection of . +/// +internal sealed class InputFileInfoCollection : IInputFileInfoCollection, IEnumerable +{ + private readonly IEnumerable _Items; + + public InputFileInfoCollection(IEnumerable items) { - /// - /// Filters the collection to only include with a specific file extension. - /// - /// A file extension to filter the collection to. - /// A filtered collection. - IInputFileInfoCollection WithExtension(string extension); + _Items = items; } - /// - /// A collection of . - /// - internal sealed class InputFileInfoCollection : IInputFileInfoCollection, IEnumerable + public InputFileInfoCollection(string basePath, string[] items) { - private readonly IEnumerable _Items; - - public InputFileInfoCollection(IEnumerable items) - { - _Items = items; - } - - public InputFileInfoCollection(string basePath, string[] items) - { - _Items = items != null && items.Length > 0 ? items.Select(i => new InputFileInfo(basePath, i)).ToArray() : Array.Empty(); - } - - public IInputFileInfoCollection WithExtension(string extension) - { - return new InputFileInfoCollection(_Items.Where(i => i.Extension == extension)); - } + _Items = items != null && items.Length > 0 ? items.Select(i => new InputFileInfo(basePath, i)).ToArray() : Array.Empty(); + } - #region IEnumerable + public IInputFileInfoCollection WithExtension(string extension) + { + return new InputFileInfoCollection(_Items.Where(i => i.Extension == extension)); + } - public IEnumerator GetEnumerator() - { - return _Items.GetEnumerator(); - } + #region IEnumerable - #endregion IEnumerable + public IEnumerator GetEnumerator() + { + return _Items.GetEnumerator(); + } - #region IEnumerable + #endregion IEnumerable - IEnumerator IEnumerable.GetEnumerator() - { - return _Items.GetEnumerator(); - } + #region IEnumerable - #endregion IEnumerable + IEnumerator IEnumerable.GetEnumerator() + { + return _Items.GetEnumerator(); } + + #endregion IEnumerable } diff --git a/src/PSRule/Data/RepositoryInfo.cs b/src/PSRule/Data/RepositoryInfo.cs index 715a3cad3c..b5b132616f 100644 --- a/src/PSRule/Data/RepositoryInfo.cs +++ b/src/PSRule/Data/RepositoryInfo.cs @@ -1,42 +1,41 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -namespace PSRule.Data +namespace PSRule.Data; + +/// +/// Repository target information. +/// +public sealed class RepositoryInfo : ITargetInfo { - /// - /// Repository target information. - /// - public sealed class RepositoryInfo : ITargetInfo + internal RepositoryInfo(string basePath, string headRef) { - internal RepositoryInfo(string basePath, string headRef) - { - FullName = basePath; - BasePath = basePath; - DisplayName = headRef; - } - - /// - /// The full path to the repository root. - /// - public string FullName { get; } - - /// - /// The full path to the repository root. - /// - public string BasePath { get; } - - /// - /// The HEAD ref. - /// - public string DisplayName { get; } - - /// - string ITargetInfo.TargetName => DisplayName; - - /// - string ITargetInfo.TargetType => typeof(RepositoryInfo).FullName; - - /// - TargetSourceInfo ITargetInfo.Source => null; + FullName = basePath; + BasePath = basePath; + DisplayName = headRef; } + + /// + /// The full path to the repository root. + /// + public string FullName { get; } + + /// + /// The full path to the repository root. + /// + public string BasePath { get; } + + /// + /// The HEAD ref. + /// + public string DisplayName { get; } + + /// + string ITargetInfo.TargetName => DisplayName; + + /// + string ITargetInfo.TargetType => typeof(RepositoryInfo).FullName; + + /// + TargetSourceInfo ITargetInfo.Source => null; } diff --git a/src/PSRule/Definitions/Baselines/Baseline.cs b/src/PSRule/Definitions/Baselines/Baseline.cs index 34ab38a91a..db07b0eb6f 100644 --- a/src/PSRule/Definitions/Baselines/Baseline.cs +++ b/src/PSRule/Definitions/Baselines/Baseline.cs @@ -8,117 +8,116 @@ using PSRule.Resources; using YamlDotNet.Serialization; -namespace PSRule.Definitions.Baselines +namespace PSRule.Definitions.Baselines; + +/// +/// A specification for a V1 baseline resource. +/// +internal interface IBaselineV1Spec { /// - /// A specification for a V1 baseline resource. + /// Options that affect property binding. /// - internal interface IBaselineV1Spec - { - /// - /// Options that affect property binding. - /// - BindingOption Binding { get; set; } - - /// - /// Allows configuration key/ values to be specified that can be used within rule definitions. - /// - ConfigurationOption Configuration { get; set; } - - /// - /// Options that configure conventions. - /// - ConventionOption Convention { get; set; } - - /// - /// Options for that affect which rules are executed by including and filtering discovered rules. - /// - RuleOption Rule { get; set; } - } + BindingOption Binding { get; set; } /// - /// A baseline resource V1. + /// Allows configuration key/ values to be specified that can be used within rule definitions. /// - [Spec(Specs.V1, Specs.Baseline)] - public sealed class Baseline : InternalResource, IResource - { - /// - /// Create a baseline instance. - /// - public Baseline(string apiVersion, SourceFile source, ResourceMetadata metadata, IResourceHelpInfo info, ISourceExtent extent, BaselineSpec spec) - : base(ResourceKind.Baseline, apiVersion, source, metadata, info, extent, spec) { } - - /// - /// The unique identifier for the baseline. - /// - [YamlIgnore()] - public string BaselineId => Name; - - /// - /// A human readable block of text, used to identify the purpose of the rule. - /// - [JsonIgnore] - [YamlIgnore] - public string Synopsis => Info.Synopsis.Text; - } + ConfigurationOption Configuration { get; set; } /// - /// A specification for a V1 baseline resource. + /// Options that configure conventions. /// - public sealed class BaselineSpec : Spec, IBaselineV1Spec - { - /// - public BindingOption Binding { get; set; } + ConventionOption Convention { get; set; } - /// - public ConfigurationOption Configuration { get; set; } + /// + /// Options for that affect which rules are executed by including and filtering discovered rules. + /// + RuleOption Rule { get; set; } +} - /// - public ConventionOption Convention { get; set; } +/// +/// A baseline resource V1. +/// +[Spec(Specs.V1, Specs.Baseline)] +public sealed class Baseline : InternalResource, IResource +{ + /// + /// Create a baseline instance. + /// + public Baseline(string apiVersion, SourceFile source, ResourceMetadata metadata, IResourceHelpInfo info, ISourceExtent extent, BaselineSpec spec) + : base(ResourceKind.Baseline, apiVersion, source, metadata, info, extent, spec) { } - /// - public RuleOption Rule { get; set; } - } + /// + /// The unique identifier for the baseline. + /// + [YamlIgnore()] + public string BaselineId => Name; - internal sealed class BaselineFilter : IResourceFilter - { - private readonly HashSet _Include; - private readonly WildcardPattern _WildcardMatch; + /// + /// A human readable block of text, used to identify the purpose of the rule. + /// + [JsonIgnore] + [YamlIgnore] + public string Synopsis => Info.Synopsis.Text; +} - public BaselineFilter(string[] include) - { - _Include = include == null || include.Length == 0 ? null : new HashSet(include, StringComparer.OrdinalIgnoreCase); - _WildcardMatch = null; - if (include != null && include.Length > 0 && WildcardPattern.ContainsWildcardCharacters(include[0])) - { - if (include.Length > 1) - throw new NotSupportedException(PSRuleResources.MatchSingleName); - - _WildcardMatch = new WildcardPattern(include[0]); - } - } +/// +/// A specification for a V1 baseline resource. +/// +public sealed class BaselineSpec : Spec, IBaselineV1Spec +{ + /// + public BindingOption Binding { get; set; } - ResourceKind IResourceFilter.Kind => ResourceKind.Baseline; + /// + public ConfigurationOption Configuration { get; set; } - public bool Match(IResource resource) - { - return _Include == null || _Include.Contains(resource.Name) || MatchWildcard(resource.Name); - } + /// + public ConventionOption Convention { get; set; } + + /// + public RuleOption Rule { get; set; } +} - private bool MatchWildcard(string name) +internal sealed class BaselineFilter : IResourceFilter +{ + private readonly HashSet _Include; + private readonly WildcardPattern _WildcardMatch; + + public BaselineFilter(string[] include) + { + _Include = include == null || include.Length == 0 ? null : new HashSet(include, StringComparer.OrdinalIgnoreCase); + _WildcardMatch = null; + if (include != null && include.Length > 0 && WildcardPattern.ContainsWildcardCharacters(include[0])) { - return _WildcardMatch != null && _WildcardMatch.IsMatch(name); + if (include.Length > 1) + throw new NotSupportedException(PSRuleResources.MatchSingleName); + + _WildcardMatch = new WildcardPattern(include[0]); } } - internal sealed class BaselineRef : ResourceRef + ResourceKind IResourceFilter.Kind => ResourceKind.Baseline; + + public bool Match(IResource resource) + { + return _Include == null || _Include.Contains(resource.Name) || MatchWildcard(resource.Name); + } + + private bool MatchWildcard(string name) { - public readonly ScopeType Type; + return _WildcardMatch != null && _WildcardMatch.IsMatch(name); + } +} - public BaselineRef(string id, ScopeType scopeType) - : base(id, ResourceKind.Baseline) - { - Type = scopeType; - } +internal sealed class BaselineRef : ResourceRef +{ + public readonly ScopeType Type; + + public BaselineRef(string id, ScopeType scopeType) + : base(id, ResourceKind.Baseline) + { + Type = scopeType; } } diff --git a/src/PSRule/Definitions/Conventions/BaseConvention.cs b/src/PSRule/Definitions/Conventions/BaseConvention.cs index dac0fbd488..eebb65badd 100644 --- a/src/PSRule/Definitions/Conventions/BaseConvention.cs +++ b/src/PSRule/Definitions/Conventions/BaseConvention.cs @@ -8,84 +8,83 @@ using PSRule.Resources; using PSRule.Runtime; -namespace PSRule.Definitions.Conventions +namespace PSRule.Definitions.Conventions; + +internal sealed class ConventionFilter : IResourceFilter { - internal sealed class ConventionFilter : IResourceFilter - { - private readonly HashSet _Include; - private readonly WildcardPattern _WildcardMatch; + private readonly HashSet _Include; + private readonly WildcardPattern _WildcardMatch; - public ConventionFilter(string[] include) + public ConventionFilter(string[] include) + { + _Include = include == null || include.Length == 0 ? null : new HashSet(include, StringComparer.OrdinalIgnoreCase); + _WildcardMatch = null; + if (include != null && include.Length > 0 && WildcardPattern.ContainsWildcardCharacters(include[0])) { - _Include = include == null || include.Length == 0 ? null : new HashSet(include, StringComparer.OrdinalIgnoreCase); - _WildcardMatch = null; - if (include != null && include.Length > 0 && WildcardPattern.ContainsWildcardCharacters(include[0])) - { - if (include.Length > 1) - throw new NotSupportedException(PSRuleResources.MatchSingleName); - - _WildcardMatch = new WildcardPattern(include[0]); - } + if (include.Length > 1) + throw new NotSupportedException(PSRuleResources.MatchSingleName); + + _WildcardMatch = new WildcardPattern(include[0]); } + } - ResourceKind IResourceFilter.Kind => ResourceKind.Convention; + ResourceKind IResourceFilter.Kind => ResourceKind.Convention; - public bool Match(IResource resource) - { - return _Include != null && - (_Include.Contains(resource.Name) || - _Include.Contains(resource.Id.Value) || - MatchWildcard(resource.Name) || - MatchWildcard(resource.Id.Value)); - } + public bool Match(IResource resource) + { + return _Include != null && + (_Include.Contains(resource.Name) || + _Include.Contains(resource.Id.Value) || + MatchWildcard(resource.Name) || + MatchWildcard(resource.Id.Value)); + } - private bool MatchWildcard(string name) - { - return _WildcardMatch != null && _WildcardMatch.IsMatch(name); - } + private bool MatchWildcard(string name) + { + return _WildcardMatch != null && _WildcardMatch.IsMatch(name); } +} - [DebuggerDisplay("{Id}")] - internal abstract class BaseConvention : IConvention +[DebuggerDisplay("{Id}")] +internal abstract class BaseConvention : IConvention +{ + protected BaseConvention(SourceFile source, string name) { - protected BaseConvention(SourceFile source, string name) - { - Source = source; - Name = name; - Id = new ResourceId(Source.Module, name, ResourceIdKind.Id); - } + Source = source; + Name = name; + Id = new ResourceId(Source.Module, name, ResourceIdKind.Id); + } - public SourceFile Source { get; } + public SourceFile Source { get; } - public ResourceId Id { get; } + public ResourceId Id { get; } - /// - /// The name of the convetion. - /// - public string Name { get; } + /// + /// The name of the convetion. + /// + public string Name { get; } - public string SourcePath => Source.Path; + public string SourcePath => Source.Path; - public string Module => Source.Module; + public string Module => Source.Module; - public virtual void Initialize(RunspaceContext context, IEnumerable input) - { + public virtual void Initialize(RunspaceContext context, IEnumerable input) + { - } + } - public virtual void Begin(RunspaceContext context, IEnumerable input) - { + public virtual void Begin(RunspaceContext context, IEnumerable input) + { - } + } - public virtual void Process(RunspaceContext context, IEnumerable input) - { + public virtual void Process(RunspaceContext context, IEnumerable input) + { - } + } - public virtual void End(RunspaceContext context, IEnumerable input) - { + public virtual void End(RunspaceContext context, IEnumerable input) + { - } } } diff --git a/src/PSRule/Definitions/Conventions/ConventionComparer.cs b/src/PSRule/Definitions/Conventions/ConventionComparer.cs index e382b197e1..3440aaa262 100644 --- a/src/PSRule/Definitions/Conventions/ConventionComparer.cs +++ b/src/PSRule/Definitions/Conventions/ConventionComparer.cs @@ -3,23 +3,22 @@ using PSRule.Runtime; -namespace PSRule.Definitions.Conventions +namespace PSRule.Definitions.Conventions; + +/// +/// Orders conventions by the order they are specified. +/// +internal sealed class ConventionComparer : IComparer { - /// - /// Orders conventions by the order they are specified. - /// - internal sealed class ConventionComparer : IComparer - { - private readonly RunspaceContext _Context; + private readonly RunspaceContext _Context; - internal ConventionComparer(RunspaceContext context) - { - _Context = context; - } + internal ConventionComparer(RunspaceContext context) + { + _Context = context; + } - public int Compare(IConvention x, IConvention y) - { - return _Context.Pipeline.GetConventionOrder(x) - _Context.Pipeline.GetConventionOrder(y); - } + public int Compare(IConvention x, IConvention y) + { + return _Context.Pipeline.GetConventionOrder(x) - _Context.Pipeline.GetConventionOrder(y); } } diff --git a/src/PSRule/Definitions/Conventions/ScriptBlockConvention.cs b/src/PSRule/Definitions/Conventions/ScriptBlockConvention.cs index 4db8f4b86b..bca7f83336 100644 --- a/src/PSRule/Definitions/Conventions/ScriptBlockConvention.cs +++ b/src/PSRule/Definitions/Conventions/ScriptBlockConvention.cs @@ -6,122 +6,121 @@ using PSRule.Pipeline; using PSRule.Runtime; -namespace PSRule.Definitions.Conventions +namespace PSRule.Definitions.Conventions; + +internal sealed class ScriptBlockConvention : BaseConvention, IDisposable, IResource { - internal sealed class ScriptBlockConvention : BaseConvention, IDisposable, IResource + private readonly LanguageScriptBlock _Initialize; + private readonly LanguageScriptBlock _Begin; + private readonly LanguageScriptBlock _Process; + private readonly LanguageScriptBlock _End; + + private bool _Disposed; + + internal ScriptBlockConvention( + SourceFile source, + ResourceMetadata metadata, + ResourceHelpInfo info, + LanguageScriptBlock begin, + LanguageScriptBlock initialize, + LanguageScriptBlock process, + LanguageScriptBlock end, + ActionPreference errorPreference, + ResourceFlags flags, + ISourceExtent extent) + : base(source, metadata.Name) { - private readonly LanguageScriptBlock _Initialize; - private readonly LanguageScriptBlock _Begin; - private readonly LanguageScriptBlock _Process; - private readonly LanguageScriptBlock _End; - - private bool _Disposed; - - internal ScriptBlockConvention( - SourceFile source, - ResourceMetadata metadata, - ResourceHelpInfo info, - LanguageScriptBlock begin, - LanguageScriptBlock initialize, - LanguageScriptBlock process, - LanguageScriptBlock end, - ActionPreference errorPreference, - ResourceFlags flags, - ISourceExtent extent) - : base(source, metadata.Name) - { - Info = info; - _Initialize = initialize; - _Begin = begin; - _Process = process; - _End = end; - Flags = flags; - Extent = extent; - } + Info = info; + _Initialize = initialize; + _Begin = begin; + _Process = process; + _End = end; + Flags = flags; + Extent = extent; + } - public IResourceHelpInfo Info { get; } + public IResourceHelpInfo Info { get; } - public ResourceFlags Flags { get; } + public ResourceFlags Flags { get; } - public ISourceExtent Extent { get; } + public ISourceExtent Extent { get; } - ResourceKind IResource.Kind => ResourceKind.Convention; + ResourceKind IResource.Kind => ResourceKind.Convention; - string IResource.ApiVersion => Specs.V1; + string IResource.ApiVersion => Specs.V1; - // Not supported with conventions. - ResourceId? IResource.Ref => null; + // Not supported with conventions. + ResourceId? IResource.Ref => null; - // Not supported with conventions. - ResourceId[] IResource.Alias => null; + // Not supported with conventions. + ResourceId[] IResource.Alias => null; - // Not supported with conventions. - ResourceTags IResource.Tags => null; + // Not supported with conventions. + ResourceTags IResource.Tags => null; - // Not supported with conventions. - ResourceLabels IResource.Labels => null; + // Not supported with conventions. + ResourceLabels IResource.Labels => null; - public override void Initialize(RunspaceContext context, IEnumerable input) - { - InvokeConventionBlock(context, Source, _Initialize, input); - } + public override void Initialize(RunspaceContext context, IEnumerable input) + { + InvokeConventionBlock(context, Source, _Initialize, input); + } - public override void Begin(RunspaceContext context, IEnumerable input) - { - InvokeConventionBlock(context, Source, _Begin, input); - } + public override void Begin(RunspaceContext context, IEnumerable input) + { + InvokeConventionBlock(context, Source, _Begin, input); + } - public override void Process(RunspaceContext context, IEnumerable input) - { - InvokeConventionBlock(context, Source, _Process, input); - } + public override void Process(RunspaceContext context, IEnumerable input) + { + InvokeConventionBlock(context, Source, _Process, input); + } - public override void End(RunspaceContext context, IEnumerable input) + public override void End(RunspaceContext context, IEnumerable input) + { + InvokeConventionBlock(context, Source, _End, input); + } + + private static void InvokeConventionBlock(RunspaceContext context, SourceFile source, LanguageScriptBlock block, IEnumerable input) + { + if (block == null) + return; + + try { - InvokeConventionBlock(context, Source, _End, input); + context.EnterLanguageScope(source); + block.Invoke(); } - - private static void InvokeConventionBlock(RunspaceContext context, SourceFile source, LanguageScriptBlock block, IEnumerable input) + finally { - if (block == null) - return; - - try - { - context.EnterLanguageScope(source); - block.Invoke(); - } - finally - { - context.ExitLanguageScope(source); - } + context.ExitLanguageScope(source); } + } - #region IDisposable + #region IDisposable - private void Dispose(bool disposing) + private void Dispose(bool disposing) + { + if (!_Disposed) { - if (!_Disposed) + if (disposing) { - if (disposing) - { - _Begin?.Dispose(); + _Begin?.Dispose(); - _Process?.Dispose(); + _Process?.Dispose(); - _End?.Dispose(); - } - _Disposed = true; + _End?.Dispose(); } + _Disposed = true; } + } - public void Dispose() - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(disposing: true); - GC.SuppressFinalize(this); - } - - #endregion IDisposable + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); } + + #endregion IDisposable } diff --git a/src/PSRule/Definitions/DependencyGraph.cs b/src/PSRule/Definitions/DependencyGraph.cs index 7041194184..350a10fada 100644 --- a/src/PSRule/Definitions/DependencyGraph.cs +++ b/src/PSRule/Definitions/DependencyGraph.cs @@ -1,146 +1,145 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -namespace PSRule.Definitions +namespace PSRule.Definitions; + +internal sealed class DependencyGraph : IDisposable where T : IDependencyTarget { - internal sealed class DependencyGraph : IDisposable where T : IDependencyTarget + private readonly Dictionary _Index; + private readonly DependencyTarget[] _Targets; + private readonly Dictionary _State; + + // Track whether Dispose has been called. + private bool _Disposed; + + public DependencyGraph(T[] targets) { - private readonly Dictionary _Index; - private readonly DependencyTarget[] _Targets; - private readonly Dictionary _State; + _Targets = new DependencyTarget[targets.Length]; + _Index = new Dictionary(targets.Length); + _State = new Dictionary(targets.Length); + Prepare(targets); + } - // Track whether Dispose has been called. - private bool _Disposed; + public int Count => _Targets.Length; - public DependencyGraph(T[] targets) - { - _Targets = new DependencyTarget[targets.Length]; - _Index = new Dictionary(targets.Length); - _State = new Dictionary(targets.Length); - Prepare(targets); - } + public enum DependencyTargetState + { + None = 0, - public int Count => _Targets.Length; + Pass = 1, - public enum DependencyTargetState - { - None = 0, + Fail = 2, - Pass = 1, + DependencyFail = 3 + } - Fail = 2, + internal sealed class DependencyTarget + { + public readonly DependencyGraph Graph; + public readonly T Value; - DependencyFail = 3 + public DependencyTarget(DependencyGraph graph, T value) + { + Graph = graph; + Value = value; } - internal sealed class DependencyTarget + private DependencyTargetState State { - public readonly DependencyGraph Graph; - public readonly T Value; - - public DependencyTarget(DependencyGraph graph, T value) - { - Graph = graph; - Value = value; - } - - private DependencyTargetState State - { - get => Graph._State.TryGetValue(this, out var state) ? state : DependencyTargetState.None; - set => Graph._State[this] = value; - } + get => Graph._State.TryGetValue(this, out var state) ? state : DependencyTargetState.None; + set => Graph._State[this] = value; + } - public bool Skipped => State == DependencyTargetState.DependencyFail; + public bool Skipped => State == DependencyTargetState.DependencyFail; - public bool Failed => State == DependencyTargetState.Fail || State == DependencyTargetState.DependencyFail; + public bool Failed => State == DependencyTargetState.Fail || State == DependencyTargetState.DependencyFail; - public bool Passed => State == DependencyTargetState.Pass; + public bool Passed => State == DependencyTargetState.Pass; - public void Pass() - { - State = DependencyTargetState.Pass; - } + public void Pass() + { + State = DependencyTargetState.Pass; + } - public void Fail() - { - State = DependencyTargetState.Fail; - } + public void Fail() + { + State = DependencyTargetState.Fail; + } - public void DependencyFail() - { - State = DependencyTargetState.DependencyFail; - } + public void DependencyFail() + { + State = DependencyTargetState.DependencyFail; } + } - public IEnumerable GetSingleTarget() + public IEnumerable GetSingleTarget() + { + _State.Clear(); + for (var t = 0; t < _Targets.Length; t++) { - _State.Clear(); - for (var t = 0; t < _Targets.Length; t++) + var target = _Targets[t]; + if (target.Value.DependsOn != null && target.Value.DependsOn.Length > 0) { - var target = _Targets[t]; - if (target.Value.DependsOn != null && target.Value.DependsOn.Length > 0) + // Process each dependency + for (var d = 0; d < target.Value.DependsOn.Length; d++) { - // Process each dependency - for (var d = 0; d < target.Value.DependsOn.Length; d++) + var dTarget = _Index[target.Value.DependsOn[d].Value]; + + // Check if dependency was already completed + if (dTarget.Passed) { - var dTarget = _Index[target.Value.DependsOn[d].Value]; - - // Check if dependency was already completed - if (dTarget.Passed) - { - continue; - } - else if (dTarget.Failed) - { - target.DependencyFail(); - break; - } - yield return dTarget; + continue; } + else if (dTarget.Failed) + { + target.DependencyFail(); + break; + } + yield return dTarget; } - yield return target; } + yield return target; } + } - public IEnumerable GetAll() - { - for (var i = 0; i < _Targets.Length; i++) - yield return _Targets[i].Value; - } + public IEnumerable GetAll() + { + for (var i = 0; i < _Targets.Length; i++) + yield return _Targets[i].Value; + } - private void Prepare(T[] targets) + private void Prepare(T[] targets) + { + for (var i = 0; i < targets.Length; i++) { - for (var i = 0; i < targets.Length; i++) - { - _Targets[i] = new DependencyTarget(this, targets[i]); - _Index.Add(targets[i].Id.Value, _Targets[i]); - } + _Targets[i] = new DependencyTarget(this, targets[i]); + _Index.Add(targets[i].Id.Value, _Targets[i]); } + } - #region IDisposable + #region IDisposable - private void Dispose(bool disposing) + private void Dispose(bool disposing) + { + if (!_Disposed) { - if (!_Disposed) + if (disposing && _Targets != null && _Targets.Length > 0 && typeof(T) is IDisposable) { - if (disposing && _Targets != null && _Targets.Length > 0 && typeof(T) is IDisposable) + for (var i = 0; i < _Targets.Length; i++) { - for (var i = 0; i < _Targets.Length; i++) - { - ((IDisposable)_Targets[i].Value).Dispose(); - } + ((IDisposable)_Targets[i].Value).Dispose(); } - - _Index.Clear(); - _Disposed = true; } - } - public void Dispose() - { - Dispose(true); + _Index.Clear(); + _Disposed = true; } + } - #endregion IDisposable + public void Dispose() + { + Dispose(true); } + + #endregion IDisposable } diff --git a/src/PSRule/Definitions/DependencyGraphBuilder.cs b/src/PSRule/Definitions/DependencyGraphBuilder.cs index 6f8cc94391..505ae5d8a9 100644 --- a/src/PSRule/Definitions/DependencyGraphBuilder.cs +++ b/src/PSRule/Definitions/DependencyGraphBuilder.cs @@ -6,88 +6,87 @@ using PSRule.Rules; using PSRule.Runtime; -namespace PSRule.Definitions +namespace PSRule.Definitions; + +internal sealed class DependencyGraphBuilder where T : IDependencyTarget { - internal sealed class DependencyGraphBuilder where T : IDependencyTarget + private readonly RunspaceContext _Context; + private readonly IEqualityComparer _Comparer; + private readonly Dictionary _Targets; + private readonly Stack _Stack; + private readonly bool _IncludeDependencies; + private readonly bool _IncludeDisabled; + + public DependencyGraphBuilder(RunspaceContext context, bool includeDependencies, bool includeDisabled) { - private readonly RunspaceContext _Context; - private readonly IEqualityComparer _Comparer; - private readonly Dictionary _Targets; - private readonly Stack _Stack; - private readonly bool _IncludeDependencies; - private readonly bool _IncludeDisabled; + _Context = context; + _Comparer = ResourceIdEqualityComparer.Default; + _Targets = new Dictionary(_Comparer); + _Stack = new Stack(); + _IncludeDependencies = includeDependencies; + _IncludeDisabled = includeDisabled; + } - public DependencyGraphBuilder(RunspaceContext context, bool includeDependencies, bool includeDisabled) + public void Include(DependencyTargetCollection index, Func filter) + { + // Include any matching items + foreach (var item in index.GetAll()) { - _Context = context; - _Comparer = ResourceIdEqualityComparer.Default; - _Targets = new Dictionary(_Comparer); - _Stack = new Stack(); - _IncludeDependencies = includeDependencies; - _IncludeDisabled = includeDisabled; + if (item.Dependency) + continue; + + if (filter == null || filter(item)) + Include(index, item, parentId: null); + else if (item is RuleBlock) + _Context.RuleExcluded(item.Id); } + } - public void Include(DependencyTargetCollection index, Func filter) - { - // Include any matching items - foreach (var item in index.GetAll()) - { - if (item.Dependency) - continue; + public DependencyGraph Build() + { + return new DependencyGraph(GetItems()); + } - if (filter == null || filter(item)) - Include(index, item, parentId: null); - else if (item is RuleBlock) - _Context.RuleExcluded(item.Id); - } - } + public T[] GetItems() + { + return _Targets.Values.ToArray(); + } - public DependencyGraph Build() - { - return new DependencyGraph(GetItems()); - } + private void Include(DependencyTargetCollection index, T item, ResourceId? parentId) + { + // Check that the item is not already in the list of targets + if (_Targets.ContainsKey(item.Id)) + return; - public T[] GetItems() - { - return _Targets.Values.ToArray(); - } + // Check for circular dependencies + if (_Stack.Contains(item.Id, _Comparer)) + throw new RuleException(string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.DependencyCircularReference, parentId, item.Id)); - private void Include(DependencyTargetCollection index, T item, ResourceId? parentId) + try { - // Check that the item is not already in the list of targets - if (_Targets.ContainsKey(item.Id)) - return; + _Stack.Push(item.Id); - // Check for circular dependencies - if (_Stack.Contains(item.Id, _Comparer)) - throw new RuleException(string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.DependencyCircularReference, parentId, item.Id)); - - try + // Check for dependencies + if (item.DependsOn != null && _IncludeDependencies) { - _Stack.Push(item.Id); - - // Check for dependencies - if (item.DependsOn != null && _IncludeDependencies) + foreach (var d in item.DependsOn) { - foreach (var d in item.DependsOn) - { - if (!index.TryGet(d, out var dep, out var kind)) - throw new RuleException(string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.DependencyNotFound, d, item.Id)); + if (!index.TryGet(d, out var dep, out var kind)) + throw new RuleException(string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.DependencyNotFound, d, item.Id)); - if (_Context != null && kind == ResourceIdKind.Alias) - _Context.WarnAliasReference(ResourceKind.Rule, item.Id.Value, dep.Id.Value, d.Value); + if (_Context != null && kind == ResourceIdKind.Alias) + _Context.WarnAliasReference(ResourceKind.Rule, item.Id.Value, dep.Id.Value, d.Value); - // Handle dependencies - if (!_Targets.ContainsKey(dep.Id)) - Include(index, dep, parentId: item.Id); - } + // Handle dependencies + if (!_Targets.ContainsKey(dep.Id)) + Include(index, dep, parentId: item.Id); } - _Targets.Add(key: item.Id, value: item); - } - finally - { - _Stack.Pop(); } + _Targets.Add(key: item.Id, value: item); + } + finally + { + _Stack.Pop(); } } } diff --git a/src/PSRule/Definitions/DependencyTargetCollection.cs b/src/PSRule/Definitions/DependencyTargetCollection.cs index 73765e714c..744aa25278 100644 --- a/src/PSRule/Definitions/DependencyTargetCollection.cs +++ b/src/PSRule/Definitions/DependencyTargetCollection.cs @@ -1,72 +1,71 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -namespace PSRule.Definitions +namespace PSRule.Definitions; + +internal sealed class DependencyTargetCollection where T : IDependencyTarget { - internal sealed class DependencyTargetCollection where T : IDependencyTarget + private readonly Dictionary _Index; + private readonly List _Items; + + public DependencyTargetCollection() { - private readonly Dictionary _Index; - private readonly List _Items; + _Items = new List(); + _Index = new Dictionary(ResourceIdEqualityComparer.Default); + } - public DependencyTargetCollection() - { - _Items = new List(); - _Index = new Dictionary(ResourceIdEqualityComparer.Default); - } + private sealed class TargetLink + { + public readonly T Link; + public readonly ResourceIdKind Kind; - private sealed class TargetLink + public TargetLink(T link, ResourceIdKind kind) { - public readonly T Link; - public readonly ResourceIdKind Kind; - - public TargetLink(T link, ResourceIdKind kind) - { - Link = link; - Kind = kind; - } + Link = link; + Kind = kind; } + } - public bool Contains(ResourceId id) - { - return _Index.ContainsKey(id); - } + public bool Contains(ResourceId id) + { + return _Index.ContainsKey(id); + } - public bool TryGet(ResourceId id, out T value, out ResourceIdKind kind) - { - value = default; - kind = default; - if (!_Index.TryGetValue(id, out var link)) - return false; + public bool TryGet(ResourceId id, out T value, out ResourceIdKind kind) + { + value = default; + kind = default; + if (!_Index.TryGetValue(id, out var link)) + return false; - value = link.Link; - kind = link.Kind; - return true; - } + value = link.Link; + kind = link.Kind; + return true; + } - public bool TryAdd(T target) - { - if (_Index.ContainsKey(target.Id) || (target.Ref.HasValue && _Index.ContainsKey(target.Ref.Value))) - return false; + public bool TryAdd(T target) + { + if (_Index.ContainsKey(target.Id) || (target.Ref.HasValue && _Index.ContainsKey(target.Ref.Value))) + return false; - for (var i = 0; target.Alias != null && i < target.Alias.Length; i++) - if (_Index.ContainsKey(target.Alias[i])) - return false; + for (var i = 0; target.Alias != null && i < target.Alias.Length; i++) + if (_Index.ContainsKey(target.Alias[i])) + return false; - // Add Id, Ref, and aliases to the index. - _Index.Add(target.Id, new TargetLink(target, ResourceIdKind.Id)); - if (target.Ref.HasValue && target.Id != target.Ref.Value) - _Index.Add(target.Ref.Value, new TargetLink(target, ResourceIdKind.Ref)); + // Add Id, Ref, and aliases to the index. + _Index.Add(target.Id, new TargetLink(target, ResourceIdKind.Id)); + if (target.Ref.HasValue && target.Id != target.Ref.Value) + _Index.Add(target.Ref.Value, new TargetLink(target, ResourceIdKind.Ref)); - for (var i = 0; target.Alias != null && i < target.Alias.Length; i++) - _Index.Add(target.Alias[i], new TargetLink(target, ResourceIdKind.Alias)); + for (var i = 0; target.Alias != null && i < target.Alias.Length; i++) + _Index.Add(target.Alias[i], new TargetLink(target, ResourceIdKind.Alias)); - _Items.Add(target); - return true; - } + _Items.Add(target); + return true; + } - public IEnumerable GetAll() - { - return _Items; - } + public IEnumerable GetAll() + { + return _Items; } } diff --git a/src/PSRule/Definitions/Expressions/Exceptions.cs b/src/PSRule/Definitions/Expressions/Exceptions.cs index ab3842f81f..b916c28e88 100644 --- a/src/PSRule/Definitions/Expressions/Exceptions.cs +++ b/src/PSRule/Definitions/Expressions/Exceptions.cs @@ -5,252 +5,251 @@ using System.Security.Permissions; using PSRule.Pipeline; -namespace PSRule.Definitions.Expressions +namespace PSRule.Definitions.Expressions; + +/// +/// A base class for runtime exceptions. +/// +public abstract class SelectorException : PipelineException +{ + /// + /// Create an empty selector exception. + /// + protected SelectorException() + : base() { } + + /// + /// Create an selector exception. + /// + protected SelectorException(string message) + : base(message) { } + + /// + /// Create an selector exception. + /// + protected SelectorException(string message, Exception innerException) + : base(message, innerException) { } + + /// + /// Create an selector exception. + /// + protected SelectorException(SerializationInfo info, StreamingContext context) + : base(info, context) { } +} + +/// +/// An expression parser exception. +/// +[Serializable] +public sealed class ExpressionParseException : SelectorException +{ + /// + /// Create an empty expression parse exception. + /// + public ExpressionParseException() { } + + /// + /// Create an expression parse exception. + /// + public ExpressionParseException(string message) + : base(message) { } + + /// + /// Create an expression parse exception. + /// + public ExpressionParseException(string message, Exception innerException) + : base(message, innerException) { } + + /// + /// Create an expression parse exception. + /// + internal ExpressionParseException(string expression, string message) + : base(message) + { + Expression = expression; + } + + /// + /// Create an expression parse exception. + /// + internal ExpressionParseException(string expression, string message, Exception innerException) + : base(message, innerException) + { + Expression = expression; + } + + private ExpressionParseException(SerializationInfo info, StreamingContext context) + : base(info, context) { } + + /// + /// The related expression. + /// + public string Expression { get; } + + /// + [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)] + public override void GetObjectData(SerializationInfo info, StreamingContext context) + { + if (info == null) throw new ArgumentNullException(nameof(info)); + base.GetObjectData(info, context); + } +} + +/// +/// A base class for an expression exception. +/// +public abstract class ExpressionException : SelectorException { /// - /// A base class for runtime exceptions. + /// Create an empty expression exception. + /// + protected ExpressionException() { } + + /// + /// Create an expression exception. /// - public abstract class SelectorException : PipelineException + protected ExpressionException(string message) + : base(message) { } + + /// + /// Create an expression exception. + /// + protected ExpressionException(string message, Exception innerException) + : base(message, innerException) { } + + /// + /// Create an expression exception. + /// + protected ExpressionException(string expression, string message) + : base(message) { - /// - /// Create an empty selector exception. - /// - protected SelectorException() - : base() { } - - /// - /// Create an selector exception. - /// - protected SelectorException(string message) - : base(message) { } - - /// - /// Create an selector exception. - /// - protected SelectorException(string message, Exception innerException) - : base(message, innerException) { } - - /// - /// Create an selector exception. - /// - protected SelectorException(SerializationInfo info, StreamingContext context) - : base(info, context) { } + Expression = expression; } /// - /// An expression parser exception. + /// Create an expression exception. + /// + protected ExpressionException(string expression, string message, Exception innerException) + : base(message, innerException) + { + Expression = expression; + } + + /// + /// Create an expression exception. + /// + protected ExpressionException(SerializationInfo info, StreamingContext context) + : base(info, context) { } + + /// + /// The related expression. + /// + public string Expression { get; } + + /// + [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)] + public override void GetObjectData(SerializationInfo info, StreamingContext context) + { + if (info == null) throw new ArgumentNullException(nameof(info)); + base.GetObjectData(info, context); + } +} + +/// +/// An expression reference exception. +/// +[Serializable] +public sealed class ExpressionReferenceException : SelectorException +{ + /// + /// Create an empty expression reference exception. + /// + public ExpressionReferenceException() { } + + /// + /// Create an expression reference exception. + /// + public ExpressionReferenceException(string message) + : base(message) { } + + /// + /// Create an expression reference exception. + /// + public ExpressionReferenceException(string message, Exception innerException) + : base(message, innerException) { } + + /// + /// Create an expression reference exception. /// - [Serializable] - public sealed class ExpressionParseException : SelectorException + internal ExpressionReferenceException(string expression, string message) + : base(message) { - /// - /// Create an empty expression parse exception. - /// - public ExpressionParseException() { } - - /// - /// Create an expression parse exception. - /// - public ExpressionParseException(string message) - : base(message) { } - - /// - /// Create an expression parse exception. - /// - public ExpressionParseException(string message, Exception innerException) - : base(message, innerException) { } - - /// - /// Create an expression parse exception. - /// - internal ExpressionParseException(string expression, string message) - : base(message) - { - Expression = expression; - } - - /// - /// Create an expression parse exception. - /// - internal ExpressionParseException(string expression, string message, Exception innerException) - : base(message, innerException) - { - Expression = expression; - } - - private ExpressionParseException(SerializationInfo info, StreamingContext context) - : base(info, context) { } - - /// - /// The related expression. - /// - public string Expression { get; } - - /// - [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)] - public override void GetObjectData(SerializationInfo info, StreamingContext context) - { - if (info == null) throw new ArgumentNullException(nameof(info)); - base.GetObjectData(info, context); - } + Expression = expression; } /// - /// A base class for an expression exception. + /// Create an expression reference exception. /// - public abstract class ExpressionException : SelectorException + internal ExpressionReferenceException(string expression, string message, Exception innerException) + : base(message, innerException) { - /// - /// Create an empty expression exception. - /// - protected ExpressionException() { } - - /// - /// Create an expression exception. - /// - protected ExpressionException(string message) - : base(message) { } - - /// - /// Create an expression exception. - /// - protected ExpressionException(string message, Exception innerException) - : base(message, innerException) { } - - /// - /// Create an expression exception. - /// - protected ExpressionException(string expression, string message) - : base(message) - { - Expression = expression; - } - - /// - /// Create an expression exception. - /// - protected ExpressionException(string expression, string message, Exception innerException) - : base(message, innerException) - { - Expression = expression; - } - - /// - /// Create an expression exception. - /// - protected ExpressionException(SerializationInfo info, StreamingContext context) - : base(info, context) { } - - /// - /// The related expression. - /// - public string Expression { get; } - - /// - [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)] - public override void GetObjectData(SerializationInfo info, StreamingContext context) - { - if (info == null) throw new ArgumentNullException(nameof(info)); - base.GetObjectData(info, context); - } + Expression = expression; } + private ExpressionReferenceException(SerializationInfo info, StreamingContext context) + : base(info, context) { } + /// - /// An expression reference exception. + /// The related expression. /// - [Serializable] - public sealed class ExpressionReferenceException : SelectorException + public string Expression { get; } + + /// + [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)] + public override void GetObjectData(SerializationInfo info, StreamingContext context) { - /// - /// Create an empty expression reference exception. - /// - public ExpressionReferenceException() { } - - /// - /// Create an expression reference exception. - /// - public ExpressionReferenceException(string message) - : base(message) { } - - /// - /// Create an expression reference exception. - /// - public ExpressionReferenceException(string message, Exception innerException) - : base(message, innerException) { } - - /// - /// Create an expression reference exception. - /// - internal ExpressionReferenceException(string expression, string message) - : base(message) - { - Expression = expression; - } - - /// - /// Create an expression reference exception. - /// - internal ExpressionReferenceException(string expression, string message, Exception innerException) - : base(message, innerException) - { - Expression = expression; - } - - private ExpressionReferenceException(SerializationInfo info, StreamingContext context) - : base(info, context) { } - - /// - /// The related expression. - /// - public string Expression { get; } - - /// - [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)] - public override void GetObjectData(SerializationInfo info, StreamingContext context) - { - if (info == null) throw new ArgumentNullException(nameof(info)); - base.GetObjectData(info, context); - } + if (info == null) throw new ArgumentNullException(nameof(info)); + base.GetObjectData(info, context); } +} +/// +/// An expression argument exception. +/// +[Serializable] +public sealed class ExpressionArgumentException : ExpressionException +{ /// - /// An expression argument exception. + /// Create an empty expression argument exception. /// - [Serializable] - public sealed class ExpressionArgumentException : ExpressionException + public ExpressionArgumentException() { } + + /// + /// Create an expression argument exception. + /// + public ExpressionArgumentException(string message) + : base(message) { } + + /// + /// Create an expression argument exception. + /// + public ExpressionArgumentException(string message, Exception innerException) + : base(message, innerException) { } + + /// + /// Create an expression argument exception. + /// + internal ExpressionArgumentException(string expression, string message) + : base(expression, message) { } + + private ExpressionArgumentException(SerializationInfo info, StreamingContext context) + : base(info, context) { } + + /// + [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)] + public override void GetObjectData(SerializationInfo info, StreamingContext context) { - /// - /// Create an empty expression argument exception. - /// - public ExpressionArgumentException() { } - - /// - /// Create an expression argument exception. - /// - public ExpressionArgumentException(string message) - : base(message) { } - - /// - /// Create an expression argument exception. - /// - public ExpressionArgumentException(string message, Exception innerException) - : base(message, innerException) { } - - /// - /// Create an expression argument exception. - /// - internal ExpressionArgumentException(string expression, string message) - : base(expression, message) { } - - private ExpressionArgumentException(SerializationInfo info, StreamingContext context) - : base(info, context) { } - - /// - [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)] - public override void GetObjectData(SerializationInfo info, StreamingContext context) - { - if (info == null) throw new ArgumentNullException(nameof(info)); - base.GetObjectData(info, context); - } + if (info == null) throw new ArgumentNullException(nameof(info)); + base.GetObjectData(info, context); } } diff --git a/src/PSRule/Definitions/Expressions/ExpressionContext.cs b/src/PSRule/Definitions/Expressions/ExpressionContext.cs index 31a8f8dae9..c26d73502e 100644 --- a/src/PSRule/Definitions/Expressions/ExpressionContext.cs +++ b/src/PSRule/Definitions/Expressions/ExpressionContext.cs @@ -6,97 +6,96 @@ using PSRule.Runtime; using PSRule.Runtime.ObjectPath; -namespace PSRule.Definitions.Expressions +namespace PSRule.Definitions.Expressions; + +internal interface IExpressionContext : IBindingContext { - internal interface IExpressionContext : IBindingContext + string LanguageScope { get; } + + void Reason(IOperand operand, string text, params object[] args); + + object Current { get; } + + RunspaceContext Context { get; } +} + +internal sealed class ExpressionContext : IExpressionContext, IBindingContext +{ + private readonly Dictionary _NameTokenCache; + + private List _Reason; + + internal ExpressionContext(RunspaceContext context, SourceFile source, ResourceKind kind, object current) { - string LanguageScope { get; } + Context = context; + Source = source; + LanguageScope = source.Module; + Kind = kind; + _NameTokenCache = new Dictionary(); + Current = current; + } + + public SourceFile Source { get; } + + public string LanguageScope { get; } + + public ResourceKind Kind { get; } + + public object Current { get; } - void Reason(IOperand operand, string text, params object[] args); + public RunspaceContext Context { get; } - object Current { get; } + [DebuggerStepThrough] + void IBindingContext.CachePathExpression(string path, PathExpression expression) + { + _NameTokenCache[path] = expression; + } + + [DebuggerStepThrough] + bool IBindingContext.GetPathExpression(string path, out PathExpression expression) + { + return _NameTokenCache.TryGetValue(path, out expression); + } + + internal void Debug(string message, params object[] args) + { + if (RunspaceContext.CurrentThread?.Writer == null) + return; + + RunspaceContext.CurrentThread.Writer.WriteDebug(message, args); + } + + internal void PushScope(RunspaceScope scope) + { + RunspaceContext.CurrentThread.PushScope(scope); + RunspaceContext.CurrentThread.EnterLanguageScope(Source); + } + + internal void PopScope(RunspaceScope scope) + { + RunspaceContext.CurrentThread.PopScope(scope); + } + + public void Reason(IOperand operand, string text, params object[] args) + { + if (string.IsNullOrEmpty(text) || !RunspaceContext.CurrentThread.IsScope(RunspaceScope.Rule)) + return; + + _Reason ??= new List(); + _Reason.Add(new ResultReason(Context.TargetObject?.Path, operand, text, args)); + } + + public void Reason(string text, params object[] args) + { + if (string.IsNullOrEmpty(text) || !RunspaceContext.CurrentThread.IsScope(RunspaceScope.Rule)) + return; - RunspaceContext Context { get; } + _Reason ??= new List(); + _Reason.Add(new ResultReason(Context.TargetObject?.Path, null, text, args)); } - internal sealed class ExpressionContext : IExpressionContext, IBindingContext + internal ResultReason[] GetReasons() { - private readonly Dictionary _NameTokenCache; - - private List _Reason; - - internal ExpressionContext(RunspaceContext context, SourceFile source, ResourceKind kind, object current) - { - Context = context; - Source = source; - LanguageScope = source.Module; - Kind = kind; - _NameTokenCache = new Dictionary(); - Current = current; - } - - public SourceFile Source { get; } - - public string LanguageScope { get; } - - public ResourceKind Kind { get; } - - public object Current { get; } - - public RunspaceContext Context { get; } - - [DebuggerStepThrough] - void IBindingContext.CachePathExpression(string path, PathExpression expression) - { - _NameTokenCache[path] = expression; - } - - [DebuggerStepThrough] - bool IBindingContext.GetPathExpression(string path, out PathExpression expression) - { - return _NameTokenCache.TryGetValue(path, out expression); - } - - internal void Debug(string message, params object[] args) - { - if (RunspaceContext.CurrentThread?.Writer == null) - return; - - RunspaceContext.CurrentThread.Writer.WriteDebug(message, args); - } - - internal void PushScope(RunspaceScope scope) - { - RunspaceContext.CurrentThread.PushScope(scope); - RunspaceContext.CurrentThread.EnterLanguageScope(Source); - } - - internal void PopScope(RunspaceScope scope) - { - RunspaceContext.CurrentThread.PopScope(scope); - } - - public void Reason(IOperand operand, string text, params object[] args) - { - if (string.IsNullOrEmpty(text) || !RunspaceContext.CurrentThread.IsScope(RunspaceScope.Rule)) - return; - - _Reason ??= new List(); - _Reason.Add(new ResultReason(Context.TargetObject?.Path, operand, text, args)); - } - - public void Reason(string text, params object[] args) - { - if (string.IsNullOrEmpty(text) || !RunspaceContext.CurrentThread.IsScope(RunspaceScope.Rule)) - return; - - _Reason ??= new List(); - _Reason.Add(new ResultReason(Context.TargetObject?.Path, null, text, args)); - } - - internal ResultReason[] GetReasons() - { - return _Reason == null || _Reason.Count == 0 ? Array.Empty() : _Reason.ToArray(); - } + return _Reason == null || _Reason.Count == 0 ? Array.Empty() : _Reason.ToArray(); } } diff --git a/src/PSRule/Definitions/Expressions/FunctionBuilder.cs b/src/PSRule/Definitions/Expressions/FunctionBuilder.cs index ff437156c9..4e9ae36c09 100644 --- a/src/PSRule/Definitions/Expressions/FunctionBuilder.cs +++ b/src/PSRule/Definitions/Expressions/FunctionBuilder.cs @@ -3,117 +3,116 @@ using System.Diagnostics; -namespace PSRule.Definitions.Expressions +namespace PSRule.Definitions.Expressions; + +internal delegate object ExpressionFnOuter(IExpressionContext context); +internal delegate object ExpressionFn(IExpressionContext context, object[] args); + +internal delegate ExpressionFnOuter ExpressionBuilderFn(IExpressionContext context, LanguageExpression.PropertyBag properties); + +internal abstract class FunctionReader { - internal delegate object ExpressionFnOuter(IExpressionContext context); - internal delegate object ExpressionFn(IExpressionContext context, object[] args); + public abstract bool TryProperty(out string propertyName); +} + +internal sealed class FunctionBuilder +{ + private readonly Stack _Stack; + private readonly FunctionFactory _Functions; + + private LanguageExpression.PropertyBag _Current; - internal delegate ExpressionFnOuter ExpressionBuilderFn(IExpressionContext context, LanguageExpression.PropertyBag properties); + internal FunctionBuilder() : this(new FunctionFactory()) { } - internal abstract class FunctionReader + internal FunctionBuilder(FunctionFactory expressionFactory) { - public abstract bool TryProperty(out string propertyName); + _Functions = expressionFactory; + _Stack = new Stack(); } - internal sealed class FunctionBuilder + public void Push() { - private readonly Stack _Stack; - private readonly FunctionFactory _Functions; - - private LanguageExpression.PropertyBag _Current; + _Current = new LanguageExpression.PropertyBag(); + _Stack.Push(_Current); + } - internal FunctionBuilder() : this(new FunctionFactory()) { } + internal void Add(string name, object value) + { + _Current.Add(name, value); + } - internal FunctionBuilder(FunctionFactory expressionFactory) - { - _Functions = expressionFactory; - _Stack = new Stack(); - } + public ExpressionFnOuter Pop() + { + var properties = _Stack.Pop(); + _Current = _Stack.Count > 0 ? _Stack.Peek() : null; + return TryFunction(properties, out var descriptor) ? descriptor.Fn(null, properties) : null; + } - public void Push() + private bool TryFunction(LanguageExpression.PropertyBag properties, out IFunctionDescriptor descriptor) + { + descriptor = null; + foreach (var property in properties) { - _Current = new LanguageExpression.PropertyBag(); - _Stack.Push(_Current); + if (_Functions.TryDescriptor(property.Key, out descriptor)) + return true; } + return false; + } +} - internal void Add(string name, object value) - { - _Current.Add(name, value); - } +internal sealed class FunctionFactory +{ + private readonly Dictionary _Descriptors; - public ExpressionFnOuter Pop() - { - var properties = _Stack.Pop(); - _Current = _Stack.Count > 0 ? _Stack.Peek() : null; - return TryFunction(properties, out var descriptor) ? descriptor.Fn(null, properties) : null; - } + public FunctionFactory() + { + _Descriptors = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (var d in Functions.Builtin) + With(d); + } - private bool TryFunction(LanguageExpression.PropertyBag properties, out IFunctionDescriptor descriptor) - { - descriptor = null; - foreach (var property in properties) - { - if (_Functions.TryDescriptor(property.Key, out descriptor)) - return true; - } - return false; - } + public bool TryDescriptor(string name, out IFunctionDescriptor descriptor) + { + return _Descriptors.TryGetValue(name, out descriptor); } - internal sealed class FunctionFactory + public void With(IFunctionDescriptor descriptor) { - private readonly Dictionary _Descriptors; + _Descriptors.Add(descriptor.Name, descriptor); + } +} - public FunctionFactory() - { - _Descriptors = new Dictionary(StringComparer.OrdinalIgnoreCase); - foreach (var d in Functions.Builtin) - With(d); - } +/// +/// A structure describing a specific function. +/// +[DebuggerDisplay("Function: {Name}")] +internal sealed class FunctionDescriptor : IFunctionDescriptor +{ + public FunctionDescriptor(string name, ExpressionBuilderFn fn) + { + Name = name; + Fn = fn; + } - public bool TryDescriptor(string name, out IFunctionDescriptor descriptor) - { - return _Descriptors.TryGetValue(name, out descriptor); - } + /// + public string Name { get; } - public void With(IFunctionDescriptor descriptor) - { - _Descriptors.Add(descriptor.Name, descriptor); - } - } + /// + public ExpressionBuilderFn Fn { get; } +} +/// +/// A structure describing a specific function. +/// +internal interface IFunctionDescriptor +{ /// - /// A structure describing a specific function. + /// The name of the function. /// - [DebuggerDisplay("Function: {Name}")] - internal sealed class FunctionDescriptor : IFunctionDescriptor - { - public FunctionDescriptor(string name, ExpressionBuilderFn fn) - { - Name = name; - Fn = fn; - } - - /// - public string Name { get; } - - /// - public ExpressionBuilderFn Fn { get; } - } + string Name { get; } /// - /// A structure describing a specific function. + /// The function delegate. /// - internal interface IFunctionDescriptor - { - /// - /// The name of the function. - /// - string Name { get; } - - /// - /// The function delegate. - /// - ExpressionBuilderFn Fn { get; } - } + ExpressionBuilderFn Fn { get; } } diff --git a/src/PSRule/Definitions/Expressions/Functions.cs b/src/PSRule/Definitions/Expressions/Functions.cs index dc6426691c..3339e94e8e 100644 --- a/src/PSRule/Definitions/Expressions/Functions.cs +++ b/src/PSRule/Definitions/Expressions/Functions.cs @@ -7,365 +7,364 @@ using PSRule.Runtime; using static PSRule.Definitions.Expressions.LanguageExpression; -namespace PSRule.Definitions.Expressions +namespace PSRule.Definitions.Expressions; + +/// +/// Implementation of Azure Resource Manager template functions as ExpressionFn. +/// +internal static class Functions { + private const string BOOLEAN = "boolean"; + private const string STRING = "string"; + private const string INTEGER = "integer"; + private const string CONCAT = "concat"; + private const string SUBSTRING = "substring"; + private const string CONFIGURATION = "configuration"; + private const string PATH = "path"; + private const string LENGTH = "length"; + private const string REPLACE = "replace"; + private const string TRIM = "trim"; + private const string FIRST = "first"; + private const string LAST = "last"; + private const string SPLIT = "split"; + private const string PADLEFT = "padLeft"; + private const string PADRIGHT = "padRight"; + private const string DELIMITER = "delimiter"; + private const string OLDSTRING = "oldstring"; + private const string NEWSTRING = "newstring"; + private const string CASESENSITIVE = "casesensitive"; + private const string TOTALLENGTH = "totalLength"; + private const string PADDINGCHARACTER = "paddingCharacter"; + + private const char SPACE = ' '; + /// - /// Implementation of Azure Resource Manager template functions as ExpressionFn. + /// The available built-in functions. /// - internal static class Functions + internal static readonly IFunctionDescriptor[] Builtin = new IFunctionDescriptor[] { - private const string BOOLEAN = "boolean"; - private const string STRING = "string"; - private const string INTEGER = "integer"; - private const string CONCAT = "concat"; - private const string SUBSTRING = "substring"; - private const string CONFIGURATION = "configuration"; - private const string PATH = "path"; - private const string LENGTH = "length"; - private const string REPLACE = "replace"; - private const string TRIM = "trim"; - private const string FIRST = "first"; - private const string LAST = "last"; - private const string SPLIT = "split"; - private const string PADLEFT = "padLeft"; - private const string PADRIGHT = "padRight"; - private const string DELIMITER = "delimiter"; - private const string OLDSTRING = "oldstring"; - private const string NEWSTRING = "newstring"; - private const string CASESENSITIVE = "casesensitive"; - private const string TOTALLENGTH = "totalLength"; - private const string PADDINGCHARACTER = "paddingCharacter"; - - private const char SPACE = ' '; - - /// - /// The available built-in functions. - /// - internal static readonly IFunctionDescriptor[] Builtin = new IFunctionDescriptor[] - { - new FunctionDescriptor(CONFIGURATION, Configuration), - new FunctionDescriptor(PATH, Path), - new FunctionDescriptor(BOOLEAN, Boolean), - new FunctionDescriptor(STRING, String), - new FunctionDescriptor(INTEGER, Integer), - new FunctionDescriptor(CONCAT, Concat), - new FunctionDescriptor(SUBSTRING, Substring), - new FunctionDescriptor(REPLACE, Replace), - new FunctionDescriptor(TRIM, Trim), - new FunctionDescriptor(FIRST, First), - new FunctionDescriptor(LAST, Last), - new FunctionDescriptor(SPLIT, Split), - new FunctionDescriptor(PADLEFT, PadLeft), - new FunctionDescriptor(PADRIGHT, PadRight), - }; + new FunctionDescriptor(CONFIGURATION, Configuration), + new FunctionDescriptor(PATH, Path), + new FunctionDescriptor(BOOLEAN, Boolean), + new FunctionDescriptor(STRING, String), + new FunctionDescriptor(INTEGER, Integer), + new FunctionDescriptor(CONCAT, Concat), + new FunctionDescriptor(SUBSTRING, Substring), + new FunctionDescriptor(REPLACE, Replace), + new FunctionDescriptor(TRIM, Trim), + new FunctionDescriptor(FIRST, First), + new FunctionDescriptor(LAST, Last), + new FunctionDescriptor(SPLIT, Split), + new FunctionDescriptor(PADLEFT, PadLeft), + new FunctionDescriptor(PADRIGHT, PadRight), + }; + + private static ExpressionFnOuter Boolean(IExpressionContext context, PropertyBag properties) + { + if (properties == null || + properties.Count == 0 || + !TryProperty(properties, BOOLEAN, out ExpressionFnOuter next)) + return null; - private static ExpressionFnOuter Boolean(IExpressionContext context, PropertyBag properties) + return (context) => { - if (properties == null || - properties.Count == 0 || - !TryProperty(properties, BOOLEAN, out ExpressionFnOuter next)) - return null; + var value = next(context); + ExpressionHelpers.TryBool(value, true, out var b); + return b; + }; + } - return (context) => - { - var value = next(context); - ExpressionHelpers.TryBool(value, true, out var b); - return b; - }; - } + private static ExpressionFnOuter String(IExpressionContext context, PropertyBag properties) + { + if (properties == null || + properties.Count == 0 || + !TryProperty(properties, STRING, out ExpressionFnOuter next)) + return null; - private static ExpressionFnOuter String(IExpressionContext context, PropertyBag properties) + return (context) => { - if (properties == null || - properties.Count == 0 || - !TryProperty(properties, STRING, out ExpressionFnOuter next)) - return null; + var value = next(context); + ExpressionHelpers.TryString(value, true, out var s); + return s; + }; + } - return (context) => - { - var value = next(context); - ExpressionHelpers.TryString(value, true, out var s); - return s; - }; - } + private static ExpressionFnOuter Integer(IExpressionContext context, PropertyBag properties) + { + if (properties == null || + properties.Count == 0 || + !TryProperty(properties, INTEGER, out ExpressionFnOuter next)) + return null; - private static ExpressionFnOuter Integer(IExpressionContext context, PropertyBag properties) + return (context) => { - if (properties == null || - properties.Count == 0 || - !TryProperty(properties, INTEGER, out ExpressionFnOuter next)) - return null; + var value = next(context); + ExpressionHelpers.TryInt(value, true, out var i); + return i; + }; + } - return (context) => - { - var value = next(context); - ExpressionHelpers.TryInt(value, true, out var i); - return i; - }; - } + private static ExpressionFnOuter Configuration(IExpressionContext context, PropertyBag properties) + { + if (properties == null || properties.Count == 0 || + !properties.TryGetString(CONFIGURATION, out var name)) + return null; - private static ExpressionFnOuter Configuration(IExpressionContext context, PropertyBag properties) + // Lookup a configuration value. + return (context) => { - if (properties == null || properties.Count == 0 || - !properties.TryGetString(CONFIGURATION, out var name)) - return null; + return context.Context.TryGetConfigurationValue(name, out var value) ? value : null; + }; + } - // Lookup a configuration value. - return (context) => - { - return context.Context.TryGetConfigurationValue(name, out var value) ? value : null; - }; - } + private static ExpressionFnOuter Path(IExpressionContext context, PropertyBag properties) + { + if (properties == null || properties.Count == 0 || + !properties.TryGetString(PATH, out var path)) + return null; - private static ExpressionFnOuter Path(IExpressionContext context, PropertyBag properties) + return (context) => { - if (properties == null || properties.Count == 0 || - !properties.TryGetString(PATH, out var path)) - return null; + return ObjectHelper.GetPath(context, context.Current, path, false, out object value) ? value : null; + }; + } - return (context) => - { - return ObjectHelper.GetPath(context, context.Current, path, false, out object value) ? value : null; - }; - } + private static ExpressionFnOuter Concat(IExpressionContext context, PropertyBag properties) + { + if (properties == null || + properties.Count == 0 || + !properties.TryGetEnumerable(CONCAT, out var values)) + return null; - private static ExpressionFnOuter Concat(IExpressionContext context, PropertyBag properties) + return (context) => { - if (properties == null || - properties.Count == 0 || - !properties.TryGetEnumerable(CONCAT, out var values)) - return null; + var sb = new StringBuilder(); + foreach (var value in values) + sb.Append(Value(context, value)); - return (context) => - { - var sb = new StringBuilder(); - foreach (var value in values) - sb.Append(Value(context, value)); + return sb.ToString(); + }; + } - return sb.ToString(); - }; - } + private static ExpressionFnOuter Substring(IExpressionContext context, PropertyBag properties) + { + if (properties == null || + properties.Count == 0 || + !TryProperty(properties, LENGTH, out int? length) || + !TryProperty(properties, SUBSTRING, out ExpressionFnOuter next)) + return null; - private static ExpressionFnOuter Substring(IExpressionContext context, PropertyBag properties) + return (context) => { - if (properties == null || - properties.Count == 0 || - !TryProperty(properties, LENGTH, out int? length) || - !TryProperty(properties, SUBSTRING, out ExpressionFnOuter next)) - return null; - - return (context) => + var value = next(context); + if (value is string s) { - var value = next(context); - if (value is string s) - { - length = s.Length < length ? s.Length : length; - return s.Substring(0, length.Value); - } - return null; - }; - } - - private static ExpressionFnOuter Replace(IExpressionContext context, PropertyBag properties) + length = s.Length < length ? s.Length : length; + return s.Substring(0, length.Value); + } + return null; + }; + } + + private static ExpressionFnOuter Replace(IExpressionContext context, PropertyBag properties) + { + if (properties == null || + properties.Count == 0 || + !properties.TryGetString(OLDSTRING, out var oldString) || + !properties.TryGetString(NEWSTRING, out var newString) || + !TryProperty(properties, REPLACE, out ExpressionFnOuter next)) + return null; + + var caseSensitive = properties.TryGetBool(CASESENSITIVE, out var cs) && cs.HasValue && cs.Value; + return (context) => { - if (properties == null || - properties.Count == 0 || - !properties.TryGetString(OLDSTRING, out var oldString) || - !properties.TryGetString(NEWSTRING, out var newString) || - !TryProperty(properties, REPLACE, out ExpressionFnOuter next)) - return null; - - var caseSensitive = properties.TryGetBool(CASESENSITIVE, out var cs) && cs.HasValue && cs.Value; - return (context) => - { - var value = next(context); - if (ExpressionHelpers.TryString(value, out var originalString)) - return originalString.Length > 0 && oldString.Length > 0 ? originalString.Replace(oldString, newString, caseSensitive) : originalString; + var value = next(context); + if (ExpressionHelpers.TryString(value, out var originalString)) + return originalString.Length > 0 && oldString.Length > 0 ? originalString.Replace(oldString, newString, caseSensitive) : originalString; + + return null; + }; + } - return null; - }; - } + private static ExpressionFnOuter Trim(IExpressionContext context, PropertyBag properties) + { + if (properties == null || + properties.Count == 0 || + !TryProperty(properties, TRIM, out ExpressionFnOuter next)) + return null; - private static ExpressionFnOuter Trim(IExpressionContext context, PropertyBag properties) + return (context) => { - if (properties == null || - properties.Count == 0 || - !TryProperty(properties, TRIM, out ExpressionFnOuter next)) - return null; + var value = next(context); + return ExpressionHelpers.TryString(value, out var s) ? s.Trim() : null; + }; + } - return (context) => - { - var value = next(context); - return ExpressionHelpers.TryString(value, out var s) ? s.Trim() : null; - }; - } + private static ExpressionFnOuter First(IExpressionContext context, PropertyBag properties) + { + if (properties == null || + properties.Count == 0 || + !TryProperty(properties, FIRST, out ExpressionFnOuter next)) + return null; - private static ExpressionFnOuter First(IExpressionContext context, PropertyBag properties) + return (context) => { - if (properties == null || - properties.Count == 0 || - !TryProperty(properties, FIRST, out ExpressionFnOuter next)) - return null; + var value = next(context); + if (ExpressionHelpers.TryString(value, out var s)) + return s.Length > 0 ? new string(s[0], 1) : null; - return (context) => - { - var value = next(context); - if (ExpressionHelpers.TryString(value, out var s)) - return s.Length > 0 ? new string(s[0], 1) : null; + return ExpressionHelpers.TryArray(value, out var array) ? Value(context, array.First()) : null; + }; + } - return ExpressionHelpers.TryArray(value, out var array) ? Value(context, array.First()) : null; - }; - } + private static ExpressionFnOuter Last(IExpressionContext context, PropertyBag properties) + { + if (properties == null || + properties.Count == 0 || + !TryProperty(properties, LAST, out ExpressionFnOuter next)) + return null; - private static ExpressionFnOuter Last(IExpressionContext context, PropertyBag properties) + return (context) => { - if (properties == null || - properties.Count == 0 || - !TryProperty(properties, LAST, out ExpressionFnOuter next)) - return null; + var value = next(context); + if (ExpressionHelpers.TryString(value, out var s)) + return s.Length > 0 ? new string(s[s.Length - 1], 1) : null; - return (context) => - { - var value = next(context); - if (ExpressionHelpers.TryString(value, out var s)) - return s.Length > 0 ? new string(s[s.Length - 1], 1) : null; + return ExpressionHelpers.TryArray(value, out var array) ? Value(context, array.Last()) : null; + }; + } - return ExpressionHelpers.TryArray(value, out var array) ? Value(context, array.Last()) : null; - }; - } + private static ExpressionFnOuter Split(IExpressionContext context, PropertyBag properties) + { + if (properties == null || + properties.Count == 0 || + !properties.TryGetStringArray(DELIMITER, out var delimiter) || + !TryProperty(properties, SPLIT, out ExpressionFnOuter next)) + return null; - private static ExpressionFnOuter Split(IExpressionContext context, PropertyBag properties) + return (context) => { - if (properties == null || - properties.Count == 0 || - !properties.TryGetStringArray(DELIMITER, out var delimiter) || - !TryProperty(properties, SPLIT, out ExpressionFnOuter next)) - return null; - - return (context) => - { - var value = next(context); - return ExpressionHelpers.TryString(value, out var s) ? s.Split(delimiter, options: StringSplitOptions.None) : null; - }; - } + var value = next(context); + return ExpressionHelpers.TryString(value, out var s) ? s.Split(delimiter, options: StringSplitOptions.None) : null; + }; + } - private static ExpressionFnOuter PadLeft(IExpressionContext context, PropertyBag properties) - { - if (properties == null || - properties.Count == 0 || + private static ExpressionFnOuter PadLeft(IExpressionContext context, PropertyBag properties) + { + if (properties == null || + properties.Count == 0 || - !TryProperty(properties, PADLEFT, out ExpressionFnOuter next)) - return null; + !TryProperty(properties, PADLEFT, out ExpressionFnOuter next)) + return null; - var paddingChar = properties.TryGetChar(PADDINGCHARACTER, out var c) ? c : SPACE; - var totalWidth = properties.TryGetInt(TOTALLENGTH, out var i) ? i : 0; - return (context) => - { - var value = next(context); - if (ExpressionHelpers.TryString(value, convert: true, value: out var s)) - return totalWidth > s.Length ? s.PadLeft(totalWidth.Value, paddingChar.Value) : s; + var paddingChar = properties.TryGetChar(PADDINGCHARACTER, out var c) ? c : SPACE; + var totalWidth = properties.TryGetInt(TOTALLENGTH, out var i) ? i : 0; + return (context) => + { + var value = next(context); + if (ExpressionHelpers.TryString(value, convert: true, value: out var s)) + return totalWidth > s.Length ? s.PadLeft(totalWidth.Value, paddingChar.Value) : s; - return null; - }; - } + return null; + }; + } - private static ExpressionFnOuter PadRight(IExpressionContext context, PropertyBag properties) + private static ExpressionFnOuter PadRight(IExpressionContext context, PropertyBag properties) + { + if (properties == null || + properties.Count == 0 || + !TryProperty(properties, PADRIGHT, out ExpressionFnOuter next)) + return null; + + var paddingChar = properties.TryGetChar(PADDINGCHARACTER, out var c) ? c : SPACE; + var totalWidth = properties.TryGetInt(TOTALLENGTH, out var i) ? i : 0; + return (context) => { - if (properties == null || - properties.Count == 0 || - !TryProperty(properties, PADRIGHT, out ExpressionFnOuter next)) - return null; - - var paddingChar = properties.TryGetChar(PADDINGCHARACTER, out var c) ? c : SPACE; - var totalWidth = properties.TryGetInt(TOTALLENGTH, out var i) ? i : 0; - return (context) => - { - var value = next(context); - if (ExpressionHelpers.TryString(value, convert: true, value: out var s)) - return totalWidth > s.Length ? s.PadRight(totalWidth.Value, paddingChar.Value) : s; + var value = next(context); + if (ExpressionHelpers.TryString(value, convert: true, value: out var s)) + return totalWidth > s.Length ? s.PadRight(totalWidth.Value, paddingChar.Value) : s; - return null; - }; - } + return null; + }; + } - #region Helper functions + #region Helper functions - private static bool TryProperty(PropertyBag properties, string name, out int? value) - { - return properties.TryGetInt(name, out value); - } + private static bool TryProperty(PropertyBag properties, string name, out int? value) + { + return properties.TryGetInt(name, out value); + } - private static bool TryProperty(PropertyBag properties, string name, out ExpressionFnOuter value) - { - value = null; - if (properties.TryGetValue(name, out var v) && v is ExpressionFnOuter fn) - value = fn; + private static bool TryProperty(PropertyBag properties, string name, out ExpressionFnOuter value) + { + value = null; + if (properties.TryGetValue(name, out var v) && v is ExpressionFnOuter fn) + value = fn; - else if (properties.TryGetBool(name, out var b_value)) - value = (context) => b_value; + else if (properties.TryGetBool(name, out var b_value)) + value = (context) => b_value; - else if (properties.TryGetLong(name, out var l_value)) - value = (context) => l_value; + else if (properties.TryGetLong(name, out var l_value)) + value = (context) => l_value; - else if (properties.TryGetInt(name, out var i_value)) - value = (context) => i_value; + else if (properties.TryGetInt(name, out var i_value)) + value = (context) => i_value; - else if (properties.TryGetValue(name, out var o_value)) - value = (context) => o_value; + else if (properties.TryGetValue(name, out var o_value)) + value = (context) => o_value; - return value != null; - } + return value != null; + } - private static object Value(IExpressionContext context, object value) - { - return value is ExpressionFnOuter fn ? fn(context) : value; - } + private static object Value(IExpressionContext context, object value) + { + return value is ExpressionFnOuter fn ? fn(context) : value; + } - #endregion Helper functions + #endregion Helper functions - #region Exceptions + #region Exceptions - private static ExpressionArgumentException ArgumentsOutOfRange(string expression, object[] args) - { - var length = args == null ? 0 : args.Length; - return new ExpressionArgumentException( - expression, - string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.ArgumentsOutOfRange, expression, length) - ); - } - - private static ExpressionArgumentException ArgumentFormatInvalid(string expression) - { - return new ExpressionArgumentException( - expression, - string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.ArgumentFormatInvalid, expression) - ); - } + private static ExpressionArgumentException ArgumentsOutOfRange(string expression, object[] args) + { + var length = args == null ? 0 : args.Length; + return new ExpressionArgumentException( + expression, + string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.ArgumentsOutOfRange, expression, length) + ); + } - private static ExpressionArgumentException ArgumentInvalidInteger(string expression, string operand) - { - return new ExpressionArgumentException( - expression, - string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.ArgumentInvalidInteger, operand, expression) - ); - } + private static ExpressionArgumentException ArgumentFormatInvalid(string expression) + { + return new ExpressionArgumentException( + expression, + string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.ArgumentFormatInvalid, expression) + ); + } - private static ExpressionArgumentException ArgumentInvalidBoolean(string expression, string operand) - { - return new ExpressionArgumentException( - expression, - string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.ArgumentInvalidBoolean, operand, expression) - ); - } + private static ExpressionArgumentException ArgumentInvalidInteger(string expression, string operand) + { + return new ExpressionArgumentException( + expression, + string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.ArgumentInvalidInteger, operand, expression) + ); + } - private static ExpressionArgumentException ArgumentInvalidString(string expression, string operand) - { - return new ExpressionArgumentException( - expression, - string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.ArgumentInvalidString, operand, expression) - ); - } + private static ExpressionArgumentException ArgumentInvalidBoolean(string expression, string operand) + { + return new ExpressionArgumentException( + expression, + string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.ArgumentInvalidBoolean, operand, expression) + ); + } - #endregion Exceptions + private static ExpressionArgumentException ArgumentInvalidString(string expression, string operand) + { + return new ExpressionArgumentException( + expression, + string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.ArgumentInvalidString, operand, expression) + ); } + + #endregion Exceptions } diff --git a/src/PSRule/Definitions/Expressions/LanguageExpressions.cs b/src/PSRule/Definitions/Expressions/LanguageExpressions.cs index b24484a925..f7d473ce96 100644 --- a/src/PSRule/Definitions/Expressions/LanguageExpressions.cs +++ b/src/PSRule/Definitions/Expressions/LanguageExpressions.cs @@ -8,1857 +8,1856 @@ using PSRule.Resources; using PSRule.Runtime; -namespace PSRule.Definitions.Expressions -{ - internal delegate bool LanguageExpressionFn(ExpressionContext context, ExpressionInfo info, object[] args, object o); +namespace PSRule.Definitions.Expressions; - internal delegate bool? LanguageExpressionOuterFn(ExpressionContext context, object o); +internal delegate bool LanguageExpressionFn(ExpressionContext context, ExpressionInfo info, object[] args, object o); - internal enum LanguageExpressionType - { - Operator = 1, +internal delegate bool? LanguageExpressionOuterFn(ExpressionContext context, object o); - Condition = 2, +internal enum LanguageExpressionType +{ + Operator = 1, - Function = 3 - } + Condition = 2, - internal sealed class ExpressionInfo - { - private readonly string _Path; + Function = 3 +} - public ExpressionInfo(string path) - { - _Path = path; - } - } +internal sealed class ExpressionInfo +{ + private readonly string _Path; - internal sealed class LanguageExpressionFactory + public ExpressionInfo(string path) { - private readonly Dictionary _Descriptors; + _Path = path; + } +} - public LanguageExpressionFactory() - { - _Descriptors = new Dictionary(LanguageExpressions.Builtin.Length, StringComparer.OrdinalIgnoreCase); - foreach (var d in LanguageExpressions.Builtin) - With(d); - } +internal sealed class LanguageExpressionFactory +{ + private readonly Dictionary _Descriptors; - public bool TryDescriptor(string name, out ILanguageExpresssionDescriptor descriptor) - { - descriptor = null; - return !string.IsNullOrEmpty(name) && - _Descriptors.TryGetValue(name, out descriptor); - } + public LanguageExpressionFactory() + { + _Descriptors = new Dictionary(LanguageExpressions.Builtin.Length, StringComparer.OrdinalIgnoreCase); + foreach (var d in LanguageExpressions.Builtin) + With(d); + } - public bool IsSubselector(string name) - { - return name == "where"; - } + public bool TryDescriptor(string name, out ILanguageExpresssionDescriptor descriptor) + { + descriptor = null; + return !string.IsNullOrEmpty(name) && + _Descriptors.TryGetValue(name, out descriptor); + } - public bool IsOperator(string name) - { - return TryDescriptor(name, out var d) && d != null && d.Type == LanguageExpressionType.Operator; - } + public bool IsSubselector(string name) + { + return name == "where"; + } - public bool IsCondition(string name) - { - return TryDescriptor(name, out var d) && d != null && d.Type == LanguageExpressionType.Condition; - } + public bool IsOperator(string name) + { + return TryDescriptor(name, out var d) && d != null && d.Type == LanguageExpressionType.Operator; + } - public bool IsFunction(string name) - { - return TryDescriptor(name, out var d) && - d != null && d.Type == LanguageExpressionType.Function; - } + public bool IsCondition(string name) + { + return TryDescriptor(name, out var d) && d != null && d.Type == LanguageExpressionType.Condition; + } - private void With(ILanguageExpresssionDescriptor descriptor) - { - _Descriptors.Add(descriptor.Name, descriptor); - } + public bool IsFunction(string name) + { + return TryDescriptor(name, out var d) && + d != null && d.Type == LanguageExpressionType.Function; } - internal sealed class LanguageExpressionBuilder + private void With(ILanguageExpresssionDescriptor descriptor) { - private const char Dot = '.'; - private const char OpenBracket = '['; - private const char CloseBracket = ']'; + _Descriptors.Add(descriptor.Name, descriptor); + } +} - private const string DOTWHERE = ".where"; - private const string LESS = "less"; - private const string LESSOREQUAL = "lessOrEqual"; - private const string GREATER = "greater"; - private const string GREATEROREQUAL = "greaterOrEqual"; - private const string COUNT = "count"; +internal sealed class LanguageExpressionBuilder +{ + private const char Dot = '.'; + private const char OpenBracket = '['; + private const char CloseBracket = ']'; - private readonly bool _Debugger; + private const string DOTWHERE = ".where"; + private const string LESS = "less"; + private const string LESSOREQUAL = "lessOrEqual"; + private const string GREATER = "greater"; + private const string GREATEROREQUAL = "greaterOrEqual"; + private const string COUNT = "count"; - private string[] _With; - private string[] _Type; - private LanguageExpression _When; - private string[] _Rule; + private readonly bool _Debugger; - public LanguageExpressionBuilder(bool debugger = true) - { - _Debugger = debugger; - } + private string[] _With; + private string[] _Type; + private LanguageExpression _When; + private string[] _Rule; - public LanguageExpressionBuilder WithSelector(string[] with) - { - if (with == null || with.Length == 0) - return this; + public LanguageExpressionBuilder(bool debugger = true) + { + _Debugger = debugger; + } - _With = with; + public LanguageExpressionBuilder WithSelector(string[] with) + { + if (with == null || with.Length == 0) return this; - } - public LanguageExpressionBuilder WithType(string[] type) - { - if (type == null || type.Length == 0) - return this; + _With = with; + return this; + } - _Type = type; + public LanguageExpressionBuilder WithType(string[] type) + { + if (type == null || type.Length == 0) return this; - } - public LanguageExpressionBuilder WithSubselector(LanguageIf subselector) - { - if (subselector == null || subselector.Expression == null) - return this; + _Type = type; + return this; + } - _When = subselector.Expression; + public LanguageExpressionBuilder WithSubselector(LanguageIf subselector) + { + if (subselector == null || subselector.Expression == null) return this; - } - public LanguageExpressionBuilder WithRule(string[] rule) - { - if (rule == null || rule.Length == 0) - return this; + _When = subselector.Expression; + return this; + } - _Rule = rule; + public LanguageExpressionBuilder WithRule(string[] rule) + { + if (rule == null || rule.Length == 0) return this; - } - public LanguageExpressionOuterFn Build(LanguageIf condition) - { - return Precondition(Expression(string.Empty, condition.Expression), _With, _Type, Expression(string.Empty, _When), _Rule); - } + _Rule = rule; + return this; + } - private static LanguageExpressionOuterFn Precondition(LanguageExpressionOuterFn expression, string[] with, string[] type, LanguageExpressionOuterFn when, string[] rule) - { - var fn = expression; - if (type != null) - fn = PreconditionType(type, fn); + public LanguageExpressionOuterFn Build(LanguageIf condition) + { + return Precondition(Expression(string.Empty, condition.Expression), _With, _Type, Expression(string.Empty, _When), _Rule); + } - if (with != null) - fn = PreconditionSelector(with, fn); + private static LanguageExpressionOuterFn Precondition(LanguageExpressionOuterFn expression, string[] with, string[] type, LanguageExpressionOuterFn when, string[] rule) + { + var fn = expression; + if (type != null) + fn = PreconditionType(type, fn); - if (when != null) - fn = PreconditionSubselector(when, fn); + if (with != null) + fn = PreconditionSelector(with, fn); - if (rule != null) - fn = PreconditionRule(rule, fn); + if (when != null) + fn = PreconditionSubselector(when, fn); - return fn; - } + if (rule != null) + fn = PreconditionRule(rule, fn); - private static LanguageExpressionOuterFn PreconditionRule(string[] rule, LanguageExpressionOuterFn fn) + return fn; + } + + private static LanguageExpressionOuterFn PreconditionRule(string[] rule, LanguageExpressionOuterFn fn) + { + return (context, o) => { - return (context, o) => + // Evaluate selector rule pre-condition + if (!AcceptsRule(rule)) { - // Evaluate selector rule pre-condition - if (!AcceptsRule(rule)) - { - context.Debug(PSRuleResources.DebugTargetRuleMismatch); - return null; - } - return fn(context, o); - }; - } + context.Debug(PSRuleResources.DebugTargetRuleMismatch); + return null; + } + return fn(context, o); + }; + } - private static LanguageExpressionOuterFn PreconditionSelector(string[] with, LanguageExpressionOuterFn fn) + private static LanguageExpressionOuterFn PreconditionSelector(string[] with, LanguageExpressionOuterFn fn) + { + return (context, o) => { - return (context, o) => + // Evaluate selector pre-condition + if (!AcceptsWith(with)) { - // Evaluate selector pre-condition - if (!AcceptsWith(with)) - { - context.Debug(PSRuleResources.DebugTargetTypeMismatch); - return null; - } - return fn(context, o); - }; - } + context.Debug(PSRuleResources.DebugTargetTypeMismatch); + return null; + } + return fn(context, o); + }; + } - private static LanguageExpressionOuterFn PreconditionType(string[] type, LanguageExpressionOuterFn fn) + private static LanguageExpressionOuterFn PreconditionType(string[] type, LanguageExpressionOuterFn fn) + { + return (context, o) => { - return (context, o) => + // Evaluate type pre-condition + if (!AcceptsType(type)) { - // Evaluate type pre-condition - if (!AcceptsType(type)) - { - context.Debug(PSRuleResources.DebugTargetTypeMismatch); - return null; - } - return fn(context, o); - }; - } + context.Debug(PSRuleResources.DebugTargetTypeMismatch); + return null; + } + return fn(context, o); + }; + } - private static LanguageExpressionOuterFn PreconditionSubselector(LanguageExpressionOuterFn subselector, LanguageExpressionOuterFn fn) + private static LanguageExpressionOuterFn PreconditionSubselector(LanguageExpressionOuterFn subselector, LanguageExpressionOuterFn fn) + { + return (context, o) => { - return (context, o) => + try { - try - { - context.PushScope(RunspaceScope.Precondition); + context.PushScope(RunspaceScope.Precondition); - // Evaluate sub-selector pre-condition - if (!AcceptsSubselector(context, subselector, o)) - { - context.Debug(PSRuleResources.DebugTargetSubselectorMismatch); - return null; - } - } - finally + // Evaluate sub-selector pre-condition + if (!AcceptsSubselector(context, subselector, o)) { - context.PopScope(RunspaceScope.Precondition); + context.Debug(PSRuleResources.DebugTargetSubselectorMismatch); + return null; } - return fn(context, o); - }; - } + } + finally + { + context.PopScope(RunspaceScope.Precondition); + } + return fn(context, o); + }; + } - private LanguageExpressionOuterFn Expression(string path, LanguageExpression expression) - { - if (expression == null) - return null; + private LanguageExpressionOuterFn Expression(string path, LanguageExpression expression) + { + if (expression == null) + return null; - path = Path(path, expression); - if (expression is LanguageOperator selectorOperator) - return Scope(Debugger(Operator(path, selectorOperator), path)); - else if (expression is LanguageCondition selectorCondition) - return Scope(Debugger(Condition(path, selectorCondition), path)); + path = Path(path, expression); + if (expression is LanguageOperator selectorOperator) + return Scope(Debugger(Operator(path, selectorOperator), path)); + else if (expression is LanguageCondition selectorCondition) + return Scope(Debugger(Condition(path, selectorCondition), path)); - throw new InvalidOperationException(); - } + throw new InvalidOperationException(); + } - private static LanguageExpressionOuterFn Scope(LanguageExpressionOuterFn fn) + private static LanguageExpressionOuterFn Scope(LanguageExpressionOuterFn fn) + { + return (context, o) => { - return (context, o) => - { - RunspaceContext.CurrentThread?.EnterLanguageScope(context.Source); + RunspaceContext.CurrentThread?.EnterLanguageScope(context.Source); - return fn(context, o); - }; - } + return fn(context, o); + }; + } + + private static LanguageExpressionOuterFn Condition(string path, LanguageCondition expression) + { + var info = new ExpressionInfo(path); + return (context, o) => expression.Descriptor.Fn(context, info, new object[] { expression.Property }, o); + } + + private static string Path(string path, LanguageExpression expression) + { + path = string.Concat(path, Dot, expression.Descriptor.Name); + return path; + } - private static LanguageExpressionOuterFn Condition(string path, LanguageCondition expression) + private LanguageExpressionOuterFn Operator(string path, LanguageOperator expression) + { + var inner = new List(expression.Children.Count); + for (var i = 0; i < expression.Children.Count; i++) { - var info = new ExpressionInfo(path); - return (context, o) => expression.Descriptor.Fn(context, info, new object[] { expression.Property }, o); + var childPath = string.Concat(path, OpenBracket, i, CloseBracket); + inner.Add(Expression(childPath, expression.Children[i])); } + var innerA = inner.ToArray(); + var info = new ExpressionInfo(path); - private static string Path(string path, LanguageExpression expression) + // Check for sub-selectors + if (expression.Property == null || expression.Property.Count == 0) { - path = string.Concat(path, Dot, expression.Descriptor.Name); - return path; + return (context, o) => expression.Descriptor.Fn(context, info, innerA, o); } - - private LanguageExpressionOuterFn Operator(string path, LanguageOperator expression) + else { - var inner = new List(expression.Children.Count); - for (var i = 0; i < expression.Children.Count; i++) - { - var childPath = string.Concat(path, OpenBracket, i, CloseBracket); - inner.Add(Expression(childPath, expression.Children[i])); - } - var innerA = inner.ToArray(); - var info = new ExpressionInfo(path); - - // Check for sub-selectors - if (expression.Property == null || expression.Property.Count == 0) - { - return (context, o) => expression.Descriptor.Fn(context, info, innerA, o); - } - else + var subselector = expression.Subselector != null ? Expression(string.Concat(path, DOTWHERE), expression.Subselector) : null; + return (context, o) => { - var subselector = expression.Subselector != null ? Expression(string.Concat(path, DOTWHERE), expression.Subselector) : null; - return (context, o) => - { - ObjectHelper.GetPath(context, o, Value(context, expression.Property["field"]), caseSensitive: false, out object[] items); + ObjectHelper.GetPath(context, o, Value(context, expression.Property["field"]), caseSensitive: false, out object[] items); - var quantifier = GetQuantifier(expression); - var pass = 0; + var quantifier = GetQuantifier(expression); + var pass = 0; - // If any fail, all fail - for (var i = 0; items != null && i < items.Length; i++) + // If any fail, all fail + for (var i = 0; items != null && i < items.Length; i++) + { + if (subselector == null || subselector(context, items[i]).GetValueOrDefault(true)) { - if (subselector == null || subselector(context, items[i]).GetValueOrDefault(true)) + if (!expression.Descriptor.Fn(context, info, innerA, items[i])) + { + if (quantifier == null) + return false; + } + else { - if (!expression.Descriptor.Fn(context, info, innerA, items[i])) - { - if (quantifier == null) - return false; - } - else - { - pass++; - } + pass++; } } - return quantifier == null || quantifier(pass); - }; - } + } + return quantifier == null || quantifier(pass); + }; } + } - /// - /// Returns a quantifier function if set for the expression. - /// - private static Func GetQuantifier(LanguageOperator expression) - { - if (expression.Property.TryGetLong(GREATEROREQUAL, out var q)) - return (number) => number >= q.Value; + /// + /// Returns a quantifier function if set for the expression. + /// + private static Func GetQuantifier(LanguageOperator expression) + { + if (expression.Property.TryGetLong(GREATEROREQUAL, out var q)) + return (number) => number >= q.Value; - if (expression.Property.TryGetLong(GREATER, out q)) - return (number) => number > q.Value; + if (expression.Property.TryGetLong(GREATER, out q)) + return (number) => number > q.Value; - if (expression.Property.TryGetLong(LESSOREQUAL, out q)) - return (number) => number <= q.Value; + if (expression.Property.TryGetLong(LESSOREQUAL, out q)) + return (number) => number <= q.Value; - if (expression.Property.TryGetLong(LESS, out q)) - return (number) => number < q.Value; + if (expression.Property.TryGetLong(LESS, out q)) + return (number) => number < q.Value; - if (expression.Property.TryGetLong(COUNT, out q)) - return (number) => number == q.Value; + if (expression.Property.TryGetLong(COUNT, out q)) + return (number) => number == q.Value; - return null; - } + return null; + } - [DebuggerStepThrough] - private static string Value(ExpressionContext context, object v) - { - return v as string; - } + [DebuggerStepThrough] + private static string Value(ExpressionContext context, object v) + { + return v as string; + } - private LanguageExpressionOuterFn Debugger(LanguageExpressionOuterFn expression, string path) - { - return !_Debugger ? expression : ((context, o) => DebuggerFn(context, path, expression, o)); - } + private LanguageExpressionOuterFn Debugger(LanguageExpressionOuterFn expression, string path) + { + return !_Debugger ? expression : ((context, o) => DebuggerFn(context, path, expression, o)); + } - private static bool? DebuggerFn(ExpressionContext context, string path, LanguageExpressionOuterFn expression, object o) - { - var result = expression(context, o); - var type = context.Kind == ResourceKind.Rule ? 'R' : 'S'; - context.Debug(PSRuleResources.LanguageExpressionTraceP2, type, path, result); - return result; - } + private static bool? DebuggerFn(ExpressionContext context, string path, LanguageExpressionOuterFn expression, object o) + { + var result = expression(context, o); + var type = context.Kind == ResourceKind.Rule ? 'R' : 'S'; + context.Debug(PSRuleResources.LanguageExpressionTraceP2, type, path, result); + return result; + } + + private static bool AcceptsType(string[] type) + { + if (type == null) + return true; - private static bool AcceptsType(string[] type) + var comparer = RunspaceContext.CurrentThread.LanguageScope.Binding.GetComparer(); + var targetType = RunspaceContext.CurrentThread.RuleRecord.TargetType; + for (var i = 0; i < type.Length; i++) { - if (type == null) + if (comparer.Equals(targetType, type[i])) return true; - - var comparer = RunspaceContext.CurrentThread.LanguageScope.Binding.GetComparer(); - var targetType = RunspaceContext.CurrentThread.RuleRecord.TargetType; - for (var i = 0; i < type.Length; i++) - { - if (comparer.Equals(targetType, type[i])) - return true; - } - return false; } + return false; + } + + private static bool AcceptsWith(string[] with) + { + if (with == null || with.Length == 0) + return true; - private static bool AcceptsWith(string[] with) + for (var i = 0; i < with.Length; i++) { - if (with == null || with.Length == 0) + if (RunspaceContext.CurrentThread.TrySelector(with[i])) return true; - - for (var i = 0; i < with.Length; i++) - { - if (RunspaceContext.CurrentThread.TrySelector(with[i])) - return true; - } - return false; } + return false; + } - private static bool AcceptsSubselector(ExpressionContext context, LanguageExpressionOuterFn subselector, object o) - { - return subselector == null || subselector.Invoke(context, o).GetValueOrDefault(false); - } + private static bool AcceptsSubselector(ExpressionContext context, LanguageExpressionOuterFn subselector, object o) + { + return subselector == null || subselector.Invoke(context, o).GetValueOrDefault(false); + } - private static bool AcceptsRule(string[] rule) - { - if (rule == null || rule.Length == 0) - return true; + private static bool AcceptsRule(string[] rule) + { + if (rule == null || rule.Length == 0) + return true; - var context = RunspaceContext.CurrentThread; + var context = RunspaceContext.CurrentThread; - var stringComparer = context.LanguageScope.Binding.GetComparer(); - var resourceIdComparer = ResourceIdEqualityComparer.Default; + var stringComparer = context.LanguageScope.Binding.GetComparer(); + var resourceIdComparer = ResourceIdEqualityComparer.Default; - var ruleRecord = context.RuleRecord; - var ruleName = ruleRecord.RuleName; - var ruleId = ruleRecord.RuleId; + var ruleRecord = context.RuleRecord; + var ruleName = ruleRecord.RuleName; + var ruleId = ruleRecord.RuleId; - for (var i = 0; i < rule.Length; i++) - { - if (stringComparer.Equals(ruleName, rule[i]) || resourceIdComparer.Equals(ruleId, rule[i])) - return true; - } - return false; + for (var i = 0; i < rule.Length; i++) + { + if (stringComparer.Equals(ruleName, rule[i]) || resourceIdComparer.Equals(ruleId, rule[i])) + return true; } + return false; } +} - /// - /// Expressions that can be used with selectors. - /// - internal sealed class LanguageExpressions +/// +/// Expressions that can be used with selectors. +/// +internal sealed class LanguageExpressions +{ + // Conditions + private const string EXISTS = "exists"; + private const string EQUALS = "equals"; + private const string NOTEQUALS = "notEquals"; + private const string HASDEFAULT = "hasDefault"; + private const string HASSCHEMA = "hasSchema"; + private const string HASVALUE = "hasValue"; + private const string MATCH = "match"; + private const string NOTMATCH = "notMatch"; + private const string IN = "in"; + private const string NOTIN = "notIn"; + private const string LESS = "less"; + private const string LESSOREQUALS = "lessOrEquals"; + private const string GREATER = "greater"; + private const string GREATEROREQUALS = "greaterOrEquals"; + private const string STARTSWITH = "startsWith"; + private const string NOTSTARTSWITH = "notStartsWith"; + private const string ENDSWITH = "endsWith"; + private const string NOTENDSWITH = "notEndsWith"; + private const string CONTAINS = "contains"; + private const string NOTCONTAINS = "notContains"; + private const string ISSTRING = "isString"; + private const string ISARRAY = "isArray"; + private const string ISBOOLEAN = "isBoolean"; + private const string ISDATETIME = "isDateTime"; + private const string ISINTEGER = "isInteger"; + private const string ISNUMERIC = "IsNumeric"; + private const string ISLOWER = "isLower"; + private const string ISUPPER = "isUpper"; + private const string SETOF = "setOf"; + private const string SUBSET = "subset"; + private const string COUNT = "count"; + private const string NOTCOUNT = "notCount"; + private const string VERSION = "version"; + private const string APIVERSION = "apiVersion"; + private const string WITHINPATH = "withinPath"; + private const string NOTWITHINPATH = "notWithinPath"; + private const string LIKE = "like"; + private const string NOTLIKE = "notLike"; + + // Operators + private const string IF = "if"; + private const string ANYOF = "anyOf"; + private const string ALLOF = "allOf"; + private const string NOT = "not"; + + // Properties + private const string FIELD = "field"; + private const string TYPE = "type"; + private const string NAME = "name"; + private const string CASESENSITIVE = "caseSensitive"; + private const string UNIQUE = "unique"; + private const string CONVERT = "convert"; + private const string IGNORESCHEME = "ignoreScheme"; + private const string INCLUDEPRERELEASE = "includePrerelease"; + private const string PROPERTY_SCHEMA = "$schema"; + private const string SOURCE = "source"; + private const string VALUE = "value"; + private const string SCOPE = "scope"; + + // Comparisons + private const string LESS_THAN = "<"; + private const string LESS_THAN_EQUALS = "<="; + private const string GREATER_THAN = ">="; + private const string GREATER_THAN_EQUALS = ">="; + private const string DOT = "."; + + // Define built-ins + internal static readonly ILanguageExpresssionDescriptor[] Builtin = new ILanguageExpresssionDescriptor[] { - // Conditions - private const string EXISTS = "exists"; - private const string EQUALS = "equals"; - private const string NOTEQUALS = "notEquals"; - private const string HASDEFAULT = "hasDefault"; - private const string HASSCHEMA = "hasSchema"; - private const string HASVALUE = "hasValue"; - private const string MATCH = "match"; - private const string NOTMATCH = "notMatch"; - private const string IN = "in"; - private const string NOTIN = "notIn"; - private const string LESS = "less"; - private const string LESSOREQUALS = "lessOrEquals"; - private const string GREATER = "greater"; - private const string GREATEROREQUALS = "greaterOrEquals"; - private const string STARTSWITH = "startsWith"; - private const string NOTSTARTSWITH = "notStartsWith"; - private const string ENDSWITH = "endsWith"; - private const string NOTENDSWITH = "notEndsWith"; - private const string CONTAINS = "contains"; - private const string NOTCONTAINS = "notContains"; - private const string ISSTRING = "isString"; - private const string ISARRAY = "isArray"; - private const string ISBOOLEAN = "isBoolean"; - private const string ISDATETIME = "isDateTime"; - private const string ISINTEGER = "isInteger"; - private const string ISNUMERIC = "IsNumeric"; - private const string ISLOWER = "isLower"; - private const string ISUPPER = "isUpper"; - private const string SETOF = "setOf"; - private const string SUBSET = "subset"; - private const string COUNT = "count"; - private const string NOTCOUNT = "notCount"; - private const string VERSION = "version"; - private const string APIVERSION = "apiVersion"; - private const string WITHINPATH = "withinPath"; - private const string NOTWITHINPATH = "notWithinPath"; - private const string LIKE = "like"; - private const string NOTLIKE = "notLike"; - // Operators - private const string IF = "if"; - private const string ANYOF = "anyOf"; - private const string ALLOF = "allOf"; - private const string NOT = "not"; - - // Properties - private const string FIELD = "field"; - private const string TYPE = "type"; - private const string NAME = "name"; - private const string CASESENSITIVE = "caseSensitive"; - private const string UNIQUE = "unique"; - private const string CONVERT = "convert"; - private const string IGNORESCHEME = "ignoreScheme"; - private const string INCLUDEPRERELEASE = "includePrerelease"; - private const string PROPERTY_SCHEMA = "$schema"; - private const string SOURCE = "source"; - private const string VALUE = "value"; - private const string SCOPE = "scope"; - - // Comparisons - private const string LESS_THAN = "<"; - private const string LESS_THAN_EQUALS = "<="; - private const string GREATER_THAN = ">="; - private const string GREATER_THAN_EQUALS = ">="; - private const string DOT = "."; - - // Define built-ins - internal static readonly ILanguageExpresssionDescriptor[] Builtin = new ILanguageExpresssionDescriptor[] - { - // Operators - new LanguageExpresssionDescriptor(IF, LanguageExpressionType.Operator, If), - new LanguageExpresssionDescriptor(ANYOF, LanguageExpressionType.Operator, AnyOf), - new LanguageExpresssionDescriptor(ALLOF, LanguageExpressionType.Operator, AllOf), - new LanguageExpresssionDescriptor(NOT, LanguageExpressionType.Operator, Not), - - // Conditions - new LanguageExpresssionDescriptor(EXISTS, LanguageExpressionType.Condition, Exists), - new LanguageExpresssionDescriptor(EQUALS, LanguageExpressionType.Condition, Equals), - new LanguageExpresssionDescriptor(NOTEQUALS, LanguageExpressionType.Condition, NotEquals), - new LanguageExpresssionDescriptor(HASVALUE, LanguageExpressionType.Condition, HasValue), - new LanguageExpresssionDescriptor(MATCH, LanguageExpressionType.Condition, Match), - new LanguageExpresssionDescriptor(NOTMATCH, LanguageExpressionType.Condition, NotMatch), - new LanguageExpresssionDescriptor(IN, LanguageExpressionType.Condition, In), - new LanguageExpresssionDescriptor(NOTIN, LanguageExpressionType.Condition, NotIn), - new LanguageExpresssionDescriptor(LESS, LanguageExpressionType.Condition, Less), - new LanguageExpresssionDescriptor(LESSOREQUALS, LanguageExpressionType.Condition, LessOrEquals), - new LanguageExpresssionDescriptor(GREATER, LanguageExpressionType.Condition, Greater), - new LanguageExpresssionDescriptor(GREATEROREQUALS, LanguageExpressionType.Condition, GreaterOrEquals), - new LanguageExpresssionDescriptor(STARTSWITH, LanguageExpressionType.Condition, StartsWith), - new LanguageExpresssionDescriptor(NOTSTARTSWITH, LanguageExpressionType.Condition, NotStartsWith), - new LanguageExpresssionDescriptor(ENDSWITH, LanguageExpressionType.Condition, EndsWith), - new LanguageExpresssionDescriptor(NOTENDSWITH, LanguageExpressionType.Condition, NotEndsWith), - new LanguageExpresssionDescriptor(CONTAINS, LanguageExpressionType.Condition, Contains), - new LanguageExpresssionDescriptor(NOTCONTAINS, LanguageExpressionType.Condition, NotContains), - new LanguageExpresssionDescriptor(ISSTRING, LanguageExpressionType.Condition, IsString), - new LanguageExpresssionDescriptor(ISARRAY, LanguageExpressionType.Condition, IsArray), - new LanguageExpresssionDescriptor(ISBOOLEAN, LanguageExpressionType.Condition, IsBoolean), - new LanguageExpresssionDescriptor(ISDATETIME, LanguageExpressionType.Condition, IsDateTime), - new LanguageExpresssionDescriptor(ISINTEGER, LanguageExpressionType.Condition, IsInteger), - new LanguageExpresssionDescriptor(ISNUMERIC, LanguageExpressionType.Condition, IsNumeric), - new LanguageExpresssionDescriptor(ISLOWER, LanguageExpressionType.Condition, IsLower), - new LanguageExpresssionDescriptor(ISUPPER, LanguageExpressionType.Condition, IsUpper), - new LanguageExpresssionDescriptor(SETOF, LanguageExpressionType.Condition, SetOf), - new LanguageExpresssionDescriptor(SUBSET, LanguageExpressionType.Condition, Subset), - new LanguageExpresssionDescriptor(COUNT, LanguageExpressionType.Condition, Count), - new LanguageExpresssionDescriptor(NOTCOUNT, LanguageExpressionType.Condition, NotCount), - new LanguageExpresssionDescriptor(HASSCHEMA, LanguageExpressionType.Condition, HasSchema), - new LanguageExpresssionDescriptor(VERSION, LanguageExpressionType.Condition, Version), - new LanguageExpresssionDescriptor(APIVERSION, LanguageExpressionType.Condition, APIVersion), - new LanguageExpresssionDescriptor(HASDEFAULT, LanguageExpressionType.Condition, HasDefault), - new LanguageExpresssionDescriptor(WITHINPATH, LanguageExpressionType.Condition, WithinPath), - new LanguageExpresssionDescriptor(NOTWITHINPATH, LanguageExpressionType.Condition, NotWithinPath), - new LanguageExpresssionDescriptor(LIKE, LanguageExpressionType.Condition, Like), - new LanguageExpresssionDescriptor(NOTLIKE, LanguageExpressionType.Condition, NotLike), - }; + new LanguageExpresssionDescriptor(IF, LanguageExpressionType.Operator, If), + new LanguageExpresssionDescriptor(ANYOF, LanguageExpressionType.Operator, AnyOf), + new LanguageExpresssionDescriptor(ALLOF, LanguageExpressionType.Operator, AllOf), + new LanguageExpresssionDescriptor(NOT, LanguageExpressionType.Operator, Not), - #region Operators - - internal static bool If(ExpressionContext context, ExpressionInfo info, object[] args, object o) - { - var inner = GetInner(args); - return inner.Length > 0 && (inner[0](context, o) ?? true); - } - - internal static bool AnyOf(ExpressionContext context, ExpressionInfo info, object[] args, object o) - { - var inner = GetInner(args); - for (var i = 0; i < inner.Length; i++) - { - if (inner[i](context, o) ?? false) - return true; - } - return false; - } + // Conditions + new LanguageExpresssionDescriptor(EXISTS, LanguageExpressionType.Condition, Exists), + new LanguageExpresssionDescriptor(EQUALS, LanguageExpressionType.Condition, Equals), + new LanguageExpresssionDescriptor(NOTEQUALS, LanguageExpressionType.Condition, NotEquals), + new LanguageExpresssionDescriptor(HASVALUE, LanguageExpressionType.Condition, HasValue), + new LanguageExpresssionDescriptor(MATCH, LanguageExpressionType.Condition, Match), + new LanguageExpresssionDescriptor(NOTMATCH, LanguageExpressionType.Condition, NotMatch), + new LanguageExpresssionDescriptor(IN, LanguageExpressionType.Condition, In), + new LanguageExpresssionDescriptor(NOTIN, LanguageExpressionType.Condition, NotIn), + new LanguageExpresssionDescriptor(LESS, LanguageExpressionType.Condition, Less), + new LanguageExpresssionDescriptor(LESSOREQUALS, LanguageExpressionType.Condition, LessOrEquals), + new LanguageExpresssionDescriptor(GREATER, LanguageExpressionType.Condition, Greater), + new LanguageExpresssionDescriptor(GREATEROREQUALS, LanguageExpressionType.Condition, GreaterOrEquals), + new LanguageExpresssionDescriptor(STARTSWITH, LanguageExpressionType.Condition, StartsWith), + new LanguageExpresssionDescriptor(NOTSTARTSWITH, LanguageExpressionType.Condition, NotStartsWith), + new LanguageExpresssionDescriptor(ENDSWITH, LanguageExpressionType.Condition, EndsWith), + new LanguageExpresssionDescriptor(NOTENDSWITH, LanguageExpressionType.Condition, NotEndsWith), + new LanguageExpresssionDescriptor(CONTAINS, LanguageExpressionType.Condition, Contains), + new LanguageExpresssionDescriptor(NOTCONTAINS, LanguageExpressionType.Condition, NotContains), + new LanguageExpresssionDescriptor(ISSTRING, LanguageExpressionType.Condition, IsString), + new LanguageExpresssionDescriptor(ISARRAY, LanguageExpressionType.Condition, IsArray), + new LanguageExpresssionDescriptor(ISBOOLEAN, LanguageExpressionType.Condition, IsBoolean), + new LanguageExpresssionDescriptor(ISDATETIME, LanguageExpressionType.Condition, IsDateTime), + new LanguageExpresssionDescriptor(ISINTEGER, LanguageExpressionType.Condition, IsInteger), + new LanguageExpresssionDescriptor(ISNUMERIC, LanguageExpressionType.Condition, IsNumeric), + new LanguageExpresssionDescriptor(ISLOWER, LanguageExpressionType.Condition, IsLower), + new LanguageExpresssionDescriptor(ISUPPER, LanguageExpressionType.Condition, IsUpper), + new LanguageExpresssionDescriptor(SETOF, LanguageExpressionType.Condition, SetOf), + new LanguageExpresssionDescriptor(SUBSET, LanguageExpressionType.Condition, Subset), + new LanguageExpresssionDescriptor(COUNT, LanguageExpressionType.Condition, Count), + new LanguageExpresssionDescriptor(NOTCOUNT, LanguageExpressionType.Condition, NotCount), + new LanguageExpresssionDescriptor(HASSCHEMA, LanguageExpressionType.Condition, HasSchema), + new LanguageExpresssionDescriptor(VERSION, LanguageExpressionType.Condition, Version), + new LanguageExpresssionDescriptor(APIVERSION, LanguageExpressionType.Condition, APIVersion), + new LanguageExpresssionDescriptor(HASDEFAULT, LanguageExpressionType.Condition, HasDefault), + new LanguageExpresssionDescriptor(WITHINPATH, LanguageExpressionType.Condition, WithinPath), + new LanguageExpresssionDescriptor(NOTWITHINPATH, LanguageExpressionType.Condition, NotWithinPath), + new LanguageExpresssionDescriptor(LIKE, LanguageExpressionType.Condition, Like), + new LanguageExpresssionDescriptor(NOTLIKE, LanguageExpressionType.Condition, NotLike), + }; + + #region Operators + + internal static bool If(ExpressionContext context, ExpressionInfo info, object[] args, object o) + { + var inner = GetInner(args); + return inner.Length > 0 && (inner[0](context, o) ?? true); + } - internal static bool AllOf(ExpressionContext context, ExpressionInfo info, object[] args, object o) + internal static bool AnyOf(ExpressionContext context, ExpressionInfo info, object[] args, object o) + { + var inner = GetInner(args); + for (var i = 0; i < inner.Length; i++) { - var inner = GetInner(args); - for (var i = 0; i < inner.Length; i++) - { - if (!inner[i](context, o) ?? true) - return false; - } - return true; + if (inner[i](context, o) ?? false) + return true; } + return false; + } - internal static bool Not(ExpressionContext context, ExpressionInfo info, object[] args, object o) + internal static bool AllOf(ExpressionContext context, ExpressionInfo info, object[] args, object o) + { + var inner = GetInner(args); + for (var i = 0; i < inner.Length; i++) { - var inner = GetInner(args); - return inner.Length > 0 && (!inner[0](context, o) ?? false); + if (!inner[i](context, o) ?? true) + return false; } + return true; + } - #endregion Operators + internal static bool Not(ExpressionContext context, ExpressionInfo info, object[] args, object o) + { + var inner = GetInner(args); + return inner.Length > 0 && (!inner[0](context, o) ?? false); + } - #region Conditions + #endregion Operators - internal static bool Exists(ExpressionContext context, ExpressionInfo info, object[] args, object o) - { - var properties = GetProperties(args); - if (TryPropertyBool(properties, EXISTS, out var propertyValue) && TryField(properties, out var field)) - { - context.ExpressionTrace(EXISTS, field, propertyValue); - return Condition( - context, - field, - propertyValue == ExpressionHelpers.Exists(context, o, field, caseSensitive: false), - ReasonStrings.Exists, - field - ); - } - return Invalid(context, EXISTS); - } + #region Conditions - internal static bool Equals(ExpressionContext context, ExpressionInfo info, object[] args, object o) + internal static bool Exists(ExpressionContext context, ExpressionInfo info, object[] args, object o) + { + var properties = GetProperties(args); + if (TryPropertyBool(properties, EXISTS, out var propertyValue) && TryField(properties, out var field)) { - var properties = GetProperties(args); - if (!TryPropertyAny(properties, EQUALS, out var propertyValue) || - !TryOperand(context, EQUALS, o, properties, out var operand) || - !GetConvert(properties, out var convert) || - !GetCaseSensitive(properties, out var caseSensitive)) - return Invalid(context, EQUALS); - - // int, string, bool + context.ExpressionTrace(EXISTS, field, propertyValue); return Condition( context, - operand, - ExpressionHelpers.Equal(Value(context, propertyValue), Value(context, operand), caseSensitive, convertExpected: true, convertActual: convert), - ReasonStrings.Assert_IsSetTo, - operand.Value + field, + propertyValue == ExpressionHelpers.Exists(context, o, field, caseSensitive: false), + ReasonStrings.Exists, + field ); } + return Invalid(context, EXISTS); + } - internal static bool NotEquals(ExpressionContext context, ExpressionInfo info, object[] args, object o) - { - var properties = GetProperties(args); - if (!TryPropertyAny(properties, NOTEQUALS, out var propertyValue)) - return Invalid(context, NOTEQUALS); + internal static bool Equals(ExpressionContext context, ExpressionInfo info, object[] args, object o) + { + var properties = GetProperties(args); + if (!TryPropertyAny(properties, EQUALS, out var propertyValue) || + !TryOperand(context, EQUALS, o, properties, out var operand) || + !GetConvert(properties, out var convert) || + !GetCaseSensitive(properties, out var caseSensitive)) + return Invalid(context, EQUALS); + + // int, string, bool + return Condition( + context, + operand, + ExpressionHelpers.Equal(Value(context, propertyValue), Value(context, operand), caseSensitive, convertExpected: true, convertActual: convert), + ReasonStrings.Assert_IsSetTo, + operand.Value + ); + } + + internal static bool NotEquals(ExpressionContext context, ExpressionInfo info, object[] args, object o) + { + var properties = GetProperties(args); + if (!TryPropertyAny(properties, NOTEQUALS, out var propertyValue)) + return Invalid(context, NOTEQUALS); + + if (TryFieldNotExists(context, o, properties)) + return PassPathNotFound(context, NOTEQUALS); + + if (!TryOperand(context, NOTEQUALS, o, properties, out var operand) || + !GetConvert(properties, out var convert) || + !GetCaseSensitive(properties, out var caseSensitive)) + return Invalid(context, NOTEQUALS); + + // int, string, bool + return Condition( + context, + operand, + !ExpressionHelpers.Equal(Value(context, propertyValue), Value(context, operand), caseSensitive, convertExpected: true, convertActual: convert), + ReasonStrings.Assert_IsSetTo, + operand.Value + ); + } - if (TryFieldNotExists(context, o, properties)) - return PassPathNotFound(context, NOTEQUALS); + internal static bool HasDefault(ExpressionContext context, ExpressionInfo info, object[] args, object o) + { + var properties = GetProperties(args); + if (!TryPropertyAny(properties, HASDEFAULT, out var propertyValue)) + return Invalid(context, HASDEFAULT); + + GetCaseSensitive(properties, out var caseSensitive); + if (TryFieldNotExists(context, o, properties)) + return PassPathNotFound(context, HASDEFAULT); + + if (!TryOperand(context, HASDEFAULT, o, properties, out var operand)) + return Invalid(context, HASDEFAULT); + + return Condition( + context, + operand, + ExpressionHelpers.Equal(propertyValue, operand.Value, caseSensitive, convertExpected: true), + ReasonStrings.Assert_IsSetTo, + operand.Value + ); + } + + internal static bool HasValue(ExpressionContext context, ExpressionInfo info, object[] args, object o) + { + var properties = GetProperties(args); + if (!TryPropertyBool(properties, HASVALUE, out var propertyValue)) + return Invalid(context, HASVALUE); + + if (TryFieldNotExists(context, o, properties) && !propertyValue.Value) + return PassPathNotFound(context, HASVALUE); + + if (!TryOperand(context, HASVALUE, o, properties, out var operand)) + return Invalid(context, HASVALUE); + + return Condition( + context, + operand, + !propertyValue.Value == ExpressionHelpers.NullOrEmpty(operand.Value), + ReasonStrings.Assert_IsSetTo, + operand.Value + ); + } - if (!TryOperand(context, NOTEQUALS, o, properties, out var operand) || - !GetConvert(properties, out var convert) || - !GetCaseSensitive(properties, out var caseSensitive)) - return Invalid(context, NOTEQUALS); + internal static bool Match(ExpressionContext context, ExpressionInfo info, object[] args, object o) + { + var properties = GetProperties(args); + if (!TryPropertyAny(properties, MATCH, out var propertyValue) || + !TryOperand(context, MATCH, o, properties, out var operand) || + !GetCaseSensitive(properties, out var caseSensitive)) + return Invalid(context, MATCH); - // int, string, bool - return Condition( + return Condition( context, operand, - !ExpressionHelpers.Equal(Value(context, propertyValue), Value(context, operand), caseSensitive, convertExpected: true, convertActual: convert), - ReasonStrings.Assert_IsSetTo, - operand.Value + ExpressionHelpers.Match(propertyValue, operand.Value, caseSensitive), + ReasonStrings.Assert_DoesNotMatch, + operand.Value, + propertyValue ); - } - - internal static bool HasDefault(ExpressionContext context, ExpressionInfo info, object[] args, object o) - { - var properties = GetProperties(args); - if (!TryPropertyAny(properties, HASDEFAULT, out var propertyValue)) - return Invalid(context, HASDEFAULT); - - GetCaseSensitive(properties, out var caseSensitive); - if (TryFieldNotExists(context, o, properties)) - return PassPathNotFound(context, HASDEFAULT); + } - if (!TryOperand(context, HASDEFAULT, o, properties, out var operand)) - return Invalid(context, HASDEFAULT); + internal static bool NotMatch(ExpressionContext context, ExpressionInfo info, object[] args, object o) + { + var properties = GetProperties(args); + if (!TryPropertyAny(properties, NOTMATCH, out var propertyValue)) + return Invalid(context, NOTMATCH); + + if (TryFieldNotExists(context, o, properties)) + return PassPathNotFound(context, NOTMATCH); + + if (!TryOperand(context, NOTMATCH, o, properties, out var operand) || + !GetCaseSensitive(properties, out var caseSensitive)) + return Invalid(context, NOTMATCH); + + return Condition( + context, + operand, + !ExpressionHelpers.Match(propertyValue, operand.Value, caseSensitive), + ReasonStrings.Assert_Matches, + operand.Value, + propertyValue + ); + } - return Condition( - context, - operand, - ExpressionHelpers.Equal(propertyValue, operand.Value, caseSensitive, convertExpected: true), - ReasonStrings.Assert_IsSetTo, - operand.Value - ); - } + internal static bool In(ExpressionContext context, ExpressionInfo info, object[] args, object o) + { + var properties = GetProperties(args); + if (!TryPropertyArray(properties, IN, out var propertyValue) || !TryOperand(context, IN, o, properties, out var operand)) + return Invalid(context, IN); - internal static bool HasValue(ExpressionContext context, ExpressionInfo info, object[] args, object o) - { - var properties = GetProperties(args); - if (!TryPropertyBool(properties, HASVALUE, out var propertyValue)) - return Invalid(context, HASVALUE); + for (var i = 0; propertyValue != null && i < propertyValue.Length; i++) + if (ExpressionHelpers.AnyValue(operand.Value, propertyValue.GetValue(i), caseSensitive: false, out _)) + return Pass(); - if (TryFieldNotExists(context, o, properties) && !propertyValue.Value) - return PassPathNotFound(context, HASVALUE); + return Fail( + context, + operand, + ReasonStrings.Assert_NotInSet, + operand.Value, + StringJoin(propertyValue) + ); + } - if (!TryOperand(context, HASVALUE, o, properties, out var operand)) - return Invalid(context, HASVALUE); + internal static bool NotIn(ExpressionContext context, ExpressionInfo info, object[] args, object o) + { + var properties = GetProperties(args); + if (!TryPropertyArray(properties, NOTIN, out var propertyValue)) + return Invalid(context, NOTIN); - return Condition( - context, - operand, - !propertyValue.Value == ExpressionHelpers.NullOrEmpty(operand.Value), - ReasonStrings.Assert_IsSetTo, - operand.Value - ); - } + if (TryFieldNotExists(context, o, properties)) + return PassPathNotFound(context, NOTIN); - internal static bool Match(ExpressionContext context, ExpressionInfo info, object[] args, object o) - { - var properties = GetProperties(args); - if (!TryPropertyAny(properties, MATCH, out var propertyValue) || - !TryOperand(context, MATCH, o, properties, out var operand) || - !GetCaseSensitive(properties, out var caseSensitive)) - return Invalid(context, MATCH); + if (!TryOperand(context, NOTIN, o, properties, out var operand)) + return Invalid(context, NOTIN); - return Condition( + for (var i = 0; propertyValue != null && i < propertyValue.Length; i++) + if (ExpressionHelpers.AnyValue(operand.Value, propertyValue.GetValue(i), caseSensitive: false, out _)) + return Fail( context, operand, - ExpressionHelpers.Match(propertyValue, operand.Value, caseSensitive), - ReasonStrings.Assert_DoesNotMatch, - operand.Value, - propertyValue + ReasonStrings.Assert_IsSetTo, + operand.Value ); - } - - internal static bool NotMatch(ExpressionContext context, ExpressionInfo info, object[] args, object o) - { - var properties = GetProperties(args); - if (!TryPropertyAny(properties, NOTMATCH, out var propertyValue)) - return Invalid(context, NOTMATCH); - - if (TryFieldNotExists(context, o, properties)) - return PassPathNotFound(context, NOTMATCH); - - if (!TryOperand(context, NOTMATCH, o, properties, out var operand) || - !GetCaseSensitive(properties, out var caseSensitive)) - return Invalid(context, NOTMATCH); - - return Condition( - context, - operand, - !ExpressionHelpers.Match(propertyValue, operand.Value, caseSensitive), - ReasonStrings.Assert_Matches, - operand.Value, - propertyValue - ); - } - - internal static bool In(ExpressionContext context, ExpressionInfo info, object[] args, object o) - { - var properties = GetProperties(args); - if (!TryPropertyArray(properties, IN, out var propertyValue) || !TryOperand(context, IN, o, properties, out var operand)) - return Invalid(context, IN); - - for (var i = 0; propertyValue != null && i < propertyValue.Length; i++) - if (ExpressionHelpers.AnyValue(operand.Value, propertyValue.GetValue(i), caseSensitive: false, out _)) - return Pass(); - return Fail( - context, - operand, - ReasonStrings.Assert_NotInSet, - operand.Value, - StringJoin(propertyValue) - ); - } + return Pass(); + } - internal static bool NotIn(ExpressionContext context, ExpressionInfo info, object[] args, object o) + internal static bool SetOf(ExpressionContext context, ExpressionInfo info, object[] args, object o) + { + var properties = GetProperties(args); + if (TryPropertyArray(properties, SETOF, out var expectedValue) && + TryField(properties, out var field) && + GetCaseSensitive(properties, out var caseSensitive)) { - var properties = GetProperties(args); - if (!TryPropertyArray(properties, NOTIN, out var propertyValue)) - return Invalid(context, NOTIN); - - if (TryFieldNotExists(context, o, properties)) - return PassPathNotFound(context, NOTIN); + context.ExpressionTrace(SETOF, field, expectedValue); + if (!ObjectHelper.GetPath(context, o, field, caseSensitive: false, out object actualValue)) + return NotHasField(context, field); - if (!TryOperand(context, NOTIN, o, properties, out var operand)) - return Invalid(context, NOTIN); - - for (var i = 0; propertyValue != null && i < propertyValue.Length; i++) - if (ExpressionHelpers.AnyValue(operand.Value, propertyValue.GetValue(i), caseSensitive: false, out _)) - return Fail( - context, - operand, - ReasonStrings.Assert_IsSetTo, - operand.Value - ); + if (!ExpressionHelpers.TryEnumerableLength(actualValue, out var count)) + return Fail(context, field, ReasonStrings.NotEnumerable, field); - return Pass(); - } + if (count != expectedValue.Length) + return Fail(context, field, ReasonStrings.Count, field, count, expectedValue.Length); - internal static bool SetOf(ExpressionContext context, ExpressionInfo info, object[] args, object o) - { - var properties = GetProperties(args); - if (TryPropertyArray(properties, SETOF, out var expectedValue) && - TryField(properties, out var field) && - GetCaseSensitive(properties, out var caseSensitive)) + for (var i = 0; expectedValue != null && i < expectedValue.Length; i++) { - context.ExpressionTrace(SETOF, field, expectedValue); - if (!ObjectHelper.GetPath(context, o, field, caseSensitive: false, out object actualValue)) - return NotHasField(context, field); - - if (!ExpressionHelpers.TryEnumerableLength(actualValue, out var count)) - return Fail(context, field, ReasonStrings.NotEnumerable, field); - - if (count != expectedValue.Length) - return Fail(context, field, ReasonStrings.Count, field, count, expectedValue.Length); - - for (var i = 0; expectedValue != null && i < expectedValue.Length; i++) - { - if (!ExpressionHelpers.AnyValue(actualValue, expectedValue.GetValue(i), caseSensitive, out _)) - return Fail(context, field, ReasonStrings.Subset, field, expectedValue.GetValue(i)); - } - return Pass(); + if (!ExpressionHelpers.AnyValue(actualValue, expectedValue.GetValue(i), caseSensitive, out _)) + return Fail(context, field, ReasonStrings.Subset, field, expectedValue.GetValue(i)); } - return Invalid(context, SETOF); + return Pass(); } + return Invalid(context, SETOF); + } - internal static bool Subset(ExpressionContext context, ExpressionInfo info, object[] args, object o) + internal static bool Subset(ExpressionContext context, ExpressionInfo info, object[] args, object o) + { + var properties = GetProperties(args); + if (TryPropertyArray(properties, SUBSET, out var expectedValue) && TryField(properties, out var field) && + GetCaseSensitive(properties, out var caseSensitive) && GetUnique(properties, out var unique)) { - var properties = GetProperties(args); - if (TryPropertyArray(properties, SUBSET, out var expectedValue) && TryField(properties, out var field) && - GetCaseSensitive(properties, out var caseSensitive) && GetUnique(properties, out var unique)) - { - context.ExpressionTrace(SUBSET, field, expectedValue); - if (!ObjectHelper.GetPath(context, o, field, caseSensitive: false, out object actualValue)) - return NotHasField(context, field); + context.ExpressionTrace(SUBSET, field, expectedValue); + if (!ObjectHelper.GetPath(context, o, field, caseSensitive: false, out object actualValue)) + return NotHasField(context, field); - if (!ExpressionHelpers.TryEnumerableLength(actualValue, out _)) - return Fail(context, field, ReasonStrings.NotEnumerable, field); + if (!ExpressionHelpers.TryEnumerableLength(actualValue, out _)) + return Fail(context, field, ReasonStrings.NotEnumerable, field); - for (var i = 0; expectedValue != null && i < expectedValue.Length; i++) - { - if (!ExpressionHelpers.CountValue(actualValue, expectedValue.GetValue(i), caseSensitive, out var count) || (count > 1 && unique)) - return count == 0 - ? Fail(context, field, ReasonStrings.Subset, field, expectedValue.GetValue(i)) - : Fail(context, field, ReasonStrings.SubsetDuplicate, field, expectedValue.GetValue(i)); - } - return Pass(); + for (var i = 0; expectedValue != null && i < expectedValue.Length; i++) + { + if (!ExpressionHelpers.CountValue(actualValue, expectedValue.GetValue(i), caseSensitive, out var count) || (count > 1 && unique)) + return count == 0 + ? Fail(context, field, ReasonStrings.Subset, field, expectedValue.GetValue(i)) + : Fail(context, field, ReasonStrings.SubsetDuplicate, field, expectedValue.GetValue(i)); } - return Invalid(context, SUBSET); + return Pass(); } + return Invalid(context, SUBSET); + } - internal static bool Count(ExpressionContext context, ExpressionInfo info, object[] args, object o) - { - var properties = GetProperties(args); - if (!TryPropertyLong(context, properties, COUNT, out var expectedValue) || - !TryOperand(context, COUNT, o, properties, out var operand)) - return Invalid(context, COUNT); - - var operandValue = Value(context, operand); - if (operandValue == null) - return Fail(context, operand, ReasonStrings.Assert_IsNull); - - // int, string, bool - return Condition( - context, - operand, - ExpressionHelpers.TryEnumerableLength(operandValue, value: out var actualValue) && actualValue == expectedValue, - ReasonStrings.Assert_Count, - actualValue, - expectedValue - ); - } + internal static bool Count(ExpressionContext context, ExpressionInfo info, object[] args, object o) + { + var properties = GetProperties(args); + if (!TryPropertyLong(context, properties, COUNT, out var expectedValue) || + !TryOperand(context, COUNT, o, properties, out var operand)) + return Invalid(context, COUNT); + + var operandValue = Value(context, operand); + if (operandValue == null) + return Fail(context, operand, ReasonStrings.Assert_IsNull); + + // int, string, bool + return Condition( + context, + operand, + ExpressionHelpers.TryEnumerableLength(operandValue, value: out var actualValue) && actualValue == expectedValue, + ReasonStrings.Assert_Count, + actualValue, + expectedValue + ); + } - internal static bool NotCount(ExpressionContext context, ExpressionInfo info, object[] args, object o) - { - var properties = GetProperties(args); - if (!TryPropertyLong(context, properties, NOTCOUNT, out var expectedValue) || - !TryOperand(context, NOTCOUNT, o, properties, out var operand)) - return Invalid(context, NOTCOUNT); + internal static bool NotCount(ExpressionContext context, ExpressionInfo info, object[] args, object o) + { + var properties = GetProperties(args); + if (!TryPropertyLong(context, properties, NOTCOUNT, out var expectedValue) || + !TryOperand(context, NOTCOUNT, o, properties, out var operand)) + return Invalid(context, NOTCOUNT); + + var operandValue = Value(context, operand); + if (operandValue == null) + return Fail(context, operand, ReasonStrings.Assert_IsNull); + + // int, string, bool + return Condition( + context, + operand, + ExpressionHelpers.TryEnumerableLength(operandValue, value: out var actualValue) && actualValue != expectedValue, + ReasonStrings.Assert_NotCount, + actualValue + ); + } - var operandValue = Value(context, operand); - if (operandValue == null) - return Fail(context, operand, ReasonStrings.Assert_IsNull); + internal static bool Less(ExpressionContext context, ExpressionInfo info, object[] args, object o) + { + var properties = GetProperties(args); + if (!TryPropertyLong(context, properties, LESS, out var propertyValue) || + !TryOperand(context, LESS, o, properties, out var operand) || + !GetConvert(properties, out var convert)) + return Invalid(context, LESS); - // int, string, bool + if (operand.Value == null) return Condition( context, operand, - ExpressionHelpers.TryEnumerableLength(operandValue, value: out var actualValue) && actualValue != expectedValue, - ReasonStrings.Assert_NotCount, - actualValue + 0 < propertyValue, + ReasonStrings.Assert_IsNullOrEmpty ); - } - - internal static bool Less(ExpressionContext context, ExpressionInfo info, object[] args, object o) - { - var properties = GetProperties(args); - if (!TryPropertyLong(context, properties, LESS, out var propertyValue) || - !TryOperand(context, LESS, o, properties, out var operand) || - !GetConvert(properties, out var convert)) - return Invalid(context, LESS); - if (operand.Value == null) - return Condition( - context, - operand, - 0 < propertyValue, - ReasonStrings.Assert_IsNullOrEmpty - ); + var operandValue = Value(context, operand); + if (!ExpressionHelpers.CompareNumeric( + operandValue, + propertyValue, + convert, + compare: out var compare, + value: out _)) + return Invalid(context, LESS); + + // int, string, bool + return Condition( + context, + operand, + compare < 0, + ReasonStrings.Assert_NotComparedTo, + operandValue, + LESS_THAN, + propertyValue + ); + } - var operandValue = Value(context, operand); - if (!ExpressionHelpers.CompareNumeric( - operandValue, - propertyValue, - convert, - compare: out var compare, - value: out _)) - return Invalid(context, LESS); + internal static bool LessOrEquals(ExpressionContext context, ExpressionInfo info, object[] args, object o) + { + var properties = GetProperties(args); + if (!TryPropertyLong(context, properties, LESSOREQUALS, out var propertyValue) || + !TryOperand(context, LESSOREQUALS, o, properties, out var operand) || + !GetConvert(properties, out var convert)) + return Invalid(context, LESSOREQUALS); - // int, string, bool + if (operand.Value == null) return Condition( context, operand, - compare < 0, - ReasonStrings.Assert_NotComparedTo, - operandValue, - LESS_THAN, - propertyValue + 0 <= propertyValue, + ReasonStrings.Assert_IsNullOrEmpty ); - } - internal static bool LessOrEquals(ExpressionContext context, ExpressionInfo info, object[] args, object o) - { - var properties = GetProperties(args); - if (!TryPropertyLong(context, properties, LESSOREQUALS, out var propertyValue) || - !TryOperand(context, LESSOREQUALS, o, properties, out var operand) || - !GetConvert(properties, out var convert)) - return Invalid(context, LESSOREQUALS); - - if (operand.Value == null) - return Condition( - context, - operand, - 0 <= propertyValue, - ReasonStrings.Assert_IsNullOrEmpty - ); + var operandValue = Value(context, operand); + if (!ExpressionHelpers.CompareNumeric( + operandValue, + propertyValue, + convert, + compare: out var compare, + value: out _)) + return Invalid(context, LESSOREQUALS); + + // int, string, bool + return Condition( + context, + operand, + compare <= 0, + ReasonStrings.Assert_NotComparedTo, + operandValue, + LESS_THAN_EQUALS, + propertyValue + ); + } - var operandValue = Value(context, operand); - if (!ExpressionHelpers.CompareNumeric( - operandValue, - propertyValue, - convert, - compare: out var compare, - value: out _)) - return Invalid(context, LESSOREQUALS); + internal static bool Greater(ExpressionContext context, ExpressionInfo info, object[] args, object o) + { + var properties = GetProperties(args); + if (!TryPropertyLong(context, properties, GREATER, out var propertyValue) || + !TryOperand(context, GREATER, o, properties, out var operand) || + !GetConvert(properties, out var convert)) + return Invalid(context, GREATER); - // int, string, bool + if (operand.Value == null) return Condition( context, operand, - compare <= 0, - ReasonStrings.Assert_NotComparedTo, - operandValue, - LESS_THAN_EQUALS, - propertyValue + 0 > propertyValue, + ReasonStrings.Assert_IsNullOrEmpty ); - } - - internal static bool Greater(ExpressionContext context, ExpressionInfo info, object[] args, object o) - { - var properties = GetProperties(args); - if (!TryPropertyLong(context, properties, GREATER, out var propertyValue) || - !TryOperand(context, GREATER, o, properties, out var operand) || - !GetConvert(properties, out var convert)) - return Invalid(context, GREATER); - if (operand.Value == null) - return Condition( - context, - operand, - 0 > propertyValue, - ReasonStrings.Assert_IsNullOrEmpty - ); + var operandValue = Value(context, operand); + if (!ExpressionHelpers.CompareNumeric( + operandValue, + propertyValue, + convert, + compare: out var compare, + value: out _)) + return Invalid(context, GREATER); + + // int, string, bool + return Condition( + context, + operand, + compare > 0, + ReasonStrings.Assert_NotComparedTo, + operandValue, + GREATER_THAN, + propertyValue + ); + } - var operandValue = Value(context, operand); - if (!ExpressionHelpers.CompareNumeric( - operandValue, - propertyValue, - convert, - compare: out var compare, - value: out _)) - return Invalid(context, GREATER); + internal static bool GreaterOrEquals(ExpressionContext context, ExpressionInfo info, object[] args, object o) + { + var properties = GetProperties(args); + if (!TryPropertyLong(context, properties, GREATEROREQUALS, out var propertyValue) || + !TryOperand(context, GREATEROREQUALS, o, properties, out var operand) || + !GetConvert(properties, out var convert)) + return Invalid(context, GREATEROREQUALS); - // int, string, bool + if (operand.Value == null) return Condition( context, operand, - compare > 0, - ReasonStrings.Assert_NotComparedTo, - operandValue, - GREATER_THAN, - propertyValue + 0 >= propertyValue, + ReasonStrings.Assert_IsNullOrEmpty ); - } - internal static bool GreaterOrEquals(ExpressionContext context, ExpressionInfo info, object[] args, object o) - { - var properties = GetProperties(args); - if (!TryPropertyLong(context, properties, GREATEROREQUALS, out var propertyValue) || - !TryOperand(context, GREATEROREQUALS, o, properties, out var operand) || - !GetConvert(properties, out var convert)) - return Invalid(context, GREATEROREQUALS); - - if (operand.Value == null) - return Condition( - context, - operand, - 0 >= propertyValue, - ReasonStrings.Assert_IsNullOrEmpty - ); + var operandValue = Value(context, operand); + if (!ExpressionHelpers.CompareNumeric( + operandValue, + propertyValue, + convert, + compare: out var compare, + value: out _)) + return Invalid(context, GREATEROREQUALS); + + // int, string, bool + return Condition( + context, + operand, + compare >= 0, + ReasonStrings.Assert_NotComparedTo, + operandValue, + GREATER_THAN_EQUALS, + propertyValue + ); + } - var operandValue = Value(context, operand); - if (!ExpressionHelpers.CompareNumeric( - operandValue, - propertyValue, - convert, - compare: out var compare, - value: out _)) - return Invalid(context, GREATEROREQUALS); + internal static bool StartsWith(ExpressionContext context, ExpressionInfo info, object[] args, object o) + { + var properties = GetProperties(args); + if (TryPropertyStringArray(properties, STARTSWITH, out var propertyValue) && + TryOperand(context, STARTSWITH, o, properties, out var operand) && + GetConvert(properties, out var convert) && + GetCaseSensitive(properties, out var caseSensitive)) + { + context.ExpressionTrace(STARTSWITH, operand.Value, propertyValue); + if (!ExpressionHelpers.TryStringOrArray(operand.Value, convert, out var value)) + return NotString(context, operand); - // int, string, bool - return Condition( + for (var i_propertyValue = 0; propertyValue != null && i_propertyValue < propertyValue.Length; i_propertyValue++) + { + for (var i_value = 0; i_value < value.Length; i_value++) + { + if (ExpressionHelpers.StartsWith(value[i_value], propertyValue[i_propertyValue], caseSensitive)) + return Pass(); + } + } + return Fail( context, operand, - compare >= 0, - ReasonStrings.Assert_NotComparedTo, - operandValue, - GREATER_THAN_EQUALS, - propertyValue + ReasonStrings.Assert_NotStartsWith, + value, + StringJoin(propertyValue) ); } + return Invalid(context, STARTSWITH); + } - internal static bool StartsWith(ExpressionContext context, ExpressionInfo info, object[] args, object o) - { - var properties = GetProperties(args); - if (TryPropertyStringArray(properties, STARTSWITH, out var propertyValue) && - TryOperand(context, STARTSWITH, o, properties, out var operand) && - GetConvert(properties, out var convert) && - GetCaseSensitive(properties, out var caseSensitive)) + internal static bool NotStartsWith(ExpressionContext context, ExpressionInfo info, object[] args, object o) + { + var properties = GetProperties(args); + if (TryPropertyStringArray(properties, NOTSTARTSWITH, out var propertyValue) && + TryOperand(context, NOTSTARTSWITH, o, properties, out var operand) && + GetConvert(properties, out var convert) && + GetCaseSensitive(properties, out var caseSensitive)) + { + context.ExpressionTrace(NOTSTARTSWITH, operand.Value, propertyValue); + if (ExpressionHelpers.TryStringOrArray(operand.Value, convert, out var value)) { - context.ExpressionTrace(STARTSWITH, operand.Value, propertyValue); - if (!ExpressionHelpers.TryStringOrArray(operand.Value, convert, out var value)) - return NotString(context, operand); - for (var i_propertyValue = 0; propertyValue != null && i_propertyValue < propertyValue.Length; i_propertyValue++) { for (var i_value = 0; i_value < value.Length; i_value++) { if (ExpressionHelpers.StartsWith(value[i_value], propertyValue[i_propertyValue], caseSensitive)) - return Pass(); + return Fail( + context, + operand, + ReasonStrings.Assert_StartsWith, + value[i_value], + propertyValue[i_propertyValue] + ); } } - return Fail( - context, - operand, - ReasonStrings.Assert_NotStartsWith, - value, - StringJoin(propertyValue) - ); } - return Invalid(context, STARTSWITH); + return Pass(); } + return Invalid(context, NOTSTARTSWITH); + } - internal static bool NotStartsWith(ExpressionContext context, ExpressionInfo info, object[] args, object o) + internal static bool EndsWith(ExpressionContext context, ExpressionInfo info, object[] args, object o) + { + var properties = GetProperties(args); + if (TryPropertyStringArray(properties, ENDSWITH, out var propertyValue) && + TryOperand(context, ENDSWITH, o, properties, out var operand) && + GetConvert(properties, out var convert) && + GetCaseSensitive(properties, out var caseSensitive)) { - var properties = GetProperties(args); - if (TryPropertyStringArray(properties, NOTSTARTSWITH, out var propertyValue) && - TryOperand(context, NOTSTARTSWITH, o, properties, out var operand) && - GetConvert(properties, out var convert) && - GetCaseSensitive(properties, out var caseSensitive)) + context.ExpressionTrace(ENDSWITH, operand.Value, propertyValue); + if (!ExpressionHelpers.TryStringOrArray(operand.Value, convert, out var value)) + return NotString(context, operand); + + for (var i_propertyValue = 0; propertyValue != null && i_propertyValue < propertyValue.Length; i_propertyValue++) { - context.ExpressionTrace(NOTSTARTSWITH, operand.Value, propertyValue); - if (ExpressionHelpers.TryStringOrArray(operand.Value, convert, out var value)) + for (var i_value = 0; i_value < value.Length; i_value++) { - for (var i_propertyValue = 0; propertyValue != null && i_propertyValue < propertyValue.Length; i_propertyValue++) - { - for (var i_value = 0; i_value < value.Length; i_value++) - { - if (ExpressionHelpers.StartsWith(value[i_value], propertyValue[i_propertyValue], caseSensitive)) - return Fail( - context, - operand, - ReasonStrings.Assert_StartsWith, - value[i_value], - propertyValue[i_propertyValue] - ); - } - } + if (ExpressionHelpers.EndsWith(value[i_value], propertyValue[i_propertyValue], caseSensitive)) + return Pass(); } - return Pass(); } - return Invalid(context, NOTSTARTSWITH); + return Fail( + context, + operand, + ReasonStrings.Assert_NotEndsWith, + value, + StringJoin(propertyValue) + ); } + return Invalid(context, ENDSWITH); + } - internal static bool EndsWith(ExpressionContext context, ExpressionInfo info, object[] args, object o) - { - var properties = GetProperties(args); - if (TryPropertyStringArray(properties, ENDSWITH, out var propertyValue) && - TryOperand(context, ENDSWITH, o, properties, out var operand) && - GetConvert(properties, out var convert) && - GetCaseSensitive(properties, out var caseSensitive)) + internal static bool NotEndsWith(ExpressionContext context, ExpressionInfo info, object[] args, object o) + { + var properties = GetProperties(args); + if (TryPropertyStringArray(properties, NOTENDSWITH, out var propertyValue) && + TryOperand(context, NOTENDSWITH, o, properties, out var operand) && + GetConvert(properties, out var convert) && + GetCaseSensitive(properties, out var caseSensitive)) + { + context.ExpressionTrace(NOTENDSWITH, operand.Value, propertyValue); + if (ExpressionHelpers.TryStringOrArray(operand.Value, convert, out var value)) { - context.ExpressionTrace(ENDSWITH, operand.Value, propertyValue); - if (!ExpressionHelpers.TryStringOrArray(operand.Value, convert, out var value)) - return NotString(context, operand); - for (var i_propertyValue = 0; propertyValue != null && i_propertyValue < propertyValue.Length; i_propertyValue++) { for (var i_value = 0; i_value < value.Length; i_value++) { if (ExpressionHelpers.EndsWith(value[i_value], propertyValue[i_propertyValue], caseSensitive)) - return Pass(); + return Fail( + context, + operand, + ReasonStrings.Assert_EndsWith, + value[i_value], + propertyValue[i_propertyValue] + ); } } - return Fail( - context, - operand, - ReasonStrings.Assert_NotEndsWith, - value, - StringJoin(propertyValue) - ); } - return Invalid(context, ENDSWITH); + return Pass(); } + return Invalid(context, NOTENDSWITH); + } - internal static bool NotEndsWith(ExpressionContext context, ExpressionInfo info, object[] args, object o) + internal static bool Contains(ExpressionContext context, ExpressionInfo info, object[] args, object o) + { + var properties = GetProperties(args); + if (TryPropertyStringArray(properties, CONTAINS, out var propertyValue) && + TryOperand(context, CONTAINS, o, properties, out var operand) && + GetConvert(properties, out var convert) && + GetCaseSensitive(properties, out var caseSensitive)) { - var properties = GetProperties(args); - if (TryPropertyStringArray(properties, NOTENDSWITH, out var propertyValue) && - TryOperand(context, NOTENDSWITH, o, properties, out var operand) && - GetConvert(properties, out var convert) && - GetCaseSensitive(properties, out var caseSensitive)) + context.ExpressionTrace(CONTAINS, operand.Value, propertyValue); + if (!ExpressionHelpers.TryStringOrArray(operand.Value, convert, out var value)) + return NotString(context, operand); + + for (var i_propertyValue = 0; propertyValue != null && i_propertyValue < propertyValue.Length; i_propertyValue++) { - context.ExpressionTrace(NOTENDSWITH, operand.Value, propertyValue); - if (ExpressionHelpers.TryStringOrArray(operand.Value, convert, out var value)) + for (var i_value = 0; i_value < value.Length; i_value++) { - for (var i_propertyValue = 0; propertyValue != null && i_propertyValue < propertyValue.Length; i_propertyValue++) - { - for (var i_value = 0; i_value < value.Length; i_value++) - { - if (ExpressionHelpers.EndsWith(value[i_value], propertyValue[i_propertyValue], caseSensitive)) - return Fail( - context, - operand, - ReasonStrings.Assert_EndsWith, - value[i_value], - propertyValue[i_propertyValue] - ); - } - } + if (ExpressionHelpers.Contains(value[i_value], propertyValue[i_propertyValue], caseSensitive)) + return Pass(); } - return Pass(); } - return Invalid(context, NOTENDSWITH); + return Fail( + context, + operand, + ReasonStrings.Assert_NotContains, + value, + StringJoin(propertyValue) + ); } + return Invalid(context, CONTAINS); + } - internal static bool Contains(ExpressionContext context, ExpressionInfo info, object[] args, object o) - { - var properties = GetProperties(args); - if (TryPropertyStringArray(properties, CONTAINS, out var propertyValue) && - TryOperand(context, CONTAINS, o, properties, out var operand) && - GetConvert(properties, out var convert) && - GetCaseSensitive(properties, out var caseSensitive)) + internal static bool NotContains(ExpressionContext context, ExpressionInfo info, object[] args, object o) + { + var properties = GetProperties(args); + if (TryPropertyStringArray(properties, NOTCONTAINS, out var propertyValue) && + TryOperand(context, NOTCONTAINS, o, properties, out var operand) && + GetConvert(properties, out var convert) && + GetCaseSensitive(properties, out var caseSensitive)) + { + context.ExpressionTrace(NOTCONTAINS, operand.Value, propertyValue); + if (ExpressionHelpers.TryStringOrArray(operand.Value, convert, out var value)) { - context.ExpressionTrace(CONTAINS, operand.Value, propertyValue); - if (!ExpressionHelpers.TryStringOrArray(operand.Value, convert, out var value)) - return NotString(context, operand); - for (var i_propertyValue = 0; propertyValue != null && i_propertyValue < propertyValue.Length; i_propertyValue++) { for (var i_value = 0; i_value < value.Length; i_value++) { if (ExpressionHelpers.Contains(value[i_value], propertyValue[i_propertyValue], caseSensitive)) - return Pass(); + return Fail( + context, + operand, + ReasonStrings.Assert_Contains, + value[i_value], + propertyValue[i_propertyValue] + ); } } - return Fail( - context, - operand, - ReasonStrings.Assert_NotContains, - value, - StringJoin(propertyValue) - ); } - return Invalid(context, CONTAINS); + return Pass(); } + return Invalid(context, NOTCONTAINS); + } - internal static bool NotContains(ExpressionContext context, ExpressionInfo info, object[] args, object o) + internal static bool IsString(ExpressionContext context, ExpressionInfo info, object[] args, object o) + { + var properties = GetProperties(args); + if (TryPropertyBool(properties, ISSTRING, out var propertyValue) && + TryOperand(context, ISSTRING, o, properties, out var operand)) { - var properties = GetProperties(args); - if (TryPropertyStringArray(properties, NOTCONTAINS, out var propertyValue) && - TryOperand(context, NOTCONTAINS, o, properties, out var operand) && - GetConvert(properties, out var convert) && - GetCaseSensitive(properties, out var caseSensitive)) - { - context.ExpressionTrace(NOTCONTAINS, operand.Value, propertyValue); - if (ExpressionHelpers.TryStringOrArray(operand.Value, convert, out var value)) - { - for (var i_propertyValue = 0; propertyValue != null && i_propertyValue < propertyValue.Length; i_propertyValue++) - { - for (var i_value = 0; i_value < value.Length; i_value++) - { - if (ExpressionHelpers.Contains(value[i_value], propertyValue[i_propertyValue], caseSensitive)) - return Fail( - context, - operand, - ReasonStrings.Assert_Contains, - value[i_value], - propertyValue[i_propertyValue] - ); - } - } - } - return Pass(); - } - return Invalid(context, NOTCONTAINS); + context.ExpressionTrace(ISSTRING, operand.Value, propertyValue); + return Condition( + context, + operand, + propertyValue == ExpressionHelpers.TryString(operand.Value, out _), + ReasonStrings.Assert_NotString, + operand.Value + ); } + return false; + } - internal static bool IsString(ExpressionContext context, ExpressionInfo info, object[] args, object o) + internal static bool IsArray(ExpressionContext context, ExpressionInfo info, object[] args, object o) + { + var properties = GetProperties(args); + if (TryPropertyBool(properties, ISARRAY, out var propertyValue) && + TryOperand(context, ISARRAY, o, properties, out var operand)) { - var properties = GetProperties(args); - if (TryPropertyBool(properties, ISSTRING, out var propertyValue) && - TryOperand(context, ISSTRING, o, properties, out var operand)) - { - context.ExpressionTrace(ISSTRING, operand.Value, propertyValue); - return Condition( - context, - operand, - propertyValue == ExpressionHelpers.TryString(operand.Value, out _), - ReasonStrings.Assert_NotString, - operand.Value - ); - } - return false; + context.ExpressionTrace(ISARRAY, operand.Value, propertyValue); + return Condition( + context, + operand, + propertyValue == ExpressionHelpers.TryArray(operand.Value, out _), + ReasonStrings.Assert_NotArray, + operand.Value + ); } + return false; + } - internal static bool IsArray(ExpressionContext context, ExpressionInfo info, object[] args, object o) + internal static bool IsBoolean(ExpressionContext context, ExpressionInfo info, object[] args, object o) + { + var properties = GetProperties(args); + if (TryPropertyBool(properties, ISBOOLEAN, out var propertyValue) && + TryOperand(context, ISBOOLEAN, o, properties, out var operand) && + GetConvert(properties, out var convert)) { - var properties = GetProperties(args); - if (TryPropertyBool(properties, ISARRAY, out var propertyValue) && - TryOperand(context, ISARRAY, o, properties, out var operand)) - { - context.ExpressionTrace(ISARRAY, operand.Value, propertyValue); - return Condition( - context, - operand, - propertyValue == ExpressionHelpers.TryArray(operand.Value, out _), - ReasonStrings.Assert_NotArray, - operand.Value - ); - } - return false; + context.ExpressionTrace(ISBOOLEAN, operand.Value, propertyValue); + return Condition( + context, + operand, + propertyValue == ExpressionHelpers.TryBool(operand.Value, convert, out _), + ReasonStrings.Assert_NotBoolean, + operand.Value + ); } + return false; + } - internal static bool IsBoolean(ExpressionContext context, ExpressionInfo info, object[] args, object o) + internal static bool IsDateTime(ExpressionContext context, ExpressionInfo info, object[] args, object o) + { + var properties = GetProperties(args); + if (TryPropertyBool(properties, ISDATETIME, out var propertyValue) && + TryOperand(context, ISDATETIME, o, properties, out var operand) && + GetConvert(properties, out var convert)) { - var properties = GetProperties(args); - if (TryPropertyBool(properties, ISBOOLEAN, out var propertyValue) && - TryOperand(context, ISBOOLEAN, o, properties, out var operand) && - GetConvert(properties, out var convert)) - { - context.ExpressionTrace(ISBOOLEAN, operand.Value, propertyValue); - return Condition( - context, - operand, - propertyValue == ExpressionHelpers.TryBool(operand.Value, convert, out _), - ReasonStrings.Assert_NotBoolean, - operand.Value - ); - } - return false; + context.ExpressionTrace(ISDATETIME, operand.Value, propertyValue); + return Condition( + context, + operand, + propertyValue == ExpressionHelpers.TryDateTime(operand.Value, convert, out _), + ReasonStrings.Assert_NotDateTime, + operand.Value + ); } + return false; + } - internal static bool IsDateTime(ExpressionContext context, ExpressionInfo info, object[] args, object o) + internal static bool IsInteger(ExpressionContext context, ExpressionInfo info, object[] args, object o) + { + var properties = GetProperties(args); + if (TryPropertyBool(properties, ISINTEGER, out var propertyValue) && + TryOperand(context, ISINTEGER, o, properties, out var operand) && + GetConvert(properties, out var convert)) { - var properties = GetProperties(args); - if (TryPropertyBool(properties, ISDATETIME, out var propertyValue) && - TryOperand(context, ISDATETIME, o, properties, out var operand) && - GetConvert(properties, out var convert)) - { - context.ExpressionTrace(ISDATETIME, operand.Value, propertyValue); - return Condition( - context, - operand, - propertyValue == ExpressionHelpers.TryDateTime(operand.Value, convert, out _), - ReasonStrings.Assert_NotDateTime, - operand.Value - ); - } - return false; + context.ExpressionTrace(ISINTEGER, operand.Value, propertyValue); + return Condition( + context, + operand, + propertyValue == (ExpressionHelpers.TryInt(operand.Value, convert, out _) || + ExpressionHelpers.TryLong(operand.Value, convert, out _) || + ExpressionHelpers.TryByte(operand.Value, convert, out _)), + ReasonStrings.Assert_NotInteger, + operand.Value + ); } + return false; + } - internal static bool IsInteger(ExpressionContext context, ExpressionInfo info, object[] args, object o) + internal static bool IsNumeric(ExpressionContext context, ExpressionInfo info, object[] args, object o) + { + var properties = GetProperties(args); + if (TryPropertyBool(properties, ISNUMERIC, out var propertyValue) && + TryOperand(context, ISNUMERIC, o, properties, out var operand) && + GetConvert(properties, out var convert)) { - var properties = GetProperties(args); - if (TryPropertyBool(properties, ISINTEGER, out var propertyValue) && - TryOperand(context, ISINTEGER, o, properties, out var operand) && - GetConvert(properties, out var convert)) - { - context.ExpressionTrace(ISINTEGER, operand.Value, propertyValue); - return Condition( - context, - operand, - propertyValue == (ExpressionHelpers.TryInt(operand.Value, convert, out _) || - ExpressionHelpers.TryLong(operand.Value, convert, out _) || - ExpressionHelpers.TryByte(operand.Value, convert, out _)), - ReasonStrings.Assert_NotInteger, - operand.Value - ); - } - return false; + context.ExpressionTrace(ISNUMERIC, operand.Value, propertyValue); + return Condition( + context, + operand, + propertyValue == (ExpressionHelpers.TryInt(operand.Value, convert, out _) || + ExpressionHelpers.TryLong(operand.Value, convert, out _) || + ExpressionHelpers.TryFloat(operand.Value, convert, out _) || + ExpressionHelpers.TryByte(operand.Value, convert, out _) || + ExpressionHelpers.TryDouble(operand.Value, convert, out _)), + ReasonStrings.Assert_NotInteger, + operand.Value + ); } + return false; + } - internal static bool IsNumeric(ExpressionContext context, ExpressionInfo info, object[] args, object o) + internal static bool WithinPath(ExpressionContext context, ExpressionInfo info, object[] args, object o) + { + var properties = GetProperties(args); + if (TryOperand(context, WITHINPATH, o, properties, out var operand) && + TryPropertyStringArray(properties, WITHINPATH, out var path) && + GetCaseSensitive(properties, out var caseSensitive)) { - var properties = GetProperties(args); - if (TryPropertyBool(properties, ISNUMERIC, out var propertyValue) && - TryOperand(context, ISNUMERIC, o, properties, out var operand) && - GetConvert(properties, out var convert)) - { - context.ExpressionTrace(ISNUMERIC, operand.Value, propertyValue); - return Condition( - context, - operand, - propertyValue == (ExpressionHelpers.TryInt(operand.Value, convert, out _) || - ExpressionHelpers.TryLong(operand.Value, convert, out _) || - ExpressionHelpers.TryFloat(operand.Value, convert, out _) || - ExpressionHelpers.TryByte(operand.Value, convert, out _) || - ExpressionHelpers.TryDouble(operand.Value, convert, out _)), - ReasonStrings.Assert_NotInteger, - operand.Value - ); - } - return false; - } + context.ExpressionTrace(WITHINPATH, operand.Value, path); - internal static bool WithinPath(ExpressionContext context, ExpressionInfo info, object[] args, object o) - { - var properties = GetProperties(args); - if (TryOperand(context, WITHINPATH, o, properties, out var operand) && - TryPropertyStringArray(properties, WITHINPATH, out var path) && - GetCaseSensitive(properties, out var caseSensitive)) + if (ExpressionHelpers.TryString(operand.Value, out var source)) { - context.ExpressionTrace(WITHINPATH, operand.Value, path); - - if (ExpressionHelpers.TryString(operand.Value, out var source)) + for (var i = 0; path != null && i < path.Length; i++) { - for (var i = 0; path != null && i < path.Length; i++) - { - if (ExpressionHelpers.WithinPath(source, path[i], caseSensitive)) - return Pass(); - } + if (ExpressionHelpers.WithinPath(source, path[i], caseSensitive)) + return Pass(); } - return Fail( - context, - operand, - ReasonStrings.WithinPath, - ExpressionHelpers.NormalizePath(Environment.GetWorkingPath(), source), - StringJoinNormalizedPath(path) - ); } - - return Invalid(context, WITHINPATH); + return Fail( + context, + operand, + ReasonStrings.WithinPath, + ExpressionHelpers.NormalizePath(Environment.GetWorkingPath(), source), + StringJoinNormalizedPath(path) + ); } - internal static bool NotWithinPath(ExpressionContext context, ExpressionInfo info, object[] args, object o) + return Invalid(context, WITHINPATH); + } + + internal static bool NotWithinPath(ExpressionContext context, ExpressionInfo info, object[] args, object o) + { + var properties = GetProperties(args); + if (TryOperand(context, NOTWITHINPATH, o, properties, out var operand) && + TryPropertyStringArray(properties, NOTWITHINPATH, out var path) && + GetCaseSensitive(properties, out var caseSensitive)) { - var properties = GetProperties(args); - if (TryOperand(context, NOTWITHINPATH, o, properties, out var operand) && - TryPropertyStringArray(properties, NOTWITHINPATH, out var path) && - GetCaseSensitive(properties, out var caseSensitive)) - { - context.ExpressionTrace(NOTWITHINPATH, operand.Value, path); + context.ExpressionTrace(NOTWITHINPATH, operand.Value, path); - if (ExpressionHelpers.TryString(operand.Value, out var source)) + if (ExpressionHelpers.TryString(operand.Value, out var source)) + { + for (var i = 0; path != null && i < path.Length; i++) { - for (var i = 0; path != null && i < path.Length; i++) - { - if (ExpressionHelpers.WithinPath(source, path[i], caseSensitive)) - return Fail( - context, - operand, - ReasonStrings.NotWithinPath, - ExpressionHelpers.NormalizePath(Environment.GetWorkingPath(), source), - ExpressionHelpers.NormalizePath(Environment.GetWorkingPath(), path[i]) - ); - } + if (ExpressionHelpers.WithinPath(source, path[i], caseSensitive)) + return Fail( + context, + operand, + ReasonStrings.NotWithinPath, + ExpressionHelpers.NormalizePath(Environment.GetWorkingPath(), source), + ExpressionHelpers.NormalizePath(Environment.GetWorkingPath(), path[i]) + ); } - return Pass(); } - - return Invalid(context, NOTWITHINPATH); + return Pass(); } - internal static bool Like(ExpressionContext context, ExpressionInfo info, object[] args, object o) + return Invalid(context, NOTWITHINPATH); + } + + internal static bool Like(ExpressionContext context, ExpressionInfo info, object[] args, object o) + { + var properties = GetProperties(args); + if (TryPropertyStringArray(properties, LIKE, out var propertyValue) && + TryOperand(context, LIKE, o, properties, out var operand) && + GetConvert(properties, out var convert) && + GetCaseSensitive(properties, out var caseSensitive)) { - var properties = GetProperties(args); - if (TryPropertyStringArray(properties, LIKE, out var propertyValue) && - TryOperand(context, LIKE, o, properties, out var operand) && - GetConvert(properties, out var convert) && - GetCaseSensitive(properties, out var caseSensitive)) - { - context.ExpressionTrace(LIKE, operand.Value, propertyValue); - if (!ExpressionHelpers.TryString(operand.Value, convert, out var value)) - return NotString(context, operand); + context.ExpressionTrace(LIKE, operand.Value, propertyValue); + if (!ExpressionHelpers.TryString(operand.Value, convert, out var value)) + return NotString(context, operand); - for (var i = 0; propertyValue != null && i < propertyValue.Length; i++) - if (ExpressionHelpers.Like(value, propertyValue[i], caseSensitive)) - return Pass(); + for (var i = 0; propertyValue != null && i < propertyValue.Length; i++) + if (ExpressionHelpers.Like(value, propertyValue[i], caseSensitive)) + return Pass(); - return Fail( - context, - operand, - ReasonStrings.Assert_NotLike, - value, - StringJoin(propertyValue) - ); - } - return Invalid(context, LIKE); + return Fail( + context, + operand, + ReasonStrings.Assert_NotLike, + value, + StringJoin(propertyValue) + ); } + return Invalid(context, LIKE); + } - internal static bool NotLike(ExpressionContext context, ExpressionInfo info, object[] args, object o) - { - var properties = GetProperties(args); - if (TryPropertyStringArray(properties, NOTLIKE, out var propertyValue) && - TryOperand(context, NOTLIKE, o, properties, out var operand) && - GetConvert(properties, out var convert) && - GetCaseSensitive(properties, out var caseSensitive)) + internal static bool NotLike(ExpressionContext context, ExpressionInfo info, object[] args, object o) + { + var properties = GetProperties(args); + if (TryPropertyStringArray(properties, NOTLIKE, out var propertyValue) && + TryOperand(context, NOTLIKE, o, properties, out var operand) && + GetConvert(properties, out var convert) && + GetCaseSensitive(properties, out var caseSensitive)) + { + context.ExpressionTrace(NOTLIKE, operand.Value, propertyValue); + if (ExpressionHelpers.TryString(operand.Value, convert, out var value)) { - context.ExpressionTrace(NOTLIKE, operand.Value, propertyValue); - if (ExpressionHelpers.TryString(operand.Value, convert, out var value)) + for (var i = 0; propertyValue != null && i < propertyValue.Length; i++) { - for (var i = 0; propertyValue != null && i < propertyValue.Length; i++) - { - if (ExpressionHelpers.Like(value, propertyValue[i], caseSensitive)) - return Fail( - context, - operand, - ReasonStrings.Assert_Like, - value, - propertyValue[i] - ); - } + if (ExpressionHelpers.Like(value, propertyValue[i], caseSensitive)) + return Fail( + context, + operand, + ReasonStrings.Assert_Like, + value, + propertyValue[i] + ); } - return Pass(); } - return Invalid(context, NOTLIKE); + return Pass(); } + return Invalid(context, NOTLIKE); + } - internal static bool IsLower(ExpressionContext context, ExpressionInfo info, object[] args, object o) + internal static bool IsLower(ExpressionContext context, ExpressionInfo info, object[] args, object o) + { + var properties = GetProperties(args); + if (TryPropertyBool(properties, ISLOWER, out var propertyValue) && + TryOperand(context, ISLOWER, o, properties, out var operand)) { - var properties = GetProperties(args); - if (TryPropertyBool(properties, ISLOWER, out var propertyValue) && - TryOperand(context, ISLOWER, o, properties, out var operand)) - { - if (!ExpressionHelpers.TryString(operand.Value, out var value)) - return Condition( - context, - operand, - !propertyValue.Value, - ReasonStrings.Assert_NotString, - operand.Value - ); - - context.ExpressionTrace(ISLOWER, operand.Value, propertyValue); + if (!ExpressionHelpers.TryString(operand.Value, out var value)) return Condition( context, operand, - propertyValue == ExpressionHelpers.IsLower(value, requireLetters: false, notLetter: out _), - ReasonStrings.Assert_IsLower, + !propertyValue.Value, + ReasonStrings.Assert_NotString, operand.Value ); - } - return false; + + context.ExpressionTrace(ISLOWER, operand.Value, propertyValue); + return Condition( + context, + operand, + propertyValue == ExpressionHelpers.IsLower(value, requireLetters: false, notLetter: out _), + ReasonStrings.Assert_IsLower, + operand.Value + ); } + return false; + } - internal static bool IsUpper(ExpressionContext context, ExpressionInfo info, object[] args, object o) + internal static bool IsUpper(ExpressionContext context, ExpressionInfo info, object[] args, object o) + { + var properties = GetProperties(args); + if (TryPropertyBool(properties, ISUPPER, out var propertyValue) && + TryOperand(context, ISUPPER, o, properties, out var operand)) { - var properties = GetProperties(args); - if (TryPropertyBool(properties, ISUPPER, out var propertyValue) && - TryOperand(context, ISUPPER, o, properties, out var operand)) - { - if (!ExpressionHelpers.TryString(operand.Value, out var value)) - return Condition( - context, - operand, - !propertyValue.Value, - ReasonStrings.Assert_NotString, - operand.Value - ); - - context.ExpressionTrace(ISUPPER, operand.Value, propertyValue); + if (!ExpressionHelpers.TryString(operand.Value, out var value)) return Condition( context, operand, - propertyValue == ExpressionHelpers.IsUpper(value, requireLetters: false, notLetter: out _), - ReasonStrings.Assert_IsUpper, + !propertyValue.Value, + ReasonStrings.Assert_NotString, operand.Value ); - } - return false; + + context.ExpressionTrace(ISUPPER, operand.Value, propertyValue); + return Condition( + context, + operand, + propertyValue == ExpressionHelpers.IsUpper(value, requireLetters: false, notLetter: out _), + ReasonStrings.Assert_IsUpper, + operand.Value + ); } + return false; + } - internal static bool HasSchema(ExpressionContext context, ExpressionInfo info, object[] args, object o) + internal static bool HasSchema(ExpressionContext context, ExpressionInfo info, object[] args, object o) + { + var properties = GetProperties(args); + if (TryPropertyArray(properties, HASSCHEMA, out var expectedValue) && + TryField(properties, out var field) && + TryPropertyBoolOrDefault(properties, IGNORESCHEME, out var ignoreScheme, false)) { - var properties = GetProperties(args); - if (TryPropertyArray(properties, HASSCHEMA, out var expectedValue) && - TryField(properties, out var field) && - TryPropertyBoolOrDefault(properties, IGNORESCHEME, out var ignoreScheme, false)) - { - context.ExpressionTrace(HASSCHEMA, field, expectedValue); - if (!ObjectHelper.GetPath(context, o, field, caseSensitive: false, out object actualValue)) - return NotHasField(context, field); + context.ExpressionTrace(HASSCHEMA, field, expectedValue); + if (!ObjectHelper.GetPath(context, o, field, caseSensitive: false, out object actualValue)) + return NotHasField(context, field); - if (!ObjectHelper.GetPath(context, actualValue, PROPERTY_SCHEMA, caseSensitive: false, out object schemaValue)) - return NotHasField(context, PROPERTY_SCHEMA); + if (!ObjectHelper.GetPath(context, actualValue, PROPERTY_SCHEMA, caseSensitive: false, out object schemaValue)) + return NotHasField(context, PROPERTY_SCHEMA); - if (!ExpressionHelpers.TryString(schemaValue, out var actualSchema)) - return NotString(context, Operand.FromPath(PROPERTY_SCHEMA, schemaValue)); + if (!ExpressionHelpers.TryString(schemaValue, out var actualSchema)) + return NotString(context, Operand.FromPath(PROPERTY_SCHEMA, schemaValue)); - if (string.IsNullOrEmpty(actualSchema)) - return NullOrEmpty(context, Operand.FromPath(PROPERTY_SCHEMA, schemaValue)); + if (string.IsNullOrEmpty(actualSchema)) + return NullOrEmpty(context, Operand.FromPath(PROPERTY_SCHEMA, schemaValue)); - if (expectedValue == null || expectedValue.Length == 0) - return Pass(); + if (expectedValue == null || expectedValue.Length == 0) + return Pass(); - if (ExpressionHelpers.AnySchema(actualSchema, expectedValue, ignoreScheme, false)) - return Pass(); + if (ExpressionHelpers.AnySchema(actualSchema, expectedValue, ignoreScheme, false)) + return Pass(); - return Fail( - context, - field, - ReasonStrings.Assert_NotSpecifiedSchema, - actualSchema - ); - } - return Invalid(context, HASSCHEMA); + return Fail( + context, + field, + ReasonStrings.Assert_NotSpecifiedSchema, + actualSchema + ); } + return Invalid(context, HASSCHEMA); + } - internal static bool Version(ExpressionContext context, ExpressionInfo info, object[] args, object o) + internal static bool Version(ExpressionContext context, ExpressionInfo info, object[] args, object o) + { + var properties = GetProperties(args); + if (TryPropertyString(properties, VERSION, out var expectedValue) && + TryOperand(context, VERSION, o, properties, out var operand) && + TryPropertyBoolOrDefault(properties, INCLUDEPRERELEASE, out var includePrerelease, false)) { - var properties = GetProperties(args); - if (TryPropertyString(properties, VERSION, out var expectedValue) && - TryOperand(context, VERSION, o, properties, out var operand) && - TryPropertyBoolOrDefault(properties, INCLUDEPRERELEASE, out var includePrerelease, false)) - { - context.ExpressionTrace(VERSION, operand.Value, expectedValue); - if (!ExpressionHelpers.TryString(operand.Value, out var version)) - return NotString(context, operand); + context.ExpressionTrace(VERSION, operand.Value, expectedValue); + if (!ExpressionHelpers.TryString(operand.Value, out var version)) + return NotString(context, operand); - if (!SemanticVersion.TryParseVersion(version, out var actualVersion)) - return Fail(context, operand, ReasonStrings.Version, operand.Value); + if (!SemanticVersion.TryParseVersion(version, out var actualVersion)) + return Fail(context, operand, ReasonStrings.Version, operand.Value); - if (!SemanticVersion.TryParseConstraint(expectedValue, out var constraint, includePrerelease)) - throw new RuleException(string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.VersionConstraintInvalid, expectedValue)); + if (!SemanticVersion.TryParseConstraint(expectedValue, out var constraint, includePrerelease)) + throw new RuleException(string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.VersionConstraintInvalid, expectedValue)); - if (constraint != null && !constraint.Equals(actualVersion)) - return Fail(context, operand, ReasonStrings.VersionContraint, actualVersion, constraint); + if (constraint != null && !constraint.Equals(actualVersion)) + return Fail(context, operand, ReasonStrings.VersionContraint, actualVersion, constraint); - return Pass(); - } - return Invalid(context, VERSION); + return Pass(); } + return Invalid(context, VERSION); + } - internal static bool APIVersion(ExpressionContext context, ExpressionInfo info, object[] args, object o) + internal static bool APIVersion(ExpressionContext context, ExpressionInfo info, object[] args, object o) + { + var properties = GetProperties(args); + if (TryPropertyString(properties, APIVERSION, out var expectedValue) && + TryOperand(context, APIVERSION, o, properties, out var operand) && + TryPropertyBoolOrDefault(properties, INCLUDEPRERELEASE, out var includePrerelease, false)) { - var properties = GetProperties(args); - if (TryPropertyString(properties, APIVERSION, out var expectedValue) && - TryOperand(context, APIVERSION, o, properties, out var operand) && - TryPropertyBoolOrDefault(properties, INCLUDEPRERELEASE, out var includePrerelease, false)) - { - context.ExpressionTrace(APIVERSION, operand.Value, expectedValue); - if (!ExpressionHelpers.TryString(operand.Value, out var version)) - return NotString(context, operand); + context.ExpressionTrace(APIVERSION, operand.Value, expectedValue); + if (!ExpressionHelpers.TryString(operand.Value, out var version)) + return NotString(context, operand); - if (!DateVersion.TryParseVersion(version, out var actualVersion)) - return Fail(context, operand, ReasonStrings.Version, operand.Value); + if (!DateVersion.TryParseVersion(version, out var actualVersion)) + return Fail(context, operand, ReasonStrings.Version, operand.Value); - if (!DateVersion.TryParseConstraint(expectedValue, out var constraint, includePrerelease)) - throw new RuleException(string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.VersionConstraintInvalid, expectedValue)); + if (!DateVersion.TryParseConstraint(expectedValue, out var constraint, includePrerelease)) + throw new RuleException(string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.VersionConstraintInvalid, expectedValue)); - if (constraint != null && !constraint.Equals(actualVersion)) - return Fail(context, operand, ReasonStrings.VersionContraint, actualVersion, constraint); + if (constraint != null && !constraint.Equals(actualVersion)) + return Fail(context, operand, ReasonStrings.VersionContraint, actualVersion, constraint); - return Pass(); - } - return Invalid(context, APIVERSION); + return Pass(); } + return Invalid(context, APIVERSION); + } - #endregion Conditions + #endregion Conditions - #region Helper methods + #region Helper methods - private static bool Condition(IExpressionContext context, string path, bool condition, string text, params object[] args) - { - if (condition) - return true; + private static bool Condition(IExpressionContext context, string path, bool condition, string text, params object[] args) + { + if (condition) + return true; - context.Reason(Operand.FromPath(path, null), text, args); - return false; - } + context.Reason(Operand.FromPath(path, null), text, args); + return false; + } - private static bool Condition(IExpressionContext context, IOperand operand, bool condition, string text, params object[] args) - { - if (condition) - return true; + private static bool Condition(IExpressionContext context, IOperand operand, bool condition, string text, params object[] args) + { + if (condition) + return true; - context.Reason(operand, text, args); - return false; - } + context.Reason(operand, text, args); + return false; + } - private static bool Fail(IExpressionContext context, string path, string text, params object[] args) - { - return Condition(context, Operand.FromPath(path, null), false, text, args); - } + private static bool Fail(IExpressionContext context, string path, string text, params object[] args) + { + return Condition(context, Operand.FromPath(path, null), false, text, args); + } - private static bool Fail(IExpressionContext context, IOperand operand, string text, params object[] args) - { - return Condition(context, operand, false, text, args); - } + private static bool Fail(IExpressionContext context, IOperand operand, string text, params object[] args) + { + return Condition(context, operand, false, text, args); + } - private static bool PassPathNotFound(ExpressionContext context, string name) - { - context.ExpressionTrace(name, PSRuleResources.ObjectPathNotFound); - return true; - } + private static bool PassPathNotFound(ExpressionContext context, string name) + { + context.ExpressionTrace(name, PSRuleResources.ObjectPathNotFound); + return true; + } - private static bool Pass() - { - return true; - } + private static bool Pass() + { + return true; + } - private static bool Invalid(IExpressionContext context, string name) - { - return false; - } + private static bool Invalid(IExpressionContext context, string name) + { + return false; + } - /// - /// Reason: The field '{0}' does not exist. - /// - private static bool NotHasField(IExpressionContext context, string path) - { - return Fail(context, path, ReasonStrings.NotHasField, path); - } + /// + /// Reason: The field '{0}' does not exist. + /// + private static bool NotHasField(IExpressionContext context, string path) + { + return Fail(context, path, ReasonStrings.NotHasField, path); + } - /// - /// Reason: Is null or empty. - /// - private static bool NullOrEmpty(ExpressionContext context, IOperand operand) - { - return Fail(context, operand, ReasonStrings.Assert_IsNullOrEmpty); - } + /// + /// Reason: Is null or empty. + /// + private static bool NullOrEmpty(ExpressionContext context, IOperand operand) + { + return Fail(context, operand, ReasonStrings.Assert_IsNullOrEmpty); + } - /// - /// Reason: The value '{0}' is not a string. - /// - private static bool NotString(ExpressionContext context, IOperand operand) - { - return Fail(context, operand, ReasonStrings.Assert_NotString, operand.Value); - } + /// + /// Reason: The value '{0}' is not a string. + /// + private static bool NotString(ExpressionContext context, IOperand operand) + { + return Fail(context, operand, ReasonStrings.Assert_NotString, operand.Value); + } - private static bool TryPropertyAny(LanguageExpression.PropertyBag properties, string propertyName, out object propertyValue) - { - return properties.TryGetValue(propertyName, out propertyValue); - } + private static bool TryPropertyAny(LanguageExpression.PropertyBag properties, string propertyName, out object propertyValue) + { + return properties.TryGetValue(propertyName, out propertyValue); + } - private static bool TryPropertyString(LanguageExpression.PropertyBag properties, string propertyName, out string propertyValue) - { - return properties.TryGetString(propertyName, out propertyValue); - } + private static bool TryPropertyString(LanguageExpression.PropertyBag properties, string propertyName, out string propertyValue) + { + return properties.TryGetString(propertyName, out propertyValue); + } - private static bool TryPropertyBool(LanguageExpression.PropertyBag properties, string propertyName, out bool? propertyValue) - { - return properties.TryGetBool(propertyName, out propertyValue); - } + private static bool TryPropertyBool(LanguageExpression.PropertyBag properties, string propertyName, out bool? propertyValue) + { + return properties.TryGetBool(propertyName, out propertyValue); + } - private static bool TryPropertyBoolOrDefault(LanguageExpression.PropertyBag properties, string propertyName, out bool propertyValue, bool defaultValue) - { - propertyValue = defaultValue; - if (properties.TryGetBool(propertyName, out var value)) - propertyValue = value.Value; + private static bool TryPropertyBoolOrDefault(LanguageExpression.PropertyBag properties, string propertyName, out bool propertyValue, bool defaultValue) + { + propertyValue = defaultValue; + if (properties.TryGetBool(propertyName, out var value)) + propertyValue = value.Value; - return true; - } + return true; + } - private static bool TryPropertyLong(ExpressionContext context, LanguageExpression.PropertyBag properties, string propertyName, out long? propertyValue) - { - propertyValue = null; - if (!properties.TryGetValue(propertyName, out var value)) - return false; + private static bool TryPropertyLong(ExpressionContext context, LanguageExpression.PropertyBag properties, string propertyName, out long? propertyValue) + { + propertyValue = null; + if (!properties.TryGetValue(propertyName, out var value)) + return false; - if (!ExpressionHelpers.TryLong(Value(context, value), true, out var l_value)) - return false; + if (!ExpressionHelpers.TryLong(Value(context, value), true, out var l_value)) + return false; - propertyValue = l_value; - return true; - } + propertyValue = l_value; + return true; + } - private static bool TryField(LanguageExpression.PropertyBag properties, out string field) - { - return properties.TryGetString(FIELD, out field); - } + private static bool TryField(LanguageExpression.PropertyBag properties, out string field) + { + return properties.TryGetString(FIELD, out field); + } - private static bool TryField(IExpressionContext context, LanguageExpression.PropertyBag properties, object o, out IOperand operand) - { - operand = null; - if (!properties.TryGetString(FIELD, out var field)) - return false; + private static bool TryField(IExpressionContext context, LanguageExpression.PropertyBag properties, object o, out IOperand operand) + { + operand = null; + if (!properties.TryGetString(FIELD, out var field)) + return false; - if (ObjectHelper.GetPath(context, o, field, caseSensitive: false, out object value)) - operand = Operand.FromPath(field, value); + if (ObjectHelper.GetPath(context, o, field, caseSensitive: false, out object value)) + operand = Operand.FromPath(field, value); - return operand != null || NotHasField(context, field); - } + return operand != null || NotHasField(context, field); + } - private static bool TryName(IExpressionContext context, LanguageExpression.PropertyBag properties, object o, out IOperand operand) + private static bool TryName(IExpressionContext context, LanguageExpression.PropertyBag properties, object o, out IOperand operand) + { + operand = null; + if (properties.TryGetString(NAME, out var svalue)) { - operand = null; - if (properties.TryGetString(NAME, out var svalue)) - { - if (svalue != DOT || context?.Context?.LanguageScope == null) - return Invalid(context, svalue); + if (svalue != DOT || context?.Context?.LanguageScope == null) + return Invalid(context, svalue); - if (!context.Context.LanguageScope.TryGetName(o, out var name, out var path) || - string.IsNullOrEmpty(name)) - return Invalid(context, svalue); + if (!context.Context.LanguageScope.TryGetName(o, out var name, out var path) || + string.IsNullOrEmpty(name)) + return Invalid(context, svalue); - operand = Operand.FromName(name, path); - } - return operand != null; + operand = Operand.FromName(name, path); } + return operand != null; + } - private static bool TryType(IExpressionContext context, LanguageExpression.PropertyBag properties, object o, out IOperand operand) + private static bool TryType(IExpressionContext context, LanguageExpression.PropertyBag properties, object o, out IOperand operand) + { + operand = null; + if (properties.TryGetString(TYPE, out var svalue)) { - operand = null; - if (properties.TryGetString(TYPE, out var svalue)) - { - if (svalue != DOT || context?.Context?.LanguageScope == null) - return Invalid(context, svalue); + if (svalue != DOT || context?.Context?.LanguageScope == null) + return Invalid(context, svalue); - if (!context.Context.LanguageScope.TryGetType(o, out var type, out var path) || - string.IsNullOrEmpty(type)) - return Invalid(context, svalue); + if (!context.Context.LanguageScope.TryGetType(o, out var type, out var path) || + string.IsNullOrEmpty(type)) + return Invalid(context, svalue); - operand = Operand.FromType(type, path); - } - return operand != null; + operand = Operand.FromType(type, path); } + return operand != null; + } - private static bool TrySource(IExpressionContext context, LanguageExpression.PropertyBag properties, out IOperand operand) + private static bool TrySource(IExpressionContext context, LanguageExpression.PropertyBag properties, out IOperand operand) + { + operand = null; + if (properties.TryGetString(SOURCE, out var sourceValue)) { - operand = null; - if (properties.TryGetString(SOURCE, out var sourceValue)) - { - var source = context?.Context?.TargetObject?.Source[sourceValue]; - if (source == null) - return Invalid(context, sourceValue); + var source = context?.Context?.TargetObject?.Source[sourceValue]; + if (source == null) + return Invalid(context, sourceValue); - operand = Operand.FromSource(ExpressionHelpers.GetObjectOriginPath(source)); - } - return operand != null; + operand = Operand.FromSource(ExpressionHelpers.GetObjectOriginPath(source)); } + return operand != null; + } - private static bool TryValue(IExpressionContext context, LanguageExpression.PropertyBag properties, out IOperand operand) + private static bool TryValue(IExpressionContext context, LanguageExpression.PropertyBag properties, out IOperand operand) + { + operand = null; + if (properties.TryGetValue(VALUE, out var value)) { - operand = null; - if (properties.TryGetValue(VALUE, out var value)) - { - // TODO: Propogate path - operand = Operand.FromValue(value); - } - return operand != null; + // TODO: Propogate path + operand = Operand.FromValue(value); } + return operand != null; + } - private static bool TryScope(IExpressionContext context, LanguageExpression.PropertyBag properties, object o, out IOperand operand) + private static bool TryScope(IExpressionContext context, LanguageExpression.PropertyBag properties, object o, out IOperand operand) + { + operand = null; + if (properties.TryGetString(SCOPE, out var svalue)) { - operand = null; - if (properties.TryGetString(SCOPE, out var svalue)) - { - if (svalue != DOT || context?.Context?.LanguageScope == null) - return Invalid(context, svalue); + if (svalue != DOT || context?.Context?.LanguageScope == null) + return Invalid(context, svalue); - if (!context.Context.LanguageScope.TryGetScope(o, out var scope)) - return Invalid(context, svalue); + if (!context.Context.LanguageScope.TryGetScope(o, out var scope)) + return Invalid(context, svalue); - operand = Operand.FromScope(scope); - } - return operand != null; + operand = Operand.FromScope(scope); } + return operand != null; + } - /// - /// Unwrap a function delegate or a literal value. - /// - private static object Value(IExpressionContext context, IOperand operand) - { - if (operand == null) - return null; + /// + /// Unwrap a function delegate or a literal value. + /// + private static object Value(IExpressionContext context, IOperand operand) + { + if (operand == null) + return null; - return operand.Value is ExpressionFnOuter fn ? fn(context) : operand.Value; - } + return operand.Value is ExpressionFnOuter fn ? fn(context) : operand.Value; + } - /// - /// Unwrap a function delegate or a literal value. - /// - private static object Value(IExpressionContext context, object value) - { - return value is ExpressionFnOuter fn ? fn(context) : value; - } + /// + /// Unwrap a function delegate or a literal value. + /// + private static object Value(IExpressionContext context, object value) + { + return value is ExpressionFnOuter fn ? fn(context) : value; + } - private static bool GetCaseSensitive(LanguageExpression.PropertyBag properties, out bool caseSensitive, bool defaultValue = false) - { - return TryPropertyBoolOrDefault(properties, CASESENSITIVE, out caseSensitive, defaultValue); - } + private static bool GetCaseSensitive(LanguageExpression.PropertyBag properties, out bool caseSensitive, bool defaultValue = false) + { + return TryPropertyBoolOrDefault(properties, CASESENSITIVE, out caseSensitive, defaultValue); + } - private static bool GetUnique(LanguageExpression.PropertyBag properties, out bool unique, bool defaultValue = false) - { - return TryPropertyBoolOrDefault(properties, UNIQUE, out unique, defaultValue); - } + private static bool GetUnique(LanguageExpression.PropertyBag properties, out bool unique, bool defaultValue = false) + { + return TryPropertyBoolOrDefault(properties, UNIQUE, out unique, defaultValue); + } - private static bool GetConvert(LanguageExpression.PropertyBag properties, out bool convert, bool defaultValue = false) - { - return TryPropertyBoolOrDefault(properties, CONVERT, out convert, defaultValue); - } + private static bool GetConvert(LanguageExpression.PropertyBag properties, out bool convert, bool defaultValue = false) + { + return TryPropertyBoolOrDefault(properties, CONVERT, out convert, defaultValue); + } - /// - /// Returns true when the field properties is specified and the specified field does not exist. - /// - private static bool TryFieldNotExists(ExpressionContext context, object o, LanguageExpression.PropertyBag properties) - { - return properties.TryGetString(FIELD, out var field) && - !ObjectHelper.GetPath(context, o, field, caseSensitive: false, out object _); - } + /// + /// Returns true when the field properties is specified and the specified field does not exist. + /// + private static bool TryFieldNotExists(ExpressionContext context, object o, LanguageExpression.PropertyBag properties) + { + return properties.TryGetString(FIELD, out var field) && + !ObjectHelper.GetPath(context, o, field, caseSensitive: false, out object _); + } - private static bool TryOperand(ExpressionContext context, string name, object o, LanguageExpression.PropertyBag properties, out IOperand operand) - { - return TryField(context, properties, o, out operand) || - TryType(context, properties, o, out operand) || - TryName(context, properties, o, out operand) || - TrySource(context, properties, out operand) || - TryValue(context, properties, out operand) || - TryScope(context, properties, o, out operand) || - Invalid(context, name); - } + private static bool TryOperand(ExpressionContext context, string name, object o, LanguageExpression.PropertyBag properties, out IOperand operand) + { + return TryField(context, properties, o, out operand) || + TryType(context, properties, o, out operand) || + TryName(context, properties, o, out operand) || + TrySource(context, properties, out operand) || + TryValue(context, properties, out operand) || + TryScope(context, properties, o, out operand) || + Invalid(context, name); + } - private static bool TryPropertyArray(LanguageExpression.PropertyBag properties, string propertyName, out Array propertyValue) + private static bool TryPropertyArray(LanguageExpression.PropertyBag properties, string propertyName, out Array propertyValue) + { + if (properties.TryGetValue(propertyName, out var array) && array is Array arrayValue) { - if (properties.TryGetValue(propertyName, out var array) && array is Array arrayValue) - { - propertyValue = arrayValue; - return true; - } - propertyValue = null; - return false; + propertyValue = arrayValue; + return true; } + propertyValue = null; + return false; + } - private static bool TryPropertyStringArray(LanguageExpression.PropertyBag properties, string propertyName, out string[] propertyValue) + private static bool TryPropertyStringArray(LanguageExpression.PropertyBag properties, string propertyName, out string[] propertyValue) + { + if (properties.TryGetStringArray(propertyName, out propertyValue)) { - if (properties.TryGetStringArray(propertyName, out propertyValue)) - { - return true; - } - else if (properties.TryGetString(propertyName, out var s)) - { - propertyValue = new string[] { s }; - return true; - } - propertyValue = null; - return false; + return true; } - - private static LanguageExpression.PropertyBag GetProperties(object[] args) + else if (properties.TryGetString(propertyName, out var s)) { - return (LanguageExpression.PropertyBag)args[0]; + propertyValue = new string[] { s }; + return true; } + propertyValue = null; + return false; + } - private static LanguageExpressionOuterFn[] GetInner(object[] args) - { - return (LanguageExpressionOuterFn[])args; - } + private static LanguageExpression.PropertyBag GetProperties(object[] args) + { + return (LanguageExpression.PropertyBag)args[0]; + } - private static string StringJoin(Array propertyValue) - { - return string.Concat("'", string.Join("', '", propertyValue), "'"); - } + private static LanguageExpressionOuterFn[] GetInner(object[] args) + { + return (LanguageExpressionOuterFn[])args; + } - private static string StringJoinNormalizedPath(string[] path) - { - var normalizedPath = path.Select(p => ExpressionHelpers.NormalizePath(Environment.GetWorkingPath(), p)); - return StringJoin(normalizedPath.ToArray()); - } + private static string StringJoin(Array propertyValue) + { + return string.Concat("'", string.Join("', '", propertyValue), "'"); + } - #endregion Helper methods + private static string StringJoinNormalizedPath(string[] path) + { + var normalizedPath = path.Select(p => ExpressionHelpers.NormalizePath(Environment.GetWorkingPath(), p)); + return StringJoin(normalizedPath.ToArray()); } + + #endregion Helper methods } diff --git a/src/PSRule/Definitions/Expressions/Primitives.cs b/src/PSRule/Definitions/Expressions/Primitives.cs index 34b9250c4c..2cc7e34fb3 100644 --- a/src/PSRule/Definitions/Expressions/Primitives.cs +++ b/src/PSRule/Definitions/Expressions/Primitives.cs @@ -4,115 +4,114 @@ using System.Diagnostics; using PSRule.Pipeline; -namespace PSRule.Definitions.Expressions +namespace PSRule.Definitions.Expressions; + +internal interface ILanguageExpresssionDescriptor { - internal interface ILanguageExpresssionDescriptor - { - string Name { get; } + string Name { get; } - LanguageExpressionType Type { get; } + LanguageExpressionType Type { get; } - LanguageExpression CreateInstance(SourceFile source, LanguageExpression.PropertyBag properties); - } + LanguageExpression CreateInstance(SourceFile source, LanguageExpression.PropertyBag properties); +} - internal sealed class LanguageExpresssionDescriptor : ILanguageExpresssionDescriptor +internal sealed class LanguageExpresssionDescriptor : ILanguageExpresssionDescriptor +{ + public LanguageExpresssionDescriptor(string name, LanguageExpressionType type, LanguageExpressionFn fn) { - public LanguageExpresssionDescriptor(string name, LanguageExpressionType type, LanguageExpressionFn fn) - { - Name = name; - Type = type; - Fn = fn; - } + Name = name; + Type = type; + Fn = fn; + } - public string Name { get; } + public string Name { get; } - public LanguageExpressionType Type { get; } + public LanguageExpressionType Type { get; } - public LanguageExpressionFn Fn { get; } + public LanguageExpressionFn Fn { get; } - public LanguageExpression CreateInstance(SourceFile source, LanguageExpression.PropertyBag properties) - { - if (Type == LanguageExpressionType.Operator) - return new LanguageOperator(this, properties); + public LanguageExpression CreateInstance(SourceFile source, LanguageExpression.PropertyBag properties) + { + if (Type == LanguageExpressionType.Operator) + return new LanguageOperator(this, properties); - if (Type == LanguageExpressionType.Condition) - return new LanguageCondition(this, properties); + if (Type == LanguageExpressionType.Condition) + return new LanguageCondition(this, properties); - return Type == LanguageExpressionType.Function ? new LanguageFunction(this) : null; - } + return Type == LanguageExpressionType.Function ? new LanguageFunction(this) : null; } +} - internal abstract class LanguageExpression +internal abstract class LanguageExpression +{ + public LanguageExpression(LanguageExpresssionDescriptor descriptor) { - public LanguageExpression(LanguageExpresssionDescriptor descriptor) - { - Descriptor = descriptor; - } - - internal sealed class PropertyBag : KeyMapDictionary - { - public PropertyBag() - : base() { } - } - - public LanguageExpresssionDescriptor Descriptor { get; } + Descriptor = descriptor; } - [DebuggerDisplay("Selector If")] - internal sealed class LanguageIf : LanguageExpression + internal sealed class PropertyBag : KeyMapDictionary { - public LanguageIf(LanguageExpression expression) - : base(null) - { - Expression = expression; - } + public PropertyBag() + : base() { } + } - public LanguageExpression Expression { get; set; } + public LanguageExpresssionDescriptor Descriptor { get; } +} + +[DebuggerDisplay("Selector If")] +internal sealed class LanguageIf : LanguageExpression +{ + public LanguageIf(LanguageExpression expression) + : base(null) + { + Expression = expression; } - [DebuggerDisplay("Selector {Descriptor.Name}")] - internal sealed class LanguageOperator : LanguageExpression + public LanguageExpression Expression { get; set; } +} + +[DebuggerDisplay("Selector {Descriptor.Name}")] +internal sealed class LanguageOperator : LanguageExpression +{ + internal LanguageOperator(LanguageExpresssionDescriptor descriptor, PropertyBag properties) + : base(descriptor) { - internal LanguageOperator(LanguageExpresssionDescriptor descriptor, PropertyBag properties) - : base(descriptor) - { - Property = properties ?? new PropertyBag(); - Children = new List(); - } + Property = properties ?? new PropertyBag(); + Children = new List(); + } - public LanguageExpression Subselector { get; set; } + public LanguageExpression Subselector { get; set; } - public PropertyBag Property { get; } + public PropertyBag Property { get; } - public List Children { get; } + public List Children { get; } - public void Add(LanguageExpression item) - { - Children.Add(item); - } + public void Add(LanguageExpression item) + { + Children.Add(item); } +} - [DebuggerDisplay("Selector {Descriptor.Name}")] - internal sealed class LanguageCondition : LanguageExpression +[DebuggerDisplay("Selector {Descriptor.Name}")] +internal sealed class LanguageCondition : LanguageExpression +{ + internal LanguageCondition(LanguageExpresssionDescriptor descriptor, PropertyBag properties) + : base(descriptor) { - internal LanguageCondition(LanguageExpresssionDescriptor descriptor, PropertyBag properties) - : base(descriptor) - { - Property = properties ?? new PropertyBag(); - } - - public PropertyBag Property { get; } - - internal void Add(PropertyBag properties) - { - Property.AddUnique(properties); - } + Property = properties ?? new PropertyBag(); } - [DebuggerDisplay("Selector {Descriptor.Name}")] - internal sealed class LanguageFunction : LanguageExpression + public PropertyBag Property { get; } + + internal void Add(PropertyBag properties) { - internal LanguageFunction(LanguageExpresssionDescriptor descriptor) - : base(descriptor) { } + Property.AddUnique(properties); } } + +[DebuggerDisplay("Selector {Descriptor.Name}")] +internal sealed class LanguageFunction : LanguageExpression +{ + internal LanguageFunction(LanguageExpresssionDescriptor descriptor) + : base(descriptor) { } +} diff --git a/src/PSRule/Definitions/IAnnotated.cs b/src/PSRule/Definitions/IAnnotated.cs index 52d2babfaa..569694bb29 100644 --- a/src/PSRule/Definitions/IAnnotated.cs +++ b/src/PSRule/Definitions/IAnnotated.cs @@ -1,21 +1,20 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -namespace PSRule.Definitions +namespace PSRule.Definitions; + +internal interface IAnnotated { - internal interface IAnnotated - { - TAnnotation GetAnnotation() where TAnnotation : T; + TAnnotation GetAnnotation() where TAnnotation : T; - void SetAnnotation(TAnnotation annotation) where TAnnotation : T; - } + void SetAnnotation(TAnnotation annotation) where TAnnotation : T; +} - internal static class AnnotatedExtensions +internal static class AnnotatedExtensions +{ + internal static TAnnotation RequireAnnotation(this IAnnotated annotated) where TAnnotation : T, new() { - internal static TAnnotation RequireAnnotation(this IAnnotated annotated) where TAnnotation : T, new() - { - var result = annotated.GetAnnotation(); - return result == null ? new TAnnotation() : result; - } + var result = annotated.GetAnnotation(); + return result == null ? new TAnnotation() : result; } } diff --git a/src/PSRule/Definitions/ICondition.cs b/src/PSRule/Definitions/ICondition.cs index e23f6553aa..64d50c4808 100644 --- a/src/PSRule/Definitions/ICondition.cs +++ b/src/PSRule/Definitions/ICondition.cs @@ -3,43 +3,42 @@ using System.Management.Automation; -namespace PSRule.Definitions +namespace PSRule.Definitions; + +/// +/// A result from an language condition. +/// +public interface IConditionResult { /// - /// A result from an language condition. + /// Determine if the condition had errors. /// - public interface IConditionResult - { - /// - /// Determine if the condition had errors. - /// - bool HadErrors { get; } + bool HadErrors { get; } - /// - /// The number of sub-conditions that were evaluated. - /// - int Count { get; } + /// + /// The number of sub-conditions that were evaluated. + /// + int Count { get; } - /// - /// The number of sub-conditions that passed. - /// - int Pass { get; } - } + /// + /// The number of sub-conditions that passed. + /// + int Pass { get; } +} +/// +/// A language condition. +/// +public interface ICondition : ILanguageBlock, IDisposable +{ /// - /// A language condition. + /// Invoke the condition to get a result. /// - public interface ICondition : ILanguageBlock, IDisposable - { - /// - /// Invoke the condition to get a result. - /// - /// Returns the result of the condition. - IConditionResult If(); + /// Returns the result of the condition. + IConditionResult If(); - /// - /// The action of error to take when execution the condition. - /// - ActionPreference ErrorAction { get; } - } + /// + /// The action of error to take when execution the condition. + /// + ActionPreference ErrorAction { get; } } diff --git a/src/PSRule/Definitions/IConvention.cs b/src/PSRule/Definitions/IConvention.cs index 3bb4fe9484..381e801ab6 100644 --- a/src/PSRule/Definitions/IConvention.cs +++ b/src/PSRule/Definitions/IConvention.cs @@ -4,18 +4,17 @@ using System.Collections; using PSRule.Runtime; -namespace PSRule.Definitions +namespace PSRule.Definitions; + +internal interface IConvention : ILanguageBlock { - internal interface IConvention : ILanguageBlock - { - string Name { get; } + string Name { get; } - void Initialize(RunspaceContext context, IEnumerable input); + void Initialize(RunspaceContext context, IEnumerable input); - void Begin(RunspaceContext context, IEnumerable input); + void Begin(RunspaceContext context, IEnumerable input); - void Process(RunspaceContext context, IEnumerable input); + void Process(RunspaceContext context, IEnumerable input); - void End(RunspaceContext context, IEnumerable input); - } + void End(RunspaceContext context, IEnumerable input); } diff --git a/src/PSRule/Definitions/IDependencyTarget.cs b/src/PSRule/Definitions/IDependencyTarget.cs index a4ecdec1b4..4acb362d19 100644 --- a/src/PSRule/Definitions/IDependencyTarget.cs +++ b/src/PSRule/Definitions/IDependencyTarget.cs @@ -1,36 +1,35 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -namespace PSRule.Definitions +namespace PSRule.Definitions; + +/// +/// An object that relies on a dependency chain. +/// +public interface IDependencyTarget { /// - /// An object that relies on a dependency chain. + /// The unique identifier of the resource. /// - public interface IDependencyTarget - { - /// - /// The unique identifier of the resource. - /// - ResourceId Id { get; } + ResourceId Id { get; } - /// - /// A unique reference for the resource. - /// - ResourceId? Ref { get; } + /// + /// A unique reference for the resource. + /// + ResourceId? Ref { get; } - /// - /// Additional aliases for the resource. - /// - ResourceId[] Alias { get; } + /// + /// Additional aliases for the resource. + /// + ResourceId[] Alias { get; } - /// - /// Resources this target depends on. - /// - ResourceId[] DependsOn { get; } + /// + /// Resources this target depends on. + /// + ResourceId[] DependsOn { get; } - /// - /// Determines if the source was imported as a dependency. - /// - bool Dependency { get; } - } + /// + /// Determines if the source was imported as a dependency. + /// + bool Dependency { get; } } diff --git a/src/PSRule/Definitions/ILanguageBlock.cs b/src/PSRule/Definitions/ILanguageBlock.cs index d2f33df7bc..a68b3f99d0 100644 --- a/src/PSRule/Definitions/ILanguageBlock.cs +++ b/src/PSRule/Definitions/ILanguageBlock.cs @@ -3,21 +3,20 @@ using PSRule.Pipeline; -namespace PSRule.Definitions +namespace PSRule.Definitions; + +/// +/// A language block. +/// +public interface ILanguageBlock { /// - /// A language block. + /// The unique identifier for the block. /// - public interface ILanguageBlock - { - /// - /// The unique identifier for the block. - /// - ResourceId Id { get; } + ResourceId Id { get; } - /// - /// The source location for the block. - /// - SourceFile Source { get; } - } + /// + /// The source location for the block. + /// + SourceFile Source { get; } } diff --git a/src/PSRule/Definitions/IResourceFilter.cs b/src/PSRule/Definitions/IResourceFilter.cs index 282aa688dd..d11355235a 100644 --- a/src/PSRule/Definitions/IResourceFilter.cs +++ b/src/PSRule/Definitions/IResourceFilter.cs @@ -1,12 +1,11 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -namespace PSRule.Definitions +namespace PSRule.Definitions; + +internal interface IResourceFilter { - internal interface IResourceFilter - { - ResourceKind Kind { get; } + ResourceKind Kind { get; } - bool Match(IResource resource); - } + bool Match(IResource resource); } diff --git a/src/PSRule/Definitions/IResourceHelpInfo.cs b/src/PSRule/Definitions/IResourceHelpInfo.cs index 6373e60725..ed5cf5c3a0 100644 --- a/src/PSRule/Definitions/IResourceHelpInfo.cs +++ b/src/PSRule/Definitions/IResourceHelpInfo.cs @@ -3,58 +3,57 @@ using Newtonsoft.Json; -namespace PSRule.Definitions +namespace PSRule.Definitions; + +/// +/// Metadata about a PSRule resource. +/// +public interface IResourceHelpInfo { /// - /// Metadata about a PSRule resource. + /// The name of the resource. /// - public interface IResourceHelpInfo - { - /// - /// The name of the resource. - /// - string Name { get; } - - /// - /// A display name of the resource if set. - /// - string DisplayName { get; } - - /// - /// A short description of the resource if set. - /// - InfoString Synopsis { get; } - - /// - /// A long description of the resource if set. - /// - InfoString Description { get; } - } + string Name { get; } + + /// + /// A display name of the resource if set. + /// + string DisplayName { get; } + + /// + /// A short description of the resource if set. + /// + InfoString Synopsis { get; } - internal sealed class ResourceHelpInfo : IResourceHelpInfo + /// + /// A long description of the resource if set. + /// + InfoString Description { get; } +} + +internal sealed class ResourceHelpInfo : IResourceHelpInfo +{ + internal ResourceHelpInfo(string name, string displayName, InfoString synopsis, InfoString description) { - internal ResourceHelpInfo(string name, string displayName, InfoString synopsis, InfoString description) - { - Name = name; - DisplayName = displayName; - Synopsis = synopsis; - Description = description; - } - - /// - [JsonProperty(PropertyName = "name")] - public string Name { get; } - - /// - [JsonProperty(PropertyName = "displayName")] - public string DisplayName { get; } - - /// - [JsonProperty(PropertyName = "synopsis")] - public InfoString Synopsis { get; } - - /// - [JsonProperty(PropertyName = "description")] - public InfoString Description { get; } + Name = name; + DisplayName = displayName; + Synopsis = synopsis; + Description = description; } + + /// + [JsonProperty(PropertyName = "name")] + public string Name { get; } + + /// + [JsonProperty(PropertyName = "displayName")] + public string DisplayName { get; } + + /// + [JsonProperty(PropertyName = "synopsis")] + public InfoString Synopsis { get; } + + /// + [JsonProperty(PropertyName = "description")] + public InfoString Description { get; } } diff --git a/src/PSRule/Definitions/IResultRecord.cs b/src/PSRule/Definitions/IResultRecord.cs index 6d86cf8b1a..c9cc3e07a0 100644 --- a/src/PSRule/Definitions/IResultRecord.cs +++ b/src/PSRule/Definitions/IResultRecord.cs @@ -1,12 +1,11 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -namespace PSRule.Definitions +namespace PSRule.Definitions; + +/// +/// A base interface for a PSRule result record. +/// +public interface IResultRecord { - /// - /// A base interface for a PSRule result record. - /// - public interface IResultRecord - { - } } diff --git a/src/PSRule/Definitions/IRuleResultV2.cs b/src/PSRule/Definitions/IRuleResultV2.cs index 8b94f97668..412e06216c 100644 --- a/src/PSRule/Definitions/IRuleResultV2.cs +++ b/src/PSRule/Definitions/IRuleResultV2.cs @@ -5,109 +5,108 @@ using PSRule.Definitions.Rules; using PSRule.Rules; -namespace PSRule.Definitions +namespace PSRule.Definitions; + +/// +/// Rule results for PSRule V2. +/// +public interface IRuleResultV2 : IResultRecord { /// - /// Rule results for PSRule V2. - /// - public interface IRuleResultV2 : IResultRecord - { - /// - /// A unique identifier for the run. - /// - string RunId { get; } - - /// - /// Help info for the rule. - /// - IRuleHelpInfoV2 Info { get; } - - /// - /// The outcome after the rule processes an object. - /// - RuleOutcome Outcome { get; } - - /// - /// If the rule fails, how serious is the result. - /// - SeverityLevel Level { get; } - - /// - /// Tags set for the rule. - /// - Hashtable Tag { get; } - - /// - /// The execution time of the rule in millisecond. - /// - long Time { get; } - } - - /// - /// Detailed rule records for PSRule v2. - /// - public interface IDetailedRuleResultV2 : IRuleResultV2 - { - /// - /// Custom data set by the rule for this target object. - /// - Hashtable Data { get; } - - /// - /// Detailed information about the rule result. - /// - IResultDetailV2 Detail { get; } - - /// - /// A set of custom fields bound for the target object. - /// - Hashtable Field { get; } - - /// - /// The bound name of the target. - /// - string TargetName { get; } - - /// - /// The bound type of the target. - /// - string TargetType { get; } - } + /// A unique identifier for the run. + /// + string RunId { get; } + + /// + /// Help info for the rule. + /// + IRuleHelpInfoV2 Info { get; } + + /// + /// The outcome after the rule processes an object. + /// + RuleOutcome Outcome { get; } + + /// + /// If the rule fails, how serious is the result. + /// + SeverityLevel Level { get; } + + /// + /// Tags set for the rule. + /// + Hashtable Tag { get; } + + /// + /// The execution time of the rule in millisecond. + /// + long Time { get; } +} + +/// +/// Detailed rule records for PSRule v2. +/// +public interface IDetailedRuleResultV2 : IRuleResultV2 +{ + /// + /// Custom data set by the rule for this target object. + /// + Hashtable Data { get; } /// /// Detailed information about the rule result. /// - public interface IResultDetailV2 - { - /// - /// Any reasons for the result. - /// - IEnumerable Reason { get; } - } - - /// - /// A reason for the rule result. - /// - public interface IResultReasonV2 - { - /// - /// The object path that failed. - /// - string Path { get; } - - /// - /// The object path including the path of the parent object. - /// - string FullPath { get; } - - /// - /// The reason message. - /// - string Message { get; } - - /// - /// Return a formatted reason string. - /// - string Format(); - } + IResultDetailV2 Detail { get; } + + /// + /// A set of custom fields bound for the target object. + /// + Hashtable Field { get; } + + /// + /// The bound name of the target. + /// + string TargetName { get; } + + /// + /// The bound type of the target. + /// + string TargetType { get; } +} + +/// +/// Detailed information about the rule result. +/// +public interface IResultDetailV2 +{ + /// + /// Any reasons for the result. + /// + IEnumerable Reason { get; } +} + +/// +/// A reason for the rule result. +/// +public interface IResultReasonV2 +{ + /// + /// The object path that failed. + /// + string Path { get; } + + /// + /// The object path including the path of the parent object. + /// + string FullPath { get; } + + /// + /// The reason message. + /// + string Message { get; } + + /// + /// Return a formatted reason string. + /// + string Format(); } diff --git a/src/PSRule/Definitions/ISuppressionInfo.cs b/src/PSRule/Definitions/ISuppressionInfo.cs index cf6f5d723a..95c3cb4a0b 100644 --- a/src/PSRule/Definitions/ISuppressionInfo.cs +++ b/src/PSRule/Definitions/ISuppressionInfo.cs @@ -1,30 +1,29 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -namespace PSRule.Definitions +namespace PSRule.Definitions; + +/// +/// Information related to suppression of a rule. +/// +internal interface ISuppressionInfo { - /// - /// Information related to suppression of a rule. - /// - internal interface ISuppressionInfo - { - ResourceId Id { get; } + ResourceId Id { get; } - InfoString Synopsis { get; } + InfoString Synopsis { get; } - int Count { get; } - } + int Count { get; } +} - internal sealed class ISuppressionInfoComparer : IEqualityComparer +internal sealed class ISuppressionInfoComparer : IEqualityComparer +{ + public bool Equals(ISuppressionInfo x, ISuppressionInfo y) { - public bool Equals(ISuppressionInfo x, ISuppressionInfo y) - { - return object.Equals(x, null) || object.Equals(y, null) ? object.Equals(x, y) : x.Equals(y); - } + return object.Equals(x, null) || object.Equals(y, null) ? object.Equals(x, y) : x.Equals(y); + } - public int GetHashCode(ISuppressionInfo obj) - { - return obj.GetHashCode(); - } + public int GetHashCode(ISuppressionInfo obj) + { + return obj.GetHashCode(); } } diff --git a/src/PSRule/Definitions/InfoString.cs b/src/PSRule/Definitions/InfoString.cs index 98f2016bbf..3e939a7fab 100644 --- a/src/PSRule/Definitions/InfoString.cs +++ b/src/PSRule/Definitions/InfoString.cs @@ -3,65 +3,64 @@ using System.Diagnostics; -namespace PSRule.Definitions +namespace PSRule.Definitions; + +/// +/// A string formatted with plain text and/ or markdown. +/// +[DebuggerDisplay("{Text}")] +public sealed class InfoString { - /// - /// A string formatted with plain text and/ or markdown. - /// - [DebuggerDisplay("{Text}")] - public sealed class InfoString - { - private string _Text; - private string _Markdown; + private string _Text; + private string _Markdown; - internal InfoString() { } + internal InfoString() { } - internal InfoString(string text, string markdown = null) - { - Text = text; - Markdown = markdown ?? text; - } + internal InfoString(string text, string markdown = null) + { + Text = text; + Markdown = markdown ?? text; + } - /// - /// Determine if the information string is empty. - /// - public bool HasValue - { - get { return Text != null || Markdown != null; } - } + /// + /// Determine if the information string is empty. + /// + public bool HasValue + { + get { return Text != null || Markdown != null; } + } - /// - /// A plain text representation. - /// - public string Text + /// + /// A plain text representation. + /// + public string Text + { + get { return _Text; } + set { - get { return _Text; } - set - { - if (!string.IsNullOrEmpty(value)) - _Text = value; - } + if (!string.IsNullOrEmpty(value)) + _Text = value; } + } - /// - /// A markdown formatted representation if set. Otherwise this is the same as . - /// - public string Markdown + /// + /// A markdown formatted representation if set. Otherwise this is the same as . + /// + public string Markdown + { + get { return _Markdown; } + set { - get { return _Markdown; } - set - { - if (!string.IsNullOrEmpty(value)) - _Markdown = value; - } + if (!string.IsNullOrEmpty(value)) + _Markdown = value; } + } - /// - /// Create an info string when not null or empty. - /// - internal static InfoString Create(string text, string markdown = null) - { - return string.IsNullOrEmpty(text) && string.IsNullOrEmpty(markdown) ? null : new InfoString(text, markdown); - } + /// + /// Create an info string when not null or empty. + /// + internal static InfoString Create(string text, string markdown = null) + { + return string.IsNullOrEmpty(text) && string.IsNullOrEmpty(markdown) ? null : new InfoString(text, markdown); } } diff --git a/src/PSRule/Definitions/ModuleConfigs/ModuleConfig.cs b/src/PSRule/Definitions/ModuleConfigs/ModuleConfig.cs index 44b8396ee5..77bb33d6de 100644 --- a/src/PSRule/Definitions/ModuleConfigs/ModuleConfig.cs +++ b/src/PSRule/Definitions/ModuleConfigs/ModuleConfig.cs @@ -6,38 +6,37 @@ using PSRule.Pipeline; using YamlDotNet.Serialization; -namespace PSRule.Definitions.ModuleConfigs +namespace PSRule.Definitions.ModuleConfigs; + +/// +/// A module configuration resource. +/// +[Spec(Specs.V1, Specs.ModuleConfig)] +internal sealed class ModuleConfigV1 : InternalResource { - /// - /// A module configuration resource. - /// - [Spec(Specs.V1, Specs.ModuleConfig)] - internal sealed class ModuleConfigV1 : InternalResource - { - public ModuleConfigV1(string apiVersion, SourceFile source, ResourceMetadata metadata, IResourceHelpInfo info, ISourceExtent extent, ModuleConfigV1Spec spec) - : base(ResourceKind.ModuleConfig, apiVersion, source, metadata, info, extent, spec) { } - - /// - /// A human readable block of text, used to identify the purpose of the module config. - /// - [JsonIgnore] - [YamlIgnore] - public string Synopsis => Info.Synopsis.Text; - } + public ModuleConfigV1(string apiVersion, SourceFile source, ResourceMetadata metadata, IResourceHelpInfo info, ISourceExtent extent, ModuleConfigV1Spec spec) + : base(ResourceKind.ModuleConfig, apiVersion, source, metadata, info, extent, spec) { } /// - /// A specification for a module configuration. + /// A human readable block of text, used to identify the purpose of the module config. /// - internal sealed class ModuleConfigV1Spec : Spec - { - public BindingOption Binding { get; set; } + [JsonIgnore] + [YamlIgnore] + public string Synopsis => Info.Synopsis.Text; +} + +/// +/// A specification for a module configuration. +/// +internal sealed class ModuleConfigV1Spec : Spec +{ + public BindingOption Binding { get; set; } - public ConfigurationOption Configuration { get; set; } + public ConfigurationOption Configuration { get; set; } - public ConventionOption Convention { get; set; } + public ConventionOption Convention { get; set; } - public OutputOption Output { get; set; } + public OutputOption Output { get; set; } - public RuleOption Rule { get; set; } - } + public RuleOption Rule { get; set; } } diff --git a/src/PSRule/Definitions/Resource.cs b/src/PSRule/Definitions/Resource.cs index a4b0a6f957..2139f60e68 100644 --- a/src/PSRule/Definitions/Resource.cs +++ b/src/PSRule/Definitions/Resource.cs @@ -14,669 +14,668 @@ using YamlDotNet.Serialization.NamingConventions; using YamlDotNet.Serialization.NodeDeserializers; -namespace PSRule.Definitions +namespace PSRule.Definitions; + +/// +/// The type of resource. +/// +public enum ResourceKind +{ + /// + /// Unknown or empty. + /// + None = 0, + + /// + /// A rule resource. + /// + Rule = 1, + + /// + /// A baseline resource. + /// + Baseline = 2, + + /// + /// A module configuration resource. + /// + ModuleConfig = 3, + + /// + /// A selector resource. + /// + Selector = 4, + + /// + /// A convention. + /// + Convention = 5, + + /// + /// A suppression group. + /// + SuppressionGroup = 6 +} + +/// +/// Additional flags that indicate the status of the resource. +/// +[Flags] +public enum ResourceFlags +{ + /// + /// No flags are set. + /// + None = 0, + + /// + /// The resource is obsolete. + /// + Obsolete = 1 +} + +/// +/// A resource langange block. +/// +public interface IResource : ILanguageBlock { /// /// The type of resource. /// - public enum ResourceKind - { - /// - /// Unknown or empty. - /// - None = 0, - - /// - /// A rule resource. - /// - Rule = 1, - - /// - /// A baseline resource. - /// - Baseline = 2, - - /// - /// A module configuration resource. - /// - ModuleConfig = 3, - - /// - /// A selector resource. - /// - Selector = 4, - - /// - /// A convention. - /// - Convention = 5, - - /// - /// A suppression group. - /// - SuppressionGroup = 6 - } + ResourceKind Kind { get; } /// - /// Additional flags that indicate the status of the resource. + /// The API version of the resource. /// - [Flags] - public enum ResourceFlags - { - /// - /// No flags are set. - /// - None = 0, - - /// - /// The resource is obsolete. - /// - Obsolete = 1 - } + string ApiVersion { get; } + + /// + /// The name of the resource. + /// + string Name { get; } + + /// + /// An optional reference identifer for the resource. + /// + ResourceId? Ref { get; } + + /// + /// Any additional aliases for the resource. + /// + ResourceId[] Alias { get; } /// - /// A resource langange block. + /// Any resource tags. /// - public interface IResource : ILanguageBlock + ResourceTags Tags { get; } + + /// + /// Any taxonomy references. + /// + ResourceLabels Labels { get; } + + /// + /// Flags for the resource. + /// + ResourceFlags Flags { get; } + + /// + /// The source location of the resource. + /// + ISourceExtent Extent { get; } + + /// + /// Additional information about the resource. + /// + IResourceHelpInfo Info { get; } +} + +internal interface IResourceVisitor +{ + bool Visit(IResource resource); +} + +internal abstract class ResourceRef +{ + public readonly string Id; + public readonly ResourceKind Kind; + + protected ResourceRef(string id, ResourceKind kind) + { + Kind = kind; + Id = id; + } +} + +/// +/// A base resource annotation. +/// +internal abstract class ResourceAnnotation +{ + +} + +/// +/// Annotation used to flag validation issues. +/// +internal sealed class ValidateResourceAnnotation : ResourceAnnotation +{ + +} + +/// +/// A resource object. +/// +public sealed class ResourceObject +{ + internal ResourceObject(IResource block) { - /// - /// The type of resource. - /// - ResourceKind Kind { get; } - - /// - /// The API version of the resource. - /// - string ApiVersion { get; } - - /// - /// The name of the resource. - /// - string Name { get; } - - /// - /// An optional reference identifer for the resource. - /// - ResourceId? Ref { get; } - - /// - /// Any additional aliases for the resource. - /// - ResourceId[] Alias { get; } - - /// - /// Any resource tags. - /// - ResourceTags Tags { get; } - - /// - /// Any taxonomy references. - /// - ResourceLabels Labels { get; } - - /// - /// Flags for the resource. - /// - ResourceFlags Flags { get; } - - /// - /// The source location of the resource. - /// - ISourceExtent Extent { get; } - - /// - /// Additional information about the resource. - /// - IResourceHelpInfo Info { get; } + Block = block; } - internal interface IResourceVisitor + internal IResource Block { get; } + + internal bool Visit(IResourceVisitor visitor) { - bool Visit(IResource resource); + return Block != null && visitor != null && visitor.Visit(Block); } +} + +internal sealed class ResourceBuilder +{ + private readonly List _Output; + private readonly IDeserializer _Deserializer; - internal abstract class ResourceRef + internal ResourceBuilder() { - public readonly string Id; - public readonly ResourceKind Kind; + _Output = new List(); + _Deserializer = new DeserializerBuilder() + .IgnoreUnmatchedProperties() + .WithNamingConvention(CamelCaseNamingConvention.Instance) + .WithTypeConverter(new FieldMapYamlTypeConverter()) + .WithTypeConverter(new StringArrayMapConverter()) + .WithTypeConverter(new StringArrayConverter()) + .WithNodeDeserializer( + inner => new ResourceNodeDeserializer(new LanguageExpressionDeserializer(inner)), + s => s.InsteadOf()) + .Build(); + } - protected ResourceRef(string id, ResourceKind kind) + internal void FromFile(SourceFile file) + { + using var reader = new StreamReader(file.Path); + var parser = new YamlDotNet.Core.Parser(reader); + parser.TryConsume(out _); + while (parser.Current is DocumentStart) { - Kind = kind; - Id = id; + var item = _Deserializer.Deserialize(parser: parser); + if (item == null || item.Block == null) + continue; + + _Output.Add(item.Block); } } - /// - /// A base resource annotation. - /// - internal abstract class ResourceAnnotation + internal IEnumerable Build() { - + return _Output.Count == 0 ? Array.Empty() : _Output.ToArray(); } +} + +/// +/// Additional resource annotations. +/// +public sealed class ResourceAnnotations : Dictionary +{ + +} +/// +/// Additional resource taxonomy references. +/// +public sealed class ResourceLabels : Dictionary +{ /// - /// Annotation used to flag validation issues. + /// Create an empty set of resource labels. /// - internal sealed class ValidateResourceAnnotation : ResourceAnnotation - { - - } + public ResourceLabels() : base(StringComparer.OrdinalIgnoreCase) { } /// - /// A resource object. + /// Convert from a hashtable to resource labels. /// - public sealed class ResourceObject + internal static ResourceLabels FromHashtable(Hashtable hashtable) { - internal ResourceObject(IResource block) - { - Block = block; - } + if (hashtable == null || hashtable.Count == 0) + return null; - internal IResource Block { get; } - - internal bool Visit(IResourceVisitor visitor) + var annotations = new ResourceLabels(); + foreach (DictionaryEntry kv in hashtable) { - return Block != null && visitor != null && visitor.Visit(Block); + var key = kv.Key.ToString(); + if (hashtable.TryGetStringArray(key, out var value)) + annotations[key] = value; } + return annotations; } - internal sealed class ResourceBuilder + internal bool Contains(string key, string[] value) { - private readonly List _Output; - private readonly IDeserializer _Deserializer; + if (!TryGetValue(key, out var actual)) + return false; - internal ResourceBuilder() + if (value == null || value.Length == 0 || (value.Length == 1 && value[0] == "*")) + return true; + + for (var i = 0; i < value.Length; i++) { - _Output = new List(); - _Deserializer = new DeserializerBuilder() - .IgnoreUnmatchedProperties() - .WithNamingConvention(CamelCaseNamingConvention.Instance) - .WithTypeConverter(new FieldMapYamlTypeConverter()) - .WithTypeConverter(new StringArrayMapConverter()) - .WithTypeConverter(new StringArrayConverter()) - .WithNodeDeserializer( - inner => new ResourceNodeDeserializer(new LanguageExpressionDeserializer(inner)), - s => s.InsteadOf()) - .Build(); + if (Array.IndexOf(actual, value[i]) != -1) + return true; } + return false; + } +} - internal void FromFile(SourceFile file) - { - using var reader = new StreamReader(file.Path); - var parser = new YamlDotNet.Core.Parser(reader); - parser.TryConsume(out _); - while (parser.Current is DocumentStart) - { - var item = _Deserializer.Deserialize(parser: parser); - if (item == null || item.Block == null) - continue; +/// +/// Additional resource tags. +/// +public sealed class ResourceTags : Dictionary +{ + private Hashtable _Hashtable; - _Output.Add(item.Block); - } - } + /// + /// Create an empty set of resource tags. + /// + public ResourceTags() : base(StringComparer.OrdinalIgnoreCase) { } - internal IEnumerable Build() - { - return _Output.Count == 0 ? Array.Empty() : _Output.ToArray(); - } + /// + /// Convert from a hashtable to resource tags. + /// + internal static ResourceTags FromHashtable(Hashtable hashtable) + { + if (hashtable == null || hashtable.Count == 0) + return null; + + var tags = new ResourceTags(); + foreach (DictionaryEntry kv in hashtable) + tags[kv.Key.ToString()] = kv.Value.ToString(); + + return tags; } /// - /// Additional resource annotations. + /// Convert from a dictionary of string pairs to resource tags. /// - public sealed class ResourceAnnotations : Dictionary + internal static ResourceTags FromDictionary(Dictionary dictionary) { + if (dictionary == null) + return null; + var tags = new ResourceTags(); + foreach (var kv in dictionary) + tags[kv.Key] = kv.Value; + + return tags; } /// - /// Additional resource taxonomy references. + /// Convert resource tags to a hashtable. /// - public sealed class ResourceLabels : Dictionary + public Hashtable ToHashtable() { - /// - /// Create an empty set of resource labels. - /// - public ResourceLabels() : base(StringComparer.OrdinalIgnoreCase) { } - - /// - /// Convert from a hashtable to resource labels. - /// - internal static ResourceLabels FromHashtable(Hashtable hashtable) - { - if (hashtable == null || hashtable.Count == 0) - return null; + _Hashtable ??= new ReadOnlyHashtable(this, StringComparer.OrdinalIgnoreCase); + return _Hashtable; + } - var annotations = new ResourceLabels(); - foreach (DictionaryEntry kv in hashtable) - { - var key = kv.Key.ToString(); - if (hashtable.TryGetStringArray(key, out var value)) - annotations[key] = value; - } - return annotations; - } + /// + /// Check if a specific resource tag exists. + /// + internal bool Contains(object key, object value) + { + if (key == null || value == null || key is not string k || !ContainsKey(k)) + return false; - internal bool Contains(string key, string[] value) + if (TryArray(value, out var values)) { - if (!TryGetValue(key, out var actual)) - return false; - - if (value == null || value.Length == 0 || (value.Length == 1 && value[0] == "*")) - return true; - - for (var i = 0; i < value.Length; i++) + for (var i = 0; i < values.Length; i++) { - if (Array.IndexOf(actual, value[i]) != -1) + if (Comparer.Equals(values[i], this[k])) return true; } return false; } + var v = value.ToString(); + return v == "*" || Comparer.Equals(v, this[k]); + } + + private static bool TryArray(object o, out string[] values) + { + values = null; + if (o is string[] sArray) + { + values = sArray; + return true; + } + if (o is IEnumerable oValues) + { + var result = new List(); + foreach (var obj in oValues) + result.Add(obj.ToString()); + + values = result.ToArray(); + return true; + } + return false; } /// - /// Additional resource tags. + /// Convert the resourecs tags to a display string for PowerShell views. /// - public sealed class ResourceTags : Dictionary + /// + public string ToViewString() { - private Hashtable _Hashtable; + var sb = new StringBuilder(); + var i = 0; - /// - /// Create an empty set of resource tags. - /// - public ResourceTags() : base(StringComparer.OrdinalIgnoreCase) { } - - /// - /// Convert from a hashtable to resource tags. - /// - internal static ResourceTags FromHashtable(Hashtable hashtable) + foreach (var kv in this) { - if (hashtable == null || hashtable.Count == 0) - return null; - - var tags = new ResourceTags(); - foreach (DictionaryEntry kv in hashtable) - tags[kv.Key.ToString()] = kv.Value.ToString(); - - return tags; + if (i > 0) + sb.Append(System.Environment.NewLine); + + sb.Append(kv.Key); + sb.Append('='); + sb.Append('\''); + sb.Append(kv.Value); + sb.Append('\''); + i++; } + return sb.ToString(); + } +} - /// - /// Convert from a dictionary of string pairs to resource tags. - /// - internal static ResourceTags FromDictionary(Dictionary dictionary) - { - if (dictionary == null) - return null; +/// +/// Additional resource metadata. +/// +public sealed class ResourceMetadata +{ + /// + /// Create an empty set of metadata. + /// + public ResourceMetadata() + { + Annotations = new ResourceAnnotations(); + Tags = new ResourceTags(); + Labels = new ResourceLabels(); + } - var tags = new ResourceTags(); - foreach (var kv in dictionary) - tags[kv.Key] = kv.Value; + /// + /// The name of the resource. + /// + public string Name { get; set; } - return tags; - } + /// + /// A non-localized display name for the resource. + /// + public string DisplayName { get; set; } - /// - /// Convert resource tags to a hashtable. - /// - public Hashtable ToHashtable() - { - _Hashtable ??= new ReadOnlyHashtable(this, StringComparer.OrdinalIgnoreCase); - return _Hashtable; - } + /// + /// A non-localized description of the resource. + /// + public string Description { get; set; } - /// - /// Check if a specific resource tag exists. - /// - internal bool Contains(object key, object value) - { - if (key == null || value == null || key is not string k || !ContainsKey(k)) - return false; + /// + /// A opaque reference for the resource. + /// + public string Ref { get; set; } - if (TryArray(value, out var values)) - { - for (var i = 0; i < values.Length; i++) - { - if (Comparer.Equals(values[i], this[k])) - return true; - } - return false; - } - var v = value.ToString(); - return v == "*" || Comparer.Equals(v, this[k]); - } + /// + /// Additional aliases for the resource. + /// + public string[] Alias { get; set; } - private static bool TryArray(object o, out string[] values) - { - values = null; - if (o is string[] sArray) - { - values = sArray; - return true; - } - if (o is IEnumerable oValues) - { - var result = new List(); - foreach (var obj in oValues) - result.Add(obj.ToString()); + /// + /// Any resource annotations. + /// + [YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitEmptyCollections)] + public ResourceAnnotations Annotations { get; set; } - values = result.ToArray(); - return true; - } - return false; - } + /// + /// Any resource tags. + /// + [YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitEmptyCollections)] + public ResourceTags Tags { get; set; } - /// - /// Convert the resourecs tags to a display string for PowerShell views. - /// - /// - public string ToViewString() - { - var sb = new StringBuilder(); - var i = 0; + /// + /// Any taxonomy references. + /// + [YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitEmptyCollections)] + public ResourceLabels Labels { get; set; } - foreach (var kv in this) - { - if (i > 0) - sb.Append(System.Environment.NewLine); - - sb.Append(kv.Key); - sb.Append('='); - sb.Append('\''); - sb.Append(kv.Value); - sb.Append('\''); - i++; - } - return sb.ToString(); - } - } + /// + /// A URL to documentation for the resource. + /// + public string Link { get; set; } +} +/// +/// The source location of the resource. +/// +public sealed class ResourceExtent +{ /// - /// Additional resource metadata. + /// The file where the resource is located. /// - public sealed class ResourceMetadata - { - /// - /// Create an empty set of metadata. - /// - public ResourceMetadata() - { - Annotations = new ResourceAnnotations(); - Tags = new ResourceTags(); - Labels = new ResourceLabels(); - } + public string File { get; set; } - /// - /// The name of the resource. - /// - public string Name { get; set; } - - /// - /// A non-localized display name for the resource. - /// - public string DisplayName { get; set; } - - /// - /// A non-localized description of the resource. - /// - public string Description { get; set; } - - /// - /// A opaque reference for the resource. - /// - public string Ref { get; set; } - - /// - /// Additional aliases for the resource. - /// - public string[] Alias { get; set; } - - /// - /// Any resource annotations. - /// - [YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitEmptyCollections)] - public ResourceAnnotations Annotations { get; set; } - - /// - /// Any resource tags. - /// - [YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitEmptyCollections)] - public ResourceTags Tags { get; set; } - - /// - /// Any taxonomy references. - /// - [YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitEmptyCollections)] - public ResourceLabels Labels { get; set; } - - /// - /// A URL to documentation for the resource. - /// - public string Link { get; set; } - } + /// + /// The name of the module if the resource is contained within a module. + /// + public string Module { get; set; } +} +/// +/// A base class for resources. +/// +/// The type for the resource specification. +[DebuggerDisplay("Kind = {Kind}, Id = {Id}")] +public abstract class Resource where TSpec : Spec, new() +{ /// - /// The source location of the resource. + /// Create a resource. /// - public sealed class ResourceExtent + protected internal Resource(ResourceKind kind, string apiVersion, SourceFile source, ResourceMetadata metadata, IResourceHelpInfo info, ISourceExtent extent, TSpec spec) { - /// - /// The file where the resource is located. - /// - public string File { get; set; } - - /// - /// The name of the module if the resource is contained within a module. - /// - public string Module { get; set; } + Kind = kind; + ApiVersion = apiVersion; + Info = info; + Source = source; + Extent = extent; + Spec = spec; + Metadata = metadata; + Name = metadata.Name; + Id = new ResourceId(source.Module, Name, ResourceIdKind.Id); } /// - /// A base class for resources. + /// The resource identifier for the resource. /// - /// The type for the resource specification. - [DebuggerDisplay("Kind = {Kind}, Id = {Id}")] - public abstract class Resource where TSpec : Spec, new() - { - /// - /// Create a resource. - /// - protected internal Resource(ResourceKind kind, string apiVersion, SourceFile source, ResourceMetadata metadata, IResourceHelpInfo info, ISourceExtent extent, TSpec spec) - { - Kind = kind; - ApiVersion = apiVersion; - Info = info; - Source = source; - Extent = extent; - Spec = spec; - Metadata = metadata; - Name = metadata.Name; - Id = new ResourceId(source.Module, Name, ResourceIdKind.Id); - } + [YamlIgnore()] + public ResourceId Id { get; } - /// - /// The resource identifier for the resource. - /// - [YamlIgnore()] - public ResourceId Id { get; } - - /// - /// The name of the resource. - /// - [YamlIgnore()] - public string Name { get; } - - /// - /// The name of the module where the resource is defined. - /// - [YamlIgnore()] - public string Module => Source.Module; - - /// - /// The file path where the resource is defined. - /// - [YamlIgnore()] - public SourceFile Source { get; } - - /// - /// Information about the resource. - /// - [YamlIgnore()] - public IResourceHelpInfo Info { get; } - - /// - /// Resource metadata details. - /// - public ResourceMetadata Metadata { get; } - - /// - /// The type of resource. - /// - public ResourceKind Kind { get; } - - /// - /// The API version of the resource. - /// - public string ApiVersion { get; } - - /// - /// The child specification of the resource. - /// - public TSpec Spec { get; } - - /// - /// The source location of the resource. - /// - public ISourceExtent Extent { get; } - } + /// + /// The name of the resource. + /// + [YamlIgnore()] + public string Name { get; } /// - /// A base class for built-in resource types. + /// The name of the module where the resource is defined. /// - /// The type of the related for the resource. - public abstract class InternalResource : Resource, IResource, IAnnotated where TSpec : Spec, new() - { - private readonly Dictionary _Annotations; + [YamlIgnore()] + public string Module => Source.Module; - private protected InternalResource(ResourceKind kind, string apiVersion, SourceFile source, ResourceMetadata metadata, IResourceHelpInfo info, ISourceExtent extent, TSpec spec) - : base(kind, apiVersion, source, metadata, info, extent, spec) - { - _Annotations = new Dictionary(); - Obsolete = ResourceHelper.IsObsolete(metadata); - Flags |= ResourceHelper.IsObsolete(metadata) ? ResourceFlags.Obsolete : ResourceFlags.None; - } + /// + /// The file path where the resource is defined. + /// + [YamlIgnore()] + public SourceFile Source { get; } - [YamlIgnore()] - internal readonly bool Obsolete; + /// + /// Information about the resource. + /// + [YamlIgnore()] + public IResourceHelpInfo Info { get; } - [YamlIgnore()] - internal ResourceFlags Flags { get; } + /// + /// Resource metadata details. + /// + public ResourceMetadata Metadata { get; } - ResourceKind IResource.Kind => Kind; + /// + /// The type of resource. + /// + public ResourceKind Kind { get; } - string IResource.ApiVersion => ApiVersion; + /// + /// The API version of the resource. + /// + public string ApiVersion { get; } - string IResource.Name => Name; + /// + /// The child specification of the resource. + /// + public TSpec Spec { get; } - // Not supported with base resources. - ResourceId? IResource.Ref => null; + /// + /// The source location of the resource. + /// + public ISourceExtent Extent { get; } +} - // Not supported with base resources. - ResourceId[] IResource.Alias => null; +/// +/// A base class for built-in resource types. +/// +/// The type of the related for the resource. +public abstract class InternalResource : Resource, IResource, IAnnotated where TSpec : Spec, new() +{ + private readonly Dictionary _Annotations; - ResourceTags IResource.Tags => Metadata.Tags; + private protected InternalResource(ResourceKind kind, string apiVersion, SourceFile source, ResourceMetadata metadata, IResourceHelpInfo info, ISourceExtent extent, TSpec spec) + : base(kind, apiVersion, source, metadata, info, extent, spec) + { + _Annotations = new Dictionary(); + Obsolete = ResourceHelper.IsObsolete(metadata); + Flags |= ResourceHelper.IsObsolete(metadata) ? ResourceFlags.Obsolete : ResourceFlags.None; + } - ResourceLabels IResource.Labels => Metadata.Labels; + [YamlIgnore()] + internal readonly bool Obsolete; - ResourceFlags IResource.Flags => Flags; + [YamlIgnore()] + internal ResourceFlags Flags { get; } - TAnnotation IAnnotated.GetAnnotation() - { - return _Annotations.TryGetValue(typeof(TAnnotation), out var annotation) ? (TAnnotation)annotation : null; - } + ResourceKind IResource.Kind => Kind; - void IAnnotated.SetAnnotation(TAnnotation annotation) - { - _Annotations[typeof(TAnnotation)] = annotation; - } + string IResource.ApiVersion => ApiVersion; + + string IResource.Name => Name; + + // Not supported with base resources. + ResourceId? IResource.Ref => null; + + // Not supported with base resources. + ResourceId[] IResource.Alias => null; + + ResourceTags IResource.Tags => Metadata.Tags; + + ResourceLabels IResource.Labels => Metadata.Labels; + + ResourceFlags IResource.Flags => Flags; + + TAnnotation IAnnotated.GetAnnotation() + { + return _Annotations.TryGetValue(typeof(TAnnotation), out var annotation) ? (TAnnotation)annotation : null; } - internal static class ResourceHelper + void IAnnotated.SetAnnotation(TAnnotation annotation) { - private const string ANNOTATION_OBSOLETE = "obsolete"; + _Annotations[typeof(TAnnotation)] = annotation; + } +} - private const char SCOPE_SEPARATOR = '\\'; +internal static class ResourceHelper +{ + private const string ANNOTATION_OBSOLETE = "obsolete"; - internal static string GetIdString(string scope, string name) - { - return name.IndexOf(SCOPE_SEPARATOR) >= 0 - ? name - : string.Concat( - LanguageScope.Normalize(scope), - SCOPE_SEPARATOR, - name - ); - } + private const char SCOPE_SEPARATOR = '\\'; - internal static void ParseIdString(string defaultScope, string id, out string scope, out string name) - { - ParseIdString(id, out scope, out name); - scope ??= LanguageScope.Normalize(defaultScope); - } + internal static string GetIdString(string scope, string name) + { + return name.IndexOf(SCOPE_SEPARATOR) >= 0 + ? name + : string.Concat( + LanguageScope.Normalize(scope), + SCOPE_SEPARATOR, + name + ); + } - internal static void ParseIdString(string id, out string scope, out string name) - { - scope = null; - name = null; - if (string.IsNullOrEmpty(id)) - return; - - var scopeSeparator = id.IndexOf(SCOPE_SEPARATOR); - scope = scopeSeparator >= 0 ? id.Substring(0, scopeSeparator) : null; - name = id.Substring(scopeSeparator + 1); - } + internal static void ParseIdString(string defaultScope, string id, out string scope, out string name) + { + ParseIdString(id, out scope, out name); + scope ??= LanguageScope.Normalize(defaultScope); + } - /// - /// Checks each resource name and converts each into a full qualified . - /// - /// The default scope to use if the resource name if not fully qualified. - /// An array of names. Qualified names (RuleIds) supplied are left intact. - /// The of the . - /// An array of RuleIds. - internal static ResourceId[] GetRuleId(string defaultScope, string[] name, ResourceIdKind kind) - { - if (name == null || name.Length == 0) - return null; + internal static void ParseIdString(string id, out string scope, out string name) + { + scope = null; + name = null; + if (string.IsNullOrEmpty(id)) + return; + + var scopeSeparator = id.IndexOf(SCOPE_SEPARATOR); + scope = scopeSeparator >= 0 ? id.Substring(0, scopeSeparator) : null; + name = id.Substring(scopeSeparator + 1); + } - var result = new ResourceId[name.Length]; - for (var i = 0; i < name.Length; i++) - result[i] = GetRuleId(defaultScope, name[i], kind); + /// + /// Checks each resource name and converts each into a full qualified . + /// + /// The default scope to use if the resource name if not fully qualified. + /// An array of names. Qualified names (RuleIds) supplied are left intact. + /// The of the . + /// An array of RuleIds. + internal static ResourceId[] GetRuleId(string defaultScope, string[] name, ResourceIdKind kind) + { + if (name == null || name.Length == 0) + return null; - return (result.Length == 0) ? null : result; - } + var result = new ResourceId[name.Length]; + for (var i = 0; i < name.Length; i++) + result[i] = GetRuleId(defaultScope, name[i], kind); - internal static ResourceId GetRuleId(string defaultScope, string name, ResourceIdKind kind) - { - return name.IndexOf(SCOPE_SEPARATOR) > 0 ? ResourceId.Parse(name, kind) : new ResourceId(defaultScope, name, kind); - } + return (result.Length == 0) ? null : result; + } - internal static ResourceId? GetIdNullable(string scope, string name, ResourceIdKind kind) - { - return string.IsNullOrEmpty(name) ? null : new ResourceId(scope, name, kind); - } + internal static ResourceId GetRuleId(string defaultScope, string name, ResourceIdKind kind) + { + return name.IndexOf(SCOPE_SEPARATOR) > 0 ? ResourceId.Parse(name, kind) : new ResourceId(defaultScope, name, kind); + } - internal static bool IsObsolete(ResourceMetadata metadata) - { - return metadata != null && - metadata.Annotations != null && - metadata.Annotations.TryGetBool(ANNOTATION_OBSOLETE, out var obsolete) - && obsolete.GetValueOrDefault(false); - } + internal static ResourceId? GetIdNullable(string scope, string name, ResourceIdKind kind) + { + return string.IsNullOrEmpty(name) ? null : new ResourceId(scope, name, kind); + } - internal static SeverityLevel GetLevel(SeverityLevel? level) - { - return !level.HasValue || level.Value == SeverityLevel.None ? RuleV1.DEFAULT_LEVEL : level.Value; - } + internal static bool IsObsolete(ResourceMetadata metadata) + { + return metadata != null && + metadata.Annotations != null && + metadata.Annotations.TryGetBool(ANNOTATION_OBSOLETE, out var obsolete) + && obsolete.GetValueOrDefault(false); + } + + internal static SeverityLevel GetLevel(SeverityLevel? level) + { + return !level.HasValue || level.Value == SeverityLevel.None ? RuleV1.DEFAULT_LEVEL : level.Value; } } diff --git a/src/PSRule/Definitions/ResourceId.cs b/src/PSRule/Definitions/ResourceId.cs index da8850fcf0..b8b7cdfbab 100644 --- a/src/PSRule/Definitions/ResourceId.cs +++ b/src/PSRule/Definitions/ResourceId.cs @@ -4,256 +4,255 @@ using System.Diagnostics; using PSRule.Runtime; -namespace PSRule.Definitions +namespace PSRule.Definitions; + +/// +/// Additional information about the type of identifier if available. +/// +internal enum ResourceIdKind { /// - /// Additional information about the type of identifier if available. + /// Not specified. /// - internal enum ResourceIdKind - { - /// - /// Not specified. - /// - None = 0, - - /// - /// Unknown. - /// - Unknown = 1, - - /// - /// The identifier is a primary resource identifier. - /// - Id = 2, - - /// - /// The identifier is a opaque reference resource identifier. - /// - Ref = 3, - - /// - /// The identifier is an alias resource identifier. - /// - Alias = 4, - } + None = 0, /// - /// A unique identifier for a resource. + /// Unknown. /// - [DebuggerDisplay("{Value}")] - public struct ResourceId : IEquatable, IEquatable - { - private const char SCOPE_SEPARATOR = '\\'; + Unknown = 1, - private readonly int _HashCode; + /// + /// The identifier is a primary resource identifier. + /// + Id = 2, - private ResourceId(string id, string scope, string name, ResourceIdKind kind) - { - Value = id; - Scope = scope; - Name = name; - Kind = kind; - _HashCode = GetHashCode(id); - } + /// + /// The identifier is a opaque reference resource identifier. + /// + Ref = 3, - internal ResourceId(string scope, string name, ResourceIdKind kind) - : this(GetIdString(scope, name), LanguageScope.Normalize(scope), name, kind) { } - - /// - /// A string representation of the resource identifier. - /// - public string Value { get; } - - /// - /// The scope of the resource. - /// - public string Scope { get; } - - /// - /// A unique name for the resource within the specified . - /// - public string Name { get; } - - /// - /// The type of resource identifier. - /// - internal ResourceIdKind Kind { get; } - - /// - /// Converts the resource identifier to a string. - /// - /// - /// This is the same as . - /// - /// A string representation of the resource identifier. - public override string ToString() - { - return Value; - } + /// + /// The identifier is an alias resource identifier. + /// + Alias = 4, +} - /// - [DebuggerStepThrough] - public override int GetHashCode() - { - return _HashCode; - } +/// +/// A unique identifier for a resource. +/// +[DebuggerDisplay("{Value}")] +public struct ResourceId : IEquatable, IEquatable +{ + private const char SCOPE_SEPARATOR = '\\'; - /// - public override bool Equals(object obj) - { - return obj is ResourceId id && Equals(id); - } + private readonly int _HashCode; - /// - public bool Equals(ResourceId id) - { - return _HashCode == id._HashCode && - EqualOrNull(Scope, id.Scope) && - EqualOrNull(Name, id.Name); - } + private ResourceId(string id, string scope, string name, ResourceIdKind kind) + { + Value = id; + Scope = scope; + Name = name; + Kind = kind; + _HashCode = GetHashCode(id); + } - /// - public bool Equals(string id) - { - return TryParse(id, out var scope, out var name) && - EqualOrNull(Scope, scope) && - EqualOrNull(Name, name); - } + internal ResourceId(string scope, string name, ResourceIdKind kind) + : this(GetIdString(scope, name), LanguageScope.Normalize(scope), name, kind) { } - /// - /// Compare two resource identifiers to determine if they are equal. - /// - public static bool operator ==(ResourceId left, ResourceId right) - { - return left.Equals(right); - } + /// + /// A string representation of the resource identifier. + /// + public string Value { get; } - /// - /// Compare two resource identifiers to determine if they are not equal. - /// - public static bool operator !=(ResourceId left, ResourceId right) - { - return !left.Equals(right); - } + /// + /// The scope of the resource. + /// + public string Scope { get; } - private static bool EqualOrNull(string x, string y) - { - return x == null || y == null || StringComparer.OrdinalIgnoreCase.Equals(x, y); - } + /// + /// A unique name for the resource within the specified . + /// + public string Name { get; } - private static int GetHashCode(string id) - { - return id.ToLowerInvariant().GetHashCode(); - } + /// + /// The type of resource identifier. + /// + internal ResourceIdKind Kind { get; } - private static string GetIdString(string scope, string name) - { - return string.Concat( - LanguageScope.Normalize(scope), - SCOPE_SEPARATOR, - name - ); - } + /// + /// Converts the resource identifier to a string. + /// + /// + /// This is the same as . + /// + /// A string representation of the resource identifier. + public override string ToString() + { + return Value; + } - internal static ResourceId Parse(string id, ResourceIdKind kind = ResourceIdKind.Unknown) - { - return TryParse(id, kind, out var value) ? value.Value : default; - } + /// + [DebuggerStepThrough] + public override int GetHashCode() + { + return _HashCode; + } - private static bool TryParse(string id, ResourceIdKind kind, out ResourceId? value) - { - value = null; - if (string.IsNullOrEmpty(id) || !TryParse(id, out var scope, out var name)) - return false; + /// + public override bool Equals(object obj) + { + return obj is ResourceId id && Equals(id); + } - value = new ResourceId(id, scope, name, kind); - return true; - } + /// + public bool Equals(ResourceId id) + { + return _HashCode == id._HashCode && + EqualOrNull(Scope, id.Scope) && + EqualOrNull(Name, id.Name); + } - private static bool TryParse(string id, out string scope, out string name) - { - scope = null; - name = null; - if (string.IsNullOrEmpty(id)) - return false; - - var scopeSeparatorIndex = id.IndexOf(SCOPE_SEPARATOR); - scope = scopeSeparatorIndex >= 0 ? id.Substring(0, scopeSeparatorIndex) : null; - name = id.Substring(scopeSeparatorIndex + 1); - return true; - } + /// + public bool Equals(string id) + { + return TryParse(id, out var scope, out var name) && + EqualOrNull(Scope, scope) && + EqualOrNull(Name, name); } /// - /// Compares two resource identifiers to determine if they are equal. + /// Compare two resource identifiers to determine if they are equal. /// - internal sealed class ResourceIdEqualityComparer : IEqualityComparer, IEqualityComparer + public static bool operator ==(ResourceId left, ResourceId right) { - public static readonly ResourceIdEqualityComparer Default = new(); + return left.Equals(right); + } - public static bool IdEquals(ResourceId x, ResourceId y) - { - return EqualOrNull(x.Scope, y.Scope) && - EqualOrNull(x.Name, y.Name); - } + /// + /// Compare two resource identifiers to determine if they are not equal. + /// + public static bool operator !=(ResourceId left, ResourceId right) + { + return !left.Equals(right); + } - public static bool IdEquals(ResourceId x, string y) - { - return x.Equals(y); - } + private static bool EqualOrNull(string x, string y) + { + return x == null || y == null || StringComparer.OrdinalIgnoreCase.Equals(x, y); + } - public static bool IdEquals(string x, string y) - { - ResourceHelper.ParseIdString(x, out var scope_x, out var name_x); - ResourceHelper.ParseIdString(y, out var scope_y, out var name_y); - return EqualOrNull(scope_x, scope_y) && - EqualOrNull(name_x, name_y); - } + private static int GetHashCode(string id) + { + return id.ToLowerInvariant().GetHashCode(); + } - #region IEqualityComparer + private static string GetIdString(string scope, string name) + { + return string.Concat( + LanguageScope.Normalize(scope), + SCOPE_SEPARATOR, + name + ); + } - public bool Equals(ResourceId x, ResourceId y) - { - return IdEquals(x, y); - } + internal static ResourceId Parse(string id, ResourceIdKind kind = ResourceIdKind.Unknown) + { + return TryParse(id, kind, out var value) ? value.Value : default; + } - public int GetHashCode(ResourceId obj) - { - unchecked // Overflow is fine - { - var hash = 17; - hash = hash * 23 + (obj.Scope != null ? obj.Scope.GetHashCode() : 0); - hash = hash * 23 + (obj.Name != null ? obj.Name.GetHashCode() : 0); - return hash; - } - } + private static bool TryParse(string id, ResourceIdKind kind, out ResourceId? value) + { + value = null; + if (string.IsNullOrEmpty(id) || !TryParse(id, out var scope, out var name)) + return false; + + value = new ResourceId(id, scope, name, kind); + return true; + } - #endregion IEqualityComparer + private static bool TryParse(string id, out string scope, out string name) + { + scope = null; + name = null; + if (string.IsNullOrEmpty(id)) + return false; + + var scopeSeparatorIndex = id.IndexOf(SCOPE_SEPARATOR); + scope = scopeSeparatorIndex >= 0 ? id.Substring(0, scopeSeparatorIndex) : null; + name = id.Substring(scopeSeparatorIndex + 1); + return true; + } +} - #region IEqualityComparer +/// +/// Compares two resource identifiers to determine if they are equal. +/// +internal sealed class ResourceIdEqualityComparer : IEqualityComparer, IEqualityComparer +{ + public static readonly ResourceIdEqualityComparer Default = new(); - public bool Equals(string x, string y) - { - return IdEquals(x, y); - } + public static bool IdEquals(ResourceId x, ResourceId y) + { + return EqualOrNull(x.Scope, y.Scope) && + EqualOrNull(x.Name, y.Name); + } + + public static bool IdEquals(ResourceId x, string y) + { + return x.Equals(y); + } + + public static bool IdEquals(string x, string y) + { + ResourceHelper.ParseIdString(x, out var scope_x, out var name_x); + ResourceHelper.ParseIdString(y, out var scope_y, out var name_y); + return EqualOrNull(scope_x, scope_y) && + EqualOrNull(name_x, name_y); + } + + #region IEqualityComparer - public int GetHashCode(string obj) + public bool Equals(ResourceId x, ResourceId y) + { + return IdEquals(x, y); + } + + public int GetHashCode(ResourceId obj) + { + unchecked // Overflow is fine { - ResourceHelper.ParseIdString(obj, out var scope, out var name); - unchecked // Overflow is fine - { - var hash = 17; - hash = hash * 23 + (scope != null ? scope.GetHashCode() : 0); - hash = hash * 23 + (name != null ? name.GetHashCode() : 0); - return hash; - } + var hash = 17; + hash = hash * 23 + (obj.Scope != null ? obj.Scope.GetHashCode() : 0); + hash = hash * 23 + (obj.Name != null ? obj.Name.GetHashCode() : 0); + return hash; } + } + + #endregion IEqualityComparer + + #region IEqualityComparer - #endregion IEqualityComparer + public bool Equals(string x, string y) + { + return IdEquals(x, y); + } - private static bool EqualOrNull(string x, string y) + public int GetHashCode(string obj) + { + ResourceHelper.ParseIdString(obj, out var scope, out var name); + unchecked // Overflow is fine { - return x == null || y == null || StringComparer.OrdinalIgnoreCase.Equals(x, y); + var hash = 17; + hash = hash * 23 + (scope != null ? scope.GetHashCode() : 0); + hash = hash * 23 + (name != null ? name.GetHashCode() : 0); + return hash; } } + + #endregion IEqualityComparer + + private static bool EqualOrNull(string x, string y) + { + return x == null || y == null || StringComparer.OrdinalIgnoreCase.Equals(x, y); + } } diff --git a/src/PSRule/Definitions/ResourceIndex.cs b/src/PSRule/Definitions/ResourceIndex.cs index ab1b881ede..a8e5bed4fd 100644 --- a/src/PSRule/Definitions/ResourceIndex.cs +++ b/src/PSRule/Definitions/ResourceIndex.cs @@ -1,65 +1,64 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -namespace PSRule.Definitions +namespace PSRule.Definitions; + +internal sealed class ResourceIndex { - internal sealed class ResourceIndex + private readonly IndexEntry[] _Items; + + public ResourceIndex(IEnumerable items) { - private readonly IndexEntry[] _Items; + _Items = Load(items); + } - public ResourceIndex(IEnumerable items) - { - _Items = Load(items); - } + internal sealed class IndexEntry + { + public readonly ResourceId Id; + public readonly ResourceId Target; - internal sealed class IndexEntry + public IndexEntry(ResourceId id, ResourceId target) { - public readonly ResourceId Id; - public readonly ResourceId Target; - - public IndexEntry(ResourceId id, ResourceId target) - { - Id = id; - Target = target; - } + Id = id; + Target = target; } + } - public bool TryFind(string id, out ResourceId value, out ResourceIdKind kind) + public bool TryFind(string id, out ResourceId value, out ResourceIdKind kind) + { + value = default; + kind = default; + for (var i = 0; i < _Items.Length; i++) { - value = default; - kind = default; - for (var i = 0; i < _Items.Length; i++) + if (_Items[i].Id.Equals(id)) { - if (_Items[i].Id.Equals(id)) - { - value = _Items[i].Target; - kind = _Items[i].Id.Kind; - return true; - } + value = _Items[i].Target; + kind = _Items[i].Id.Kind; + return true; } - return false; } + return false; + } - private static IndexEntry[] Load(IEnumerable items) + private static IndexEntry[] Load(IEnumerable items) + { + var results = new List(); + foreach (var item in items) { - var results = new List(); - foreach (var item in items) - { - var ids = item.GetIds(); - foreach (var id in ids) - results.Add(new IndexEntry(id, item.Id)); - } - return results.ToArray(); + var ids = item.GetIds(); + foreach (var id in ids) + results.Add(new IndexEntry(id, item.Id)); } + return results.ToArray(); + } - public bool IsEmpty() - { - return _Items == null || _Items.Length == 0; - } + public bool IsEmpty() + { + return _Items == null || _Items.Length == 0; + } - public IndexEntry[] GetItems() - { - return _Items; - } + public IndexEntry[] GetItems() + { + return _Items; } } diff --git a/src/PSRule/Definitions/ResourceValidator.cs b/src/PSRule/Definitions/ResourceValidator.cs index 05d81311d3..4894cd00a1 100644 --- a/src/PSRule/Definitions/ResourceValidator.cs +++ b/src/PSRule/Definitions/ResourceValidator.cs @@ -6,94 +6,93 @@ using PSRule.Resources; using PSRule.Runtime; -namespace PSRule.Definitions +namespace PSRule.Definitions; + +internal interface IResourceValidator : IResourceVisitor { - internal interface IResourceValidator : IResourceVisitor - { +} + +/// +/// A helper class to help validate a resource object. +/// +internal sealed class ResourceValidator : IResourceValidator +{ + private const string ERRORID_INVALIDRESOURCENAME = "PSRule.Parse.InvalidResourceName"; + + private static readonly Regex ValidName = new("^[^<>:/\\\\|?*\"'`+@._\\-\x00-\x1F][^<>:/\\\\|?*\"'`+@\x00-\x1F]{1,126}[^<>:/\\\\|?*\"'`+@._\\-\x00-\x1F]$", RegexOptions.Compiled, TimeSpan.FromSeconds(5)); + + private readonly RunspaceContext _Context; + + public ResourceValidator(RunspaceContext context) + { + _Context = context; } - /// - /// A helper class to help validate a resource object. - /// - internal sealed class ResourceValidator : IResourceValidator + internal static bool IsNameValid(string name) { - private const string ERRORID_INVALIDRESOURCENAME = "PSRule.Parse.InvalidResourceName"; + return !string.IsNullOrEmpty(name) && ValidName.Match(name).Success; + } - private static readonly Regex ValidName = new("^[^<>:/\\\\|?*\"'`+@._\\-\x00-\x1F][^<>:/\\\\|?*\"'`+@\x00-\x1F]{1,126}[^<>:/\\\\|?*\"'`+@._\\-\x00-\x1F]$", RegexOptions.Compiled, TimeSpan.FromSeconds(5)); + public bool Visit(IResource resource) + { + return VisitName(resource, resource.Name) && + VisitName(resource, resource.Ref) && + VisitName(resource, resource.Alias); + } - private readonly RunspaceContext _Context; + private bool VisitName(IResource resource, string name) + { + if (IsNameValid(name)) + return true; - public ResourceValidator(RunspaceContext context) - { - _Context = context; - } + ReportError(ERRORID_INVALIDRESOURCENAME, PSRuleResources.InvalidResourceName, name, ReportExtent(resource.Extent)); + return false; + } - internal static bool IsNameValid(string name) - { - return !string.IsNullOrEmpty(name) && ValidName.Match(name).Success; - } + private bool VisitName(IResource resource, ResourceId? name) + { + return !name.HasValue || VisitName(resource, name.Value.Name); + } - public bool Visit(IResource resource) - { - return VisitName(resource, resource.Name) && - VisitName(resource, resource.Ref) && - VisitName(resource, resource.Alias); - } + private bool VisitName(IResource resource, ResourceId[] name) + { + if (name == null || name.Length == 0) + return true; - private bool VisitName(IResource resource, string name) - { - if (IsNameValid(name)) - return true; + for (var i = 0; i < name.Length; i++) + if (!VisitName(resource, name[i].Name)) + return false; - ReportError(ERRORID_INVALIDRESOURCENAME, PSRuleResources.InvalidResourceName, name, ReportExtent(resource.Extent)); - return false; - } + return true; + } - private bool VisitName(IResource resource, ResourceId? name) - { - return !name.HasValue || VisitName(resource, name.Value.Name); - } + private static string ReportExtent(ISourceExtent extent) + { + return string.Concat(extent.File, " line ", extent.Line); + } - private bool VisitName(IResource resource, ResourceId[] name) - { - if (name == null || name.Length == 0) - return true; + private void ReportError(string errorId, string message, params object[] args) + { + if (_Context == null) + return; - for (var i = 0; i < name.Length; i++) - if (!VisitName(resource, name[i].Name)) - return false; + ReportError(new Pipeline.ParseException( + message: string.Format(Thread.CurrentThread.CurrentCulture, message, args), + errorId: errorId + )); + } - return true; - } - - private static string ReportExtent(ISourceExtent extent) - { - return string.Concat(extent.File, " line ", extent.Line); - } - - private void ReportError(string errorId, string message, params object[] args) - { - if (_Context == null) - return; - - ReportError(new Pipeline.ParseException( - message: string.Format(Thread.CurrentThread.CurrentCulture, message, args), - errorId: errorId - )); - } - - private void ReportError(Pipeline.ParseException exception) - { - if (_Context == null) - return; - - _Context.WriteError(new ErrorRecord( - exception: exception, - errorId: exception.ErrorId, - errorCategory: ErrorCategory.InvalidOperation, - targetObject: null - )); - } + private void ReportError(Pipeline.ParseException exception) + { + if (_Context == null) + return; + + _Context.WriteError(new ErrorRecord( + exception: exception, + errorId: exception.ErrorId, + errorCategory: ErrorCategory.InvalidOperation, + targetObject: null + )); } } diff --git a/src/PSRule/Definitions/ResultDetail.cs b/src/PSRule/Definitions/ResultDetail.cs index 32fbf6d6b4..d6c6d633b3 100644 --- a/src/PSRule/Definitions/ResultDetail.cs +++ b/src/PSRule/Definitions/ResultDetail.cs @@ -1,29 +1,28 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -namespace PSRule.Definitions -{ - internal sealed class ResultDetail : IResultDetailV2 - { - private readonly IList _Reason; +namespace PSRule.Definitions; - internal ResultDetail() - { - _Reason = new List(); - } +internal sealed class ResultDetail : IResultDetailV2 +{ + private readonly IList _Reason; - internal int Count => _Reason.Count; + internal ResultDetail() + { + _Reason = new List(); + } - internal void Add(ResultReason reason) - { - _Reason.Add(reason); - } + internal int Count => _Reason.Count; - internal string[] GetReasonStrings() - { - return _Reason.GetStrings(); - } + internal void Add(ResultReason reason) + { + _Reason.Add(reason); + } - IEnumerable IResultDetailV2.Reason => _Reason; + internal string[] GetReasonStrings() + { + return _Reason.GetStrings(); } + + IEnumerable IResultDetailV2.Reason => _Reason; } diff --git a/src/PSRule/Definitions/ResultReason.cs b/src/PSRule/Definitions/ResultReason.cs index 4c3525c6fd..1e2432458f 100644 --- a/src/PSRule/Definitions/ResultReason.cs +++ b/src/PSRule/Definitions/ResultReason.cs @@ -3,102 +3,101 @@ using PSRule.Runtime; -namespace PSRule.Definitions +namespace PSRule.Definitions; + +internal sealed class ResultReason : IResultReasonV2 { - internal sealed class ResultReason : IResultReasonV2 - { - private string _Path; - private string _Formatted; - private string _Message; - private string _FullPath; - private readonly string _ParentPath; + private string _Path; + private string _Formatted; + private string _Message; + private string _FullPath; + private readonly string _ParentPath; - internal ResultReason(string parentPath, IOperand operand, string text, object[] args) - { - _ParentPath = parentPath; - Operand = operand; - _Path = Operand?.Path; - Text = text; - Args = args; - } + internal ResultReason(string parentPath, IOperand operand, string text, object[] args) + { + _ParentPath = parentPath; + Operand = operand; + _Path = Operand?.Path; + Text = text; + Args = args; + } - internal IOperand Operand { get; } + internal IOperand Operand { get; } - /// - /// The object path that failed. - /// - public string Path + /// + /// The object path that failed. + /// + public string Path + { + get { - get - { - _Path ??= GetPath(); - return _Path; - } + _Path ??= GetPath(); + return _Path; } + } - /// - /// A prefix to add to the object path that failed. - /// - internal string Prefix + /// + /// A prefix to add to the object path that failed. + /// + internal string Prefix + { + get { return Operand?.Prefix; } + set { - get { return Operand?.Prefix; } - set + if (Operand != null && Operand.Prefix != value) { - if (Operand != null && Operand.Prefix != value) - { - Operand.Prefix = value; - _Formatted = _Path = _FullPath = null; - } + Operand.Prefix = value; + _Formatted = _Path = _FullPath = null; } } + } - /// - /// The object path including the path of the parent object. - /// - public string FullPath + /// + /// The object path including the path of the parent object. + /// + public string FullPath + { + get { - get - { - _FullPath ??= GetFullPath(); - return _FullPath; - } + _FullPath ??= GetFullPath(); + return _FullPath; } + } - public string Text { get; } + public string Text { get; } - public object[] Args { get; } + public object[] Args { get; } - public string Message + public string Message + { + get { - get - { - _Message ??= Args == null || Args.Length == 0 ? Text : string.Format(Thread.CurrentThread.CurrentCulture, Text, Args); - return _Message; - } + _Message ??= Args == null || Args.Length == 0 ? Text : string.Format(Thread.CurrentThread.CurrentCulture, Text, Args); + return _Message; } + } - public override string ToString() - { - return Format(); - } + public override string ToString() + { + return Format(); + } - public string Format() - { - _Formatted ??= string.Concat( - Operand?.ToString(), - Message - ); - return _Formatted; - } + public string Format() + { + _Formatted ??= string.Concat( + Operand?.ToString(), + Message + ); + return _Formatted; + } - private string GetPath() - { - return Runtime.Operand.JoinPath(Prefix, Operand?.Path); - } + private string GetPath() + { + return Runtime.Operand.JoinPath(Prefix, Operand?.Path); + } - private string GetFullPath() - { - return Runtime.Operand.JoinPath(_ParentPath, Path); - } + private string GetFullPath() + { + return Runtime.Operand.JoinPath(_ParentPath, Path); } } diff --git a/src/PSRule/Definitions/Rules/Rule.cs b/src/PSRule/Definitions/Rules/Rule.cs index 95faf04a73..a3b56cd6d1 100644 --- a/src/PSRule/Definitions/Rules/Rule.cs +++ b/src/PSRule/Definitions/Rules/Rule.cs @@ -6,177 +6,176 @@ using PSRule.Pipeline; using YamlDotNet.Serialization; -namespace PSRule.Definitions.Rules +namespace PSRule.Definitions.Rules; + +/// +/// If the rule fails, how serious is the result. +/// +public enum SeverityLevel +{ + /// + /// Severity is unset. + /// + None = 0, + + /// + /// A failure generates an error. + /// + Error = 1, + + /// + /// A failure generates a warning. + /// + Warning = 2, + + /// + /// A failure generate an informational message. + /// + Information = 3 +} + +/// +/// A rule resource V1. +/// +public interface IRuleV1 : IResource, IDependencyTarget { /// /// If the rule fails, how serious is the result. /// - public enum SeverityLevel - { - /// - /// Severity is unset. - /// - None = 0, - - /// - /// A failure generates an error. - /// - Error = 1, - - /// - /// A failure generates a warning. - /// - Warning = 2, - - /// - /// A failure generate an informational message. - /// - Information = 3 - } + SeverityLevel Level { get; } /// - /// A rule resource V1. + /// A recommendation for the rule. /// - public interface IRuleV1 : IResource, IDependencyTarget - { - /// - /// If the rule fails, how serious is the result. - /// - SeverityLevel Level { get; } - - /// - /// A recommendation for the rule. - /// - InfoString Recommendation { get; } - - /// - /// A short description of the rule. - /// - string Synopsis { get; } - - /// - /// Any additional tags assigned to the rule. - /// - ResourceTags Tag { get; } - } + InfoString Recommendation { get; } /// - /// A specification for a rule resource. + /// A short description of the rule. /// - internal interface IRuleSpec - { - /// - /// The of the rule condition that will be evaluated. - /// - LanguageIf Condition { get; } - - /// - /// If the rule fails, how serious is the result. - /// - SeverityLevel? Level { get; } - - /// - /// An optional type pre-condition before the rule is evaluated. - /// - string[] Type { get; } - - /// - /// An optional selector pre-condition before the rule is evaluated. - /// - string[] With { get; } - - /// - /// An optional sub-selector pre-condition before the rule is evaluated. - /// - LanguageIf Where { get; } - } + string Synopsis { get; } + + /// + /// Any additional tags assigned to the rule. + /// + ResourceTags Tag { get; } +} + +/// +/// A specification for a rule resource. +/// +internal interface IRuleSpec +{ + /// + /// The of the rule condition that will be evaluated. + /// + LanguageIf Condition { get; } + + /// + /// If the rule fails, how serious is the result. + /// + SeverityLevel? Level { get; } + + /// + /// An optional type pre-condition before the rule is evaluated. + /// + string[] Type { get; } + + /// + /// An optional selector pre-condition before the rule is evaluated. + /// + string[] With { get; } /// - /// A rule resource V1. + /// An optional sub-selector pre-condition before the rule is evaluated. /// - [Spec(Specs.V1, Specs.Rule)] - internal sealed class RuleV1 : InternalResource, IResource, IRuleV1 + LanguageIf Where { get; } +} + +/// +/// A rule resource V1. +/// +[Spec(Specs.V1, Specs.Rule)] +internal sealed class RuleV1 : InternalResource, IResource, IRuleV1 +{ + internal const SeverityLevel DEFAULT_LEVEL = SeverityLevel.Error; + + public RuleV1(string apiVersion, SourceFile source, ResourceMetadata metadata, IResourceHelpInfo info, ISourceExtent extent, RuleV1Spec spec) + : base(ResourceKind.Rule, apiVersion, source, metadata, info, extent, spec) { - internal const SeverityLevel DEFAULT_LEVEL = SeverityLevel.Error; - - public RuleV1(string apiVersion, SourceFile source, ResourceMetadata metadata, IResourceHelpInfo info, ISourceExtent extent, RuleV1Spec spec) - : base(ResourceKind.Rule, apiVersion, source, metadata, info, extent, spec) - { - Ref = ResourceHelper.GetIdNullable(source.Module, metadata.Ref, ResourceIdKind.Ref); - Alias = ResourceHelper.GetRuleId(source.Module, metadata.Alias, ResourceIdKind.Alias); - Level = ResourceHelper.GetLevel(spec.Level); - } - - /// - [JsonIgnore] - [YamlIgnore] - public ResourceId? Ref { get; } - - /// - [JsonIgnore] - [YamlIgnore] - public ResourceId[] Alias { get; } - - /// - /// If the rule fails, how serious is the result. - /// - [JsonIgnore] - [YamlIgnore] - public SeverityLevel Level { get; } - - /// - /// A human readable block of text, used to identify the purpose of the rule. - /// - [JsonIgnore] - [YamlIgnore] - public string Synopsis => Info.Synopsis.Text; - - /// - ResourceId? IDependencyTarget.Ref => Ref; - - /// - ResourceId[] IDependencyTarget.Alias => Alias; - - // Not supported with resource rules. - ResourceId[] IDependencyTarget.DependsOn => Array.Empty(); - - /// - bool IDependencyTarget.Dependency => Source.IsDependency(); - - /// - ResourceId? IResource.Ref => Ref; - - /// - ResourceId[] IResource.Alias => Alias; - - /// - ResourceTags IRuleV1.Tag => Metadata.Tags; - - /// - InfoString IRuleV1.Recommendation => InfoString.Create(Spec?.Recommend); + Ref = ResourceHelper.GetIdNullable(source.Module, metadata.Ref, ResourceIdKind.Ref); + Alias = ResourceHelper.GetRuleId(source.Module, metadata.Alias, ResourceIdKind.Alias); + Level = ResourceHelper.GetLevel(spec.Level); } + /// + [JsonIgnore] + [YamlIgnore] + public ResourceId? Ref { get; } + + /// + [JsonIgnore] + [YamlIgnore] + public ResourceId[] Alias { get; } + /// - /// A specification for a V1 rule resource. + /// If the rule fails, how serious is the result. /// - internal sealed class RuleV1Spec : Spec, IRuleSpec - { - /// - public LanguageIf Condition { get; set; } + [JsonIgnore] + [YamlIgnore] + public SeverityLevel Level { get; } - /// - public SeverityLevel? Level { get; set; } + /// + /// A human readable block of text, used to identify the purpose of the rule. + /// + [JsonIgnore] + [YamlIgnore] + public string Synopsis => Info.Synopsis.Text; - /// - public string Recommend { get; set; } + /// + ResourceId? IDependencyTarget.Ref => Ref; - /// - public string[] Type { get; set; } + /// + ResourceId[] IDependencyTarget.Alias => Alias; - /// - public string[] With { get; set; } + // Not supported with resource rules. + ResourceId[] IDependencyTarget.DependsOn => Array.Empty(); - /// - public LanguageIf Where { get; set; } - } + /// + bool IDependencyTarget.Dependency => Source.IsDependency(); + + /// + ResourceId? IResource.Ref => Ref; + + /// + ResourceId[] IResource.Alias => Alias; + + /// + ResourceTags IRuleV1.Tag => Metadata.Tags; + + /// + InfoString IRuleV1.Recommendation => InfoString.Create(Spec?.Recommend); +} + +/// +/// A specification for a V1 rule resource. +/// +internal sealed class RuleV1Spec : Spec, IRuleSpec +{ + /// + public LanguageIf Condition { get; set; } + + /// + public SeverityLevel? Level { get; set; } + + /// + public string Recommend { get; set; } + + /// + public string[] Type { get; set; } + + /// + public string[] With { get; set; } + + /// + public LanguageIf Where { get; set; } } diff --git a/src/PSRule/Definitions/Rules/RuleFilter.cs b/src/PSRule/Definitions/Rules/RuleFilter.cs index 3ac4f5b10f..f3c7bba231 100644 --- a/src/PSRule/Definitions/Rules/RuleFilter.cs +++ b/src/PSRule/Definitions/Rules/RuleFilter.cs @@ -6,132 +6,131 @@ using PSRule.Configuration; using PSRule.Resources; -namespace PSRule.Definitions.Rules +namespace PSRule.Definitions.Rules; + +/// +/// A filter to include or exclude rules from being processed by id or tag. +/// +internal sealed class RuleFilter : IResourceFilter { + internal readonly string[] Include; + internal readonly string[] Excluded; + internal readonly Hashtable Tag; + internal readonly ResourceLabels Labels; + internal readonly bool IncludeLocal; + internal readonly WildcardPattern WildcardMatch; + /// - /// A filter to include or exclude rules from being processed by id or tag. + /// Filter rules by id or tag. /// - internal sealed class RuleFilter : IResourceFilter + /// Only accept these rules by name. + /// Only accept rules that have these tags. + /// Rule that are always excluded by name. + /// Determine if local rules are automatically included. + /// Only accept rules that have these labels. + public RuleFilter(string[] include, Hashtable tag, string[] exclude, bool? includeLocal, ResourceLabels labels) { - internal readonly string[] Include; - internal readonly string[] Excluded; - internal readonly Hashtable Tag; - internal readonly ResourceLabels Labels; - internal readonly bool IncludeLocal; - internal readonly WildcardPattern WildcardMatch; - - /// - /// Filter rules by id or tag. - /// - /// Only accept these rules by name. - /// Only accept rules that have these tags. - /// Rule that are always excluded by name. - /// Determine if local rules are automatically included. - /// Only accept rules that have these labels. - public RuleFilter(string[] include, Hashtable tag, string[] exclude, bool? includeLocal, ResourceLabels labels) + Include = include == null || include.Length == 0 ? null : include; + Excluded = exclude == null || exclude.Length == 0 ? null : exclude; + Tag = tag ?? null; + Labels = labels ?? null; + IncludeLocal = includeLocal ?? RuleOption.Default.IncludeLocal.Value; + WildcardMatch = null; + + if (include != null && include.Length > 0 && WildcardPattern.ContainsWildcardCharacters(include[0])) { - Include = include == null || include.Length == 0 ? null : include; - Excluded = exclude == null || exclude.Length == 0 ? null : exclude; - Tag = tag ?? null; - Labels = labels ?? null; - IncludeLocal = includeLocal ?? RuleOption.Default.IncludeLocal.Value; - WildcardMatch = null; - - if (include != null && include.Length > 0 && WildcardPattern.ContainsWildcardCharacters(include[0])) - { - if (include.Length > 1) - throw new NotSupportedException(PSRuleResources.MatchSingleName); - - WildcardMatch = new WildcardPattern(include[0]); - } - } + if (include.Length > 1) + throw new NotSupportedException(PSRuleResources.MatchSingleName); - ResourceKind IResourceFilter.Kind => ResourceKind.Rule; - - internal bool Match(string name, ResourceTags tag, ResourceLabels labels) - { - return !IsExcluded(new ResourceId[] { ResourceId.Parse(name) }) && - IsIncluded(new ResourceId[] { ResourceId.Parse(name) }, tag, labels); + WildcardMatch = new WildcardPattern(include[0]); } + } - /// - /// Matches if the RuleId is contained or any tag is matched - /// - /// Return true if rule is matched, otherwise false. - public bool Match(IResource resource) - { - var ids = resource.GetIds(); - return !IsExcluded(ids) && (IncludeLocal && resource.IsLocalScope() || IsIncluded(ids, resource.Tags, resource.Labels)); - } + ResourceKind IResourceFilter.Kind => ResourceKind.Rule; - private bool IsExcluded(IEnumerable ids) - { - if (Excluded == null) - return false; + internal bool Match(string name, ResourceTags tag, ResourceLabels labels) + { + return !IsExcluded(new ResourceId[] { ResourceId.Parse(name) }) && + IsIncluded(new ResourceId[] { ResourceId.Parse(name) }, tag, labels); + } - foreach (var id in ids) - { - if (Contains(id, Excluded)) - return true; - } - return false; - } + /// + /// Matches if the RuleId is contained or any tag is matched + /// + /// Return true if rule is matched, otherwise false. + public bool Match(IResource resource) + { + var ids = resource.GetIds(); + return !IsExcluded(ids) && (IncludeLocal && resource.IsLocalScope() || IsIncluded(ids, resource.Tags, resource.Labels)); + } - private bool IsIncluded(IEnumerable ids, ResourceTags tag, ResourceLabels labels) - { - foreach (var id in ids) - { - if (Include == null || Contains(id, Include) || MatchWildcard(id.Name)) - return TagEquals(tag) && LabelEquals(labels); - } + private bool IsExcluded(IEnumerable ids) + { + if (Excluded == null) return false; - } - private bool TagEquals(ResourceTags tag) + foreach (var id in ids) { - if (Tag == null) + if (Contains(id, Excluded)) return true; + } + return false; + } - if (tag == null || Tag.Count > tag.Count) - return false; + private bool IsIncluded(IEnumerable ids, ResourceTags tag, ResourceLabels labels) + { + foreach (var id in ids) + { + if (Include == null || Contains(id, Include) || MatchWildcard(id.Name)) + return TagEquals(tag) && LabelEquals(labels); + } + return false; + } - foreach (DictionaryEntry entry in Tag) - { - if (!tag.Contains(entry.Key, entry.Value)) - return false; - } + private bool TagEquals(ResourceTags tag) + { + if (Tag == null) return true; - } - private bool LabelEquals(ResourceLabels labels) - { - if (Labels == null) - return true; + if (tag == null || Tag.Count > tag.Count) + return false; - if (labels == null || Labels.Count > labels.Count) + foreach (DictionaryEntry entry in Tag) + { + if (!tag.Contains(entry.Key, entry.Value)) return false; + } + return true; + } - foreach (var taxon in Labels) - { - if (!labels.Contains(taxon.Key, taxon.Value)) - return false; - } + private bool LabelEquals(ResourceLabels labels) + { + if (Labels == null) return true; - } - private static bool Contains(ResourceId id, string[] set) - { - for (var i = 0; set != null && i < set.Length; i++) - { - if (ResourceIdEqualityComparer.IdEquals(id, set[i])) - return true; - } + if (labels == null || Labels.Count > labels.Count) return false; + + foreach (var taxon in Labels) + { + if (!labels.Contains(taxon.Key, taxon.Value)) + return false; } + return true; + } - private bool MatchWildcard(string name) + private static bool Contains(ResourceId id, string[] set) + { + for (var i = 0; set != null && i < set.Length; i++) { - return WildcardMatch != null && WildcardMatch.IsMatch(name); + if (ResourceIdEqualityComparer.IdEquals(id, set[i])) + return true; } + return false; + } + + private bool MatchWildcard(string name) + { + return WildcardMatch != null && WildcardMatch.IsMatch(name); } } diff --git a/src/PSRule/Definitions/Rules/RuleVisitor.cs b/src/PSRule/Definitions/Rules/RuleVisitor.cs index 49d4df5f48..2912c91dbf 100644 --- a/src/PSRule/Definitions/Rules/RuleVisitor.cs +++ b/src/PSRule/Definitions/Rules/RuleVisitor.cs @@ -8,62 +8,61 @@ using PSRule.Resources; using PSRule.Runtime; -namespace PSRule.Definitions.Rules +namespace PSRule.Definitions.Rules; + +/// +/// A rule visitor. +/// +[DebuggerDisplay("Id: {Id}")] +internal sealed class RuleVisitor : ICondition { - /// - /// A rule visitor. - /// - [DebuggerDisplay("Id: {Id}")] - internal sealed class RuleVisitor : ICondition + private readonly LanguageExpressionOuterFn _Condition; + private readonly RunspaceContext _Context; + + public RuleVisitor(RunspaceContext context, ResourceId id, SourceFile source, IRuleSpec spec) { - private readonly LanguageExpressionOuterFn _Condition; - private readonly RunspaceContext _Context; + _Context = context; + ErrorAction = ActionPreference.Stop; + Id = id; + Source = source; + InstanceId = Guid.NewGuid(); + var builder = new LanguageExpressionBuilder(); + _Condition = builder + .WithSelector(spec.With) + .WithType(spec.Type) + .WithSubselector(spec.Where) + .Build(spec.Condition); + } - public RuleVisitor(RunspaceContext context, ResourceId id, SourceFile source, IRuleSpec spec) - { - _Context = context; - ErrorAction = ActionPreference.Stop; - Id = id; - Source = source; - InstanceId = Guid.NewGuid(); - var builder = new LanguageExpressionBuilder(); - _Condition = builder - .WithSelector(spec.With) - .WithType(spec.Type) - .WithSubselector(spec.Where) - .Build(spec.Condition); - } + public Guid InstanceId { get; } - public Guid InstanceId { get; } + public SourceFile Source { get; } - public SourceFile Source { get; } + public ResourceId Id { get; } - public ResourceId Id { get; } + public ActionPreference ErrorAction { get; } - public ActionPreference ErrorAction { get; } + public void Dispose() + { + // Do nothing + } - public void Dispose() + public IConditionResult If() + { + var context = new ExpressionContext(_Context, Source, ResourceKind.Rule, _Context.TargetObject.Value); + context.Debug(PSRuleResources.RuleMatchTrace, Id); + context.PushScope(RunspaceScope.Rule); + try { - // Do nothing - } + var result = _Condition(context, _Context.TargetObject.Value); + if (result.HasValue && !result.Value) + _Context.WriteReason(context.GetReasons()); - public IConditionResult If() + return result.HasValue ? new RuleConditionResult(result.Value ? 1 : 0, 1, false) : null; + } + finally { - var context = new ExpressionContext(_Context, Source, ResourceKind.Rule, _Context.TargetObject.Value); - context.Debug(PSRuleResources.RuleMatchTrace, Id); - context.PushScope(RunspaceScope.Rule); - try - { - var result = _Condition(context, _Context.TargetObject.Value); - if (result.HasValue && !result.Value) - _Context.WriteReason(context.GetReasons()); - - return result.HasValue ? new RuleConditionResult(result.Value ? 1 : 0, 1, false) : null; - } - finally - { - context.PopScope(RunspaceScope.Rule); - } + context.PopScope(RunspaceScope.Rule); } } } diff --git a/src/PSRule/Definitions/Selectors/Selector.cs b/src/PSRule/Definitions/Selectors/Selector.cs index 3235bda342..e66c8e0aa1 100644 --- a/src/PSRule/Definitions/Selectors/Selector.cs +++ b/src/PSRule/Definitions/Selectors/Selector.cs @@ -4,23 +4,22 @@ using PSRule.Definitions.Expressions; using PSRule.Pipeline; -namespace PSRule.Definitions.Selectors +namespace PSRule.Definitions.Selectors; + +/// +/// A selector resource V1. +/// +[Spec(Specs.V1, Specs.Selector)] +internal sealed class SelectorV1 : InternalResource { - /// - /// A selector resource V1. - /// - [Spec(Specs.V1, Specs.Selector)] - internal sealed class SelectorV1 : InternalResource - { - public SelectorV1(string apiVersion, SourceFile source, ResourceMetadata metadata, IResourceHelpInfo info, ISourceExtent extent, SelectorV1Spec spec) - : base(ResourceKind.Selector, apiVersion, source, metadata, info, extent, spec) { } - } + public SelectorV1(string apiVersion, SourceFile source, ResourceMetadata metadata, IResourceHelpInfo info, ISourceExtent extent, SelectorV1Spec spec) + : base(ResourceKind.Selector, apiVersion, source, metadata, info, extent, spec) { } +} - /// - /// A specification for a V1 selector resource. - /// - internal sealed class SelectorV1Spec : Spec - { - public LanguageIf If { get; set; } - } +/// +/// A specification for a V1 selector resource. +/// +internal sealed class SelectorV1Spec : Spec +{ + public LanguageIf If { get; set; } } diff --git a/src/PSRule/Definitions/Selectors/SelectorVisitor.cs b/src/PSRule/Definitions/Selectors/SelectorVisitor.cs index 363093fc5d..63b6f2bfc0 100644 --- a/src/PSRule/Definitions/Selectors/SelectorVisitor.cs +++ b/src/PSRule/Definitions/Selectors/SelectorVisitor.cs @@ -7,40 +7,39 @@ using PSRule.Resources; using PSRule.Runtime; -namespace PSRule.Definitions.Selectors +namespace PSRule.Definitions.Selectors; + +internal interface ISelector : ILanguageBlock { - internal interface ISelector : ILanguageBlock - { +} + +[DebuggerDisplay("Id: {Id}")] +internal sealed class SelectorVisitor : ISelector +{ + private readonly LanguageExpressionOuterFn _Fn; + private readonly RunspaceContext _Context; + + public SelectorVisitor(RunspaceContext context, ResourceId id, SourceFile source, LanguageIf expression) + { + _Context = context; + Id = id; + Source = source; + InstanceId = Guid.NewGuid(); + var builder = new LanguageExpressionBuilder(); + _Fn = builder.Build(expression); } - [DebuggerDisplay("Id: {Id}")] - internal sealed class SelectorVisitor : ISelector + public Guid InstanceId { get; } + + public ResourceId Id { get; } + + public SourceFile Source { get; } + + public bool Match(object o) { - private readonly LanguageExpressionOuterFn _Fn; - private readonly RunspaceContext _Context; - - public SelectorVisitor(RunspaceContext context, ResourceId id, SourceFile source, LanguageIf expression) - { - _Context = context; - Id = id; - Source = source; - InstanceId = Guid.NewGuid(); - var builder = new LanguageExpressionBuilder(); - _Fn = builder.Build(expression); - } - - public Guid InstanceId { get; } - - public ResourceId Id { get; } - - public SourceFile Source { get; } - - public bool Match(object o) - { - var context = new ExpressionContext(_Context, Source, ResourceKind.Selector, o); - context.Debug(PSRuleResources.SelectorMatchTrace, Id); - return _Fn(context, o).GetValueOrDefault(false); - } + var context = new ExpressionContext(_Context, Source, ResourceKind.Selector, o); + context.Debug(PSRuleResources.SelectorMatchTrace, Id); + return _Fn(context, o).GetValueOrDefault(false); } } diff --git a/src/PSRule/Definitions/SourceExtent.cs b/src/PSRule/Definitions/SourceExtent.cs index 1ef6263ea6..4ec73631a7 100644 --- a/src/PSRule/Definitions/SourceExtent.cs +++ b/src/PSRule/Definitions/SourceExtent.cs @@ -1,49 +1,48 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -namespace PSRule.Definitions +namespace PSRule.Definitions; + +/// +/// A source location for a PSRule expression. +/// +public interface ISourceExtent { /// - /// A source location for a PSRule expression. + /// The source file path. + /// + string File { get; } + + /// + /// The first line of the expression. + /// + int? Line { get; } + + /// + /// The first position of the expression. /// - public interface ISourceExtent + int? Position { get; } +} + +internal sealed class SourceExtent : ISourceExtent +{ + internal SourceExtent(string file, int? line) + : this(file, line, null) { - /// - /// The source file path. - /// - string File { get; } - - /// - /// The first line of the expression. - /// - int? Line { get; } - - /// - /// The first position of the expression. - /// - int? Position { get; } + File = file; + Line = line; } - internal sealed class SourceExtent : ISourceExtent + internal SourceExtent(string file, int? line, int? position) { - internal SourceExtent(string file, int? line) - : this(file, line, null) - { - File = file; - Line = line; - } - - internal SourceExtent(string file, int? line, int? position) - { - File = file; - Line = line; - Position = position; - } + File = file; + Line = line; + Position = position; + } - public string File { get; } + public string File { get; } - public int? Line { get; } + public int? Line { get; } - public int? Position { get; } - } + public int? Position { get; } } diff --git a/src/PSRule/Definitions/Spec.cs b/src/PSRule/Definitions/Spec.cs index d6983bf877..fba441e9df 100644 --- a/src/PSRule/Definitions/Spec.cs +++ b/src/PSRule/Definitions/Spec.cs @@ -7,56 +7,55 @@ using PSRule.Definitions.Selectors; using PSRule.Definitions.SuppressionGroups; -namespace PSRule.Definitions +namespace PSRule.Definitions; + +/// +/// The base class for a resource specification. +/// +public abstract class Spec { + private const string FullNameSeparator = "/"; + /// - /// The base class for a resource specification. + /// Create an instance of the resource specication. /// - public abstract class Spec + protected Spec() { } + + /// + /// Get a fully qualified name for the resource type. + /// + /// The specific API version of the resource. + /// The type name of the resource. + /// A fully qualified type name string. + public static string GetFullName(string apiVersion, string name) { - private const string FullNameSeparator = "/"; - - /// - /// Create an instance of the resource specication. - /// - protected Spec() { } - - /// - /// Get a fully qualified name for the resource type. - /// - /// The specific API version of the resource. - /// The type name of the resource. - /// A fully qualified type name string. - public static string GetFullName(string apiVersion, string name) - { - return string.Concat(apiVersion, FullNameSeparator, name); - } + return string.Concat(apiVersion, FullNameSeparator, name); } +} + +internal static class Specs +{ + /// + /// The API version for V1 resources. + /// + internal const string V1 = "github.com/microsoft/PSRule/v1"; + + // Resource names for different types of resources. + internal const string Rule = "Rule"; + internal const string Baseline = "Baseline"; + internal const string ModuleConfig = "ModuleConfig"; + internal const string Selector = "Selector"; + internal const string SuppressionGroup = "SuppressionGroup"; - internal static class Specs + /// + /// The built-in resource types. + /// + public static readonly ISpecDescriptor[] BuiltinTypes = new ISpecDescriptor[] { - /// - /// The API version for V1 resources. - /// - internal const string V1 = "github.com/microsoft/PSRule/v1"; - - // Resource names for different types of resources. - internal const string Rule = "Rule"; - internal const string Baseline = "Baseline"; - internal const string ModuleConfig = "ModuleConfig"; - internal const string Selector = "Selector"; - internal const string SuppressionGroup = "SuppressionGroup"; - - /// - /// The built-in resource types. - /// - public static readonly ISpecDescriptor[] BuiltinTypes = new ISpecDescriptor[] - { - new SpecDescriptor(V1, Rule), - new SpecDescriptor(V1, Baseline), - new SpecDescriptor(V1, ModuleConfig), - new SpecDescriptor(V1, Selector), - new SpecDescriptor(V1, SuppressionGroup) - }; - } + new SpecDescriptor(V1, Rule), + new SpecDescriptor(V1, Baseline), + new SpecDescriptor(V1, ModuleConfig), + new SpecDescriptor(V1, Selector), + new SpecDescriptor(V1, SuppressionGroup) + }; } diff --git a/src/PSRule/Definitions/SpecFactory.cs b/src/PSRule/Definitions/SpecFactory.cs index 3c1ef3bc08..185eacd2bb 100644 --- a/src/PSRule/Definitions/SpecFactory.cs +++ b/src/PSRule/Definitions/SpecFactory.cs @@ -4,90 +4,89 @@ using PSRule.Annotations; using PSRule.Pipeline; -namespace PSRule.Definitions +namespace PSRule.Definitions; + +internal sealed class SpecFactory { - internal sealed class SpecFactory + private readonly Dictionary _Descriptors; + + public SpecFactory() { - private readonly Dictionary _Descriptors; - - public SpecFactory() - { - _Descriptors = new Dictionary(); - foreach (var d in Specs.BuiltinTypes) - With(d); - } - - public bool TryDescriptor(string apiVersion, string name, out ISpecDescriptor descriptor) - { - var fullName = Spec.GetFullName(apiVersion, name); - return _Descriptors.TryGetValue(fullName, out descriptor); - } - - public void With(string name, string apiVersion) where T : Resource, IResource where TSpec : Spec, new() - { - var descriptor = new SpecDescriptor(name, apiVersion); - _Descriptors.Add(descriptor.FullName, descriptor); - } - - private void With(ISpecDescriptor descriptor) - { - _Descriptors.Add(descriptor.FullName, descriptor); - } + _Descriptors = new Dictionary(); + foreach (var d in Specs.BuiltinTypes) + With(d); } - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] - internal sealed class SpecAttribute : Attribute + public bool TryDescriptor(string apiVersion, string name, out ISpecDescriptor descriptor) { - public SpecAttribute() - { + var fullName = Spec.GetFullName(apiVersion, name); + return _Descriptors.TryGetValue(fullName, out descriptor); + } - } + public void With(string name, string apiVersion) where T : Resource, IResource where TSpec : Spec, new() + { + var descriptor = new SpecDescriptor(name, apiVersion); + _Descriptors.Add(descriptor.FullName, descriptor); + } - public SpecAttribute(string apiVersion, string kind) - { - ApiVersion = apiVersion; - Kind = kind; - } + private void With(ISpecDescriptor descriptor) + { + _Descriptors.Add(descriptor.FullName, descriptor); + } +} - public string ApiVersion { get; } +[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] +internal sealed class SpecAttribute : Attribute +{ + public SpecAttribute() + { - public string Kind { get; } } - internal sealed class SpecDescriptor : ISpecDescriptor where T : Resource, IResource where TSpec : Spec, new() + public SpecAttribute(string apiVersion, string kind) { - public SpecDescriptor(string apiVersion, string name) - { - ApiVersion = apiVersion; - Name = name; - FullName = Spec.GetFullName(apiVersion, name); - } + ApiVersion = apiVersion; + Kind = kind; + } - public string Name { get; } + public string ApiVersion { get; } - public string ApiVersion { get; } + public string Kind { get; } +} - public string FullName { get; } +internal sealed class SpecDescriptor : ISpecDescriptor where T : Resource, IResource where TSpec : Spec, new() +{ + public SpecDescriptor(string apiVersion, string name) + { + ApiVersion = apiVersion; + Name = name; + FullName = Spec.GetFullName(apiVersion, name); + } - public Type SpecType => typeof(TSpec); + public string Name { get; } - public IResource CreateInstance(SourceFile source, ResourceMetadata metadata, CommentMetadata comment, ISourceExtent extent, object spec) - { - var info = new ResourceHelpInfo(metadata.Name, metadata.DisplayName, new InfoString(comment?.Synopsis), InfoString.Create(metadata.Description)); - return (IResource)Activator.CreateInstance(typeof(T), ApiVersion, source, metadata, info, extent, spec); - } - } + public string ApiVersion { get; } + + public string FullName { get; } + + public Type SpecType => typeof(TSpec); - internal interface ISpecDescriptor + public IResource CreateInstance(SourceFile source, ResourceMetadata metadata, CommentMetadata comment, ISourceExtent extent, object spec) { - string Name { get; } + var info = new ResourceHelpInfo(metadata.Name, metadata.DisplayName, new InfoString(comment?.Synopsis), InfoString.Create(metadata.Description)); + return (IResource)Activator.CreateInstance(typeof(T), ApiVersion, source, metadata, info, extent, spec); + } +} - string ApiVersion { get; } +internal interface ISpecDescriptor +{ + string Name { get; } - string FullName { get; } + string ApiVersion { get; } - Type SpecType { get; } + string FullName { get; } - IResource CreateInstance(SourceFile source, ResourceMetadata metadata, CommentMetadata comment, ISourceExtent extent, object spec); - } + Type SpecType { get; } + + IResource CreateInstance(SourceFile source, ResourceMetadata metadata, CommentMetadata comment, ISourceExtent extent, object spec); } diff --git a/src/PSRule/Definitions/SuppressionGroups/SuppressionGroup.cs b/src/PSRule/Definitions/SuppressionGroups/SuppressionGroup.cs index 00c21d821d..6462f5acb1 100644 --- a/src/PSRule/Definitions/SuppressionGroups/SuppressionGroup.cs +++ b/src/PSRule/Definitions/SuppressionGroups/SuppressionGroup.cs @@ -4,54 +4,53 @@ using PSRule.Definitions.Expressions; using PSRule.Pipeline; -namespace PSRule.Definitions.SuppressionGroups +namespace PSRule.Definitions.SuppressionGroups; + +/// +/// A specification for a V1 suppression group resource. +/// +internal interface ISuppressionGroupV1Spec { /// - /// A specification for a V1 suppression group resource. + /// The date time that the suppression is valid until. + /// After this date time, the suppression is ignored. + /// When not set, the suppression does not expire. + /// This RFC3339 (ISO 8601) formatted date time using the format yyyy-MM-ddTHH:mm:ssZ. /// - internal interface ISuppressionGroupV1Spec - { - /// - /// The date time that the suppression is valid until. - /// After this date time, the suppression is ignored. - /// When not set, the suppression does not expire. - /// This RFC3339 (ISO 8601) formatted date time using the format yyyy-MM-ddTHH:mm:ssZ. - /// - DateTime? ExpiresOn { get; set; } - - /// - /// This only applies to rules that match the specified rule names. - /// - string[] Rule { get; } - - /// - /// An expression. If the expression evaluates as true and rules specified by are suppressed. - /// - LanguageIf If { get; } - } + DateTime? ExpiresOn { get; set; } /// - /// A suppression group resource V1. + /// This only applies to rules that match the specified rule names. /// - [Spec(Specs.V1, Specs.SuppressionGroup)] - internal sealed class SuppressionGroupV1 : InternalResource - { - public SuppressionGroupV1(string apiVersion, SourceFile source, ResourceMetadata metadata, IResourceHelpInfo info, ISourceExtent extent, SuppressionGroupV1Spec spec) - : base(ResourceKind.SuppressionGroup, apiVersion, source, metadata, info, extent, spec) { } - } + string[] Rule { get; } /// - /// A specification for a V1 suppression group resource. + /// An expression. If the expression evaluates as true and rules specified by are suppressed. /// - internal sealed class SuppressionGroupV1Spec : Spec, ISuppressionGroupV1Spec - { - /// - public DateTime? ExpiresOn { get; set; } + LanguageIf If { get; } +} + +/// +/// A suppression group resource V1. +/// +[Spec(Specs.V1, Specs.SuppressionGroup)] +internal sealed class SuppressionGroupV1 : InternalResource +{ + public SuppressionGroupV1(string apiVersion, SourceFile source, ResourceMetadata metadata, IResourceHelpInfo info, ISourceExtent extent, SuppressionGroupV1Spec spec) + : base(ResourceKind.SuppressionGroup, apiVersion, source, metadata, info, extent, spec) { } +} + +/// +/// A specification for a V1 suppression group resource. +/// +internal sealed class SuppressionGroupV1Spec : Spec, ISuppressionGroupV1Spec +{ + /// + public DateTime? ExpiresOn { get; set; } - /// - public string[] Rule { get; set; } + /// + public string[] Rule { get; set; } - /// - public LanguageIf If { get; set; } - } + /// + public LanguageIf If { get; set; } } diff --git a/src/PSRule/Definitions/SuppressionGroups/SuppressionGroupVisitor.cs b/src/PSRule/Definitions/SuppressionGroups/SuppressionGroupVisitor.cs index abaca8de81..14b9ef2906 100644 --- a/src/PSRule/Definitions/SuppressionGroups/SuppressionGroupVisitor.cs +++ b/src/PSRule/Definitions/SuppressionGroups/SuppressionGroupVisitor.cs @@ -6,88 +6,87 @@ using PSRule.Resources; using PSRule.Runtime; -namespace PSRule.Definitions.SuppressionGroups +namespace PSRule.Definitions.SuppressionGroups; + +internal sealed class SuppressionGroupVisitor { - internal sealed class SuppressionGroupVisitor + private readonly LanguageExpressionOuterFn _Fn; + private readonly SuppressionInfo _Info; + private readonly RunspaceContext _Context; + + public SuppressionGroupVisitor(RunspaceContext context, ResourceId id, SourceFile source, ISuppressionGroupV1Spec spec, IResourceHelpInfo info) + { + _Context = context; + Id = id; + Source = source; + InstanceId = Guid.NewGuid(); + Rule = spec.Rule; + _Info = new SuppressionInfo(id, info); + _Fn = new LanguageExpressionBuilder() + .WithRule(Rule) + .Build(spec.If); + } + + /// + /// Tracking information about the suppression. + /// + private sealed class SuppressionInfo : ISuppressionInfo { - private readonly LanguageExpressionOuterFn _Fn; - private readonly SuppressionInfo _Info; - private readonly RunspaceContext _Context; + private readonly IResourceHelpInfo _Info; - public SuppressionGroupVisitor(RunspaceContext context, ResourceId id, SourceFile source, ISuppressionGroupV1Spec spec, IResourceHelpInfo info) + public SuppressionInfo(ResourceId id, IResourceHelpInfo info) { - _Context = context; Id = id; - Source = source; - InstanceId = Guid.NewGuid(); - Rule = spec.Rule; - _Info = new SuppressionInfo(id, info); - _Fn = new LanguageExpressionBuilder() - .WithRule(Rule) - .Build(spec.If); + _Info = info; } - /// - /// Tracking information about the suppression. - /// - private sealed class SuppressionInfo : ISuppressionInfo - { - private readonly IResourceHelpInfo _Info; - - public SuppressionInfo(ResourceId id, IResourceHelpInfo info) - { - Id = id; - _Info = info; - } - - public ResourceId Id { get; } + public ResourceId Id { get; } - public InfoString Synopsis => _Info.Synopsis; + public InfoString Synopsis => _Info.Synopsis; - public int Count { get; private set; } + public int Count { get; private set; } - public override string ToString() - { - return Id.Value; - } + public override string ToString() + { + return Id.Value; + } - public override int GetHashCode() - { - return Id.GetHashCode(); - } + public override int GetHashCode() + { + return Id.GetHashCode(); + } - public override bool Equals(object obj) - { - return obj is SuppressionInfo info && - Id.Equals(info.Id); - } + public override bool Equals(object obj) + { + return obj is SuppressionInfo info && + Id.Equals(info.Id); + } - internal void Hit() - { - Count++; - } + internal void Hit() + { + Count++; } + } - public ResourceId Id { get; } + public ResourceId Id { get; } - public SourceFile Source { get; } + public SourceFile Source { get; } - public Guid InstanceId { get; } + public Guid InstanceId { get; } - public string[] Rule { get; } + public string[] Rule { get; } - public bool TryMatch(object o, out ISuppressionInfo suppression) + public bool TryMatch(object o, out ISuppressionInfo suppression) + { + suppression = null; + var context = new ExpressionContext(_Context, Source, ResourceKind.SuppressionGroup, o); + context.Debug(PSRuleResources.SelectorMatchTrace, Id); + if (_Fn(context, o).GetValueOrDefault(false)) { - suppression = null; - var context = new ExpressionContext(_Context, Source, ResourceKind.SuppressionGroup, o); - context.Debug(PSRuleResources.SelectorMatchTrace, Id); - if (_Fn(context, o).GetValueOrDefault(false)) - { - _Info.Hit(); - suppression = _Info; - return true; - } - return false; + _Info.Hit(); + suppression = _Info; + return true; } + return false; } } diff --git a/src/PSRule/Help/HelpLexer.cs b/src/PSRule/Help/HelpLexer.cs index b2fd2e4730..843197a966 100644 --- a/src/PSRule/Help/HelpLexer.cs +++ b/src/PSRule/Help/HelpLexer.cs @@ -7,168 +7,167 @@ using PSRule.Definitions; using PSRule.Resources; -namespace PSRule.Help +namespace PSRule.Help; + +internal abstract class HelpLexer : MarkdownLexer { - internal abstract class HelpLexer : MarkdownLexer - { - protected const int RULE_NAME_HEADING_LEVEL = 1; - protected const int RULE_ENTRIES_HEADING_LEVEL = 2; + protected const int RULE_NAME_HEADING_LEVEL = 1; + protected const int RULE_ENTRIES_HEADING_LEVEL = 2; - private const string Space = " "; + private const string Space = " "; - protected readonly ResourceSet _Strings; + protected readonly ResourceSet _Strings; - protected HelpLexer(string culture) - { - var cultureInfo = string.IsNullOrEmpty(culture) ? Thread.CurrentThread.CurrentCulture : CultureInfo.GetCultureInfo(culture); - _Strings = DocumentStrings.ResourceManager.GetResourceSet(cultureInfo, createIfNotExists: true, tryParents: true); - } + protected HelpLexer(string culture) + { + var cultureInfo = string.IsNullOrEmpty(culture) ? Thread.CurrentThread.CurrentCulture : CultureInfo.GetCultureInfo(culture); + _Strings = DocumentStrings.ResourceManager.GetResourceSet(cultureInfo, createIfNotExists: true, tryParents: true); + } - /// - /// Read synopsis. - /// - protected bool Synopsis(TokenStream stream, IHelpDocument doc) - { - if (!IsHeading(stream.Current, RULE_ENTRIES_HEADING_LEVEL, _Strings.GetString("Synopsis"))) - return false; + /// + /// Read synopsis. + /// + protected bool Synopsis(TokenStream stream, IHelpDocument doc) + { + if (!IsHeading(stream.Current, RULE_ENTRIES_HEADING_LEVEL, _Strings.GetString("Synopsis"))) + return false; - doc.Synopsis = InfoString(stream); - stream.SkipUntilHeader(); - return true; - } + doc.Synopsis = InfoString(stream); + stream.SkipUntilHeader(); + return true; + } - /// - /// Read description. - /// - protected bool Description(TokenStream stream, IHelpDocument doc) - { - if (!IsHeading(stream.Current, RULE_ENTRIES_HEADING_LEVEL, _Strings.GetString("Description"))) - return false; + /// + /// Read description. + /// + protected bool Description(TokenStream stream, IHelpDocument doc) + { + if (!IsHeading(stream.Current, RULE_ENTRIES_HEADING_LEVEL, _Strings.GetString("Description"))) + return false; - doc.Description = InfoString(stream, includeNonYamlFencedBlocks: true); - stream.SkipUntilHeader(); - return true; - } + doc.Description = InfoString(stream, includeNonYamlFencedBlocks: true); + stream.SkipUntilHeader(); + return true; + } - /// - /// Read links. - /// - protected bool Links(TokenStream stream, IHelpDocument doc) + /// + /// Read links. + /// + protected bool Links(TokenStream stream, IHelpDocument doc) + { + if (!IsHeading(stream.Current, RULE_ENTRIES_HEADING_LEVEL, _Strings.GetString("Links"))) + return false; + + var links = new List(); + stream.Next(); + while (stream.IsTokenType( + MarkdownTokenType.Link, + MarkdownTokenType.LinkReference, + MarkdownTokenType.LineBreak, + MarkdownTokenType.Text)) { - if (!IsHeading(stream.Current, RULE_ENTRIES_HEADING_LEVEL, _Strings.GetString("Links"))) - return false; - - var links = new List(); - stream.Next(); - while (stream.IsTokenType( - MarkdownTokenType.Link, - MarkdownTokenType.LinkReference, - MarkdownTokenType.LineBreak, - MarkdownTokenType.Text)) + if (stream.IsTokenType(MarkdownTokenType.LineBreak) || stream.IsTokenType(MarkdownTokenType.Text)) { - if (stream.IsTokenType(MarkdownTokenType.LineBreak) || stream.IsTokenType(MarkdownTokenType.Text)) - { - stream.Next(); - continue; - } - - var link = new Link - { - Name = stream.Current.Meta, - Uri = stream.Current.Text - }; - - // Update link to point to resolved target - if (stream.IsTokenType(MarkdownTokenType.LinkReference)) - { - var target = stream.ResolveLinkTarget(link.Uri); - link.Uri = target.Text; - } - links.Add(link); stream.Next(); + continue; } - stream.SkipUntilHeader(); - doc.Links = links.ToArray(); - return true; - } - protected static InfoString InfoString(TokenStream stream, bool includeNonYamlFencedBlocks = false) - { - stream.Next(); - var text = ReadText(stream, includeNonYamlFencedBlocks); - return new InfoString(text, null); - } + var link = new Link + { + Name = stream.Current.Meta, + Uri = stream.Current.Text + }; - protected static TextBlock TextBlock(TokenStream stream, bool includeNonYamlFencedBlocks = false) - { - var useBreak = stream.Current.IsDoubleLineEnding(); + // Update link to point to resolved target + if (stream.IsTokenType(MarkdownTokenType.LinkReference)) + { + var target = stream.ResolveLinkTarget(link.Uri); + link.Uri = target.Text; + } + links.Add(link); stream.Next(); - var text = ReadText(stream, includeNonYamlFencedBlocks); - return new TextBlock(text: text, formatOption: useBreak ? FormatOptions.LineBreak : FormatOptions.None); } + stream.SkipUntilHeader(); + doc.Links = links.ToArray(); + return true; + } - /// - /// Read tokens from the stream as text. - /// - private static string ReadText(TokenStream stream, bool includeNonYamlFencedBlocks) + protected static InfoString InfoString(TokenStream stream, bool includeNonYamlFencedBlocks = false) + { + stream.Next(); + var text = ReadText(stream, includeNonYamlFencedBlocks); + return new InfoString(text, null); + } + + protected static TextBlock TextBlock(TokenStream stream, bool includeNonYamlFencedBlocks = false) + { + var useBreak = stream.Current.IsDoubleLineEnding(); + stream.Next(); + var text = ReadText(stream, includeNonYamlFencedBlocks); + return new TextBlock(text: text, formatOption: useBreak ? FormatOptions.LineBreak : FormatOptions.None); + } + + /// + /// Read tokens from the stream as text. + /// + private static string ReadText(TokenStream stream, bool includeNonYamlFencedBlocks) + { + var sb = new StringBuilder(); + while (stream.IsTokenType(MarkdownTokenType.Text, MarkdownTokenType.Link, MarkdownTokenType.FencedBlock, MarkdownTokenType.LineBreak)) { - var sb = new StringBuilder(); - while (stream.IsTokenType(MarkdownTokenType.Text, MarkdownTokenType.Link, MarkdownTokenType.FencedBlock, MarkdownTokenType.LineBreak)) + if (stream.IsTokenType(MarkdownTokenType.Text)) { - if (stream.IsTokenType(MarkdownTokenType.Text)) - { - AppendEnding(sb, stream.Peak(-1)); - sb.Append(stream.Current.Text); - } - else if (stream.IsTokenType(MarkdownTokenType.Link)) - { - AppendEnding(sb, stream.Peak(-1)); - sb.Append(stream.Current.Meta); - if (!string.IsNullOrEmpty(stream.Current.Text)) - sb.AppendFormat(Thread.CurrentThread.CurrentCulture, " ({0})", stream.Current.Text); - } - else if (stream.IsTokenType(MarkdownTokenType.LinkReference)) - { - AppendEnding(sb, stream.Peak(-1)); - sb.Append(stream.Current.Meta); - } - else if (stream.IsTokenType(MarkdownTokenType.FencedBlock)) + AppendEnding(sb, stream.Peak(-1)); + sb.Append(stream.Current.Text); + } + else if (stream.IsTokenType(MarkdownTokenType.Link)) + { + AppendEnding(sb, stream.Peak(-1)); + sb.Append(stream.Current.Meta); + if (!string.IsNullOrEmpty(stream.Current.Text)) + sb.AppendFormat(Thread.CurrentThread.CurrentCulture, " ({0})", stream.Current.Text); + } + else if (stream.IsTokenType(MarkdownTokenType.LinkReference)) + { + AppendEnding(sb, stream.Peak(-1)); + sb.Append(stream.Current.Meta); + } + else if (stream.IsTokenType(MarkdownTokenType.FencedBlock)) + { + // Only process fenced blocks if specified, and never process yaml blocks + if (!includeNonYamlFencedBlocks || string.Equals(stream.Current.Meta, "yaml", StringComparison.OrdinalIgnoreCase)) { - // Only process fenced blocks if specified, and never process yaml blocks - if (!includeNonYamlFencedBlocks || string.Equals(stream.Current.Meta, "yaml", StringComparison.OrdinalIgnoreCase)) - { - if (stream.PeakTokenType(-1) == MarkdownTokenType.LineBreak) - AppendEnding(sb, stream.Peak(-1)); - - break; - } - AppendEnding(sb, stream.Peak(-1), preserveEnding: true); - sb.Append(stream.Current.Text); - } - else if (stream.IsTokenType(MarkdownTokenType.LineBreak)) - AppendEnding(sb, stream.Peak(-1)); + if (stream.PeakTokenType(-1) == MarkdownTokenType.LineBreak) + AppendEnding(sb, stream.Peak(-1)); - stream.Next(); + break; + } + AppendEnding(sb, stream.Peak(-1), preserveEnding: true); + sb.Append(stream.Current.Text); } - - if (stream.EOF && stream.Peak(-1).Flag.HasFlag(MarkdownTokens.Preserve) && stream.Peak(-1).Flag.HasFlag(MarkdownTokens.LineEnding)) + else if (stream.IsTokenType(MarkdownTokenType.LineBreak)) AppendEnding(sb, stream.Peak(-1)); - return sb.ToString(); + stream.Next(); } - private static void AppendEnding(StringBuilder stringBuilder, MarkdownToken token, bool preserveEnding = false) - { - if (token == null || stringBuilder.Length == 0 || !token.Flag.IsEnding()) - return; + if (stream.EOF && stream.Peak(-1).Flag.HasFlag(MarkdownTokens.Preserve) && stream.Peak(-1).Flag.HasFlag(MarkdownTokens.LineEnding)) + AppendEnding(sb, stream.Peak(-1)); + + return sb.ToString(); + } + + private static void AppendEnding(StringBuilder stringBuilder, MarkdownToken token, bool preserveEnding = false) + { + if (token == null || stringBuilder.Length == 0 || !token.Flag.IsEnding()) + return; - if (!preserveEnding && token.Flag.ShouldPreserve()) - preserveEnding = true; + if (!preserveEnding && token.Flag.ShouldPreserve()) + preserveEnding = true; - if (token.IsDoubleLineEnding()) - stringBuilder.Append(preserveEnding ? string.Concat(System.Environment.NewLine, System.Environment.NewLine) : System.Environment.NewLine); - else if (token.IsSingleLineEnding()) - stringBuilder.Append(preserveEnding ? System.Environment.NewLine : Space); - } + if (token.IsDoubleLineEnding()) + stringBuilder.Append(preserveEnding ? string.Concat(System.Environment.NewLine, System.Environment.NewLine) : System.Environment.NewLine); + else if (token.IsSingleLineEnding()) + stringBuilder.Append(preserveEnding ? System.Environment.NewLine : Space); } } diff --git a/src/PSRule/Help/MarkdownConvert.cs b/src/PSRule/Help/MarkdownConvert.cs index f65a343b9c..c9a4376243 100644 --- a/src/PSRule/Help/MarkdownConvert.cs +++ b/src/PSRule/Help/MarkdownConvert.cs @@ -3,40 +3,39 @@ using System.Management.Automation; -namespace PSRule.Help +namespace PSRule.Help; + +internal static class MarkdownConvert { - internal static class MarkdownConvert + private const char Dash = '-'; + private const string TripleDash = "---"; + + public static PSObject[] DeserializeObject(string markdown) + { + var result = YamlHeader(markdown); + return result == null ? System.Array.Empty() : new PSObject[] { result }; + } + + private static PSObject YamlHeader(string markdown) { - private const char Dash = '-'; - private const string TripleDash = "---"; - - public static PSObject[] DeserializeObject(string markdown) - { - var result = YamlHeader(markdown); - return result == null ? System.Array.Empty() : new PSObject[] { result }; - } - - private static PSObject YamlHeader(string markdown) - { - var stream = new MarkdownStream(markdown); - if (stream.EOF || stream.Line > 1 || stream.Current != Dash) - return null; - - // Check if the line is just dashes indicating start of yaml header - if (!stream.PeakLine(Dash, out var count) || count < 2) - return null; - - stream.Skip(count + 1); - stream.SkipLineEnding(); - - var yaml = stream.CaptureUntil(TripleDash, onNewLine: true).Trim(); - var d = new YamlDotNet.Serialization.DeserializerBuilder() - .IgnoreUnmatchedProperties() - .WithTypeConverter(new PSObjectYamlTypeConverter()) - .WithNodeTypeResolver(new PSObjectYamlTypeResolver()) - .Build(); - - return d.Deserialize(yaml); - } + var stream = new MarkdownStream(markdown); + if (stream.EOF || stream.Line > 1 || stream.Current != Dash) + return null; + + // Check if the line is just dashes indicating start of yaml header + if (!stream.PeakLine(Dash, out var count) || count < 2) + return null; + + stream.Skip(count + 1); + stream.SkipLineEnding(); + + var yaml = stream.CaptureUntil(TripleDash, onNewLine: true).Trim(); + var d = new YamlDotNet.Serialization.DeserializerBuilder() + .IgnoreUnmatchedProperties() + .WithTypeConverter(new PSObjectYamlTypeConverter()) + .WithNodeTypeResolver(new PSObjectYamlTypeResolver()) + .Build(); + + return d.Deserialize(yaml); } } diff --git a/src/PSRule/Help/MarkdownLexer.cs b/src/PSRule/Help/MarkdownLexer.cs index 56ff737df6..6c308fa436 100644 --- a/src/PSRule/Help/MarkdownLexer.cs +++ b/src/PSRule/Help/MarkdownLexer.cs @@ -1,34 +1,33 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -namespace PSRule.Help +namespace PSRule.Help; + +internal abstract class MarkdownLexer { - internal abstract class MarkdownLexer - { - protected MarkdownLexer() { } + protected MarkdownLexer() { } - protected static bool IsHeading(MarkdownToken token, int level) - { - return token.Type == MarkdownTokenType.Header && - token.Depth == level; - } + protected static bool IsHeading(MarkdownToken token, int level) + { + return token.Type == MarkdownTokenType.Header && + token.Depth == level; + } - protected static bool IsHeading(MarkdownToken token, int level, string text) - { - return token.Type == MarkdownTokenType.Header && - token.Depth == level && - string.Equals(text, token.Text, StringComparison.OrdinalIgnoreCase); - } + protected static bool IsHeading(MarkdownToken token, int level, string text) + { + return token.Type == MarkdownTokenType.Header && + token.Depth == level && + string.Equals(text, token.Text, StringComparison.OrdinalIgnoreCase); + } - protected static Dictionary YamlHeader(TokenStream stream) + protected static Dictionary YamlHeader(TokenStream stream) + { + var metadata = new Dictionary(StringComparer.OrdinalIgnoreCase); + while (!stream.EOF && stream.IsTokenType(MarkdownTokenType.YamlKeyValue)) { - var metadata = new Dictionary(StringComparer.OrdinalIgnoreCase); - while (!stream.EOF && stream.IsTokenType(MarkdownTokenType.YamlKeyValue)) - { - metadata[stream.Current.Meta] = stream.Current.Text; - stream.Next(); - } - return metadata.Count == 0 ? null : metadata; + metadata[stream.Current.Meta] = stream.Current.Text; + stream.Next(); } + return metadata.Count == 0 ? null : metadata; } } diff --git a/src/PSRule/Help/MarkdownReader.cs b/src/PSRule/Help/MarkdownReader.cs index f978884b78..7f44bc0e1d 100644 --- a/src/PSRule/Help/MarkdownReader.cs +++ b/src/PSRule/Help/MarkdownReader.cs @@ -1,436 +1,435 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -namespace PSRule.Help +namespace PSRule.Help; + +internal enum MarkdownReaderMode { - internal enum MarkdownReaderMode - { - None, + None, - List - } + List +} + +/// +/// Stateful markdown reader. +/// +internal sealed class MarkdownReader +{ + private readonly TokenStream _Output; + private readonly bool _YamlHeaderOnly; /// - /// Stateful markdown reader. + /// Preserve formatting skips processing inlines and treats them as raw text. /// - internal sealed class MarkdownReader - { - private readonly TokenStream _Output; - private readonly bool _YamlHeaderOnly; - - /// - /// Preserve formatting skips processing inlines and treats them as raw text. - /// - private readonly bool _PreserveFormatting; - - private MarkdownReaderMode _Context; - private MarkdownStream _Stream; - - /// - /// Line ending characters: \r, \n - /// - private static readonly char[] LineEndingCharacters = new char[] { '\r', '\n' }; - - private const char Hash = '#'; - private const char Asterix = '*'; - private const char Backtick = '`'; - private const char Underscore = '_'; - private const char Whitespace = ' '; - private const char Colon = ':'; - private const char Dash = '-'; - private const char BracketOpen = '['; - private const char BracketClose = ']'; - private const char ParenthesesOpen = '('; - private const char ParenthesesClose = ')'; - private const char EqualSign = '='; - private const string TripleBacktick = "```"; - private static readonly char[] LinkNameStopCharacters = new char[] { '\r', '\n', ']' }; - private static readonly char[] LinkUrlStopCharacters = new char[] { '\r', '\n', ')' }; - private static readonly char[] YamlHeaderStopCharacters = new char[] { '\r', '\n', ':' }; - - private const string TripleDash = "---"; - - internal MarkdownReader(bool yamlHeaderOnly) - { - _Output = new TokenStream(); - _YamlHeaderOnly = yamlHeaderOnly; - _PreserveFormatting = false; - } + private readonly bool _PreserveFormatting; - public TokenStream Read(string markdown, string path) - { - if (string.IsNullOrEmpty(markdown)) - return _Output; + private MarkdownReaderMode _Context; + private MarkdownStream _Stream; - _Context = MarkdownReaderMode.None; - _Stream = new MarkdownStream(markdown); + /// + /// Line ending characters: \r, \n + /// + private static readonly char[] LineEndingCharacters = new char[] { '\r', '\n' }; + + private const char Hash = '#'; + private const char Asterix = '*'; + private const char Backtick = '`'; + private const char Underscore = '_'; + private const char Whitespace = ' '; + private const char Colon = ':'; + private const char Dash = '-'; + private const char BracketOpen = '['; + private const char BracketClose = ']'; + private const char ParenthesesOpen = '('; + private const char ParenthesesClose = ')'; + private const char EqualSign = '='; + private const string TripleBacktick = "```"; + private static readonly char[] LinkNameStopCharacters = new char[] { '\r', '\n', ']' }; + private static readonly char[] LinkUrlStopCharacters = new char[] { '\r', '\n', ')' }; + private static readonly char[] YamlHeaderStopCharacters = new char[] { '\r', '\n', ':' }; + + private const string TripleDash = "---"; + + internal MarkdownReader(bool yamlHeaderOnly) + { + _Output = new TokenStream(); + _YamlHeaderOnly = yamlHeaderOnly; + _PreserveFormatting = false; + } + + public TokenStream Read(string markdown, string path) + { + if (string.IsNullOrEmpty(markdown)) + return _Output; - YamlHeader(); - if (_YamlHeaderOnly) - return _Output; + _Context = MarkdownReaderMode.None; + _Stream = new MarkdownStream(markdown); - while (!_Stream.EOF) - { - var processed = UnderlineHeader() || - HashHeader() || - FencedBlock() || - Link() || - LineBreak(); - - if (!processed) - Text(); - } + YamlHeader(); + if (_YamlHeaderOnly) return _Output; - } - private void YamlHeader() + while (!_Stream.EOF) { - if (_Stream.EOF || _Stream.Line > 1 || _Stream.Current != Dash) - return; + var processed = UnderlineHeader() || + HashHeader() || + FencedBlock() || + Link() || + LineBreak(); + + if (!processed) + Text(); + } + return _Output; + } + + private void YamlHeader() + { + if (_Stream.EOF || _Stream.Line > 1 || _Stream.Current != Dash) + return; - // Check if the line is just dashes indicating start of yaml header - if (!_Stream.PeakLine(Dash, out var count) || count < 2) - return; + // Check if the line is just dashes indicating start of yaml header + if (!_Stream.PeakLine(Dash, out var count) || count < 2) + return; - _Stream.Skip(count + 1); - _Stream.SkipLineEnding(); + _Stream.Skip(count + 1); + _Stream.SkipLineEnding(); - while (!_Stream.EOF && !_Stream.IsSequence(TripleDash, onNewLine: true)) + while (!_Stream.EOF && !_Stream.IsSequence(TripleDash, onNewLine: true)) + { + var key = _Stream.CaptureUntil(YamlHeaderStopCharacters).Trim(); + _Stream.SkipWhitespace(); + + if (!string.IsNullOrEmpty(key) && _Stream.Skip(Colon)) { - var key = _Stream.CaptureUntil(YamlHeaderStopCharacters).Trim(); _Stream.SkipWhitespace(); - if (!string.IsNullOrEmpty(key) && _Stream.Skip(Colon)) - { - _Stream.SkipWhitespace(); - - var value = _Stream.CaptureUntil(LineEndingCharacters).TrimEnd(); - _Stream.SkipLineEnding(); - _Output.YamlKeyValue(key, value); - } - else - { - _Stream.Next(); - } + var value = _Stream.CaptureUntil(LineEndingCharacters).TrimEnd(); + _Stream.SkipLineEnding(); + _Output.YamlKeyValue(key, value); + } + else + { + _Stream.Next(); } - _Stream.Skip(TripleDash); - _Stream.SkipLineEnding(); } + _Stream.Skip(TripleDash); + _Stream.SkipLineEnding(); + } - private bool UnderlineHeader() - { - if ((_Stream.Current != Dash && _Stream.Current != EqualSign) || !_Stream.IsStartOfLine) - return false; + private bool UnderlineHeader() + { + if ((_Stream.Current != Dash && _Stream.Current != EqualSign) || !_Stream.IsStartOfLine) + return false; - // Check the line is made up of the same characters - if (!_Stream.PeakLine(_Stream.Current, out var count)) - return false; + // Check the line is made up of the same characters + if (!_Stream.PeakLine(_Stream.Current, out var count)) + return false; - var currentChar = _Stream.Current; + var currentChar = _Stream.Current; - // Remove the previous token and replace with a header - if (_Output.Current?.Type == MarkdownTokenType.Text) - { - var previousToken = _Output.Pop(); + // Remove the previous token and replace with a header + if (_Output.Current?.Type == MarkdownTokenType.Text) + { + var previousToken = _Output.Pop(); - _Stream.Skip(count + 1); - _Output.Header(currentChar == EqualSign ? 1 : 2, previousToken.Text, null, lineBreak: (_Stream.SkipLineEnding(max: 0) > 1)); + _Stream.Skip(count + 1); + _Output.Header(currentChar == EqualSign ? 1 : 2, previousToken.Text, null, lineBreak: (_Stream.SkipLineEnding(max: 0) > 1)); - return true; - } - return false; + return true; } + return false; + } - /// - /// Process hash header. - /// - private bool HashHeader() - { - if (_Stream.Current != Hash || !_Stream.IsStartOfLine) - return false; + /// + /// Process hash header. + /// + private bool HashHeader() + { + if (_Stream.Current != Hash || !_Stream.IsStartOfLine) + return false; - _Stream.MarkExtentStart(); - _Stream.Next(); + _Stream.MarkExtentStart(); + _Stream.Next(); - // Get the header depth - var headerDepth = _Stream.Skip(Hash, max: 0) + 1; + // Get the header depth + var headerDepth = _Stream.Skip(Hash, max: 0) + 1; - // Capture to the end of the line - _Stream.SkipWhitespace(); - var text = _Stream.CaptureLine(); - var extent = _Stream.GetExtent(); + // Capture to the end of the line + _Stream.SkipWhitespace(); + var text = _Stream.CaptureLine(); + var extent = _Stream.GetExtent(); - _Output.Header(headerDepth, text, extent, lineBreak: (_Stream.SkipLineEnding(max: 0) > 1)); - return true; - } + _Output.Header(headerDepth, text, extent, lineBreak: (_Stream.SkipLineEnding(max: 0) > 1)); + return true; + } - /// - /// Process a fenced block. - /// - private bool FencedBlock() + /// + /// Process a fenced block. + /// + private bool FencedBlock() + { + if (_Stream.Current != Backtick || !_Stream.IsSequence(TripleBacktick, onNewLine: true)) { - if (_Stream.Current != Backtick || !_Stream.IsSequence(TripleBacktick, onNewLine: true)) - { - return false; - } + return false; + } - _Stream.MarkExtentStart(); + _Stream.MarkExtentStart(); - // Skip backticks - _Stream.Skip(3); + // Skip backticks + _Stream.Skip(3); - // Get info-string - var info = _Stream.CaptureLine(); - _Stream.SkipLineEnding(); + // Get info-string + var info = _Stream.CaptureLine(); + _Stream.SkipLineEnding(); - // Capture text within code fence - var text = _Stream.CaptureUntil(TripleBacktick, onNewLine: true, ignoreEscaping: true); + // Capture text within code fence + var text = _Stream.CaptureUntil(TripleBacktick, onNewLine: true, ignoreEscaping: true); - // Skip backticks - _Stream.Skip(TripleBacktick); + // Skip backticks + _Stream.Skip(TripleBacktick); + + // Write code fence beginning + _Output.FencedBlock(info, text, null, lineBreak: _Stream.SkipLineEnding(max: 0) > 1); + return true; + } + + private bool LineBreak() + { + if (_Stream.Current != '\r' && _Stream.Current != '\n') + return false; + + if (_Stream.IsSequence("\r\n")) + { + var breakCount = _Stream.SkipLineEnding(max: 0, ignoreEscaping: _PreserveFormatting); + + if (_PreserveFormatting) + _Output.LineBreak(count: breakCount); - // Write code fence beginning - _Output.FencedBlock(info, text, null, lineBreak: _Stream.SkipLineEnding(max: 0) > 1); return true; } + return false; + } - private bool LineBreak() - { - if (_Stream.Current != '\r' && _Stream.Current != '\n') - return false; + private void Text() + { + _Stream.MarkExtentStart(); - if (_Stream.IsSequence("\r\n")) - { - var breakCount = _Stream.SkipLineEnding(max: 0, ignoreEscaping: _PreserveFormatting); + // Set the default style + var textStyle = MarkdownTokens.None; - if (_PreserveFormatting) - _Output.LineBreak(count: breakCount); + var startOfLine = _Stream.IsStartOfLine; - return true; - } - return false; - } + // Get the text + var text = _PreserveFormatting ? _Stream.CaptureUntil(LineEndingCharacters, ignoreEscaping: true) : UnwrapStyleMarkers(_Stream, out textStyle); - private void Text() - { - _Stream.MarkExtentStart(); + // Set the line ending + var ending = GetEnding(_Stream.SkipLineEnding(max: 2)); - // Set the default style - var textStyle = MarkdownTokens.None; + if (string.IsNullOrWhiteSpace(text) && !_PreserveFormatting) + return; - var startOfLine = _Stream.IsStartOfLine; + if (_Context != MarkdownReaderMode.List && startOfLine && IsList(text)) + { + _Context = MarkdownReaderMode.List; + if (_Output.Current != null && _Output.Current.Flag.IsEnding() && !_Output.Current.Flag.ShouldPreserve()) + _Output.Current.Flag |= MarkdownTokens.Preserve; + } - // Get the text - var text = _PreserveFormatting ? _Stream.CaptureUntil(LineEndingCharacters, ignoreEscaping: true) : UnwrapStyleMarkers(_Stream, out textStyle); + // Override line ending if the line was a list item so that the line ending is preserved + if (_Context == MarkdownReaderMode.List && ending.IsEnding()) + ending |= MarkdownTokens.Preserve; - // Set the line ending - var ending = GetEnding(_Stream.SkipLineEnding(max: 2)); + // Add the text to the output stream + _Output.Text(text, flag: textStyle | ending); - if (string.IsNullOrWhiteSpace(text) && !_PreserveFormatting) - return; + if (_Context == MarkdownReaderMode.List && ending.IsEnding()) + _Context = MarkdownReaderMode.None; + } - if (_Context != MarkdownReaderMode.List && startOfLine && IsList(text)) - { - _Context = MarkdownReaderMode.List; - if (_Output.Current != null && _Output.Current.Flag.IsEnding() && !_Output.Current.Flag.ShouldPreserve()) - _Output.Current.Flag |= MarkdownTokens.Preserve; - } + private static string UnwrapStyleMarkers(MarkdownStream stream, out MarkdownTokens flag) + { + flag = MarkdownTokens.None; - // Override line ending if the line was a list item so that the line ending is preserved - if (_Context == MarkdownReaderMode.List && ending.IsEnding()) - ending |= MarkdownTokens.Preserve; + // Check for style + var styleChar = stream.Current; + var stylePrevious = stream.Previous; + var styleCount = styleChar == Asterix || styleChar == Underscore ? stream.Skip(styleChar, max: 0) : 0; + var codeCount = styleChar == Backtick ? stream.Skip(Backtick, max: 0) : 0; - // Add the text to the output stream - _Output.Text(text, flag: textStyle | ending); + CharacterMatchDelegate stop = IsTextStop; + if (codeCount > 0) + stop = IsCodeStop; - if (_Context == MarkdownReaderMode.List && ending.IsEnding()) - _Context = MarkdownReaderMode.None; - } + var text = stream.CaptureUntil(stop, ignoreEscaping: false); - private static string UnwrapStyleMarkers(MarkdownStream stream, out MarkdownTokens flag) + // Check for italic and bold endings + if (styleCount > 0) { - flag = MarkdownTokens.None; + if (stream.Current == styleChar) + { + var styleEnding = stream.Skip(styleChar, max: styleCount); - // Check for style - var styleChar = stream.Current; - var stylePrevious = stream.Previous; - var styleCount = styleChar == Asterix || styleChar == Underscore ? stream.Skip(styleChar, max: 0) : 0; - var codeCount = styleChar == Backtick ? stream.Skip(Backtick, max: 0) : 0; + // Add back underscores within text + if (styleChar == Underscore && stylePrevious != Whitespace) + return Pad(text, styleChar, left: styleCount, right: styleCount); - CharacterMatchDelegate stop = IsTextStop; - if (codeCount > 0) - stop = IsCodeStop; + // Add back asterixes/underscores that are part of text + if (styleEnding < styleCount) + text = Pad(text, styleChar, left: styleCount - styleEnding); - var text = stream.CaptureUntil(stop, ignoreEscaping: false); + if (styleEnding == 1 || styleEnding == 3) + flag |= MarkdownTokens.Italic; - // Check for italic and bold endings - if (styleCount > 0) - { - if (stream.Current == styleChar) - { - var styleEnding = stream.Skip(styleChar, max: styleCount); - - // Add back underscores within text - if (styleChar == Underscore && stylePrevious != Whitespace) - return Pad(text, styleChar, left: styleCount, right: styleCount); - - // Add back asterixes/underscores that are part of text - if (styleEnding < styleCount) - text = Pad(text, styleChar, left: styleCount - styleEnding); - - if (styleEnding == 1 || styleEnding == 3) - flag |= MarkdownTokens.Italic; - - if (styleEnding >= 2) - flag |= MarkdownTokens.Bold; - } - else - { - // Add back asterixes/underscores that are part of text - text = Pad(text, styleChar, left: styleCount); - } + if (styleEnding >= 2) + flag |= MarkdownTokens.Bold; } - - if (codeCount > 0) + else { - if (stream.Current == styleChar) - { - var codeEnding = stream.Skip(styleChar, max: 1); - - // Add back backticks that are part of text - if (codeEnding < codeCount) - text = Pad(text, styleChar, left: codeCount - codeEnding); - - if (codeEnding == 1) - flag |= MarkdownTokens.Code; - } - else - { - // Add back backticks that are part of text - text = Pad(text, styleChar, left: codeCount); - } + // Add back asterixes/underscores that are part of text + text = Pad(text, styleChar, left: styleCount); } - return text; - } - - private static string Pad(string text, char c, int left = 0, int right = 0) - { - return text.PadLeft(text.Length + left, c).PadRight(text.Length + left + right, c); } - private static bool IsList(string text) + if (codeCount > 0) { - var clean = text.Trim(); - - if (string.IsNullOrEmpty(clean)) - return false; + if (stream.Current == styleChar) + { + var codeEnding = stream.Skip(styleChar, max: 1); - var firstChar = clean[0]; + // Add back backticks that are part of text + if (codeEnding < codeCount) + text = Pad(text, styleChar, left: codeCount - codeEnding); - return firstChar == Dash || firstChar == Asterix; - } - - private static MarkdownTokens GetEnding(int lineEndings) - { - return lineEndings == 0 ? MarkdownTokens.None : (lineEndings == 1) ? MarkdownTokens.LineEnding : MarkdownTokens.LineBreak; + if (codeEnding == 1) + flag |= MarkdownTokens.Code; + } + else + { + // Add back backticks that are part of text + text = Pad(text, styleChar, left: codeCount); + } } + return text; + } - /// - /// Process link. - /// - private bool Link() - { - if (_Stream.Current != BracketOpen || _Stream.IsEscaped) - return false; + private static string Pad(string text, char c, int left = 0, int right = 0) + { + return text.PadLeft(text.Length + left, c).PadRight(text.Length + left + right, c); + } - _Stream.MarkExtentStart(); - _Stream.Checkpoint(); + private static bool IsList(string text) + { + var clean = text.Trim(); - // Skip [ - _Stream.Next(); + if (string.IsNullOrEmpty(clean)) + return false; - // Find end ] - var text = _Stream.CaptureUntil(LinkNameStopCharacters); + var firstChar = clean[0]; - // Check if closing bracket was found in line - if (_Stream.Current != BracketClose) - { - // Ignore and add as text - _Stream.Rollback(); - _Stream.Next(); + return firstChar == Dash || firstChar == Asterix; + } - var ending = GetEnding(_Stream.SkipLineEnding(max: 0)); + private static MarkdownTokens GetEnding(int lineEndings) + { + return lineEndings == 0 ? MarkdownTokens.None : (lineEndings == 1) ? MarkdownTokens.LineEnding : MarkdownTokens.LineBreak; + } - _Output.Text("[", flag: ending); + /// + /// Process link. + /// + private bool Link() + { + if (_Stream.Current != BracketOpen || _Stream.IsEscaped) + return false; - return true; - } + _Stream.MarkExtentStart(); + _Stream.Checkpoint(); - // Skip ] - _Stream.Next(); + // Skip [ + _Stream.Next(); - if (string.IsNullOrEmpty(text)) - { - var ending = GetEnding(_Stream.SkipLineEnding(max: 0)); + // Find end ] + var text = _Stream.CaptureUntil(LinkNameStopCharacters); - _Output.Text("[]", flag: ending); + // Check if closing bracket was found in line + if (_Stream.Current != BracketClose) + { + // Ignore and add as text + _Stream.Rollback(); + _Stream.Next(); - return true; - } + var ending = GetEnding(_Stream.SkipLineEnding(max: 0)); - // Check for link destination indicated by '('. i.e. [text](destination) - if (_Stream.Skip(ParenthesesOpen)) - { - var uri = _Stream.CaptureUntil(LinkUrlStopCharacters); + _Output.Text("[", flag: ending); - // Check if closing bracket was found in line - if (_Stream.Current != ParenthesesClose) - { - // TODO: Looks like error, double check, will position be lost - return true; - } + return true; + } - // Skip ) - _Stream.Next(); + // Skip ] + _Stream.Next(); - _Output.Link(text, uri); - } - // Check for link label indicated by '['. i.e. [text][label] - else if (_Stream.Skip(BracketOpen)) - { - var linkRef = _Stream.CaptureUntil(LinkNameStopCharacters); + if (string.IsNullOrEmpty(text)) + { + var ending = GetEnding(_Stream.SkipLineEnding(max: 0)); - // Skip ] - _Stream.Next(); + _Output.Text("[]", flag: ending); - _Output.LinkReference(text, linkRef); - } - // Check for link reference definition indicated by ':'. i.e. [label]: destination - else if (_Stream.Skip(Colon)) - { - _Stream.SkipWhitespace(); + return true; + } - var destination = _Stream.CaptureUntil(LineEndingCharacters); + // Check for link destination indicated by '('. i.e. [text](destination) + if (_Stream.Skip(ParenthesesOpen)) + { + var uri = _Stream.CaptureUntil(LinkUrlStopCharacters); - _Output.LinkReferenceDefinition(text, destination); - } - else + // Check if closing bracket was found in line + if (_Stream.Current != ParenthesesClose) { - _Output.LinkReference(text, text); + // TODO: Looks like error, double check, will position be lost + return true; } - var extent = _Stream.GetExtent(); - return true; - } + // Skip ) + _Stream.Next(); - private static bool IsTextStop(char c) + _Output.Link(text, uri); + } + // Check for link label indicated by '['. i.e. [text][label] + else if (_Stream.Skip(BracketOpen)) { - return c == '\r' || c == '\n' || c == '[' || c == '*' || c == '`' || c == '_'; + var linkRef = _Stream.CaptureUntil(LinkNameStopCharacters); + + // Skip ] + _Stream.Next(); + + _Output.LinkReference(text, linkRef); } + // Check for link reference definition indicated by ':'. i.e. [label]: destination + else if (_Stream.Skip(Colon)) + { + _Stream.SkipWhitespace(); + + var destination = _Stream.CaptureUntil(LineEndingCharacters); - private static bool IsCodeStop(char c) + _Output.LinkReferenceDefinition(text, destination); + } + else { - return c == '\r' || c == '\n' || c == '`'; + _Output.LinkReference(text, text); } + + var extent = _Stream.GetExtent(); + return true; + } + + private static bool IsTextStop(char c) + { + return c == '\r' || c == '\n' || c == '[' || c == '*' || c == '`' || c == '_'; + } + + private static bool IsCodeStop(char c) + { + return c == '\r' || c == '\n' || c == '`'; } } diff --git a/src/PSRule/Help/MarkdownStream.cs b/src/PSRule/Help/MarkdownStream.cs index 1a2056e6b1..d9ae6d2863 100644 --- a/src/PSRule/Help/MarkdownStream.cs +++ b/src/PSRule/Help/MarkdownStream.cs @@ -3,539 +3,538 @@ using System.Diagnostics; -namespace PSRule.Help -{ - internal delegate bool CharacterMatchDelegate(char c); +namespace PSRule.Help; - [DebuggerDisplay("StartPos = (L: {Start}, C: {Column}), EndPos = (L: {End}, C: {Column.End}), Text = {Text}")] - internal sealed class SourceExtent - { - private readonly string _Source; +internal delegate bool CharacterMatchDelegate(char c); - // Lazily cache extracted text - private string _Text; +[DebuggerDisplay("StartPos = (L: {Start}, C: {Column}), EndPos = (L: {End}, C: {Column.End}), Text = {Text}")] +internal sealed class SourceExtent +{ + private readonly string _Source; - internal SourceExtent(string source, string path, int start, int end, int line, int column) - { - _Text = null; - _Source = source; - Path = path; - Start = start; - End = end; - Line = line; - Column = column; - } + // Lazily cache extracted text + private string _Text; - public readonly string Path; + internal SourceExtent(string source, string path, int start, int end, int line, int column) + { + _Text = null; + _Source = source; + Path = path; + Start = start; + End = end; + Line = line; + Column = column; + } + + public readonly string Path; - public readonly int Start; + public readonly int Start; - public readonly int End; + public readonly int End; - public readonly int Line; + public readonly int Line; - public readonly int Column; + public readonly int Column; - public string Text + public string Text + { + get { - get + if (_Text == null) { - if (_Text == null) - { - _Text = _Source.Substring(Start, (End - Start)); - } - - return _Text; + _Text = _Source.Substring(Start, (End - Start)); } + + return _Text; } } +} - [DebuggerDisplay("Position = {Position}, Current = {Current}")] - internal sealed class MarkdownStream +[DebuggerDisplay("Position = {Position}, Current = {Current}")] +internal sealed class MarkdownStream +{ + private sealed class StreamCursor { - private sealed class StreamCursor - { - public int Position; - public int Line; - public int Column; - } + public int Position; + public int Line; + public int Column; + } - private readonly string _Source; - private readonly int _Length; - - /// - /// The current character position in the markdown string. Call Next() to change the position. - /// - private int _Position; - private int _Line; - private int _Column; - private char _Current; - private char _Previous; - private int _EscapeLength; - - private int? _ExtentMarker; - private StreamCursor _Checkpoint; - - // The maximum length of a markdown document. ~32 MB in UTF-8 - private const int MaxLength = 4194304; - - private const char NewLine = '\n'; - private const char CarrageReturn = '\r'; - private const char Whitespace = ' '; - private const char Backtick = '`'; - private const char BracketOpen = '['; - private const char BracketClose = ']'; - private const char ParenthesesOpen = '('; - private const char ParenthesesClose = ')'; - private const char AngleOpen = '<'; - private const char AngleClose = '>'; - private const char Backslash = '\\'; - private static readonly char[] NewLineStopCharacters = new char[] { '\r', '\n' }; - - public MarkdownStream(string markdown) - { - _Source = markdown; - _Length = _Source.Length; - _Position = 0; - _Line = 0; - _Column = 0; - _EscapeLength = 0; + private readonly string _Source; + private readonly int _Length; + + /// + /// The current character position in the markdown string. Call Next() to change the position. + /// + private int _Position; + private int _Line; + private int _Column; + private char _Current; + private char _Previous; + private int _EscapeLength; + + private int? _ExtentMarker; + private StreamCursor _Checkpoint; + + // The maximum length of a markdown document. ~32 MB in UTF-8 + private const int MaxLength = 4194304; + + private const char NewLine = '\n'; + private const char CarrageReturn = '\r'; + private const char Whitespace = ' '; + private const char Backtick = '`'; + private const char BracketOpen = '['; + private const char BracketClose = ']'; + private const char ParenthesesOpen = '('; + private const char ParenthesesClose = ')'; + private const char AngleOpen = '<'; + private const char AngleClose = '>'; + private const char Backslash = '\\'; + private static readonly char[] NewLineStopCharacters = new char[] { '\r', '\n' }; + + public MarkdownStream(string markdown) + { + _Source = markdown; + _Length = _Source.Length; + _Position = 0; + _Line = 0; + _Column = 0; + _EscapeLength = 0; - if (_Length < 0 || _Length > MaxLength) - throw new ArgumentOutOfRangeException(nameof(markdown)); + if (_Length < 0 || _Length > MaxLength) + throw new ArgumentOutOfRangeException(nameof(markdown)); - UpdateCurrent(); + UpdateCurrent(); - if (_Source.Length > 0) - _Line = 1; - } + if (_Source.Length > 0) + _Line = 1; + } - #region Properties + #region Properties - public bool EOF => _Position >= _Length; + public bool EOF => _Position >= _Length; - public bool IsStartOfLine => _Column == 0; + public bool IsStartOfLine => _Column == 0; - /// - /// The character at the current position in the stream. - /// - public char Current => _Current; + /// + /// The character at the current position in the stream. + /// + public char Current => _Current; - public char Previous => _Previous; + public char Previous => _Previous; - public int Line => _Line; + public int Line => _Line; - public int Column => _Column; + public int Column => _Column; #if DEBUG - /// - /// Used for interactive debugging of current position and next characters in the stream. - /// - public string Preview => _Source.Substring(_Position); + /// + /// Used for interactive debugging of current position and next characters in the stream. + /// + public string Preview => _Source.Substring(_Position); #endif - public int Position => _Position; + public int Position => _Position; - private int Remaining => _Length - Position; + private int Remaining => _Length - Position; - public bool IsEscaped => _EscapeLength > 0; + public bool IsEscaped => _EscapeLength > 0; - #endregion Properties + #endregion Properties - /// - /// Skip if the current character is whitespace. - /// - public void SkipWhitespace() - { - Skip(Whitespace, max: 0); - } + /// + /// Skip if the current character is whitespace. + /// + public void SkipWhitespace() + { + Skip(Whitespace, max: 0); + } - /// - /// If the current character and sequential characters are line ending control characters, skip ahead. - /// - /// The number of line endings to skip. When max is 0, sequential line endings will be skipped. - /// Determines if escaped characters are skipped. - /// The number of line endings skipped. - public int SkipLineEnding(int max = 1, bool ignoreEscaping = false) + /// + /// If the current character and sequential characters are line ending control characters, skip ahead. + /// + /// The number of line endings to skip. When max is 0, sequential line endings will be skipped. + /// Determines if escaped characters are skipped. + /// The number of line endings skipped. + public int SkipLineEnding(int max = 1, bool ignoreEscaping = false) + { + var skipped = 0; + while ((Current == CarrageReturn || Current == NewLine) && (max == 0 || skipped < max)) { - var skipped = 0; - while ((Current == CarrageReturn || Current == NewLine) && (max == 0 || skipped < max)) - { - if (Remaining == 0) - break; + if (Remaining == 0) + break; - if (Current == CarrageReturn && Peak() == NewLine) - Next(); + if (Current == CarrageReturn && Peak() == NewLine) + Next(); - Next(ignoreEscaping); - skipped++; - } - return skipped; + Next(ignoreEscaping); + skipped++; } + return skipped; + } - /// - /// Skip ahead if the next character is expected. - /// - /// The character to skip. - public int SkipNext(char c) + /// + /// Skip ahead if the next character is expected. + /// + /// The character to skip. + public int SkipNext(char c) + { + var skipped = 0; + while (Peak() == c) { - var skipped = 0; - while (Peak() == c) - { - Next(); - skipped++; - } - return skipped; + Next(); + skipped++; } + return skipped; + } - public bool Skip(char c) - { - if (_Current != c) - return false; + public bool Skip(char c) + { + if (_Current != c) + return false; - Next(); - return true; - } + Next(); + return true; + } - /// - /// Skip ahead if the current character is expected. Keep skipping when the character is repeated. - /// - /// The character to skip. - /// The maximum number of characters to skip. - /// The number of characters that where skipped. - public int Skip(char c, int max) + /// + /// Skip ahead if the current character is expected. Keep skipping when the character is repeated. + /// + /// The character to skip. + /// The maximum number of characters to skip. + /// The number of characters that where skipped. + public int Skip(char c, int max) + { + var skipped = 0; + while (Current == c && (max == 0 || skipped < max)) { - var skipped = 0; - while (Current == c && (max == 0 || skipped < max)) - { - Next(); - skipped++; - } - return skipped; + Next(); + skipped++; } + return skipped; + } - public int Skip(string sequence, int max = 0, bool ignoreEscaping = false) + public int Skip(string sequence, int max = 0, bool ignoreEscaping = false) + { + var skipped = 0; + while (IsSequence(sequence) && (max == 0 || skipped < max)) { - var skipped = 0; - while (IsSequence(sequence) && (max == 0 || skipped < max)) - { - Skip(sequence.Length, ignoreEscaping); - skipped++; - } - return skipped; + Skip(sequence.Length, ignoreEscaping); + skipped++; } + return skipped; + } - /// - /// Skip ahead a number of characters. Use Next() in preference of Skip if the number to skip is 1. - /// - /// The number of characters to skip - /// Determines if escaped characters are skipped. - public void Skip(int toSkip, bool ignoreEscaping = false) - { - toSkip = HasRemaining(toSkip) ? toSkip : Remaining; - for (var i = 0; i < toSkip; i++) - Next(ignoreEscaping); - } + /// + /// Skip ahead a number of characters. Use Next() in preference of Skip if the number to skip is 1. + /// + /// The number of characters to skip + /// Determines if escaped characters are skipped. + public void Skip(int toSkip, bool ignoreEscaping = false) + { + toSkip = HasRemaining(toSkip) ? toSkip : Remaining; + for (var i = 0; i < toSkip; i++) + Next(ignoreEscaping); + } - /// - /// Peak at the n'th character from the current position. Check remaining characters prior to calling. - /// - /// The offset from the current position. - /// The character at the offset. - public char Peak(int offset = 1) - { - return _Source[_Position + offset]; - } + /// + /// Peak at the n'th character from the current position. Check remaining characters prior to calling. + /// + /// The offset from the current position. + /// The character at the offset. + public char Peak(int offset = 1) + { + return _Source[_Position + offset]; + } - public bool PeakAnyOf(int offset = 1, params char[] c) - { - return c.Contains(Peak(offset)); - } + public bool PeakAnyOf(int offset = 1, params char[] c) + { + return c.Contains(Peak(offset)); + } - public bool PeakLine(char c, out int count) + public bool PeakLine(char c, out int count) + { + var offset = 1; + + while (Peak(offset) == c) { - var offset = 1; + offset++; + } - while (Peak(offset) == c) - { - offset++; - } + count = offset - 1; - count = offset - 1; + return NewLineStopCharacters.Contains(Peak(offset)); + } - return NewLineStopCharacters.Contains(Peak(offset)); - } + public int PeakCount(char c) + { + var count = 1; - public int PeakCount(char c) + while (Peak(count) == c) { - var count = 1; + count++; + } - while (Peak(count) == c) - { - count++; - } + return count; + } - return count; - } + public void MarkExtentStart() + { + _ExtentMarker = _Position; + } - public void MarkExtentStart() + /// + /// Get the extent and clear previous marker. + /// + /// + public SourceExtent GetExtent() + { + if (!_ExtentMarker.HasValue) { - _ExtentMarker = _Position; + return null; } - /// - /// Get the extent and clear previous marker. - /// - /// - public SourceExtent GetExtent() - { - if (!_ExtentMarker.HasValue) - { - return null; - } + var extent = new SourceExtent( + source: _Source, + path: null, + start: _ExtentMarker.Value, + end: _Position, + line: _Line, + column: _Column); - var extent = new SourceExtent( - source: _Source, - path: null, - start: _ExtentMarker.Value, - end: _Position, - line: _Line, - column: _Column); + _ExtentMarker = null; - _ExtentMarker = null; + return extent; + } - return extent; - } + /// + /// Create a position checkpoint that can be rolled back. + /// + public void Checkpoint() + { + _Checkpoint = new StreamCursor { Position = _Position, Line = _Line, Column = _Column }; + } + + /// + /// Rollback a position checkpoint. + /// + public void Rollback() + { + _Position = _Checkpoint.Position; + _Line = _Checkpoint.Line; + _Column = _Checkpoint.Column; + + UpdateCurrent(); + } - /// - /// Create a position checkpoint that can be rolled back. - /// - public void Checkpoint() + /// + /// Move to the next character in the stream. + /// + /// Is True when more characters exist in the stream. + public bool Next(bool ignoreEscaping = false) + { + _Position += _EscapeLength > 0 ? _EscapeLength + 1 : 1; + if (_Position >= _Length) { - _Checkpoint = new StreamCursor { Position = _Position, Line = _Line, Column = _Column }; + _Current = char.MinValue; + return false; } - /// - /// Rollback a position checkpoint. - /// - public void Rollback() + // Update line and column counters + if (_Current == NewLine) { - _Position = _Checkpoint.Position; - _Line = _Checkpoint.Line; - _Column = _Checkpoint.Column; - - UpdateCurrent(); + _Line++; + _Column = 0; } - - /// - /// Move to the next character in the stream. - /// - /// Is True when more characters exist in the stream. - public bool Next(bool ignoreEscaping = false) + else { - _Position += _EscapeLength > 0 ? _EscapeLength + 1 : 1; - if (_Position >= _Length) - { - _Current = char.MinValue; - return false; - } + _Column += _EscapeLength + 1; + } + UpdateCurrent(ignoreEscaping); + return true; + } - // Update line and column counters - if (_Current == NewLine) - { - _Line++; - _Column = 0; - } - else + private void UpdateCurrent(bool ignoreEscaping = false) + { + // Handle escape sequences + _EscapeLength = ignoreEscaping ? 0 : GetEscapeCount(_Position); + + _Previous = _Current; + _Current = _Source[_Position + _EscapeLength]; + } + + private int GetEscapeCount(int position) + { + // Check for escape sequences + if (position < _Length && _Source[position] == Backslash) + { + var next = _Source[position + 1]; + + // Check against list of escapable characters + if (next == Backslash || + next == BracketOpen || + next == ParenthesesOpen || + next == AngleOpen || + next == AngleClose || + next == Backtick || + next == BracketClose || + next == ParenthesesClose) { - _Column += _EscapeLength + 1; + return 1; } - UpdateCurrent(ignoreEscaping); - return true; } - private void UpdateCurrent(bool ignoreEscaping = false) - { - // Handle escape sequences - _EscapeLength = ignoreEscaping ? 0 : GetEscapeCount(_Position); + return 0; + } - _Previous = _Current; - _Current = _Source[_Position + _EscapeLength]; - } + public string CaptureWithinLineUntil(char c) + { + var start = Position; + var length = 0; - private int GetEscapeCount(int position) + while (Current != c && !EOF) { - // Check for escape sequences - if (position < _Length && _Source[position] == Backslash) - { - var next = _Source[position + 1]; - - // Check against list of escapable characters - if (next == Backslash || - next == BracketOpen || - next == ParenthesesOpen || - next == AngleOpen || - next == AngleClose || - next == Backtick || - next == BracketClose || - next == ParenthesesClose) - { - return 1; - } - } + if (NewLineStopCharacters.Contains(Current)) + break; - return 0; + length++; + Next(); } + return Substring(start, length); + } - public string CaptureWithinLineUntil(char c) - { - var start = Position; - var length = 0; - - while (Current != c && !EOF) - { - if (NewLineStopCharacters.Contains(Current)) - break; + /// + /// Capture text until the sequence is found. + /// + /// A specific sequence that ends the capture. + /// + /// Interprets the string literally instead of processing escape sequences. + /// Returns the captured text up until the sequence. + public string CaptureUntil(string sequence, bool onNewLine = false, bool ignoreEscaping = false) + { + var start = Position; + var length = 0; - length++; - Next(); - } - return Substring(start, length); + while (!IsSequence(sequence, onNewLine) && !EOF) + { + length++; + Next(ignoreEscaping); } - /// - /// Capture text until the sequence is found. - /// - /// A specific sequence that ends the capture. - /// - /// Interprets the string literally instead of processing escape sequences. - /// Returns the captured text up until the sequence. - public string CaptureUntil(string sequence, bool onNewLine = false, bool ignoreEscaping = false) + // Back track line endings so they are not included in the captured string + for (var i = 1; i < length; i++) { - var start = Position; - var length = 0; + if (!IsLineEnding(Peak(-i))) + break; - while (!IsSequence(sequence, onNewLine) && !EOF) - { - length++; - Next(ignoreEscaping); - } + length--; + } + return Substring(start, length, ignoreEscaping); + } - // Back track line endings so they are not included in the captured string - for (var i = 1; i < length; i++) - { - if (!IsLineEnding(Peak(-i))) - break; + public string CaptureUntil(char[] c, bool ignoreEscaping = false) + { + var start = Position; + var length = 0; - length--; - } - return Substring(start, length, ignoreEscaping); + while (!EOF) + { + if (!IsEscaped && c.Contains(Current)) + break; + + length++; + Next(ignoreEscaping); } + return Substring(start, length, ignoreEscaping); + } - public string CaptureUntil(char[] c, bool ignoreEscaping = false) - { - var start = Position; - var length = 0; + public string CaptureUntil(CharacterMatchDelegate match, bool ignoreEscaping = false) + { + var start = Position; + var length = 0; - while (!EOF) - { - if (!IsEscaped && c.Contains(Current)) - break; + while (!EOF) + { + if (!IsEscaped && match(Current)) + break; - length++; - Next(ignoreEscaping); - } - return Substring(start, length, ignoreEscaping); + length++; + Next(ignoreEscaping); } + return Substring(start, length, ignoreEscaping); + } - public string CaptureUntil(CharacterMatchDelegate match, bool ignoreEscaping = false) - { - var start = Position; - var length = 0; + private string Substring(int start, int length, bool ignoreEscaping = false) + { + var newLine = System.Environment.NewLine.ToCharArray(); - while (!EOF) + var position = start; + var i = 0; + var buffer = new char[length * 2]; + while (i < length) + { + var ending = GetLineEndingCount(_Source, position); + if (ending > 0) { - if (!IsEscaped && match(Current)) - break; + newLine.CopyTo(buffer, i); + i += newLine.Length; + position += ending; - length++; - Next(ignoreEscaping); + // Adjust based on difference in line endings + length += newLine.Length - ending; + continue; } - return Substring(start, length, ignoreEscaping); + var offset = ignoreEscaping ? 0 : GetEscapeCount(position); + buffer[i] = _Source[position + offset]; + position += offset + 1; + i++; } + return new string(buffer, 0, i); + } - private string Substring(int start, int length, bool ignoreEscaping = false) - { - var newLine = System.Environment.NewLine.ToCharArray(); + /// + /// Capture text until the end of the line. + /// + /// Returns the captured text up until the end of the line. + public string CaptureLine() + { + return CaptureUntil(NewLineStopCharacters); + } - var position = start; - var i = 0; - var buffer = new char[length * 2]; - while (i < length) - { - var ending = GetLineEndingCount(_Source, position); - if (ending > 0) - { - newLine.CopyTo(buffer, i); - i += newLine.Length; - position += ending; - - // Adjust based on difference in line endings - length += newLine.Length - ending; - continue; - } - var offset = ignoreEscaping ? 0 : GetEscapeCount(position); - buffer[i] = _Source[position + offset]; - position += offset + 1; - i++; - } - return new string(buffer, 0, i); - } + public bool IsSequence(string sequence, bool onNewLine = false) + { + if (onNewLine && !IsStartOfLine) + return false; - /// - /// Capture text until the end of the line. - /// - /// Returns the captured text up until the end of the line. - public string CaptureLine() - { - return CaptureUntil(NewLineStopCharacters); - } + if (!HasRemaining(sequence.Length)) + return false; - public bool IsSequence(string sequence, bool onNewLine = false) + for (var i = 0; i < sequence.Length; i++) { - if (onNewLine && !IsStartOfLine) + if (Peak(i) != sequence[i]) return false; - - if (!HasRemaining(sequence.Length)) - return false; - - for (var i = 0; i < sequence.Length; i++) - { - if (Peak(i) != sequence[i]) - return false; - } - return true; } + return true; + } - private bool HasRemaining(int length) - { - return Remaining >= length; - } + private bool HasRemaining(int length) + { + return Remaining >= length; + } - private static bool IsLineEnding(char c) - { - return c == CarrageReturn || c == NewLine; - } + private static bool IsLineEnding(char c) + { + return c == CarrageReturn || c == NewLine; + } - private static int GetLineEndingCount(string s, int pos) - { - var c = s[pos]; - if (!IsLineEnding(c)) - return 0; + private static int GetLineEndingCount(string s, int pos) + { + var c = s[pos]; + if (!IsLineEnding(c)) + return 0; - return c == CarrageReturn && pos < s.Length - 1 && s[pos + 1] == NewLine ? 2 : 1; - } + return c == CarrageReturn && pos < s.Length - 1 && s[pos + 1] == NewLine ? 2 : 1; } } diff --git a/src/PSRule/Help/MarkdownToken.cs b/src/PSRule/Help/MarkdownToken.cs index 97d58ae837..96bcb55ec1 100644 --- a/src/PSRule/Help/MarkdownToken.cs +++ b/src/PSRule/Help/MarkdownToken.cs @@ -3,85 +3,84 @@ using System.Diagnostics; -namespace PSRule.Help +namespace PSRule.Help; + +internal enum MarkdownTokenType { - internal enum MarkdownTokenType - { - None = 0, + None = 0, - Text, + Text, - Header, + Header, - FencedBlock, + FencedBlock, - LineBreak, + LineBreak, - ParagraphStart, + ParagraphStart, - ParagraphEnd, + ParagraphEnd, - LinkReference, + LinkReference, - Link, + Link, - LinkReferenceDefinition, + LinkReferenceDefinition, - YamlKeyValue - } + YamlKeyValue +} - [Flags()] - internal enum MarkdownTokens - { - None = 0, +[Flags()] +internal enum MarkdownTokens +{ + None = 0, - Italic = 1, + Italic = 1, - Bold = 2, + Bold = 2, - Code = 4, + Code = 4, - LineEnding = 8, + LineEnding = 8, - LineBreak = 16, + LineBreak = 16, - Preserve = 32, + Preserve = 32, + + // Accelerators + PreserveLineEnding = 40 +} - // Accelerators - PreserveLineEnding = 40 +internal static class MarkdownTokenFlagExtensions +{ + public static bool IsEnding(this MarkdownTokens flags) + { + return flags.HasFlag(MarkdownTokens.LineEnding) || flags.HasFlag(MarkdownTokens.LineBreak); } - internal static class MarkdownTokenFlagExtensions + public static bool IsLineBreak(this MarkdownTokens flags) { - public static bool IsEnding(this MarkdownTokens flags) - { - return flags.HasFlag(MarkdownTokens.LineEnding) || flags.HasFlag(MarkdownTokens.LineBreak); - } - - public static bool IsLineBreak(this MarkdownTokens flags) - { - return flags.HasFlag(MarkdownTokens.LineBreak); - } - - public static bool ShouldPreserve(this MarkdownTokens flags) - { - return flags.HasFlag(MarkdownTokens.Preserve); - } + return flags.HasFlag(MarkdownTokens.LineBreak); } - [DebuggerDisplay("Type = {Type}, Text = {Text}")] - internal sealed class MarkdownToken + public static bool ShouldPreserve(this MarkdownTokens flags) { - public SourceExtent Extent { get; set; } + return flags.HasFlag(MarkdownTokens.Preserve); + } +} - public MarkdownTokenType Type { get; set; } +[DebuggerDisplay("Type = {Type}, Text = {Text}")] +internal sealed class MarkdownToken +{ + public SourceExtent Extent { get; set; } - public string Text { get; set; } + public MarkdownTokenType Type { get; set; } - public string Meta { get; set; } + public string Text { get; set; } - public int Depth { get; set; } + public string Meta { get; set; } - public MarkdownTokens Flag { get; set; } - } + public int Depth { get; set; } + + public MarkdownTokens Flag { get; set; } } diff --git a/src/PSRule/Help/MarkdownTokenExtensions.cs b/src/PSRule/Help/MarkdownTokenExtensions.cs index ac15cc0ea9..03c8615420 100644 --- a/src/PSRule/Help/MarkdownTokenExtensions.cs +++ b/src/PSRule/Help/MarkdownTokenExtensions.cs @@ -1,25 +1,24 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -namespace PSRule.Help +namespace PSRule.Help; + +internal static class MarkdownTokenExtensions { - internal static class MarkdownTokenExtensions + public static bool IsSingleLineEnding(this MarkdownToken token) { - public static bool IsSingleLineEnding(this MarkdownToken token) - { - return token.Flag.HasFlag(MarkdownTokens.LineEnding) || - token.Type == MarkdownTokenType.LineBreak; - } + return token.Flag.HasFlag(MarkdownTokens.LineEnding) || + token.Type == MarkdownTokenType.LineBreak; + } - public static bool IsPreservableLineEnding(this MarkdownToken token) - { - return (token.Flag.HasFlag(MarkdownTokens.LineEnding) && token.Flag.HasFlag(MarkdownTokens.Preserve)) || - token.Type == MarkdownTokenType.LineBreak; - } + public static bool IsPreservableLineEnding(this MarkdownToken token) + { + return (token.Flag.HasFlag(MarkdownTokens.LineEnding) && token.Flag.HasFlag(MarkdownTokens.Preserve)) || + token.Type == MarkdownTokenType.LineBreak; + } - public static bool IsDoubleLineEnding(this MarkdownToken token) - { - return token.Flag.HasFlag(MarkdownTokens.LineBreak) && token.Type != MarkdownTokenType.LineBreak; - } + public static bool IsDoubleLineEnding(this MarkdownToken token) + { + return token.Flag.HasFlag(MarkdownTokens.LineBreak) && token.Type != MarkdownTokenType.LineBreak; } } diff --git a/src/PSRule/Help/MetadataLexer.cs b/src/PSRule/Help/MetadataLexer.cs index 10ceabeb00..c6651c7b50 100644 --- a/src/PSRule/Help/MetadataLexer.cs +++ b/src/PSRule/Help/MetadataLexer.cs @@ -1,17 +1,16 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -namespace PSRule.Help +namespace PSRule.Help; + +internal sealed class MetadataLexer : MarkdownLexer { - internal sealed class MetadataLexer : MarkdownLexer - { - public MetadataLexer() { } + public MetadataLexer() { } - public Dictionary Process(TokenStream stream) - { - // Look for yaml header - stream.MoveTo(0); - return YamlHeader(stream); - } + public Dictionary Process(TokenStream stream) + { + // Look for yaml header + stream.MoveTo(0); + return YamlHeader(stream); } } diff --git a/src/PSRule/Help/Models.cs b/src/PSRule/Help/Models.cs index d8186e150c..b4041edd19 100644 --- a/src/PSRule/Help/Models.cs +++ b/src/PSRule/Help/Models.cs @@ -3,110 +3,109 @@ using PSRule.Definitions; -namespace PSRule.Help +namespace PSRule.Help; + +/// +/// Define options that determine how markdown will be rendered. +/// +[Flags()] +internal enum FormatOptions { + None = 0, + /// - /// Define options that determine how markdown will be rendered. + /// Add a line break after headers. /// - [Flags()] - internal enum FormatOptions - { - None = 0, - - /// - /// Add a line break after headers. - /// - LineBreak = 1 - } + LineBreak = 1 +} +/// +/// Markdown text content. +/// +internal sealed class TextBlock +{ /// - /// Markdown text content. + /// The text of the section body. /// - internal sealed class TextBlock - { - /// - /// The text of the section body. - /// - public readonly string Text; - - /// - /// Additional options that determine how the section will be formated when rendering markdown. - /// - public readonly FormatOptions FormatOption; - - public TextBlock(string text, FormatOptions formatOption = FormatOptions.None) - { - Text = text; - FormatOption = formatOption; - } - - public override string ToString() - { - return Text; - } - } + public readonly string Text; /// - /// YAML link. + /// Additional options that determine how the section will be formated when rendering markdown. /// - internal sealed class Link - { - public string Name; + public readonly FormatOptions FormatOption; - public string Uri; + public TextBlock(string text, FormatOptions formatOption = FormatOptions.None) + { + Text = text; + FormatOption = formatOption; } - internal interface IHelpDocument + public override string ToString() { - string Name { get; } + return Text; + } +} + +/// +/// YAML link. +/// +internal sealed class Link +{ + public string Name; - InfoString Synopsis { get; set; } + public string Uri; +} - InfoString Description { get; set; } +internal interface IHelpDocument +{ + string Name { get; } - Link[] Links { get; set; } - } + InfoString Synopsis { get; set; } + + InfoString Description { get; set; } + + Link[] Links { get; set; } +} - internal sealed class RuleDocument : IHelpDocument +internal sealed class RuleDocument : IHelpDocument +{ + public RuleDocument(string name) { - public RuleDocument(string name) - { - Name = name; - } + Name = name; + } - public string Name { get; } + public string Name { get; } - public InfoString Synopsis { get; set; } + public InfoString Synopsis { get; set; } - public InfoString Description { get; set; } + public InfoString Description { get; set; } - public TextBlock Notes { get; set; } + public TextBlock Notes { get; set; } - public InfoString Recommendation { get; set; } + public InfoString Recommendation { get; set; } - public Link[] Links { get; set; } + public Link[] Links { get; set; } - public ResourceTags Annotations { get; set; } - } + public ResourceTags Annotations { get; set; } +} - internal sealed class ResourceHelpDocument : IHelpDocument +internal sealed class ResourceHelpDocument : IHelpDocument +{ + public ResourceHelpDocument(string name) { - public ResourceHelpDocument(string name) - { - Name = name; - } + Name = name; + } - public string Name { get; } + public string Name { get; } - public InfoString Synopsis { get; set; } + public InfoString Synopsis { get; set; } - public InfoString Description { get; set; } + public InfoString Description { get; set; } - public Link[] Links { get; set; } + public Link[] Links { get; set; } - internal IResourceHelpInfo ToInfo() - { - return new ResourceHelpInfo(Name, Name, Synopsis, Description); - } + internal IResourceHelpInfo ToInfo() + { + return new ResourceHelpInfo(Name, Name, Synopsis, Description); } } diff --git a/src/PSRule/Help/ResourceHelpLexer.cs b/src/PSRule/Help/ResourceHelpLexer.cs index dfe159fbc5..5e02f9d036 100644 --- a/src/PSRule/Help/ResourceHelpLexer.cs +++ b/src/PSRule/Help/ResourceHelpLexer.cs @@ -1,43 +1,42 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -namespace PSRule.Help +namespace PSRule.Help; + +internal sealed class ResourceHelpLexer : HelpLexer { - internal sealed class ResourceHelpLexer : HelpLexer + public ResourceHelpLexer(string culture) : base(culture) { } + + public ResourceHelpDocument Process(TokenStream stream) { - public ResourceHelpLexer(string culture) : base(culture) { } + // Look for yaml header + stream.MoveTo(0); + var metadata = YamlHeader(stream); + ResourceHelpDocument doc = null; - public ResourceHelpDocument Process(TokenStream stream) + // Process sections + while (!stream.EOF) { - // Look for yaml header - stream.MoveTo(0); - var metadata = YamlHeader(stream); - ResourceHelpDocument doc = null; - - // Process sections - while (!stream.EOF) + if (IsHeading(stream.Current, RULE_NAME_HEADING_LEVEL)) { - if (IsHeading(stream.Current, RULE_NAME_HEADING_LEVEL)) - { - doc = new ResourceHelpDocument(stream.Current.Text) - { - //Annotations = ResourceTags.FromDictionary(metadata) - }; - } - else if (doc != null && IsHeading(stream.Current, RULE_ENTRIES_HEADING_LEVEL)) + doc = new ResourceHelpDocument(stream.Current.Text) { - var matching = Synopsis(stream, doc) || - Description(stream, doc) || - Links(stream, doc); - - if (matching) - continue; - } + //Annotations = ResourceTags.FromDictionary(metadata) + }; + } + else if (doc != null && IsHeading(stream.Current, RULE_ENTRIES_HEADING_LEVEL)) + { + var matching = Synopsis(stream, doc) || + Description(stream, doc) || + Links(stream, doc); - // Skip the current token - stream.Next(); + if (matching) + continue; } - return doc; + + // Skip the current token + stream.Next(); } + return doc; } } diff --git a/src/PSRule/Help/RuleHelpLexer.cs b/src/PSRule/Help/RuleHelpLexer.cs index 3cbca5f7e2..cc41d2c7aa 100644 --- a/src/PSRule/Help/RuleHelpLexer.cs +++ b/src/PSRule/Help/RuleHelpLexer.cs @@ -3,74 +3,73 @@ using PSRule.Definitions; -namespace PSRule.Help +namespace PSRule.Help; + +/// +/// A lexer that interprets markdown as rule help. +/// +internal sealed class RuleHelpLexer : HelpLexer { - /// - /// A lexer that interprets markdown as rule help. - /// - internal sealed class RuleHelpLexer : HelpLexer + public RuleHelpLexer(string culture) : base(culture) { } + + public RuleDocument Process(TokenStream stream) { - public RuleHelpLexer(string culture) : base(culture) { } + // Look for yaml header + stream.MoveTo(0); + var metadata = YamlHeader(stream); + RuleDocument doc = null; - public RuleDocument Process(TokenStream stream) + // Process sections + while (!stream.EOF) { - // Look for yaml header - stream.MoveTo(0); - var metadata = YamlHeader(stream); - RuleDocument doc = null; - - // Process sections - while (!stream.EOF) + if (IsHeading(stream.Current, RULE_NAME_HEADING_LEVEL)) { - if (IsHeading(stream.Current, RULE_NAME_HEADING_LEVEL)) - { - doc = new RuleDocument(stream.Current.Text) - { - Annotations = ResourceTags.FromDictionary(metadata) - }; - } - else if (doc != null && IsHeading(stream.Current, RULE_ENTRIES_HEADING_LEVEL)) + doc = new RuleDocument(stream.Current.Text) { - var matching = Synopsis(stream, doc) || - Description(stream, doc) || - Recommendation(stream, doc) || - Notes(stream, doc) || - Links(stream, doc); - - if (matching) - continue; - } + Annotations = ResourceTags.FromDictionary(metadata) + }; + } + else if (doc != null && IsHeading(stream.Current, RULE_ENTRIES_HEADING_LEVEL)) + { + var matching = Synopsis(stream, doc) || + Description(stream, doc) || + Recommendation(stream, doc) || + Notes(stream, doc) || + Links(stream, doc); - // Skip the current token - stream.Next(); + if (matching) + continue; } - return doc; + + // Skip the current token + stream.Next(); } + return doc; + } - /// - /// Read recommendation. - /// - private bool Recommendation(TokenStream stream, RuleDocument doc) - { - if (!IsHeading(stream.Current, RULE_ENTRIES_HEADING_LEVEL, _Strings.GetString("Recommendation"))) - return false; + /// + /// Read recommendation. + /// + private bool Recommendation(TokenStream stream, RuleDocument doc) + { + if (!IsHeading(stream.Current, RULE_ENTRIES_HEADING_LEVEL, _Strings.GetString("Recommendation"))) + return false; - doc.Recommendation = InfoString(stream); - stream.SkipUntilHeader(); - return true; - } + doc.Recommendation = InfoString(stream); + stream.SkipUntilHeader(); + return true; + } - /// - /// Read notes. - /// - private bool Notes(TokenStream stream, RuleDocument doc) - { - if (!IsHeading(stream.Current, RULE_ENTRIES_HEADING_LEVEL, _Strings.GetString("Notes"))) - return false; + /// + /// Read notes. + /// + private bool Notes(TokenStream stream, RuleDocument doc) + { + if (!IsHeading(stream.Current, RULE_ENTRIES_HEADING_LEVEL, _Strings.GetString("Notes"))) + return false; - doc.Notes = TextBlock(stream, includeNonYamlFencedBlocks: true); - stream.SkipUntilHeader(); - return true; - } + doc.Notes = TextBlock(stream, includeNonYamlFencedBlocks: true); + stream.SkipUntilHeader(); + return true; } } diff --git a/src/PSRule/Help/TokenStream.cs b/src/PSRule/Help/TokenStream.cs index 923f5e3fbd..cfd54a5bcb 100644 --- a/src/PSRule/Help/TokenStream.cs +++ b/src/PSRule/Help/TokenStream.cs @@ -4,329 +4,328 @@ using System.Collections; using System.Diagnostics; -namespace PSRule.Help +namespace PSRule.Help; + +internal static class TokenStreamExtensions { - internal static class TokenStreamExtensions + /// + /// Add a header. + /// + public static void Header(this TokenStream stream, int depth, string text, SourceExtent extent, bool lineBreak) { - /// - /// Add a header. - /// - public static void Header(this TokenStream stream, int depth, string text, SourceExtent extent, bool lineBreak) + stream.Add(new MarkdownToken() { - stream.Add(new MarkdownToken() - { - Depth = depth, - Extent = extent, - Text = text, - Type = MarkdownTokenType.Header, - Flag = lineBreak ? MarkdownTokens.LineBreak : MarkdownTokens.LineEnding | MarkdownTokens.Preserve - }); - } + Depth = depth, + Extent = extent, + Text = text, + Type = MarkdownTokenType.Header, + Flag = lineBreak ? MarkdownTokens.LineBreak : MarkdownTokens.LineEnding | MarkdownTokens.Preserve + }); + } - public static void YamlKeyValue(this TokenStream stream, string key, string value) + public static void YamlKeyValue(this TokenStream stream, string key, string value) + { + stream.Add(new MarkdownToken() { - stream.Add(new MarkdownToken() - { - Meta = key, - Text = value, - Type = MarkdownTokenType.YamlKeyValue - }); - } + Meta = key, + Text = value, + Type = MarkdownTokenType.YamlKeyValue + }); + } - /// - /// Add a code fence. - /// - public static void FencedBlock(this TokenStream stream, string meta, string text, SourceExtent extent, bool lineBreak) + /// + /// Add a code fence. + /// + public static void FencedBlock(this TokenStream stream, string meta, string text, SourceExtent extent, bool lineBreak) + { + stream.Add(new MarkdownToken() { - stream.Add(new MarkdownToken() - { - Extent = extent, - Meta = meta, - Text = text, - Type = MarkdownTokenType.FencedBlock, - Flag = (lineBreak ? MarkdownTokens.LineBreak : MarkdownTokens.LineEnding) | MarkdownTokens.Preserve - }); - } + Extent = extent, + Meta = meta, + Text = text, + Type = MarkdownTokenType.FencedBlock, + Flag = (lineBreak ? MarkdownTokens.LineBreak : MarkdownTokens.LineEnding) | MarkdownTokens.Preserve + }); + } - /// - /// Add a line break. - /// - public static void LineBreak(this TokenStream stream, int count) + /// + /// Add a line break. + /// + public static void LineBreak(this TokenStream stream, int count) + { + // Ignore line break at the very start of file + if (stream.Count == 0) { - // Ignore line break at the very start of file - if (stream.Count == 0) - { - return; - } - - for (var i = 0; i < count; i++) - { - stream.Add(new MarkdownToken() { Type = MarkdownTokenType.LineBreak, Flag = MarkdownTokens.LineBreak }); - } + return; } - public static void Text(this TokenStream stream, string text, MarkdownTokens flag = MarkdownTokens.None) + for (var i = 0; i < count; i++) { - if (MergeText(stream.Current, text, flag)) - { - return; - } - - stream.Add(new MarkdownToken() { Type = MarkdownTokenType.Text, Text = text, Flag = flag }); + stream.Add(new MarkdownToken() { Type = MarkdownTokenType.LineBreak, Flag = MarkdownTokens.LineBreak }); } + } - private static bool MergeText(MarkdownToken current, string text, MarkdownTokens flag) + public static void Text(this TokenStream stream, string text, MarkdownTokens flag = MarkdownTokens.None) + { + if (MergeText(stream.Current, text, flag)) { - // Only allow merge if the previous token was text - if (current == null || current.Type != MarkdownTokenType.Text) - { - return false; - } - - if (current.Flag.ShouldPreserve()) - { - return false; - } - - // If the previous token was text, lessen the break but still don't allow merging - if (current.Flag.HasFlag(MarkdownTokens.LineBreak) && !current.Flag.ShouldPreserve()) - { - return false; - } - - // Text must have the same flags set - if (current.Flag.HasFlag(MarkdownTokens.Italic) != flag.HasFlag(MarkdownTokens.Italic)) - { - return false; - } - - if (current.Flag.HasFlag(MarkdownTokens.Bold) != flag.HasFlag(MarkdownTokens.Bold)) - { - return false; - } - - if (current.Flag.HasFlag(MarkdownTokens.Code) != flag.HasFlag(MarkdownTokens.Code)) - { - return false; - } - - if (!current.Flag.IsEnding()) - { - current.Text = string.Concat(current.Text, text); - } - else if (current.Flag == MarkdownTokens.LineEnding) - { - return false; - } + return; + } - // Take on the ending of the merged token - current.Flag = flag; + stream.Add(new MarkdownToken() { Type = MarkdownTokenType.Text, Text = text, Flag = flag }); + } - return true; + private static bool MergeText(MarkdownToken current, string text, MarkdownTokens flag) + { + // Only allow merge if the previous token was text + if (current == null || current.Type != MarkdownTokenType.Text) + { + return false; } - public static void Link(this TokenStream stream, string text, string uri) + if (current.Flag.ShouldPreserve()) { - stream.Add(new MarkdownToken() { Type = MarkdownTokenType.Link, Meta = text, Text = uri }); + return false; } - public static void LinkReference(this TokenStream stream, string text, string linkRef) + // If the previous token was text, lessen the break but still don't allow merging + if (current.Flag.HasFlag(MarkdownTokens.LineBreak) && !current.Flag.ShouldPreserve()) { - stream.Add(new MarkdownToken() { Type = MarkdownTokenType.LinkReference, Meta = text, Text = linkRef }); + return false; } - public static void LinkReferenceDefinition(this TokenStream stream, string text, string linkTarget) + // Text must have the same flags set + if (current.Flag.HasFlag(MarkdownTokens.Italic) != flag.HasFlag(MarkdownTokens.Italic)) { - stream.Add(new MarkdownToken() { Type = MarkdownTokenType.LinkReferenceDefinition, Meta = text, Text = linkTarget }); + return false; } - /// - /// Add a marker for the start of a paragraph. - /// - public static void ParagraphStart(this TokenStream stream) + if (current.Flag.HasFlag(MarkdownTokens.Bold) != flag.HasFlag(MarkdownTokens.Bold)) { - stream.Add(new MarkdownToken() { Type = MarkdownTokenType.ParagraphStart }); + return false; } - /// - /// Add a marker for the end of a paragraph. - /// - public static void ParagraphEnd(this TokenStream stream) + if (current.Flag.HasFlag(MarkdownTokens.Code) != flag.HasFlag(MarkdownTokens.Code)) { - if (stream.Count > 0) - { - if (stream.Current.Type == MarkdownTokenType.ParagraphStart) - { - stream.Pop(); - - return; - } - - stream.Add(new MarkdownToken() { Type = MarkdownTokenType.ParagraphEnd }); - } + return false; } - public static IEnumerable GetSection(this TokenStream stream, string header) + if (!current.Flag.IsEnding()) { - return stream.Count == 0 - ? Enumerable.Empty() - : stream - // Skip until we reach the header - .SkipWhile(token => token.Type != MarkdownTokenType.Header || token.Text != header) - - // Get all tokens to the next header - .Skip(1) - .TakeWhile(token => token.Type != MarkdownTokenType.Header); + current.Text = string.Concat(current.Text, text); } - - public static IEnumerable GetSections(this TokenStream stream) + else if (current.Flag == MarkdownTokens.LineEnding) { - return stream.Count == 0 - ? Enumerable.Empty() - : stream - // Skip until we reach the header - .SkipWhile(token => token.Type != MarkdownTokenType.Header) - - // Get all tokens to the next header - .Skip(1) - .TakeWhile(token => token.Type != MarkdownTokenType.Header); + return false; } + + // Take on the ending of the merged token + current.Flag = flag; + + return true; } - [DebuggerDisplay("Current = {Current?.Text}")] - internal sealed class TokenStream : IEnumerable + public static void Link(this TokenStream stream, string text, string uri) { - private readonly List _Token; - private readonly Dictionary _LinkTargetIndex; + stream.Add(new MarkdownToken() { Type = MarkdownTokenType.Link, Meta = text, Text = uri }); + } - private int _Position; + public static void LinkReference(this TokenStream stream, string text, string linkRef) + { + stream.Add(new MarkdownToken() { Type = MarkdownTokenType.LinkReference, Meta = text, Text = linkRef }); + } - public TokenStream() - { - _Token = new List(); - _LinkTargetIndex = new Dictionary(StringComparer.OrdinalIgnoreCase); - } + public static void LinkReferenceDefinition(this TokenStream stream, string text, string linkTarget) + { + stream.Add(new MarkdownToken() { Type = MarkdownTokenType.LinkReferenceDefinition, Meta = text, Text = linkTarget }); + } - public TokenStream(IEnumerable tokens) - : this() + /// + /// Add a marker for the start of a paragraph. + /// + public static void ParagraphStart(this TokenStream stream) + { + stream.Add(new MarkdownToken() { Type = MarkdownTokenType.ParagraphStart }); + } + + /// + /// Add a marker for the end of a paragraph. + /// + public static void ParagraphEnd(this TokenStream stream) + { + if (stream.Count > 0) { - foreach (var token in tokens) - Add(token); - } + if (stream.Current.Type == MarkdownTokenType.ParagraphStart) + { + stream.Pop(); + + return; + } - #region Properties + stream.Add(new MarkdownToken() { Type = MarkdownTokenType.ParagraphEnd }); + } + } - public bool EOF => _Position >= _Token.Count; + public static IEnumerable GetSection(this TokenStream stream, string header) + { + return stream.Count == 0 + ? Enumerable.Empty() + : stream + // Skip until we reach the header + .SkipWhile(token => token.Type != MarkdownTokenType.Header || token.Text != header) + + // Get all tokens to the next header + .Skip(1) + .TakeWhile(token => token.Type != MarkdownTokenType.Header); + } - public MarkdownToken Current => (_Token.Count <= _Position) ? null : _Token[_Position]; + public static IEnumerable GetSections(this TokenStream stream) + { + return stream.Count == 0 + ? Enumerable.Empty() + : stream + // Skip until we reach the header + .SkipWhile(token => token.Type != MarkdownTokenType.Header) + + // Get all tokens to the next header + .Skip(1) + .TakeWhile(token => token.Type != MarkdownTokenType.Header); + } +} - public int Position => _Position; +[DebuggerDisplay("Current = {Current?.Text}")] +internal sealed class TokenStream : IEnumerable +{ + private readonly List _Token; + private readonly Dictionary _LinkTargetIndex; - public int Count => _Token.Count; + private int _Position; - #endregion Properties + public TokenStream() + { + _Token = new List(); + _LinkTargetIndex = new Dictionary(StringComparer.OrdinalIgnoreCase); + } - public bool IsTokenType(params MarkdownTokenType[] tokenType) - { - return Current != null && tokenType != null && - (tokenType.Length == 1 ? tokenType[0] == Current.Type : tokenType.Contains(Current.Type)); - } + public TokenStream(IEnumerable tokens) + : this() + { + foreach (var token in tokens) + Add(token); + } - public MarkdownTokenType PeakTokenType(int offset = 1) - { - var p = _Position + offset; - return p < 0 || p >= _Token.Count ? MarkdownTokenType.None : _Token[p].Type; - } + #region Properties - public MarkdownToken Peak(int offset = 1) - { - var p = _Position + offset; - return p < 0 || p >= _Token.Count ? null : _Token[p]; - } + public bool EOF => _Position >= _Token.Count; - public void SkipUntilHeader() - { - SkipUntil(MarkdownTokenType.Header); - } + public MarkdownToken Current => (_Token.Count <= _Position) ? null : _Token[_Position]; - public void SkipUntil(MarkdownTokenType tokenType) - { - while (!EOF && Current.Type != tokenType) - Next(); - } + public int Position => _Position; - public IEnumerable CaptureUntil(MarkdownTokenType tokenType) - { - var start = Position; - var count = 0; - while (!EOF && Current.Type != tokenType) - { - count++; - Next(); - } - return _Token.GetRange(start, count); - } + public int Count => _Token.Count; - public IEnumerable CaptureWhile(params MarkdownTokenType[] tokenType) - { - var start = Position; - var count = 0; - while (!EOF && IsTokenType(tokenType)) - { - count++; - Next(); - } - return _Token.GetRange(start, count); - } + #endregion Properties - public void Add(MarkdownToken token) - { - _Token.Add(token); - _Position = _Token.Count - 1; + public bool IsTokenType(params MarkdownTokenType[] tokenType) + { + return Current != null && tokenType != null && + (tokenType.Length == 1 ? tokenType[0] == Current.Type : tokenType.Contains(Current.Type)); + } - // CommonMark specifies that link labels are case-insensitive, and - // first reference definition takes prescidence when multiple definitions use the same link label - if (token.Type == MarkdownTokenType.LinkReferenceDefinition && !_LinkTargetIndex.ContainsKey(token.Meta)) - { - _LinkTargetIndex[token.Meta] = token; - } - } + public MarkdownTokenType PeakTokenType(int offset = 1) + { + var p = _Position + offset; + return p < 0 || p >= _Token.Count ? MarkdownTokenType.None : _Token[p].Type; + } - public bool Next() - { - _Position++; - return !EOF; - } + public MarkdownToken Peak(int offset = 1) + { + var p = _Position + offset; + return p < 0 || p >= _Token.Count ? null : _Token[p]; + } - public MarkdownToken Pop() - { - if (Count == 0) - return null; + public void SkipUntilHeader() + { + SkipUntil(MarkdownTokenType.Header); + } - var token = _Token[_Position]; - _Token.RemoveAt(_Position); - _Position = _Token.Count - 1; - return token; - } + public void SkipUntil(MarkdownTokenType tokenType) + { + while (!EOF && Current.Type != tokenType) + Next(); + } - public void MoveTo(int position) + public IEnumerable CaptureUntil(MarkdownTokenType tokenType) + { + var start = Position; + var count = 0; + while (!EOF && Current.Type != tokenType) { - _Position = position; + count++; + Next(); } + return _Token.GetRange(start, count); + } - public MarkdownToken ResolveLinkTarget(string name) + public IEnumerable CaptureWhile(params MarkdownTokenType[] tokenType) + { + var start = Position; + var count = 0; + while (!EOF && IsTokenType(tokenType)) { - return !_LinkTargetIndex.ContainsKey(name) ? null : _LinkTargetIndex[name]; + count++; + Next(); } + return _Token.GetRange(start, count); + } - public IEnumerator GetEnumerator() - { - return ((IEnumerable)_Token).GetEnumerator(); - } + public void Add(MarkdownToken token) + { + _Token.Add(token); + _Position = _Token.Count - 1; - IEnumerator IEnumerable.GetEnumerator() + // CommonMark specifies that link labels are case-insensitive, and + // first reference definition takes prescidence when multiple definitions use the same link label + if (token.Type == MarkdownTokenType.LinkReferenceDefinition && !_LinkTargetIndex.ContainsKey(token.Meta)) { - return ((IEnumerable)_Token).GetEnumerator(); + _LinkTargetIndex[token.Meta] = token; } } + + public bool Next() + { + _Position++; + return !EOF; + } + + public MarkdownToken Pop() + { + if (Count == 0) + return null; + + var token = _Token[_Position]; + _Token.RemoveAt(_Position); + _Position = _Token.Count - 1; + return token; + } + + public void MoveTo(int position) + { + _Position = position; + } + + public MarkdownToken ResolveLinkTarget(string name) + { + return !_LinkTargetIndex.ContainsKey(name) ? null : _LinkTargetIndex[name]; + } + + public IEnumerator GetEnumerator() + { + return ((IEnumerable)_Token).GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable)_Token).GetEnumerator(); + } } diff --git a/src/PSRule/Host/Host.cs b/src/PSRule/Host/Host.cs index d2143fb193..409c41f80c 100644 --- a/src/PSRule/Host/Host.cs +++ b/src/PSRule/Host/Host.cs @@ -6,170 +6,169 @@ using PSRule.Commands; using PSRule.Runtime; -namespace PSRule.Host +namespace PSRule.Host; + +/// +/// A dynamic variable $PSRule used during Rule execution. +/// +internal sealed class PSRuleVariable : PSVariable { - /// - /// A dynamic variable $PSRule used during Rule execution. - /// - internal sealed class PSRuleVariable : PSVariable + private const string VARIABLE_NAME = "PSRule"; + + private readonly Runtime.PSRule _Value; + + public PSRuleVariable() + : base(VARIABLE_NAME, null, ScopedItemOptions.ReadOnly) { - private const string VARIABLE_NAME = "PSRule"; + _Value = new Runtime.PSRule(RunspaceContext.CurrentThread); + } - private readonly Runtime.PSRule _Value; + public override object Value => _Value; +} - public PSRuleVariable() - : base(VARIABLE_NAME, null, ScopedItemOptions.ReadOnly) - { - _Value = new Runtime.PSRule(RunspaceContext.CurrentThread); - } +/// +/// A dynamic variable $Rule used during Rule execution. +/// +internal sealed class RuleVariable : PSVariable +{ + private const string VARIABLE_NAME = "Rule"; - public override object Value => _Value; - } + private readonly Rule _Value; - /// - /// A dynamic variable $Rule used during Rule execution. - /// - internal sealed class RuleVariable : PSVariable + public RuleVariable() + : base(VARIABLE_NAME, null, ScopedItemOptions.ReadOnly) { - private const string VARIABLE_NAME = "Rule"; + _Value = new Rule(); + } - private readonly Rule _Value; + public override object Value => _Value; +} - public RuleVariable() - : base(VARIABLE_NAME, null, ScopedItemOptions.ReadOnly) - { - _Value = new Rule(); - } +/// +/// A dynamic variable $LocalizedData used during Rule execution. +/// +internal sealed class LocalizedDataVariable : PSVariable +{ + private const string VARIABLE_NAME = "LocalizedData"; - public override object Value => _Value; - } + private readonly LocalizedData _Value; - /// - /// A dynamic variable $LocalizedData used during Rule execution. - /// - internal sealed class LocalizedDataVariable : PSVariable + public LocalizedDataVariable() + : base(VARIABLE_NAME, null, ScopedItemOptions.ReadOnly) { - private const string VARIABLE_NAME = "LocalizedData"; + _Value = new LocalizedData(); + } - private readonly LocalizedData _Value; + public override object Value => _Value; +} - public LocalizedDataVariable() - : base(VARIABLE_NAME, null, ScopedItemOptions.ReadOnly) - { - _Value = new LocalizedData(); - } +/// +/// An assertion helper variable $Assert used during Rule execution. +/// +internal sealed class AssertVariable : PSVariable +{ + private const string VARIABLE_NAME = "Assert"; + private readonly Assert _Value; - public override object Value => _Value; + public AssertVariable() + : base(VARIABLE_NAME, null, ScopedItemOptions.ReadOnly) + { + _Value = new Assert(); } - /// - /// An assertion helper variable $Assert used during Rule execution. - /// - internal sealed class AssertVariable : PSVariable + public override object Value => _Value; +} + +/// +/// A dynamic variable used during Rule execution. +/// +internal sealed class TargetObjectVariable : PSVariable +{ + private const string VARIABLE_NAME = "TargetObject"; + + public TargetObjectVariable() + : base(VARIABLE_NAME, null, ScopedItemOptions.ReadOnly) { - private const string VARIABLE_NAME = "Assert"; - private readonly Assert _Value; - public AssertVariable() - : base(VARIABLE_NAME, null, ScopedItemOptions.ReadOnly) - { - _Value = new Assert(); - } + } - public override object Value => _Value; + public override object Value => RunspaceContext.CurrentThread.TargetObject?.Value; +} + +internal sealed class ConfigurationVariable : PSVariable +{ + private const string VARIABLE_NAME = "Configuration"; + private readonly Runtime.Configuration _Value; + + public ConfigurationVariable() + : base(VARIABLE_NAME, null, ScopedItemOptions.ReadOnly) + { + _Value = new Runtime.Configuration(RunspaceContext.CurrentThread); } + public override object Value => _Value; +} + +internal static class HostState +{ /// - /// A dynamic variable used during Rule execution. + /// Define language commands. /// - internal sealed class TargetObjectVariable : PSVariable + private static readonly SessionStateCmdletEntry[] BuiltInCmdlets = new SessionStateCmdletEntry[] { - private const string VARIABLE_NAME = "TargetObject"; + new("New-RuleDefinition", typeof(NewRuleDefinitionCommand), null), + new("Export-PSRuleConvention", typeof(ExportConventionCommand), null), + new("Write-Recommendation", typeof(WriteRecommendationCommand), null), + new("Write-Reason", typeof(WriteReasonCommand), null), + new("Assert-Exists", typeof(AssertExistsCommand), null), + new("Assert-Within", typeof(AssertWithinCommand), null), + new("Assert-Match", typeof(AssertMatchCommand), null), + new("Assert-TypeOf", typeof(AssertTypeOfCommand), null), + new("Assert-AllOf", typeof(AssertAllOfCommand), null), + new("Assert-AnyOf", typeof(AssertAnyOfCommand), null), + }; - public TargetObjectVariable() - : base(VARIABLE_NAME, null, ScopedItemOptions.ReadOnly) - { + /// + /// Define language aliases. + /// + private static readonly SessionStateAliasEntry[] BuiltInAliases = new SessionStateAliasEntry[] + { + new(LanguageKeywords.Rule, "New-RuleDefinition", string.Empty, ScopedItemOptions.ReadOnly), + new(LanguageKeywords.Recommend, "Write-Recommendation", string.Empty, ScopedItemOptions.ReadOnly), + new(LanguageKeywords.Reason, "Write-Reason", string.Empty, ScopedItemOptions.ReadOnly), + new(LanguageKeywords.Exists, "Assert-Exists", string.Empty, ScopedItemOptions.ReadOnly), + new(LanguageKeywords.Within, "Assert-Within", string.Empty, ScopedItemOptions.ReadOnly), + new(LanguageKeywords.Match, "Assert-Match", string.Empty, ScopedItemOptions.ReadOnly), + new(LanguageKeywords.TypeOf, "Assert-TypeOf", string.Empty, ScopedItemOptions.ReadOnly), + new(LanguageKeywords.AllOf, "Assert-AllOf", string.Empty, ScopedItemOptions.ReadOnly), + new(LanguageKeywords.AnyOf, "Assert-AnyOf", string.Empty, ScopedItemOptions.ReadOnly), + }; - } + /// + /// Create a default session state. + /// + public static InitialSessionState CreateSessionState(Options.SessionState initialSessionState) + { + var state = initialSessionState == Options.SessionState.Minimal ? + InitialSessionState.CreateDefault2() : InitialSessionState.CreateDefault(); - public override object Value => RunspaceContext.CurrentThread.TargetObject?.Value; - } + // Add in language elements + state.Commands.Add(BuiltInCmdlets); + state.Commands.Add(BuiltInAliases); - internal sealed class ConfigurationVariable : PSVariable - { - private const string VARIABLE_NAME = "Configuration"; - private readonly Runtime.Configuration _Value; + // Set thread options + state.ThreadOptions = PSThreadOptions.UseCurrentThread; - public ConfigurationVariable() - : base(VARIABLE_NAME, null, ScopedItemOptions.ReadOnly) - { - _Value = new Runtime.Configuration(RunspaceContext.CurrentThread); - } + // Set execution policy + SetExecutionPolicy(state, executionPolicy: Microsoft.PowerShell.ExecutionPolicy.RemoteSigned); - public override object Value => _Value; + return state; } - internal static class HostState + private static void SetExecutionPolicy(InitialSessionState state, Microsoft.PowerShell.ExecutionPolicy executionPolicy) { - /// - /// Define language commands. - /// - private static readonly SessionStateCmdletEntry[] BuiltInCmdlets = new SessionStateCmdletEntry[] - { - new SessionStateCmdletEntry("New-RuleDefinition", typeof(NewRuleDefinitionCommand), null), - new SessionStateCmdletEntry("Export-PSRuleConvention", typeof(ExportConventionCommand), null), - new SessionStateCmdletEntry("Write-Recommendation", typeof(WriteRecommendationCommand), null), - new SessionStateCmdletEntry("Write-Reason", typeof(WriteReasonCommand), null), - new SessionStateCmdletEntry("Assert-Exists", typeof(AssertExistsCommand), null), - new SessionStateCmdletEntry("Assert-Within", typeof(AssertWithinCommand), null), - new SessionStateCmdletEntry("Assert-Match", typeof(AssertMatchCommand), null), - new SessionStateCmdletEntry("Assert-TypeOf", typeof(AssertTypeOfCommand), null), - new SessionStateCmdletEntry("Assert-AllOf", typeof(AssertAllOfCommand), null), - new SessionStateCmdletEntry("Assert-AnyOf", typeof(AssertAnyOfCommand), null), - }; - - /// - /// Define language aliases. - /// - private static readonly SessionStateAliasEntry[] BuiltInAliases = new SessionStateAliasEntry[] - { - new SessionStateAliasEntry(LanguageKeywords.Rule, "New-RuleDefinition", string.Empty, ScopedItemOptions.ReadOnly), - new SessionStateAliasEntry(LanguageKeywords.Recommend, "Write-Recommendation", string.Empty, ScopedItemOptions.ReadOnly), - new SessionStateAliasEntry(LanguageKeywords.Reason, "Write-Reason", string.Empty, ScopedItemOptions.ReadOnly), - new SessionStateAliasEntry(LanguageKeywords.Exists, "Assert-Exists", string.Empty, ScopedItemOptions.ReadOnly), - new SessionStateAliasEntry(LanguageKeywords.Within, "Assert-Within", string.Empty, ScopedItemOptions.ReadOnly), - new SessionStateAliasEntry(LanguageKeywords.Match, "Assert-Match", string.Empty, ScopedItemOptions.ReadOnly), - new SessionStateAliasEntry(LanguageKeywords.TypeOf, "Assert-TypeOf", string.Empty, ScopedItemOptions.ReadOnly), - new SessionStateAliasEntry(LanguageKeywords.AllOf, "Assert-AllOf", string.Empty, ScopedItemOptions.ReadOnly), - new SessionStateAliasEntry(LanguageKeywords.AnyOf, "Assert-AnyOf", string.Empty, ScopedItemOptions.ReadOnly), - }; - - /// - /// Create a default session state. - /// - public static InitialSessionState CreateSessionState(Options.SessionState initialSessionState) - { - var state = initialSessionState == Options.SessionState.Minimal ? - InitialSessionState.CreateDefault2() : InitialSessionState.CreateDefault(); - - // Add in language elements - state.Commands.Add(BuiltInCmdlets); - state.Commands.Add(BuiltInAliases); - - // Set thread options - state.ThreadOptions = PSThreadOptions.UseCurrentThread; - - // Set execution policy - SetExecutionPolicy(state, executionPolicy: Microsoft.PowerShell.ExecutionPolicy.RemoteSigned); - - return state; - } - - private static void SetExecutionPolicy(InitialSessionState state, Microsoft.PowerShell.ExecutionPolicy executionPolicy) - { - // Only set execution policy on Windows - if (System.Environment.OSVersion.Platform == PlatformID.Win32NT) - state.ExecutionPolicy = executionPolicy; - } + // Only set execution policy on Windows + if (System.Environment.OSVersion.Platform == PlatformID.Win32NT) + state.ExecutionPolicy = executionPolicy; } } diff --git a/src/PSRule/Host/HostHelper.cs b/src/PSRule/Host/HostHelper.cs index e6bd60c062..c42d309d0b 100644 --- a/src/PSRule/Host/HostHelper.cs +++ b/src/PSRule/Host/HostHelper.cs @@ -25,868 +25,867 @@ using YamlDotNet.Serialization.NodeDeserializers; using Rule = PSRule.Rules.Rule; -namespace PSRule.Host +namespace PSRule.Host; + +internal static class HostHelper { - internal static class HostHelper - { - private const string Markdown_Extension = ".md"; + private const string Markdown_Extension = ".md"; - internal static IRuleV1[] GetRule(Source[] source, RunspaceContext context, bool includeDependencies) - { - var rules = ToRuleV1(GetLanguageBlock(context, source), context); - var builder = new DependencyGraphBuilder(context, includeDependencies, includeDisabled: true); - builder.Include(rules, filter: (b) => Match(context, b)); - return builder.GetItems(); - } + internal static IRuleV1[] GetRule(Source[] source, RunspaceContext context, bool includeDependencies) + { + var rules = ToRuleV1(GetLanguageBlock(context, source), context); + var builder = new DependencyGraphBuilder(context, includeDependencies, includeDisabled: true); + builder.Include(rules, filter: (b) => Match(context, b)); + return builder.GetItems(); + } - internal static RuleHelpInfo[] GetRuleHelp(Source[] source, RunspaceContext context) - { - return ToRuleHelp(ToRuleBlockV1(GetLanguageBlock(context, source), context, skipDuplicateName: true).GetAll(), context); - } + internal static RuleHelpInfo[] GetRuleHelp(Source[] source, RunspaceContext context) + { + return ToRuleHelp(ToRuleBlockV1(GetLanguageBlock(context, source), context, skipDuplicateName: true).GetAll(), context); + } - internal static DependencyGraph GetRuleBlockGraph(Source[] source, RunspaceContext context) - { - var blocks = GetLanguageBlock(context, source); - var rules = ToRuleBlockV1(blocks, context, skipDuplicateName: false); - Import(GetConventions(blocks, context), context); - var builder = new DependencyGraphBuilder(context, includeDependencies: true, includeDisabled: false); - builder.Include(rules, filter: (b) => Match(context, b)); - return builder.Build(); - } + internal static DependencyGraph GetRuleBlockGraph(Source[] source, RunspaceContext context) + { + var blocks = GetLanguageBlock(context, source); + var rules = ToRuleBlockV1(blocks, context, skipDuplicateName: false); + Import(GetConventions(blocks, context), context); + var builder = new DependencyGraphBuilder(context, includeDependencies: true, includeDisabled: false); + builder.Include(rules, filter: (b) => Match(context, b)); + return builder.Build(); + } - private static IEnumerable GetYamlJsonLanguageBlocks(Source[] source, RunspaceContext context) - { - var results = new List(); - results.AddRange(GetYamlLanguageBlocks(source, context)); - results.AddRange(GetJsonLanguageBlocks(source, context)); - return results; - } + private static IEnumerable GetYamlJsonLanguageBlocks(Source[] source, RunspaceContext context) + { + var results = new List(); + results.AddRange(GetYamlLanguageBlocks(source, context)); + results.AddRange(GetJsonLanguageBlocks(source, context)); + return results; + } - /// - /// Read YAML/JSON objects and return baselines. - /// - internal static IEnumerable GetBaseline(Source[] source, RunspaceContext context) - { - return ToBaselineV1(GetYamlJsonLanguageBlocks(source, context), context); - } + /// + /// Read YAML/JSON objects and return baselines. + /// + internal static IEnumerable GetBaseline(Source[] source, RunspaceContext context) + { + return ToBaselineV1(GetYamlJsonLanguageBlocks(source, context), context); + } - /// - /// Read YAML/JSON objects and return module configurations. - /// - internal static IEnumerable GetModuleConfigForTests(Source[] source, RunspaceContext context) - { - return ToModuleConfigV1(GetYamlJsonLanguageBlocks(source, context), context); - } + /// + /// Read YAML/JSON objects and return module configurations. + /// + internal static IEnumerable GetModuleConfigForTests(Source[] source, RunspaceContext context) + { + return ToModuleConfigV1(GetYamlJsonLanguageBlocks(source, context), context); + } - /// - /// Read YAML/JSON objects and return selectors. - /// - internal static IEnumerable GetSelectorForTests(Source[] source, RunspaceContext context) - { - return ToSelectorV1(GetYamlJsonLanguageBlocks(source, context), context); - } + /// + /// Read YAML/JSON objects and return selectors. + /// + internal static IEnumerable GetSelectorForTests(Source[] source, RunspaceContext context) + { + return ToSelectorV1(GetYamlJsonLanguageBlocks(source, context), context); + } - /// - /// Read YAML/JSON objects and return suppression groups. - /// - internal static IEnumerable GetSuppressionGroupForTests(Source[] source, RunspaceContext context) - { - return ToSuppressionGroupV1(GetYamlJsonLanguageBlocks(source, context), context); - } + /// + /// Read YAML/JSON objects and return suppression groups. + /// + internal static IEnumerable GetSuppressionGroupForTests(Source[] source, RunspaceContext context) + { + return ToSuppressionGroupV1(GetYamlJsonLanguageBlocks(source, context), context); + } - internal static IEnumerable ImportResource(Source[] source, RunspaceContext context) - { - return source == null || source.Length == 0 ? Array.Empty() : GetYamlJsonLanguageBlocks(source, context); - } + internal static IEnumerable ImportResource(Source[] source, RunspaceContext context) + { + return source == null || source.Length == 0 ? Array.Empty() : GetYamlJsonLanguageBlocks(source, context); + } - /// - /// Called from PowerShell to get additional metdata from a language block, such as comment help. - /// - internal static CommentMetadata GetCommentMeta(string path, int lineNumber, int offset) - { - var context = RunspaceContext.CurrentThread; - if (lineNumber < 0 || RunspaceContext.CurrentThread.IsScope(RunspaceScope.None) || context.Source.SourceContentCache == null) - return new CommentMetadata(); + /// + /// Called from PowerShell to get additional metdata from a language block, such as comment help. + /// + internal static CommentMetadata GetCommentMeta(string path, int lineNumber, int offset) + { + var context = RunspaceContext.CurrentThread; + if (lineNumber < 0 || RunspaceContext.CurrentThread.IsScope(RunspaceScope.None) || context.Source.SourceContentCache == null) + return new CommentMetadata(); - var lines = context.Source.SourceContentCache; - var i = lineNumber; - var comments = new List(); + var lines = context.Source.SourceContentCache; + var i = lineNumber; + var comments = new List(); - // Back track lines with comments immediately before block - for (; i >= 0 && lines[i].Contains("#"); i--) - comments.Insert(0, lines[i]); + // Back track lines with comments immediately before block + for (; i >= 0 && lines[i].Contains("#"); i--) + comments.Insert(0, lines[i]); - // Check if any comments were found - var metadata = new CommentMetadata(); - if (comments.Count > 0) + // Check if any comments were found + var metadata = new CommentMetadata(); + if (comments.Count > 0) + { + foreach (var comment in comments) { - foreach (var comment in comments) - { - if (comment.StartsWith("# Description: ", StringComparison.OrdinalIgnoreCase)) - metadata.Synopsis = comment.Substring(15); + if (comment.StartsWith("# Description: ", StringComparison.OrdinalIgnoreCase)) + metadata.Synopsis = comment.Substring(15); - if (comment.StartsWith("# Synopsis: ", StringComparison.OrdinalIgnoreCase)) - metadata.Synopsis = comment.Substring(12); - } + if (comment.StartsWith("# Synopsis: ", StringComparison.OrdinalIgnoreCase)) + metadata.Synopsis = comment.Substring(12); } - return metadata; } + return metadata; + } - internal static void UnblockFile(IPipelineWriter writer, string[] publisher, string[] path) - { - var ps = PowerShell.Create(); - try - { - ps.Runspace.SessionStateProxy.SetVariable("trustedPublisher", publisher); - ps.Runspace.SessionStateProxy.SetVariable("trustedPath", path); - ps.AddScript("$trustedPath | ForEach-Object { Get-AuthenticodeSignature -FilePath $_ } | Where-Object { $_.Status -eq 'Valid' -and $_.SignerCertificate.Subject -in $trustedPublisher } | ForEach-Object { Unblock-File -Path $_.Path -Confirm:$False; }"); - ps.Invoke(); - if (ps.HadErrors) - { - foreach (var error in ps.Streams.Error) - writer.WriteError(error); - } - } - finally + internal static void UnblockFile(IPipelineWriter writer, string[] publisher, string[] path) + { + var ps = PowerShell.Create(); + try + { + ps.Runspace.SessionStateProxy.SetVariable("trustedPublisher", publisher); + ps.Runspace.SessionStateProxy.SetVariable("trustedPath", path); + ps.AddScript("$trustedPath | ForEach-Object { Get-AuthenticodeSignature -FilePath $_ } | Where-Object { $_.Status -eq 'Valid' -and $_.SignerCertificate.Subject -in $trustedPublisher } | ForEach-Object { Unblock-File -Path $_.Path -Confirm:$False; }"); + ps.Invoke(); + if (ps.HadErrors) { - ps.Runspace = null; - ps.Dispose(); + foreach (var error in ps.Streams.Error) + writer.WriteError(error); } } - - private static ILanguageBlock[] GetLanguageBlock(RunspaceContext context, Source[] sources) + finally { - var results = new List(); - results.AddRange(GetPSLanguageBlocks(context, sources)); - results.AddRange(GetYamlJsonLanguageBlocks(sources, context)); - return results.ToArray(); + ps.Runspace = null; + ps.Dispose(); } + } + + private static ILanguageBlock[] GetLanguageBlock(RunspaceContext context, Source[] sources) + { + var results = new List(); + results.AddRange(GetPSLanguageBlocks(context, sources)); + results.AddRange(GetYamlJsonLanguageBlocks(sources, context)); + return results.ToArray(); + } - /// - /// Execute PowerShell script files to get language blocks. - /// - private static ILanguageBlock[] GetPSLanguageBlocks(RunspaceContext context, Source[] sources) + /// + /// Execute PowerShell script files to get language blocks. + /// + private static ILanguageBlock[] GetPSLanguageBlocks(RunspaceContext context, Source[] sources) + { + var results = new List(); + var ps = context.GetPowerShell(); + + try { - var results = new List(); - var ps = context.GetPowerShell(); + context.Writer.EnterScope("[Discovery.Rule]"); + context.PushScope(RunspaceScope.Source); - try + // Process scripts + foreach (var source in sources) { - context.Writer.EnterScope("[Discovery.Rule]"); - context.PushScope(RunspaceScope.Source); - - // Process scripts - foreach (var source in sources) + foreach (var file in source.File) { - foreach (var file in source.File) + if (file.Type != SourceType.Script) + continue; + + ps.Commands.Clear(); + context.VerboseRuleDiscovery(path: file.Path); + context.EnterLanguageScope(file); + try { - if (file.Type != SourceType.Script) - continue; + var scriptAst = System.Management.Automation.Language.Parser.ParseFile(file.Path, out var tokens, out var errors); + var visitor = new RuleLanguageAst(); + scriptAst.Visit(visitor); - ps.Commands.Clear(); - context.VerboseRuleDiscovery(path: file.Path); - context.EnterLanguageScope(file); - try + if (visitor.Errors != null && visitor.Errors.Count > 0) { - var scriptAst = System.Management.Automation.Language.Parser.ParseFile(file.Path, out var tokens, out var errors); - var visitor = new RuleLanguageAst(); - scriptAst.Visit(visitor); - - if (visitor.Errors != null && visitor.Errors.Count > 0) - { - foreach (var record in visitor.Errors) - context.WriteError(record); + foreach (var record in visitor.Errors) + context.WriteError(record); - continue; - } - if (errors != null && errors.Length > 0) - { - foreach (var error in errors) - context.WriteError(error); + continue; + } + if (errors != null && errors.Length > 0) + { + foreach (var error in errors) + context.WriteError(error); - continue; - } + continue; + } - // Invoke script - ps.AddScript(string.Concat("& '", file.Path, "'"), true); - var invokeResults = ps.Invoke(); + // Invoke script + ps.AddScript(string.Concat("& '", file.Path, "'"), true); + var invokeResults = ps.Invoke(); - // Discovery has errors so skip this file - if (ps.HadErrors) - continue; + // Discovery has errors so skip this file + if (ps.HadErrors) + continue; - foreach (var ir in invokeResults) - { - if (ir.BaseObject is ILanguageBlock block) - results.Add(block); - } - } - finally + foreach (var ir in invokeResults) { - context.ExitLanguageScope(file); + if (ir.BaseObject is ILanguageBlock block) + results.Add(block); } } + finally + { + context.ExitLanguageScope(file); + } } } - finally - { - context.Writer.ExitScope(); - context.PopScope(RunspaceScope.Source); - ps.Runspace = null; - ps.Dispose(); - } - return results.ToArray(); } - - /// - /// Get language blocks from YAML source files. - /// - private static ILanguageBlock[] GetYamlLanguageBlocks(Source[] sources, RunspaceContext context) + finally { - var result = new Collection(); - var visitor = new ResourceValidator(context); - var d = new DeserializerBuilder() - .IgnoreUnmatchedProperties() - .WithNamingConvention(CamelCaseNamingConvention.Instance) - .WithTypeConverter(new FieldMapYamlTypeConverter()) - .WithTypeConverter(new StringArrayMapConverter()) - .WithTypeConverter(new Converters.Yaml.StringArrayConverter()) - .WithTypeConverter(new PSObjectYamlTypeConverter()) - .WithNodeTypeResolver(new PSOptionYamlTypeResolver()) - .WithNodeDeserializer( - inner => new ResourceNodeDeserializer(new LanguageExpressionDeserializer(inner)), - s => s.InsteadOf()) - .Build(); + context.Writer.ExitScope(); + context.PopScope(RunspaceScope.Source); + ps.Runspace = null; + ps.Dispose(); + } + return results.ToArray(); + } - try + /// + /// Get language blocks from YAML source files. + /// + private static ILanguageBlock[] GetYamlLanguageBlocks(Source[] sources, RunspaceContext context) + { + var result = new Collection(); + var visitor = new ResourceValidator(context); + var d = new DeserializerBuilder() + .IgnoreUnmatchedProperties() + .WithNamingConvention(CamelCaseNamingConvention.Instance) + .WithTypeConverter(new FieldMapYamlTypeConverter()) + .WithTypeConverter(new StringArrayMapConverter()) + .WithTypeConverter(new Converters.Yaml.StringArrayConverter()) + .WithTypeConverter(new PSObjectYamlTypeConverter()) + .WithNodeTypeResolver(new PSOptionYamlTypeResolver()) + .WithNodeDeserializer( + inner => new ResourceNodeDeserializer(new LanguageExpressionDeserializer(inner)), + s => s.InsteadOf()) + .Build(); + + try + { + context.Writer?.EnterScope("[Discovery.Resource]"); + context.PushScope(RunspaceScope.Resource); + foreach (var source in sources) { - context.Writer?.EnterScope("[Discovery.Resource]"); - context.PushScope(RunspaceScope.Resource); - foreach (var source in sources) + foreach (var file in source.File) { - foreach (var file in source.File) - { - if (file.Type != SourceType.Yaml) - continue; + if (file.Type != SourceType.Yaml) + continue; - context.VerboseRuleDiscovery(path: file.Path); - context.EnterLanguageScope(file); - try + context.VerboseRuleDiscovery(path: file.Path); + context.EnterLanguageScope(file); + try + { + using var reader = new StreamReader(file.Path); + var parser = new Parser(reader); + parser.TryConsume(out _); + while (parser.Current is DocumentStart) { - using var reader = new StreamReader(file.Path); - var parser = new Parser(reader); - parser.TryConsume(out _); - while (parser.Current is DocumentStart) - { - var item = d.Deserialize(parser); - if (item == null || item.Block == null) - continue; + var item = d.Deserialize(parser); + if (item == null || item.Block == null) + continue; - if (item.Visit(visitor)) - result.Add(item.Block); - } - } - finally - { - context.ExitLanguageScope(file); + if (item.Visit(visitor)) + result.Add(item.Block); } } + finally + { + context.ExitLanguageScope(file); + } } } - finally - { - context.Writer?.ExitScope(); - context.PopScope(RunspaceScope.Resource); - } - return result.Count == 0 ? Array.Empty() : result.ToArray(); } + finally + { + context.Writer?.ExitScope(); + context.PopScope(RunspaceScope.Resource); + } + return result.Count == 0 ? Array.Empty() : result.ToArray(); + } - /// - /// Get language blocks from JSON source files. - /// - private static ILanguageBlock[] GetJsonLanguageBlocks(Source[] sources, RunspaceContext context) + /// + /// Get language blocks from JSON source files. + /// + private static ILanguageBlock[] GetJsonLanguageBlocks(Source[] sources, RunspaceContext context) + { + var result = new Collection(); + var visitor = new ResourceValidator(context); + var deserializer = new JsonSerializer { - var result = new Collection(); - var visitor = new ResourceValidator(context); - var deserializer = new JsonSerializer - { - DateTimeZoneHandling = DateTimeZoneHandling.Utc - }; - deserializer.Converters.Add(new ResourceObjectJsonConverter()); - deserializer.Converters.Add(new FieldMapJsonConverter()); - deserializer.Converters.Add(new StringArrayJsonConverter()); - deserializer.Converters.Add(new LanguageExpressionJsonConverter()); + DateTimeZoneHandling = DateTimeZoneHandling.Utc + }; + deserializer.Converters.Add(new ResourceObjectJsonConverter()); + deserializer.Converters.Add(new FieldMapJsonConverter()); + deserializer.Converters.Add(new StringArrayJsonConverter()); + deserializer.Converters.Add(new LanguageExpressionJsonConverter()); - try - { - context.Writer?.EnterScope("[Discovery.Resource]"); - context.PushScope(RunspaceScope.Resource); + try + { + context.Writer?.EnterScope("[Discovery.Resource]"); + context.PushScope(RunspaceScope.Resource); - foreach (var source in sources) + foreach (var source in sources) + { + foreach (var file in source.File) { - foreach (var file in source.File) + if (file.Type != SourceType.Json) + continue; + + context.VerboseRuleDiscovery(file.Path); + context.EnterLanguageScope(file); + try { - if (file.Type != SourceType.Json) - continue; + using var reader = new JsonTextReader(new StreamReader(file.Path)); - context.VerboseRuleDiscovery(file.Path); - context.EnterLanguageScope(file); - try + // Consume lines until start of array + reader.SkipComments(out _); + if (reader.TryConsume(JsonToken.StartArray)) { - using var reader = new JsonTextReader(new StreamReader(file.Path)); - - // Consume lines until start of array reader.SkipComments(out _); - if (reader.TryConsume(JsonToken.StartArray)) + while (reader.TokenType != JsonToken.EndArray) { - reader.SkipComments(out _); - while (reader.TokenType != JsonToken.EndArray) - { - var value = deserializer.Deserialize(reader); - if (value?.Block != null && value.Visit(visitor)) - result.Add(value.Block); - - // Consume all end objects at the end of each resource - while (reader.TryConsume(JsonToken.EndObject)) { } - } + var value = deserializer.Deserialize(reader); + if (value?.Block != null && value.Visit(visitor)) + result.Add(value.Block); + + // Consume all end objects at the end of each resource + while (reader.TryConsume(JsonToken.EndObject)) { } } } - finally - { - context.ExitLanguageScope(file); - } + } + finally + { + context.ExitLanguageScope(file); } } } - finally - { - context.Writer?.ExitScope(); - context.PopScope(RunspaceScope.Resource); - } - return result.Count == 0 ? Array.Empty() : result.ToArray(); } - - public static void InvokeRuleBlock(RunspaceContext context, RuleBlock ruleBlock, RuleRecord ruleRecord) + finally { - RunspaceContext.CurrentThread = context; - var condition = ruleBlock.Condition; - context.VerboseObjectStart(); + context.Writer?.ExitScope(); + context.PopScope(RunspaceScope.Resource); + } + return result.Count == 0 ? Array.Empty() : result.ToArray(); + } - try + public static void InvokeRuleBlock(RunspaceContext context, RuleBlock ruleBlock, RuleRecord ruleRecord) + { + RunspaceContext.CurrentThread = context; + var condition = ruleBlock.Condition; + context.VerboseObjectStart(); + + try + { + context.EnterLanguageScope(ruleBlock.Source); + var invokeResult = condition.If(); + if (invokeResult == null) { - context.EnterLanguageScope(ruleBlock.Source); - var invokeResult = condition.If(); - if (invokeResult == null) - { - ruleRecord.OutcomeReason = RuleOutcomeReason.PreconditionFail; - return; - } - else if (invokeResult.HadErrors || context.HadErrors) - { - ruleRecord.OutcomeReason = RuleOutcomeReason.None; - ruleRecord.Outcome = RuleOutcome.Error; - } - else if (invokeResult.Count == 0) - { - ruleRecord.OutcomeReason = RuleOutcomeReason.Inconclusive; - ruleRecord.Outcome = RuleOutcome.Fail; - context.WarnRuleInconclusive(ruleRecord.RuleId); - } - else - { - ruleRecord.OutcomeReason = RuleOutcomeReason.Processed; - ruleRecord.Outcome = invokeResult.AllOf() ? RuleOutcome.Pass : RuleOutcome.Fail; - } - context.VerboseConditionResult(pass: invokeResult.Pass, count: invokeResult.Count, outcome: ruleRecord.Outcome); + ruleRecord.OutcomeReason = RuleOutcomeReason.PreconditionFail; + return; + } + else if (invokeResult.HadErrors || context.HadErrors) + { + ruleRecord.OutcomeReason = RuleOutcomeReason.None; + ruleRecord.Outcome = RuleOutcome.Error; } - catch (CmdletInvocationException) + else if (invokeResult.Count == 0) { - throw; + ruleRecord.OutcomeReason = RuleOutcomeReason.Inconclusive; + ruleRecord.Outcome = RuleOutcome.Fail; + context.WarnRuleInconclusive(ruleRecord.RuleId); } - catch (Exception ex) + else { - context.Error(ex); + ruleRecord.OutcomeReason = RuleOutcomeReason.Processed; + ruleRecord.Outcome = invokeResult.AllOf() ? RuleOutcome.Pass : RuleOutcome.Fail; } - // TODO: Exit scope - //finally - //{ - // context.ExitSourceScope(); - //} + context.VerboseConditionResult(pass: invokeResult.Pass, count: invokeResult.Count, outcome: ruleRecord.Outcome); } - - /// - /// Convert matching langauge blocks to rules. - /// - private static DependencyTargetCollection ToRuleV1(ILanguageBlock[] blocks, RunspaceContext context) + catch (CmdletInvocationException) { - // Index rules by RuleId - var results = new DependencyTargetCollection(); + throw; + } + catch (Exception ex) + { + context.Error(ex); + } + // TODO: Exit scope + //finally + //{ + // context.ExitSourceScope(); + //} + } - // Keep track of rule names and ids that have been added - var knownRuleNames = new HashSet(StringComparer.OrdinalIgnoreCase); - var knownRuleIds = new HashSet(ResourceIdEqualityComparer.Default); + /// + /// Convert matching langauge blocks to rules. + /// + private static DependencyTargetCollection ToRuleV1(ILanguageBlock[] blocks, RunspaceContext context) + { + // Index rules by RuleId + var results = new DependencyTargetCollection(); + + // Keep track of rule names and ids that have been added + var knownRuleNames = new HashSet(StringComparer.OrdinalIgnoreCase); + var knownRuleIds = new HashSet(ResourceIdEqualityComparer.Default); - foreach (var block in blocks.OfType()) + foreach (var block in blocks.OfType()) + { + if (knownRuleIds.ContainsIds(block.Id, block.Ref, block.Alias, out var duplicateId)) { - if (knownRuleIds.ContainsIds(block.Id, block.Ref, block.Alias, out var duplicateId)) - { - context.DuplicateResourceId(block.Id, duplicateId.Value); - continue; - } + context.DuplicateResourceId(block.Id, duplicateId.Value); + continue; + } - if (knownRuleNames.ContainsNames(block.Id, block.Ref, block.Alias, out var duplicateName)) - context.WarnDuplicateRuleName(duplicateName); + if (knownRuleNames.ContainsNames(block.Id, block.Ref, block.Alias, out var duplicateName)) + context.WarnDuplicateRuleName(duplicateName); + results.TryAdd(new Rule + { + Id = block.Id, + Ref = block.Ref, + Alias = block.Alias, + Source = block.Source, + Tag = block.Tag, + Level = block.Level, + Info = block.Info, + DependsOn = block.DependsOn, + Flags = block.Flags, + Extent = block.Extent, + Labels = block.Labels, + }); + knownRuleNames.AddNames(block.Id, block.Ref, block.Alias); + knownRuleIds.AddIds(block.Id, block.Ref, block.Alias); + } + + foreach (var block in blocks.OfType()) + { + if (knownRuleIds.ContainsIds(block.Id, block.Ref, block.Alias, out var duplicateId)) + { + context.DuplicateResourceId(block.Id, duplicateId.Value); + continue; + } + + if (knownRuleNames.ContainsNames(block.Id, block.Ref, block.Alias, out var duplicateName)) + context.WarnDuplicateRuleName(duplicateName); + + context.EnterLanguageScope(block.Source); + try + { + var info = GetRuleHelpInfo(context, block); results.TryAdd(new Rule { Id = block.Id, Ref = block.Ref, Alias = block.Alias, Source = block.Source, - Tag = block.Tag, + Tag = block.Metadata.Tags, Level = block.Level, - Info = block.Info, - DependsOn = block.DependsOn, + Info = info, + DependsOn = null, // TODO: No support for DependsOn yet Flags = block.Flags, Extent = block.Extent, - Labels = block.Labels, + Labels = block.Metadata.Labels, }); knownRuleNames.AddNames(block.Id, block.Ref, block.Alias); knownRuleIds.AddIds(block.Id, block.Ref, block.Alias); } - - foreach (var block in blocks.OfType()) + finally { - if (knownRuleIds.ContainsIds(block.Id, block.Ref, block.Alias, out var duplicateId)) - { - context.DuplicateResourceId(block.Id, duplicateId.Value); - continue; - } - - if (knownRuleNames.ContainsNames(block.Id, block.Ref, block.Alias, out var duplicateName)) - context.WarnDuplicateRuleName(duplicateName); - - context.EnterLanguageScope(block.Source); - try - { - var info = GetRuleHelpInfo(context, block); - results.TryAdd(new Rule - { - Id = block.Id, - Ref = block.Ref, - Alias = block.Alias, - Source = block.Source, - Tag = block.Metadata.Tags, - Level = block.Level, - Info = info, - DependsOn = null, // TODO: No support for DependsOn yet - Flags = block.Flags, - Extent = block.Extent, - Labels = block.Metadata.Labels, - }); - knownRuleNames.AddNames(block.Id, block.Ref, block.Alias); - knownRuleIds.AddIds(block.Id, block.Ref, block.Alias); - } - finally - { - context.ExitLanguageScope(block.Source); - } + context.ExitLanguageScope(block.Source); } - return results; } + return results; + } - private static DependencyTargetCollection ToRuleBlockV1(ILanguageBlock[] blocks, RunspaceContext context, bool skipDuplicateName) - { - // Index rules by RuleId - var results = new DependencyTargetCollection(); + private static DependencyTargetCollection ToRuleBlockV1(ILanguageBlock[] blocks, RunspaceContext context, bool skipDuplicateName) + { + // Index rules by RuleId + var results = new DependencyTargetCollection(); - // Keep track of rule names and ids that have been added - var knownRuleNames = new HashSet(StringComparer.OrdinalIgnoreCase); - var knownRuleIds = new HashSet(ResourceIdEqualityComparer.Default); + // Keep track of rule names and ids that have been added + var knownRuleNames = new HashSet(StringComparer.OrdinalIgnoreCase); + var knownRuleIds = new HashSet(ResourceIdEqualityComparer.Default); - foreach (var block in blocks.OfType()) + foreach (var block in blocks.OfType()) + { + if (knownRuleIds.ContainsIds(block.Id, block.Ref, block.Alias, out var duplicateId)) { - if (knownRuleIds.ContainsIds(block.Id, block.Ref, block.Alias, out var duplicateId)) - { - context.DuplicateResourceId(block.Id, duplicateId.Value); - continue; - } - if (knownRuleNames.ContainsNames(block.Id, block.Ref, block.Alias, out var duplicateName)) - { - context.WarnDuplicateRuleName(duplicateName); - if (skipDuplicateName) - continue; - } - - results.TryAdd(block); - knownRuleNames.AddNames(block.Id, block.Ref, block.Alias); - knownRuleIds.AddIds(block.Id, block.Ref, block.Alias); + context.DuplicateResourceId(block.Id, duplicateId.Value); + continue; } - - foreach (var block in blocks.OfType()) + if (knownRuleNames.ContainsNames(block.Id, block.Ref, block.Alias, out var duplicateName)) { - var ruleName = block.Name; - if (knownRuleIds.ContainsIds(block.Id, block.Ref, block.Alias, out var duplicateId)) - { - context.DuplicateResourceId(block.Id, duplicateId.Value); + context.WarnDuplicateRuleName(duplicateName); + if (skipDuplicateName) continue; - } - if (knownRuleNames.ContainsNames(block.Id, block.Ref, block.Alias, out var duplicateName)) - { - context.WarnDuplicateRuleName(duplicateName); - if (skipDuplicateName) - continue; - } - - context.EnterLanguageScope(block.Source); - try - { - var info = GetRuleHelpInfo(context, block) ?? new RuleHelpInfo( - ruleName, - ruleName, - block.Source.Module, - synopsis: new InfoString(block.Synopsis) - ); - MergeAnnotations(info, block.Metadata); - - results.TryAdd(new RuleBlock - ( - source: block.Source, - id: block.Id, - @ref: block.Ref, - level: block.Level, - info: info, - condition: new RuleVisitor(context, block.Id, block.Source, block.Spec), - alias: block.Alias, - tag: block.Metadata.Tags, - dependsOn: null, // TODO: No support for DependsOn yet - configuration: null, // TODO: No support for rule configuration use module or workspace config - extent: null, - flags: block.Flags, - labels: block.Metadata.Labels - )); - knownRuleNames.AddNames(block.Id, block.Ref, block.Alias); - knownRuleIds.AddIds(block.Id, block.Ref, block.Alias); - } - finally - { - context.ExitLanguageScope(block.Source); - } } - return results; + + results.TryAdd(block); + knownRuleNames.AddNames(block.Id, block.Ref, block.Alias); + knownRuleIds.AddIds(block.Id, block.Ref, block.Alias); } - private static void MergeAnnotations(RuleHelpInfo info, ResourceMetadata metadata) + foreach (var block in blocks.OfType()) { - if (info == null || metadata == null || metadata.Annotations == null || metadata.Annotations.Count == 0) - return; + var ruleName = block.Name; + if (knownRuleIds.ContainsIds(block.Id, block.Ref, block.Alias, out var duplicateId)) + { + context.DuplicateResourceId(block.Id, duplicateId.Value); + continue; + } + if (knownRuleNames.ContainsNames(block.Id, block.Ref, block.Alias, out var duplicateName)) + { + context.WarnDuplicateRuleName(duplicateName); + if (skipDuplicateName) + continue; + } - info.Annotations ??= new Hashtable(StringComparer.OrdinalIgnoreCase); - foreach (var kv in metadata.Annotations) + context.EnterLanguageScope(block.Source); + try { - if (!info.Annotations.ContainsKey(kv.Key)) - info.Annotations[kv.Key] = kv.Value; + var info = GetRuleHelpInfo(context, block) ?? new RuleHelpInfo( + ruleName, + ruleName, + block.Source.Module, + synopsis: new InfoString(block.Synopsis) + ); + MergeAnnotations(info, block.Metadata); + + results.TryAdd(new RuleBlock + ( + source: block.Source, + id: block.Id, + @ref: block.Ref, + level: block.Level, + info: info, + condition: new RuleVisitor(context, block.Id, block.Source, block.Spec), + alias: block.Alias, + tag: block.Metadata.Tags, + dependsOn: null, // TODO: No support for DependsOn yet + configuration: null, // TODO: No support for rule configuration use module or workspace config + extent: null, + flags: block.Flags, + labels: block.Metadata.Labels + )); + knownRuleNames.AddNames(block.Id, block.Ref, block.Alias); + knownRuleIds.AddIds(block.Id, block.Ref, block.Alias); + } + finally + { + context.ExitLanguageScope(block.Source); } - if (!info.HasOnlineHelp()) - info.SetOnlineHelpUrl(metadata.Link); } + return results; + } - private static RuleHelpInfo[] ToRuleHelp(IEnumerable blocks, RunspaceContext context) + private static void MergeAnnotations(RuleHelpInfo info, ResourceMetadata metadata) + { + if (info == null || metadata == null || metadata.Annotations == null || metadata.Annotations.Count == 0) + return; + + info.Annotations ??= new Hashtable(StringComparer.OrdinalIgnoreCase); + foreach (var kv in metadata.Annotations) { - // Index rules by RuleId - var results = new Dictionary(StringComparer.OrdinalIgnoreCase); + if (!info.Annotations.ContainsKey(kv.Key)) + info.Annotations[kv.Key] = kv.Value; + } + if (!info.HasOnlineHelp()) + info.SetOnlineHelpUrl(metadata.Link); + } - foreach (var block in blocks.OfType()) - { - context.EnterLanguageScope(block.Source); - try - { - // Ignore rule blocks that don't match - if (!Match(context, block)) - continue; + private static RuleHelpInfo[] ToRuleHelp(IEnumerable blocks, RunspaceContext context) + { + // Index rules by RuleId + var results = new Dictionary(StringComparer.OrdinalIgnoreCase); - if (!results.ContainsKey(block.Id.Value)) - results[block.Id.Value] = block.Info; - } - finally - { - context.ExitLanguageScope(block.Source); - } + foreach (var block in blocks.OfType()) + { + context.EnterLanguageScope(block.Source); + try + { + // Ignore rule blocks that don't match + if (!Match(context, block)) + continue; + if (!results.ContainsKey(block.Id.Value)) + results[block.Id.Value] = block.Info; + } + finally + { + context.ExitLanguageScope(block.Source); } - return results.Values.ToArray(); + } + return results.Values.ToArray(); + } - private static Baseline[] ToBaselineV1(IEnumerable blocks, RunspaceContext context) - { - if (blocks == null) - return Array.Empty(); + private static Baseline[] ToBaselineV1(IEnumerable blocks, RunspaceContext context) + { + if (blocks == null) + return Array.Empty(); - // Index baselines by BaselineId - var results = new Dictionary(StringComparer.OrdinalIgnoreCase); + // Index baselines by BaselineId + var results = new Dictionary(StringComparer.OrdinalIgnoreCase); - foreach (var block in blocks.OfType().ToArray()) + foreach (var block in blocks.OfType().ToArray()) + { + context.EnterLanguageScope(block.Source); + try { - context.EnterLanguageScope(block.Source); - try - { - // Ignore baselines that don't match - if (!Match(context, block)) - continue; + // Ignore baselines that don't match + if (!Match(context, block)) + continue; - if (!results.ContainsKey(block.BaselineId)) - results[block.BaselineId] = block; + if (!results.ContainsKey(block.BaselineId)) + results[block.BaselineId] = block; - } - finally - { - context.ExitLanguageScope(block.Source); - } } - return results.Values.ToArray(); + finally + { + context.ExitLanguageScope(block.Source); + } } + return results.Values.ToArray(); + } - private static SuppressionGroupV1[] ToSuppressionGroupV1(IEnumerable blocks, RunspaceContext context) - { - if (blocks == null) - return Array.Empty(); + private static SuppressionGroupV1[] ToSuppressionGroupV1(IEnumerable blocks, RunspaceContext context) + { + if (blocks == null) + return Array.Empty(); - // Index suppression groups by Id - var results = new Dictionary(StringComparer.OrdinalIgnoreCase); + // Index suppression groups by Id + var results = new Dictionary(StringComparer.OrdinalIgnoreCase); - foreach (var block in blocks.OfType().ToArray()) + foreach (var block in blocks.OfType().ToArray()) + { + context.EnterLanguageScope(block.Source); + try { - context.EnterLanguageScope(block.Source); - try - { - // Ignore suppression groups that don't match - if (!Match(context, block)) - continue; + // Ignore suppression groups that don't match + if (!Match(context, block)) + continue; - UpdateHelpInfo(context, block); - if (!results.ContainsKey(block.Id.Value)) - results[block.Id.Value] = block; - } - finally - { - context.ExitLanguageScope(block.Source); - } + UpdateHelpInfo(context, block); + if (!results.ContainsKey(block.Id.Value)) + results[block.Id.Value] = block; } - return results.Values.ToArray(); - } - - private static ModuleConfigV1[] ToModuleConfigV1(IEnumerable blocks, RunspaceContext context) - { - if (blocks == null) - return Array.Empty(); - - // Index configurations by Name - var results = new Dictionary(StringComparer.OrdinalIgnoreCase); - foreach (var block in blocks.OfType().ToArray()) + finally { - if (!results.ContainsKey(block.Name)) - results[block.Name] = block; + context.ExitLanguageScope(block.Source); } - return results.Values.ToArray(); } + return results.Values.ToArray(); + } - /// - /// Get conventions. - /// - private static IConvention[] GetConventions(ILanguageBlock[] blocks, RunspaceContext context) + private static ModuleConfigV1[] ToModuleConfigV1(IEnumerable blocks, RunspaceContext context) + { + if (blocks == null) + return Array.Empty(); + + // Index configurations by Name + var results = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (var block in blocks.OfType().ToArray()) { - // Index by Id - var index = new HashSet(StringComparer.OrdinalIgnoreCase); - var results = new List(blocks.Length); + if (!results.ContainsKey(block.Name)) + results[block.Name] = block; + } + return results.Values.ToArray(); + } + + /// + /// Get conventions. + /// + private static IConvention[] GetConventions(ILanguageBlock[] blocks, RunspaceContext context) + { + // Index by Id + var index = new HashSet(StringComparer.OrdinalIgnoreCase); + var results = new List(blocks.Length); - foreach (var block in blocks.OfType().ToArray()) + foreach (var block in blocks.OfType().ToArray()) + { + context.EnterLanguageScope(block.Source); + try { - context.EnterLanguageScope(block.Source); - try - { - // Ignore blocks that don't match - if (!Match(context, block)) - continue; + // Ignore blocks that don't match + if (!Match(context, block)) + continue; - if (!index.Contains(block.Id.Value)) - results.Add(block); + if (!index.Contains(block.Id.Value)) + results.Add(block); - } - finally - { - context.ExitLanguageScope(block.Source); - } } - return Sort(context, results.ToArray()); + finally + { + context.ExitLanguageScope(block.Source); + } } + return Sort(context, results.ToArray()); + } - private static SelectorV1[] ToSelectorV1(IEnumerable blocks, RunspaceContext context) - { - if (blocks == null) - return Array.Empty(); + private static SelectorV1[] ToSelectorV1(IEnumerable blocks, RunspaceContext context) + { + if (blocks == null) + return Array.Empty(); - // Index selectors by Id - var results = new Dictionary(StringComparer.OrdinalIgnoreCase); + // Index selectors by Id + var results = new Dictionary(StringComparer.OrdinalIgnoreCase); - foreach (var block in blocks.OfType().ToArray()) + foreach (var block in blocks.OfType().ToArray()) + { + context.EnterLanguageScope(block.Source); + try { - context.EnterLanguageScope(block.Source); - try - { - // Ignore selectors that don't match - if (!Match(context, block)) - continue; + // Ignore selectors that don't match + if (!Match(context, block)) + continue; - if (!results.ContainsKey(block.Id.Value)) - results[block.Id.Value] = block; - } - finally - { - context.ExitLanguageScope(block.Source); - } + if (!results.ContainsKey(block.Id.Value)) + results[block.Id.Value] = block; + } + finally + { + context.ExitLanguageScope(block.Source); } - return results.Values.ToArray(); } + return results.Values.ToArray(); + } - private static void Import(IConvention[] blocks, RunspaceContext context) - { - foreach (var resource in blocks) - context.Import(resource); - } + private static void Import(IConvention[] blocks, RunspaceContext context) + { + foreach (var resource in blocks) + context.Import(resource); + } - private static bool Match(RunspaceContext context, RuleBlock resource) - { - var filter = context.LanguageScope.GetFilter(ResourceKind.Rule); - return filter == null || filter.Match(resource); - } + private static bool Match(RunspaceContext context, RuleBlock resource) + { + var filter = context.LanguageScope.GetFilter(ResourceKind.Rule); + return filter == null || filter.Match(resource); + } - private static bool Match(RunspaceContext context, IRuleV1 resource) - { - context.EnterLanguageScope(resource.Source); - var filter = context.LanguageScope.GetFilter(ResourceKind.Rule); - return filter == null || filter.Match(resource); - } + private static bool Match(RunspaceContext context, IRuleV1 resource) + { + context.EnterLanguageScope(resource.Source); + var filter = context.LanguageScope.GetFilter(ResourceKind.Rule); + return filter == null || filter.Match(resource); + } - private static bool Match(RunspaceContext context, Baseline resource) - { - var filter = context.LanguageScope.GetFilter(ResourceKind.Baseline); - return filter == null || filter.Match(resource); - } + private static bool Match(RunspaceContext context, Baseline resource) + { + var filter = context.LanguageScope.GetFilter(ResourceKind.Baseline); + return filter == null || filter.Match(resource); + } - private static bool Match(RunspaceContext context, ScriptBlockConvention block) - { - var filter = context.LanguageScope.GetFilter(ResourceKind.Convention); - return filter == null || filter.Match(block); - } + private static bool Match(RunspaceContext context, ScriptBlockConvention block) + { + var filter = context.LanguageScope.GetFilter(ResourceKind.Convention); + return filter == null || filter.Match(block); + } - private static bool Match(RunspaceContext context, SelectorV1 resource) - { - var filter = context.LanguageScope.GetFilter(ResourceKind.Selector); - return filter == null || filter.Match(resource); - } + private static bool Match(RunspaceContext context, SelectorV1 resource) + { + var filter = context.LanguageScope.GetFilter(ResourceKind.Selector); + return filter == null || filter.Match(resource); + } - private static bool Match(RunspaceContext context, SuppressionGroupV1 suppressionGroup) - { - var filter = context.LanguageScope.GetFilter(ResourceKind.SuppressionGroup); - return filter == null || filter.Match(suppressionGroup); - } + private static bool Match(RunspaceContext context, SuppressionGroupV1 suppressionGroup) + { + var filter = context.LanguageScope.GetFilter(ResourceKind.SuppressionGroup); + return filter == null || filter.Match(suppressionGroup); + } - private static IConvention[] Sort(RunspaceContext context, IConvention[] conventions) - { - Array.Sort(conventions, new ConventionComparer(context)); - return conventions; - } + private static IConvention[] Sort(RunspaceContext context, IConvention[] conventions) + { + Array.Sort(conventions, new ConventionComparer(context)); + return conventions; + } - internal static RuleHelpInfo GetRuleHelpInfo(RunspaceContext context, string name, string defaultSynopsis, string defaultDisplayName, InfoString defaultDescription, InfoString defaultRecommendation) - { - return !TryHelpPath(context, name, out var path, out var culture) || !TryDocument(path, culture, out var document) - ? new RuleHelpInfo( - name: name, - displayName: defaultDisplayName ?? name, - moduleName: context.Source.File.Module, - synopsis: InfoString.Create(defaultSynopsis), - description: defaultDescription, - recommendation: defaultRecommendation - ) - : new RuleHelpInfo( - name: name, - displayName: document.Name ?? defaultDisplayName ?? name, - moduleName: context.Source.File.Module, - synopsis: document.Synopsis ?? new InfoString(defaultSynopsis), - description: document.Description ?? defaultDescription, - recommendation: document.Recommendation ?? defaultRecommendation ?? document.Synopsis ?? InfoString.Create(defaultSynopsis) - ) - { - Notes = document.Notes?.Text, - Links = GetLinks(document.Links), - Annotations = document.Annotations?.ToHashtable() - }; - } + internal static RuleHelpInfo GetRuleHelpInfo(RunspaceContext context, string name, string defaultSynopsis, string defaultDisplayName, InfoString defaultDescription, InfoString defaultRecommendation) + { + return !TryHelpPath(context, name, out var path, out var culture) || !TryDocument(path, culture, out var document) + ? new RuleHelpInfo( + name: name, + displayName: defaultDisplayName ?? name, + moduleName: context.Source.File.Module, + synopsis: InfoString.Create(defaultSynopsis), + description: defaultDescription, + recommendation: defaultRecommendation + ) + : new RuleHelpInfo( + name: name, + displayName: document.Name ?? defaultDisplayName ?? name, + moduleName: context.Source.File.Module, + synopsis: document.Synopsis ?? new InfoString(defaultSynopsis), + description: document.Description ?? defaultDescription, + recommendation: document.Recommendation ?? defaultRecommendation ?? document.Synopsis ?? InfoString.Create(defaultSynopsis) + ) + { + Notes = document.Notes?.Text, + Links = GetLinks(document.Links), + Annotations = document.Annotations?.ToHashtable() + }; + } - private static RuleHelpInfo GetRuleHelpInfo(RunspaceContext context, IRuleV1 rule) - { - return GetRuleHelpInfo(context, rule.Name, rule.Synopsis, rule.Info.DisplayName, rule.Info.Description, rule.Recommendation); - } + private static RuleHelpInfo GetRuleHelpInfo(RunspaceContext context, IRuleV1 rule) + { + return GetRuleHelpInfo(context, rule.Name, rule.Synopsis, rule.Info.DisplayName, rule.Info.Description, rule.Recommendation); + } - internal static void UpdateHelpInfo(RunspaceContext context, IResource resource) - { - if (context == null || resource == null || !TryHelpPath(context, resource.Name, out var path, out var culture) || !TryHelpInfo(path, culture, out var info)) - return; + internal static void UpdateHelpInfo(RunspaceContext context, IResource resource) + { + if (context == null || resource == null || !TryHelpPath(context, resource.Name, out var path, out var culture) || !TryHelpInfo(path, culture, out var info)) + return; - resource.Info.Update(info); - } + resource.Info.Update(info); + } - private static bool TryHelpPath(RunspaceContext context, string name, out string path, out string culture) - { - path = null; - culture = null; - if (string.IsNullOrEmpty(context.Source.File.HelpPath)) - return false; - - var helpFileName = string.Concat(name, Markdown_Extension); - path = context.GetLocalizedPath(helpFileName, out culture); - return path != null; - } + private static bool TryHelpPath(RunspaceContext context, string name, out string path, out string culture) + { + path = null; + culture = null; + if (string.IsNullOrEmpty(context.Source.File.HelpPath)) + return false; + + var helpFileName = string.Concat(name, Markdown_Extension); + path = context.GetLocalizedPath(helpFileName, out culture); + return path != null; + } - private static bool TryDocument(string path, string culture, out RuleDocument document) - { - document = null; - var markdown = File.ReadAllText(path); - if (string.IsNullOrEmpty(markdown)) - return false; - - var reader = new MarkdownReader(yamlHeaderOnly: false); - var stream = reader.Read(markdown, path); - var lexer = new RuleHelpLexer(culture); - document = lexer.Process(stream); - return document != null; - } + private static bool TryDocument(string path, string culture, out RuleDocument document) + { + document = null; + var markdown = File.ReadAllText(path); + if (string.IsNullOrEmpty(markdown)) + return false; + + var reader = new MarkdownReader(yamlHeaderOnly: false); + var stream = reader.Read(markdown, path); + var lexer = new RuleHelpLexer(culture); + document = lexer.Process(stream); + return document != null; + } - private static bool TryHelpInfo(string path, string culture, out IResourceHelpInfo info) - { - info = null; - var markdown = File.ReadAllText(path); - if (string.IsNullOrEmpty(markdown)) - return false; - - var reader = new MarkdownReader(yamlHeaderOnly: false); - var stream = reader.Read(markdown, path); - var lexer = new ResourceHelpLexer(culture); - info = lexer.Process(stream).ToInfo(); - return info != null; - } + private static bool TryHelpInfo(string path, string culture, out IResourceHelpInfo info) + { + info = null; + var markdown = File.ReadAllText(path); + if (string.IsNullOrEmpty(markdown)) + return false; + + var reader = new MarkdownReader(yamlHeaderOnly: false); + var stream = reader.Read(markdown, path); + var lexer = new ResourceHelpLexer(culture); + info = lexer.Process(stream).ToInfo(); + return info != null; + } - private static Rules.Link[] GetLinks(Help.Link[] links) - { - if (links == null || links.Length == 0) - return null; + private static Rules.Link[] GetLinks(Help.Link[] links) + { + if (links == null || links.Length == 0) + return null; - var result = new Rules.Link[links.Length]; - for (var i = 0; i < links.Length; i++) - result[i] = new Rules.Link(links[i].Name, links[i].Uri); + var result = new Rules.Link[links.Length]; + for (var i = 0; i < links.Length; i++) + result[i] = new Rules.Link(links[i].Name, links[i].Uri); - return result; - } + return result; } } diff --git a/src/PSRule/Host/RuleLanguageAst.cs b/src/PSRule/Host/RuleLanguageAst.cs index e6fd52fb6b..57833bc8f1 100644 --- a/src/PSRule/Host/RuleLanguageAst.cs +++ b/src/PSRule/Host/RuleLanguageAst.cs @@ -6,242 +6,241 @@ using PSRule.Definitions; using PSRule.Resources; -namespace PSRule.Host +namespace PSRule.Host; + +internal sealed class RuleLanguageAst : AstVisitor { - internal sealed class RuleLanguageAst : AstVisitor + private const string PARAMETER_NAME = "Name"; + private const string PARAMETER_REF = "Ref"; + private const string PARAMETER_ALIAS = "Alias"; + private const string PARAMETER_BODY = "Body"; + private const string PARAMETER_ERRORACTION = "ErrorAction"; + private const string RULE_KEYWORD = "Rule"; + private const string ERRORID_PARAMETERNOTFOUND = "PSRule.Parse.RuleParameterNotFound"; + private const string ERRORID_INVALIDRULENESTING = "PSRule.Parse.InvalidRuleNesting"; + private const string ERRORID_INVALIDERRORACTION = "PSRule.Parse.InvalidErrorAction"; + private const string ERRORID_INVALIDRESOURCENAME = "PSRule.Parse.InvalidResourceName"; + + private readonly StringComparer _Comparer; + + internal List Errors; + + internal RuleLanguageAst() { - private const string PARAMETER_NAME = "Name"; - private const string PARAMETER_REF = "Ref"; - private const string PARAMETER_ALIAS = "Alias"; - private const string PARAMETER_BODY = "Body"; - private const string PARAMETER_ERRORACTION = "ErrorAction"; - private const string RULE_KEYWORD = "Rule"; - private const string ERRORID_PARAMETERNOTFOUND = "PSRule.Parse.RuleParameterNotFound"; - private const string ERRORID_INVALIDRULENESTING = "PSRule.Parse.InvalidRuleNesting"; - private const string ERRORID_INVALIDERRORACTION = "PSRule.Parse.InvalidErrorAction"; - private const string ERRORID_INVALIDRESOURCENAME = "PSRule.Parse.InvalidResourceName"; - - private readonly StringComparer _Comparer; - - internal List Errors; + _Comparer = StringComparer.OrdinalIgnoreCase; + } - internal RuleLanguageAst() + private sealed class ParameterBindResult + { + public ParameterBindResult() { - _Comparer = StringComparer.OrdinalIgnoreCase; + Bound = new Dictionary(StringComparer.OrdinalIgnoreCase); + Unbound = new List(); + _Offset = 0; } - private sealed class ParameterBindResult - { - public ParameterBindResult() - { - Bound = new Dictionary(StringComparer.OrdinalIgnoreCase); - Unbound = new List(); - _Offset = 0; - } - - public Dictionary Bound; - public List Unbound; - - private int _Offset; - - public bool Has(string parameterName, out TAst parameterValue) where TAst : CommandElementAst - { - var result = Bound.TryGetValue(parameterName, out var value) && value is TAst; - parameterValue = result ? value as TAst : null; - return result; - } - - public bool Has(string parameterName, int position, out TAst value) where TAst : CommandElementAst - { - // Try bound - if (Has(parameterName, out value)) - { - _Offset++; - return true; - } - var relative = position - _Offset; - var result = Unbound.Count > relative && relative >= 0 && position >= 0 && Unbound[relative] is TAst; - value = result ? Unbound[relative] as TAst : null; - return result; - } - } + public Dictionary Bound; + public List Unbound; - public override AstVisitAction VisitCommand(CommandAst commandAst) - { - return IsRule(commandAst) ? VisitRule(commandAst) : base.VisitCommand(commandAst); - } + private int _Offset; - /// - /// Visit a rule. - /// - private AstVisitAction VisitRule(CommandAst commandAst) + public bool Has(string parameterName, out TAst parameterValue) where TAst : CommandElementAst { - return NotNested(commandAst) && - TryBindParameters(commandAst, out var bindResult) && - VisitNameParameter(commandAst, bindResult) && - VisitBodyParameter(commandAst, bindResult) && - VisitRefParameter(commandAst, bindResult) && - VisitAliasParameter(commandAst, bindResult) && - VisitErrorAction(commandAst, bindResult) ? base.VisitCommand(commandAst) : AstVisitAction.SkipChildren; + var result = Bound.TryGetValue(parameterName, out var value) && value is TAst; + parameterValue = result ? value as TAst : null; + return result; } - /// - /// Determines if the rule has a Body parameter. - /// - private bool VisitBodyParameter(CommandAst commandAst, ParameterBindResult bindResult) + public bool Has(string parameterName, int position, out TAst value) where TAst : CommandElementAst { - if (bindResult.Has(PARAMETER_BODY, 1, out ScriptBlockExpressionAst _)) + // Try bound + if (Has(parameterName, out value)) + { + _Offset++; return true; - - ReportError(ERRORID_PARAMETERNOTFOUND, PSRuleResources.RuleParameterNotFound, PARAMETER_BODY, ReportExtent(commandAst.Extent)); - return false; + } + var relative = position - _Offset; + var result = Unbound.Count > relative && relative >= 0 && position >= 0 && Unbound[relative] is TAst; + value = result ? Unbound[relative] as TAst : null; + return result; } + } - /// - /// Determines if the rule has a Name parameter. - /// - private bool VisitNameParameter(CommandAst commandAst, ParameterBindResult bindResult) - { - if (bindResult.Has(PARAMETER_NAME, 0, out StringConstantExpressionAst value)) - return IsNameValid(value); + public override AstVisitAction VisitCommand(CommandAst commandAst) + { + return IsRule(commandAst) ? VisitRule(commandAst) : base.VisitCommand(commandAst); + } - ReportError(ERRORID_PARAMETERNOTFOUND, PSRuleResources.RuleParameterNotFound, PARAMETER_NAME, ReportExtent(commandAst.Extent)); - return false; - } + /// + /// Visit a rule. + /// + private AstVisitAction VisitRule(CommandAst commandAst) + { + return NotNested(commandAst) && + TryBindParameters(commandAst, out var bindResult) && + VisitNameParameter(commandAst, bindResult) && + VisitBodyParameter(commandAst, bindResult) && + VisitRefParameter(commandAst, bindResult) && + VisitAliasParameter(commandAst, bindResult) && + VisitErrorAction(commandAst, bindResult) ? base.VisitCommand(commandAst) : AstVisitAction.SkipChildren; + } - private bool VisitRefParameter(CommandAst commandAst, ParameterBindResult bindResult) - { - if (!bindResult.Has(PARAMETER_REF, -1, out StringConstantExpressionAst value)) - return true; + /// + /// Determines if the rule has a Body parameter. + /// + private bool VisitBodyParameter(CommandAst commandAst, ParameterBindResult bindResult) + { + if (bindResult.Has(PARAMETER_BODY, 1, out ScriptBlockExpressionAst _)) + return true; + + ReportError(ERRORID_PARAMETERNOTFOUND, PSRuleResources.RuleParameterNotFound, PARAMETER_BODY, ReportExtent(commandAst.Extent)); + return false; + } + /// + /// Determines if the rule has a Name parameter. + /// + private bool VisitNameParameter(CommandAst commandAst, ParameterBindResult bindResult) + { + if (bindResult.Has(PARAMETER_NAME, 0, out StringConstantExpressionAst value)) return IsNameValid(value); - } - private bool VisitAliasParameter(CommandAst commandAst, ParameterBindResult bindResult) - { - if (!bindResult.Has(PARAMETER_ALIAS, -1, out ArrayLiteralAst value) || value.Elements.Count == 0) - return true; + ReportError(ERRORID_PARAMETERNOTFOUND, PSRuleResources.RuleParameterNotFound, PARAMETER_NAME, ReportExtent(commandAst.Extent)); + return false; + } - return IsNameValid(value); - } + private bool VisitRefParameter(CommandAst commandAst, ParameterBindResult bindResult) + { + if (!bindResult.Has(PARAMETER_REF, -1, out StringConstantExpressionAst value)) + return true; - /// - /// Determines if the rule name is valid. - /// - private bool IsNameValid(StringConstantExpressionAst name) - { - if (ResourceValidator.IsNameValid(name.Value)) - return true; + return IsNameValid(value); + } - ReportError(ERRORID_INVALIDRESOURCENAME, PSRuleResources.InvalidResourceName, name.Value, ReportExtent(name.Extent)); - return false; - } + private bool VisitAliasParameter(CommandAst commandAst, ParameterBindResult bindResult) + { + if (!bindResult.Has(PARAMETER_ALIAS, -1, out ArrayLiteralAst value) || value.Elements.Count == 0) + return true; - private bool IsNameValid(ArrayLiteralAst arrayAst) - { - for (var i = 0; i < arrayAst.Elements.Count; i++) - if (arrayAst.Elements[i] == null || (arrayAst.Elements[i] is StringConstantExpressionAst value && IsNameValid(value))) - return false; + return IsNameValid(value); + } + /// + /// Determines if the rule name is valid. + /// + private bool IsNameValid(StringConstantExpressionAst name) + { + if (ResourceValidator.IsNameValid(name.Value)) return true; - } - /// - /// Determines if the rule is nested in another rule. - /// - private bool NotNested(CommandAst commandAst) - { - if (GetParentBlock(commandAst)?.Parent == null) - return true; + ReportError(ERRORID_INVALIDRESOURCENAME, PSRuleResources.InvalidResourceName, name.Value, ReportExtent(name.Extent)); + return false; + } - ReportError(ERRORID_INVALIDRULENESTING, PSRuleResources.InvalidRuleNesting, ReportExtent(commandAst.Extent)); - return false; - } + private bool IsNameValid(ArrayLiteralAst arrayAst) + { + for (var i = 0; i < arrayAst.Elements.Count; i++) + if (arrayAst.Elements[i] == null || (arrayAst.Elements[i] is StringConstantExpressionAst value && IsNameValid(value))) + return false; - /// - /// Determine if the rule has allowed ErrorAction options. - /// - private bool VisitErrorAction(CommandAst commandAst, ParameterBindResult bindResult) - { - if (!bindResult.Has(PARAMETER_ERRORACTION, -1, out StringConstantExpressionAst value)) - return true; + return true; + } - if (!Enum.TryParse(value.Value, out ActionPreference result) || (result == ActionPreference.Ignore || result == ActionPreference.Stop)) - return true; + /// + /// Determines if the rule is nested in another rule. + /// + private bool NotNested(CommandAst commandAst) + { + if (GetParentBlock(commandAst)?.Parent == null) + return true; - ReportError(ERRORID_INVALIDERRORACTION, PSRuleResources.InvalidErrorAction, value.Value, ReportExtent(commandAst.Extent)); - return false; - } + ReportError(ERRORID_INVALIDRULENESTING, PSRuleResources.InvalidRuleNesting, ReportExtent(commandAst.Extent)); + return false; + } - /// - /// Determines if the command is a rule definition. - /// - private bool IsRule(CommandAst commandAst) - { - return _Comparer.Equals(commandAst.GetCommandName(), RULE_KEYWORD); - } + /// + /// Determine if the rule has allowed ErrorAction options. + /// + private bool VisitErrorAction(CommandAst commandAst, ParameterBindResult bindResult) + { + if (!bindResult.Has(PARAMETER_ERRORACTION, -1, out StringConstantExpressionAst value)) + return true; - private static bool TryBindParameters(CommandAst commandAst, out ParameterBindResult bindResult) - { - bindResult = BindParameters(commandAst); - return bindResult != null; - } + if (!Enum.TryParse(value.Value, out ActionPreference result) || (result == ActionPreference.Ignore || result == ActionPreference.Stop)) + return true; + + ReportError(ERRORID_INVALIDERRORACTION, PSRuleResources.InvalidErrorAction, value.Value, ReportExtent(commandAst.Extent)); + return false; + } - private static ParameterBindResult BindParameters(CommandAst commandAst) + /// + /// Determines if the command is a rule definition. + /// + private bool IsRule(CommandAst commandAst) + { + return _Comparer.Equals(commandAst.GetCommandName(), RULE_KEYWORD); + } + + private static bool TryBindParameters(CommandAst commandAst, out ParameterBindResult bindResult) + { + bindResult = BindParameters(commandAst); + return bindResult != null; + } + + private static ParameterBindResult BindParameters(CommandAst commandAst) + { + var result = new ParameterBindResult(); + var i = 1; + var next = 2; + for (; i < commandAst.CommandElements.Count; i++, next++) { - var result = new ParameterBindResult(); - var i = 1; - var next = 2; - for (; i < commandAst.CommandElements.Count; i++, next++) + // Is named parameter + if (commandAst.CommandElements[i] is CommandParameterAst parameter && next < commandAst.CommandElements.Count) { - // Is named parameter - if (commandAst.CommandElements[i] is CommandParameterAst parameter && next < commandAst.CommandElements.Count) - { - result.Bound.Add(parameter.ParameterName, commandAst.CommandElements[next]); - i++; - next++; - } - else - { - result.Unbound.Add(commandAst.CommandElements[i]); - } + result.Bound.Add(parameter.ParameterName, commandAst.CommandElements[next]); + i++; + next++; + } + else + { + result.Unbound.Add(commandAst.CommandElements[i]); } - return result; } + return result; + } - private void ReportError(string errorId, string message, params object[] args) - { - ReportError(new Pipeline.ParseException( - message: string.Format(Thread.CurrentThread.CurrentCulture, message, args), - errorId: errorId - )); - } + private void ReportError(string errorId, string message, params object[] args) + { + ReportError(new Pipeline.ParseException( + message: string.Format(Thread.CurrentThread.CurrentCulture, message, args), + errorId: errorId + )); + } - private void ReportError(Pipeline.ParseException exception) - { - if (Errors == null) - Errors = new List(); - - Errors.Add(new ErrorRecord( - exception: exception, - errorId: exception.ErrorId, - errorCategory: ErrorCategory.InvalidOperation, - targetObject: null - )); - } + private void ReportError(Pipeline.ParseException exception) + { + if (Errors == null) + Errors = new List(); + + Errors.Add(new ErrorRecord( + exception: exception, + errorId: exception.ErrorId, + errorCategory: ErrorCategory.InvalidOperation, + targetObject: null + )); + } - private static string ReportExtent(IScriptExtent extent) - { - return string.Concat(extent.File, " line ", extent.StartLineNumber); - } + private static string ReportExtent(IScriptExtent extent) + { + return string.Concat(extent.File, " line ", extent.StartLineNumber); + } - private static ScriptBlockAst GetParentBlock(Ast ast) - { - var block = ast; - while (block != null && block is not ScriptBlockAst) - block = block.Parent; + private static ScriptBlockAst GetParentBlock(Ast ast) + { + var block = ast; + while (block != null && block is not ScriptBlockAst) + block = block.Parent; - return (ScriptBlockAst)block; - } + return (ScriptBlockAst)block; } } diff --git a/src/PSRule/Pipeline/AssertPipeline.cs b/src/PSRule/Pipeline/AssertPipeline.cs index 928be7b58c..49775737b9 100644 --- a/src/PSRule/Pipeline/AssertPipeline.cs +++ b/src/PSRule/Pipeline/AssertPipeline.cs @@ -9,225 +9,224 @@ using PSRule.Resources; using PSRule.Rules; -namespace PSRule.Pipeline +namespace PSRule.Pipeline; + +/// +/// A helper to construct the pipeline for Assert-PSRule. +/// +internal sealed class AssertPipelineBuilder : InvokePipelineBuilderBase { + private AssertWriter _Writer; + + internal AssertPipelineBuilder(Source[] source, IHostContext hostContext) + : base(source, hostContext) { } + /// - /// A helper to construct the pipeline for Assert-PSRule. + /// A writer for outputting assertions. /// - internal sealed class AssertPipelineBuilder : InvokePipelineBuilderBase + private sealed class AssertWriter : PipelineWriter { - private AssertWriter _Writer; - - internal AssertPipelineBuilder(Source[] source, IHostContext hostContext) - : base(source, hostContext) { } - - /// - /// A writer for outputting assertions. - /// - private sealed class AssertWriter : PipelineWriter + internal readonly IAssertFormatter _Formatter; + private readonly PipelineWriter _InnerWriter; + private readonly string _ResultVariableName; + private readonly IHostContext _HostContext; + private readonly List _Results; + private int _ErrorCount; + private int _FailCount; + private int _TotalCount; + private bool _PSError; + private SeverityLevel _Level; + + internal AssertWriter(PSRuleOption option, Source[] source, PipelineWriter inner, PipelineWriter next, OutputStyle style, string resultVariableName, IHostContext hostContext) + : base(inner, option, hostContext.ShouldProcess) { - internal readonly IAssertFormatter _Formatter; - private readonly PipelineWriter _InnerWriter; - private readonly string _ResultVariableName; - private readonly IHostContext _HostContext; - private readonly List _Results; - private int _ErrorCount; - private int _FailCount; - private int _TotalCount; - private bool _PSError; - private SeverityLevel _Level; - - internal AssertWriter(PSRuleOption option, Source[] source, PipelineWriter inner, PipelineWriter next, OutputStyle style, string resultVariableName, IHostContext hostContext) - : base(inner, option, hostContext.ShouldProcess) - { - _InnerWriter = next; - _ResultVariableName = resultVariableName; - _HostContext = hostContext; - if (!string.IsNullOrEmpty(resultVariableName)) - _Results = new List(); + _InnerWriter = next; + _ResultVariableName = resultVariableName; + _HostContext = hostContext; + if (!string.IsNullOrEmpty(resultVariableName)) + _Results = new List(); - _Formatter = GetFormatter(style, source, inner, option); - } + _Formatter = GetFormatter(style, source, inner, option); + } - private static IAssertFormatter GetFormatter(OutputStyle style, Source[] source, PipelineWriter inner, PSRuleOption option) - { - if (style == OutputStyle.AzurePipelines) - return new AzurePipelinesFormatter(source, inner, option); + private static IAssertFormatter GetFormatter(OutputStyle style, Source[] source, PipelineWriter inner, PSRuleOption option) + { + if (style == OutputStyle.AzurePipelines) + return new AzurePipelinesFormatter(source, inner, option); - if (style == OutputStyle.GitHubActions) - return new GitHubActionsFormatter(source, inner, option); + if (style == OutputStyle.GitHubActions) + return new GitHubActionsFormatter(source, inner, option); - if (style == OutputStyle.VisualStudioCode) - return new VisualStudioCodeFormatter(source, inner, option); + if (style == OutputStyle.VisualStudioCode) + return new VisualStudioCodeFormatter(source, inner, option); - return style == OutputStyle.Plain ? - (IAssertFormatter)new PlainFormatter(source, inner, option) : - new ClientFormatter(source, inner, option); - } + return style == OutputStyle.Plain ? + (IAssertFormatter)new PlainFormatter(source, inner, option) : + new ClientFormatter(source, inner, option); + } - public override void WriteObject(object sendToPipeline, bool enumerateCollection) - { - if (sendToPipeline is not InvokeResult result) - return; + public override void WriteObject(object sendToPipeline, bool enumerateCollection) + { + if (sendToPipeline is not InvokeResult result) + return; - ProcessResult(result); - _InnerWriter?.WriteObject(sendToPipeline, enumerateCollection); - } + ProcessResult(result); + _InnerWriter?.WriteObject(sendToPipeline, enumerateCollection); + } - public override void WriteWarning(string message) - { - var warningPreference = _HostContext.GetPreferenceVariable(WarningPreference); - if (warningPreference == ActionPreference.Ignore || warningPreference == ActionPreference.SilentlyContinue) - return; + public override void WriteWarning(string message) + { + var warningPreference = _HostContext.GetPreferenceVariable(WarningPreference); + if (warningPreference == ActionPreference.Ignore || warningPreference == ActionPreference.SilentlyContinue) + return; - _Formatter.Warning(new WarningRecord(message)); - } + _Formatter.Warning(new WarningRecord(message)); + } - public override void WriteError(ErrorRecord errorRecord) - { - HadErrors = true; - var errorPreference = _HostContext.GetPreferenceVariable(ErrorPreference); - if (errorPreference == ActionPreference.Ignore || errorPreference == ActionPreference.SilentlyContinue) - return; + public override void WriteError(ErrorRecord errorRecord) + { + HadErrors = true; + var errorPreference = _HostContext.GetPreferenceVariable(ErrorPreference); + if (errorPreference == ActionPreference.Ignore || errorPreference == ActionPreference.SilentlyContinue) + return; - _PSError = true; - _Formatter.Error(errorRecord); - } + _PSError = true; + _Formatter.Error(errorRecord); + } - public override void Begin() - { - base.Begin(); - _Formatter.Begin(); - } + public override void Begin() + { + base.Begin(); + _Formatter.Begin(); + } - public override void End() + public override void End() + { + _Formatter.End(_TotalCount, _FailCount, _ErrorCount); + base.End(); + try { - _Formatter.End(_TotalCount, _FailCount, _ErrorCount); - base.End(); - try + if (_ErrorCount > 0) { - if (_ErrorCount > 0) - { - HadErrors = true; - base.WriteError(new ErrorRecord( - new FailPipelineException(PSRuleResources.RuleErrorPipelineException), - "PSRule.Error", - ErrorCategory.InvalidOperation, - null)); - } - else if (_FailCount > 0 && _Level == SeverityLevel.Error) - { - HadFailures = true; - base.WriteError(new ErrorRecord( - new FailPipelineException(PSRuleResources.RuleFailPipelineException), - "PSRule.Fail", - ErrorCategory.InvalidData, - null)); - } - else if (_PSError) - { - HadErrors = true; - base.WriteError(new ErrorRecord( - new FailPipelineException(PSRuleResources.ErrorPipelineException), - "PSRule.Error", - ErrorCategory.InvalidOperation, - null)); - } - if (_FailCount > 0 && _Level == SeverityLevel.Warning) - { - HadFailures = true; - base.WriteWarning(PSRuleResources.RuleFailPipelineException); - } - - if (_FailCount > 0 && _Level == SeverityLevel.Information) - { - HadFailures = true; - base.WriteHost(new HostInformationMessage() { Message = PSRuleResources.RuleFailPipelineException }); - } - - if (_Results != null && _HostContext != null) - _HostContext.SetVariable(_ResultVariableName, _Results.ToArray()); + HadErrors = true; + base.WriteError(new ErrorRecord( + new FailPipelineException(PSRuleResources.RuleErrorPipelineException), + "PSRule.Error", + ErrorCategory.InvalidOperation, + null)); } - finally + else if (_FailCount > 0 && _Level == SeverityLevel.Error) { - _InnerWriter?.End(); + HadFailures = true; + base.WriteError(new ErrorRecord( + new FailPipelineException(PSRuleResources.RuleFailPipelineException), + "PSRule.Fail", + ErrorCategory.InvalidData, + null)); + } + else if (_PSError) + { + HadErrors = true; + base.WriteError(new ErrorRecord( + new FailPipelineException(PSRuleResources.ErrorPipelineException), + "PSRule.Error", + ErrorCategory.InvalidOperation, + null)); + } + if (_FailCount > 0 && _Level == SeverityLevel.Warning) + { + HadFailures = true; + base.WriteWarning(PSRuleResources.RuleFailPipelineException); + } + + if (_FailCount > 0 && _Level == SeverityLevel.Information) + { + HadFailures = true; + base.WriteHost(new HostInformationMessage() { Message = PSRuleResources.RuleFailPipelineException }); } - } - private void ProcessResult(InvokeResult result) + if (_Results != null && _HostContext != null) + _HostContext.SetVariable(_ResultVariableName, _Results.ToArray()); + } + finally { - _Formatter.Result(result); - _FailCount += result.Fail; - _ErrorCount += result.Error; - _TotalCount += result.Total; - _Level = _Level.GetWorstCase(result.Level); - _Results?.AddRange(result.AsRecord()); + _InnerWriter?.End(); } } - protected override PipelineWriter PrepareWriter() + private void ProcessResult(InvokeResult result) { - return GetWriter(); + _Formatter.Result(result); + _FailCount += result.Fail; + _ErrorCount += result.Error; + _TotalCount += result.Total; + _Level = _Level.GetWorstCase(result.Level); + _Results?.AddRange(result.AsRecord()); } + } - protected override PipelineWriter GetOutput(bool writeHost = false) - { - return base.GetOutput(writeHost: true); - } + protected override PipelineWriter PrepareWriter() + { + return GetWriter(); + } - private AssertWriter GetWriter() - { - if (_Writer == null) - { - var next = ShouldOutput() ? base.PrepareWriter() : null; - _Writer = new AssertWriter( - option: Option, - source: Source, - inner: GetOutput(writeHost: true), - next: next, - style: Option.Output.Style ?? OutputOption.Default.Style.Value, - resultVariableName: _ResultVariableName, - hostContext: HostContext - ); - } - return _Writer; - } + protected override PipelineWriter GetOutput(bool writeHost = false) + { + return base.GetOutput(writeHost: true); + } - private bool ShouldOutput() + private AssertWriter GetWriter() + { + if (_Writer == null) { - return !(string.IsNullOrEmpty(Option.Output.Path) || - Option.Output.Format == OutputFormat.Wide || - Option.Output.Format == OutputFormat.None); + var next = ShouldOutput() ? base.PrepareWriter() : null; + _Writer = new AssertWriter( + option: Option, + source: Source, + inner: GetOutput(writeHost: true), + next: next, + style: Option.Output.Style ?? OutputOption.Default.Style.Value, + resultVariableName: _ResultVariableName, + hostContext: HostContext + ); } + return _Writer; + } - public sealed override IPipeline Build(IPipelineWriter writer = null) - { - writer ??= PrepareWriter(); - Unblock(writer); - return !RequireModules() || !RequireSources() - ? null - : (IPipeline)new InvokeRulePipeline( - context: PrepareContext( - bindTargetName: BindTargetNameHook, - bindTargetType: BindTargetTypeHook, - bindField: BindFieldHook), - source: Source, - writer: HandleJobSummary(writer ?? PrepareWriter()), - outcome: RuleOutcome.Processed); - } + private bool ShouldOutput() + { + return !(string.IsNullOrEmpty(Option.Output.Path) || + Option.Output.Format == OutputFormat.Wide || + Option.Output.Format == OutputFormat.None); + } - private IPipelineWriter HandleJobSummary(IPipelineWriter writer) - { - if (string.IsNullOrEmpty(Option.Output.JobSummaryPath)) - return writer; + public sealed override IPipeline Build(IPipelineWriter writer = null) + { + writer ??= PrepareWriter(); + Unblock(writer); + return !RequireModules() || !RequireSources() + ? null + : (IPipeline)new InvokeRulePipeline( + context: PrepareContext( + bindTargetName: BindTargetNameHook, + bindTargetType: BindTargetTypeHook, + bindField: BindFieldHook), + source: Source, + writer: HandleJobSummary(writer ?? PrepareWriter()), + outcome: RuleOutcome.Processed); + } - return new JobSummaryWriter - ( - inner: writer, - option: Option, - shouldProcess: ShouldProcess, - source: Source - ); - } + private IPipelineWriter HandleJobSummary(IPipelineWriter writer) + { + if (string.IsNullOrEmpty(Option.Output.JobSummaryPath)) + return writer; + + return new JobSummaryWriter + ( + inner: writer, + option: Option, + shouldProcess: ShouldProcess, + source: Source + ); } } diff --git a/src/PSRule/Pipeline/CommandLineBuilder.cs b/src/PSRule/Pipeline/CommandLineBuilder.cs index 5e5f200b50..d926349afb 100644 --- a/src/PSRule/Pipeline/CommandLineBuilder.cs +++ b/src/PSRule/Pipeline/CommandLineBuilder.cs @@ -6,77 +6,76 @@ #nullable enable -namespace PSRule.Pipeline +namespace PSRule.Pipeline; + +/// +/// A helper to create a PSRule pipeline that can be used via the .NET SDK. +/// +public static class CommandLineBuilder { /// - /// A helper to create a PSRule pipeline that can be used via the .NET SDK. + /// Create a builder for an Invoke pipeline. /// - public static class CommandLineBuilder + /// + /// Invoke pipelines process objects and produce records indicating the outcome of each rule. + /// + /// The name of modules containing rules to process. + /// Options that configure PSRule. + /// An implementation of a host context that will receive output and results. + /// An optional lock file. + /// A builder object to configure the pipeline. + public static IInvokePipelineBuilder Invoke(string[] module, PSRuleOption option, IHostContext hostContext, LockFile? file = null) { - /// - /// Create a builder for an Invoke pipeline. - /// - /// - /// Invoke pipelines process objects and produce records indicating the outcome of each rule. - /// - /// The name of modules containing rules to process. - /// Options that configure PSRule. - /// An implementation of a host context that will receive output and results. - /// An optional lock file. - /// A builder object to configure the pipeline. - public static IInvokePipelineBuilder Invoke(string[] module, PSRuleOption option, IHostContext hostContext, LockFile? file = null) - { - var sourcePipeline = new SourcePipelineBuilder(hostContext, option, GetLocalPath()); - LoadModules(sourcePipeline, module, option, file); + var sourcePipeline = new SourcePipelineBuilder(hostContext, option, GetLocalPath()); + LoadModules(sourcePipeline, module, option, file); - var source = sourcePipeline.Build(); - var pipeline = new InvokeRulePipelineBuilder(source, hostContext); - pipeline.Configure(option); - return pipeline; - } + var source = sourcePipeline.Build(); + var pipeline = new InvokeRulePipelineBuilder(source, hostContext); + pipeline.Configure(option); + return pipeline; + } - /// - /// Create a builder for an Assert pipeline. - /// - /// - /// Assert pipelines process objects with rules and produce text-based output suitable for output to a CI pipeline. - /// - /// The name of modules containing rules to process. - /// Options that configure PSRule. - /// An implementation of a host context that will receive output and results. - /// An optional lock file. - /// A builder object to configure the pipeline. - public static IInvokePipelineBuilder Assert(string[] module, PSRuleOption option, IHostContext hostContext, LockFile? file = null) - { - var sourcePipeline = new SourcePipelineBuilder(hostContext, option, GetLocalPath()); - LoadModules(sourcePipeline, module, option, file); + /// + /// Create a builder for an Assert pipeline. + /// + /// + /// Assert pipelines process objects with rules and produce text-based output suitable for output to a CI pipeline. + /// + /// The name of modules containing rules to process. + /// Options that configure PSRule. + /// An implementation of a host context that will receive output and results. + /// An optional lock file. + /// A builder object to configure the pipeline. + public static IInvokePipelineBuilder Assert(string[] module, PSRuleOption option, IHostContext hostContext, LockFile? file = null) + { + var sourcePipeline = new SourcePipelineBuilder(hostContext, option, GetLocalPath()); + LoadModules(sourcePipeline, module, option, file); - var source = sourcePipeline.Build(); - var pipeline = new AssertPipelineBuilder(source, hostContext); - pipeline.Configure(option); - return pipeline; - } + var source = sourcePipeline.Build(); + var pipeline = new AssertPipelineBuilder(source, hostContext); + pipeline.Configure(option); + return pipeline; + } - private static void LoadModules(SourcePipelineBuilder builder, string[] module, PSRuleOption option, LockFile? file) + private static void LoadModules(SourcePipelineBuilder builder, string[] module, PSRuleOption option, LockFile? file) + { + for (var i = 0; module != null && i < module.Length; i++) { - for (var i = 0; module != null && i < module.Length; i++) - { - var version = file != null && file.Modules.TryGetValue(module[i], out var entry) ? entry.Version.ToString() : null; - builder.ModuleByName(module[i], version); - } - - for (var i = 0; option.Include.Module != null && i < option.Include.Module.Length; i++) - { - var version = file != null && file.Modules.TryGetValue(option.Include.Module[i], out var entry) ? entry.Version.ToString() : null; - builder.ModuleByName(option.Include.Module[i], version); - } + var version = file != null && file.Modules.TryGetValue(module[i], out var entry) ? entry.Version.ToString() : null; + builder.ModuleByName(module[i], version); } - private static string? GetLocalPath() + for (var i = 0; option.Include.Module != null && i < option.Include.Module.Length; i++) { - return string.IsNullOrEmpty(AppContext.BaseDirectory) ? null : Environment.GetRootedBasePath(AppContext.BaseDirectory); + var version = file != null && file.Modules.TryGetValue(option.Include.Module[i], out var entry) ? entry.Version.ToString() : null; + builder.ModuleByName(option.Include.Module[i], version); } } + + private static string? GetLocalPath() + { + return string.IsNullOrEmpty(AppContext.BaseDirectory) ? null : Environment.GetRootedBasePath(AppContext.BaseDirectory); + } } #nullable restore diff --git a/src/PSRule/Pipeline/Dependencies/LockFile.cs b/src/PSRule/Pipeline/Dependencies/LockFile.cs index 748366e9d8..c72a24ff01 100644 --- a/src/PSRule/Pipeline/Dependencies/LockFile.cs +++ b/src/PSRule/Pipeline/Dependencies/LockFile.cs @@ -52,7 +52,7 @@ public static LockFile Read(string path) { new SemanticVersionConverter() }, - + }); } return new LockFile(); diff --git a/src/PSRule/Pipeline/Exceptions.cs b/src/PSRule/Pipeline/Exceptions.cs index a501ff0a55..bc5c484a61 100644 --- a/src/PSRule/Pipeline/Exceptions.cs +++ b/src/PSRule/Pipeline/Exceptions.cs @@ -5,559 +5,558 @@ using System.Runtime.Serialization; using System.Security.Permissions; -namespace PSRule.Pipeline +namespace PSRule.Pipeline; + +/// +/// A base class for all pipeline exceptions. +/// +public abstract class PipelineException : Exception { /// - /// A base class for all pipeline exceptions. - /// - public abstract class PipelineException : Exception - { - /// - /// Initialize a new instance of a PSRule exception. - /// - protected PipelineException() - : base() { } - - /// - /// Initialize a new instance of a PSRule exception. - /// - protected PipelineException(string message) - : base(message) { } - - /// - /// Initialize a new instance of a PSRule exception. - /// - protected PipelineException(string message, Exception innerException) - : base(message, innerException) { } + /// Initialize a new instance of a PSRule exception. + /// + protected PipelineException() + : base() { } + + /// + /// Initialize a new instance of a PSRule exception. + /// + protected PipelineException(string message) + : base(message) { } - /// - /// Initialize a new instance of a PSRule exception. - /// - protected PipelineException(SerializationInfo info, StreamingContext context) - : base(info, context) { } + /// + /// Initialize a new instance of a PSRule exception. + /// + protected PipelineException(string message, Exception innerException) + : base(message, innerException) { } + + /// + /// Initialize a new instance of a PSRule exception. + /// + protected PipelineException(SerializationInfo info, StreamingContext context) + : base(info, context) { } +} + +/// +/// A base class for runtime exceptions. +/// +public abstract class RuntimeException : PipelineException +{ + /// + /// Initialize a new instance of a PSRule exception that is thrown during runtime. + /// + protected RuntimeException() + : base() { } + + /// + /// Initialize a new instance of a PSRule exception that is thrown during runtime. + /// + protected RuntimeException(string message) + : base(message) { } + + /// + /// Initialize a new instance of a PSRule exception that is thrown during runtime. + /// + protected RuntimeException(string message, Exception innerException) + : base(message, innerException) { } + + /// + /// Initialize a new instance of a PSRule exception that is thrown during runtime. + /// + protected RuntimeException(Exception innerException, InvocationInfo invocationInfo, string ruleId) + : base(innerException?.Message, innerException) + { + CommandInvocation = invocationInfo; + RuleId = ruleId; } /// - /// A base class for runtime exceptions. - /// - public abstract class RuntimeException : PipelineException + /// Initialize a new instance of a PSRule exception that is thrown during runtime. + /// + protected RuntimeException(SerializationInfo info, StreamingContext context) + : base(info, context) { } + + /// + /// Additional information about the invocation when executing PowerShell language elements. + /// + public InvocationInfo CommandInvocation { get; } + + /// + /// A unique identifier for the rule that was executing if currently within the context of a rule. + /// + public string RuleId { get; } +} + +/// +/// An exception when building the pipeline. +/// +[Serializable] +public sealed class PipelineBuilderException : PipelineException +{ + /// + /// Creates a pipeline builder exception. + /// + public PipelineBuilderException() + : base() { } + + /// + /// Creates a pipeline builder exception. + /// + /// The detail of the exception. + public PipelineBuilderException(string message) + : base(message) { } + + /// + /// Creates a pipeline builder exception. + /// + /// The detail of the exception. + /// A nested exception that caused the issue. + public PipelineBuilderException(string message, Exception innerException) + : base(message, innerException) { } + + private PipelineBuilderException(SerializationInfo info, StreamingContext context) + : base(info, context) { } + + /// + [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)] + public override void GetObjectData(SerializationInfo info, StreamingContext context) + { + if (info == null) + throw new ArgumentNullException(nameof(info)); + + base.GetObjectData(info, context); + } +} + +/// +/// A serialization exception. +/// +[Serializable] +public sealed class PipelineSerializationException : PipelineException +{ + /// + /// Creates a serialization exception. + /// + public PipelineSerializationException() + { + } + + /// + /// Creates a serialization exception. + /// + internal PipelineSerializationException(string message, string path, Exception innerException) + : this(message, innerException) + { + Path = path; + } + + /// + /// Creates a serialization exception. + /// + /// The detail of the exception. + public PipelineSerializationException(string message) : base(message) { - /// - /// Initialize a new instance of a PSRule exception that is thrown during runtime. - /// - protected RuntimeException() - : base() { } - - /// - /// Initialize a new instance of a PSRule exception that is thrown during runtime. - /// - protected RuntimeException(string message) - : base(message) { } - - /// - /// Initialize a new instance of a PSRule exception that is thrown during runtime. - /// - protected RuntimeException(string message, Exception innerException) - : base(message, innerException) { } - - /// - /// Initialize a new instance of a PSRule exception that is thrown during runtime. - /// - protected RuntimeException(Exception innerException, InvocationInfo invocationInfo, string ruleId) - : base(innerException?.Message, innerException) - { - CommandInvocation = invocationInfo; - RuleId = ruleId; - } - - /// - /// Initialize a new instance of a PSRule exception that is thrown during runtime. - /// - protected RuntimeException(SerializationInfo info, StreamingContext context) - : base(info, context) { } - - /// - /// Additional information about the invocation when executing PowerShell language elements. - /// - public InvocationInfo CommandInvocation { get; } - - /// - /// A unique identifier for the rule that was executing if currently within the context of a rule. - /// - public string RuleId { get; } - } - - /// - /// An exception when building the pipeline. - /// - [Serializable] - public sealed class PipelineBuilderException : PipelineException - { - /// - /// Creates a pipeline builder exception. - /// - public PipelineBuilderException() - : base() { } - - /// - /// Creates a pipeline builder exception. - /// - /// The detail of the exception. - public PipelineBuilderException(string message) - : base(message) { } - - /// - /// Creates a pipeline builder exception. - /// - /// The detail of the exception. - /// A nested exception that caused the issue. - public PipelineBuilderException(string message, Exception innerException) - : base(message, innerException) { } - - private PipelineBuilderException(SerializationInfo info, StreamingContext context) - : base(info, context) { } - - /// - [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)] - public override void GetObjectData(SerializationInfo info, StreamingContext context) - { - if (info == null) - throw new ArgumentNullException(nameof(info)); - - base.GetObjectData(info, context); - } - } - - /// - /// A serialization exception. - /// - [Serializable] - public sealed class PipelineSerializationException : PipelineException - { - /// - /// Creates a serialization exception. - /// - public PipelineSerializationException() - { - } - - /// - /// Creates a serialization exception. - /// - internal PipelineSerializationException(string message, string path, Exception innerException) - : this(message, innerException) - { - Path = path; - } - - /// - /// Creates a serialization exception. - /// - /// The detail of the exception. - public PipelineSerializationException(string message) : base(message) - { - } - - /// - /// Creates a serialization exception. - /// - /// The detail of the exception. - /// Additional argument to add to the format string. - internal PipelineSerializationException(string message, params object[] args) - : base(string.Format(Thread.CurrentThread.CurrentCulture, message, args)) { } - - /// - /// Creates a serialization exception. - /// - /// The detail of the exception. - /// A nested exception that caused the issue. - public PipelineSerializationException(string message, Exception innerException) - : base(message, innerException) { } - - /// - /// Creates a serialization exception. - /// - private PipelineSerializationException(SerializationInfo info, StreamingContext context) - : base(info, context) { } - - /// - /// The path to the file. - /// - public string Path { get; } - - /// - [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)] - public override void GetObjectData(SerializationInfo info, StreamingContext context) - { - if (info == null) - throw new ArgumentNullException(nameof(info)); - - base.GetObjectData(info, context); - } - } - - /// - /// A parser exception. - /// - [Serializable] - public sealed class ParseException : PipelineException - { - /// - /// Creates a parser exception. - /// - public ParseException() - { - } - - /// - /// Creates a parser exception. - /// - public ParseException(string message) - : base(message) { } - - /// - /// Creates a parser exception. - /// - public ParseException(string message, Exception innerException) - : base(message, innerException) { } - - /// - /// Creates a parser exception. - /// - /// The detail of the exception. - /// A unique identifier related to the exception. - internal ParseException(string message, string errorId) : base(message) - { - ErrorId = errorId; - } - - /// - /// Creates a parser exception. - /// - /// The detail of the exception. - /// A unique identifier related to the exception. - /// A nested exception that caused the issue. - internal ParseException(string message, string errorId, Exception innerException) : base(message, innerException) - { - ErrorId = errorId; - } - - /// - /// Creates a parser exception. - /// - private ParseException(SerializationInfo info, StreamingContext context) : base(info, context) - { - ErrorId = info.GetString("ErrorId"); - } - - /// - /// An associated identifier related to why the exception was thrown. - /// - public string ErrorId { get; } - - /// - [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)] - public override void GetObjectData(SerializationInfo info, StreamingContext context) - { - if (info == null) - throw new ArgumentNullException(nameof(info)); - - info.AddValue("ErrorId", ErrorId); - base.GetObjectData(info, context); - } - } - - /// - /// A rule runtime exception. - /// - [Serializable] - public sealed class RuleException : RuntimeException - { - /// - /// Creates a rule runtime exception. - /// - public RuleException() - { - } - - /// - /// Creates a rule runtime exception. - /// - /// The detail of the exception. - public RuleException(string message) - : base(message) { } - - /// - /// Creates a rule runtime exception. - /// - /// The detail of the exception. - /// A nested exception that caused the issue. - public RuleException(string message, Exception innerException) - : base(message, innerException) { } - - /// - /// Creates a rule runtime exception. - /// - internal RuleException(Exception innerException, InvocationInfo invocationInfo, string ruleId, ErrorRecord errorRecord) - : base(innerException, invocationInfo, ruleId) - { - ErrorRecord = errorRecord; - } - - /// - /// Creates a rule runtime exception. - /// - private RuleException(SerializationInfo info, StreamingContext context) - : base(info, context) { } - - /// - /// An associated error record related to the exception. - /// - public ErrorRecord ErrorRecord { get; } - - /// - [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)] - public override void GetObjectData(SerializationInfo info, StreamingContext context) - { - if (info == null) - throw new ArgumentNullException(nameof(info)); - - base.GetObjectData(info, context); - } - } - - /// - /// A base class for configuration exceptions. - /// - public abstract class ConfigurationException : PipelineException - { - /// - /// Initialize a new instance of a PSRule exception that is thrown when attempting to read configuration. - /// - protected ConfigurationException() - : base() { } - - /// - /// Initialize a new instance of a PSRule exception that is thrown when attempting to read configuration. - /// - protected ConfigurationException(string message) - : base(message) { } - - /// - /// Initialize a new instance of a PSRule exception that is thrown when attempting to read configuration. - /// - protected ConfigurationException(string message, Exception innerException) - : base(message, innerException) { } - - /// - /// Initialize a new instance of a PSRule exception that is thrown when attempting to read configuration. - /// - protected ConfigurationException(SerializationInfo info, StreamingContext context) - : base(info, context) { } - } - - /// - /// A pipeline configuration exception. - /// - [Serializable] - public sealed class PipelineConfigurationException : ConfigurationException - { - /// - /// Creates a pipeline configuration exception. - /// - public PipelineConfigurationException() - { - } - - /// - /// Creates a pipeline configuration exception. - /// - /// The name of the option that caused the exception. - /// The detail of the exception. - public PipelineConfigurationException(string optionName, string message) : base(message) - { - } - - /// - /// Creates a pipeline configuration exception. - /// - public PipelineConfigurationException(string message) : base(message) - { - } - - /// - /// Creates a pipeline configuration exception. - /// - public PipelineConfigurationException(string message, Exception innerException) : base(message, innerException) - { - } - - /// - /// Creates a pipeline configuration exception. - /// - /// The name of the option that caused the exception. - /// The detail of the exception. - /// A nested exception that caused the issue. - public PipelineConfigurationException(string optionName, string message, Exception innerException) : base(message, innerException) - { - } - - /// - private PipelineConfigurationException(SerializationInfo info, StreamingContext context) : base(info, context) - { - } - - /// - [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)] - public override void GetObjectData(SerializationInfo info, StreamingContext context) - { - if (info == null) - throw new ArgumentNullException(nameof(info)); - - base.GetObjectData(info, context); - } - } - - /// - /// A configuration parse exception. - /// - [Serializable] - public sealed class ConfigurationParseException : ConfigurationException - { - /// - /// Creates a configuration parse exception. - /// - public ConfigurationParseException() - { - } - - /// - /// Creates a configuration parse exception. - /// - /// The path to the options file thay cause the exception. - /// The detail of the exception. - public ConfigurationParseException(string path, string message) - : base(message) - { - Path = path; - } - - /// - /// Creates a configuration parse exception. - /// - public ConfigurationParseException(string message) - : base(message) { } - - /// - /// Creates a configuration parse exception. - /// - public ConfigurationParseException(string message, Exception innerException) - : base(message, innerException) { } - - /// - /// Creates a configuration parse exception. - /// - /// The path to the options file thay cause the exception - /// The detail of the exception. - /// A nested exception that caused the issue. - public ConfigurationParseException(string path, string message, Exception innerException) - : base(message, innerException) - { - Path = path; - } - - /// - private ConfigurationParseException(SerializationInfo info, StreamingContext context) - : base(info, context) { } - - /// - [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)] - public override void GetObjectData(SerializationInfo info, StreamingContext context) - { - if (info == null) - throw new ArgumentNullException(nameof(info)); - - base.GetObjectData(info, context); - } - - /// - /// The path to the options file used. - /// - public string Path { get; } - } - - /// - /// An exception thrown by PSRule when the calling PowerShell environment should fail because one or more rules failed or errored. - /// - [Serializable] - public sealed class FailPipelineException : PipelineException - { - /// - public FailPipelineException() - { - } - - /// - public FailPipelineException(string message) : base(message) - { - } - - /// - public FailPipelineException(string message, Exception innerException) : base(message, innerException) - { - } - - /// - private FailPipelineException(SerializationInfo info, StreamingContext context) : base(info, context) - { - } - - /// - [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)] - public override void GetObjectData(SerializationInfo info, StreamingContext context) - { - if (info == null) - throw new ArgumentNullException(nameof(info)); - - base.GetObjectData(info, context); - } - } - - /// - /// An exception thrown by PSRule when a runtime property or method is used outside of the intended scope. - /// Avoid using runtime variables outside of a PSRule pipeline. - /// - [Serializable] - public sealed class RuntimeScopeException : PipelineException - { - /// - public RuntimeScopeException() - { - } - - /// - public RuntimeScopeException(string message) : base(message) - { - } - - /// - public RuntimeScopeException(string message, Exception innerException) : base(message, innerException) - { - } - - /// - private RuntimeScopeException(SerializationInfo info, StreamingContext context) : base(info, context) - { - } - - /// - [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)] - public override void GetObjectData(SerializationInfo info, StreamingContext context) - { - if (info == null) - throw new ArgumentNullException(nameof(info)); - - base.GetObjectData(info, context); - } + } + + /// + /// Creates a serialization exception. + /// + /// The detail of the exception. + /// Additional argument to add to the format string. + internal PipelineSerializationException(string message, params object[] args) + : base(string.Format(Thread.CurrentThread.CurrentCulture, message, args)) { } + + /// + /// Creates a serialization exception. + /// + /// The detail of the exception. + /// A nested exception that caused the issue. + public PipelineSerializationException(string message, Exception innerException) + : base(message, innerException) { } + + /// + /// Creates a serialization exception. + /// + private PipelineSerializationException(SerializationInfo info, StreamingContext context) + : base(info, context) { } + + /// + /// The path to the file. + /// + public string Path { get; } + + /// + [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)] + public override void GetObjectData(SerializationInfo info, StreamingContext context) + { + if (info == null) + throw new ArgumentNullException(nameof(info)); + + base.GetObjectData(info, context); + } +} + +/// +/// A parser exception. +/// +[Serializable] +public sealed class ParseException : PipelineException +{ + /// + /// Creates a parser exception. + /// + public ParseException() + { + } + + /// + /// Creates a parser exception. + /// + public ParseException(string message) + : base(message) { } + + /// + /// Creates a parser exception. + /// + public ParseException(string message, Exception innerException) + : base(message, innerException) { } + + /// + /// Creates a parser exception. + /// + /// The detail of the exception. + /// A unique identifier related to the exception. + internal ParseException(string message, string errorId) : base(message) + { + ErrorId = errorId; + } + + /// + /// Creates a parser exception. + /// + /// The detail of the exception. + /// A unique identifier related to the exception. + /// A nested exception that caused the issue. + internal ParseException(string message, string errorId, Exception innerException) : base(message, innerException) + { + ErrorId = errorId; + } + + /// + /// Creates a parser exception. + /// + private ParseException(SerializationInfo info, StreamingContext context) : base(info, context) + { + ErrorId = info.GetString("ErrorId"); + } + + /// + /// An associated identifier related to why the exception was thrown. + /// + public string ErrorId { get; } + + /// + [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)] + public override void GetObjectData(SerializationInfo info, StreamingContext context) + { + if (info == null) + throw new ArgumentNullException(nameof(info)); + + info.AddValue("ErrorId", ErrorId); + base.GetObjectData(info, context); + } +} + +/// +/// A rule runtime exception. +/// +[Serializable] +public sealed class RuleException : RuntimeException +{ + /// + /// Creates a rule runtime exception. + /// + public RuleException() + { + } + + /// + /// Creates a rule runtime exception. + /// + /// The detail of the exception. + public RuleException(string message) + : base(message) { } + + /// + /// Creates a rule runtime exception. + /// + /// The detail of the exception. + /// A nested exception that caused the issue. + public RuleException(string message, Exception innerException) + : base(message, innerException) { } + + /// + /// Creates a rule runtime exception. + /// + internal RuleException(Exception innerException, InvocationInfo invocationInfo, string ruleId, ErrorRecord errorRecord) + : base(innerException, invocationInfo, ruleId) + { + ErrorRecord = errorRecord; + } + + /// + /// Creates a rule runtime exception. + /// + private RuleException(SerializationInfo info, StreamingContext context) + : base(info, context) { } + + /// + /// An associated error record related to the exception. + /// + public ErrorRecord ErrorRecord { get; } + + /// + [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)] + public override void GetObjectData(SerializationInfo info, StreamingContext context) + { + if (info == null) + throw new ArgumentNullException(nameof(info)); + + base.GetObjectData(info, context); + } +} + +/// +/// A base class for configuration exceptions. +/// +public abstract class ConfigurationException : PipelineException +{ + /// + /// Initialize a new instance of a PSRule exception that is thrown when attempting to read configuration. + /// + protected ConfigurationException() + : base() { } + + /// + /// Initialize a new instance of a PSRule exception that is thrown when attempting to read configuration. + /// + protected ConfigurationException(string message) + : base(message) { } + + /// + /// Initialize a new instance of a PSRule exception that is thrown when attempting to read configuration. + /// + protected ConfigurationException(string message, Exception innerException) + : base(message, innerException) { } + + /// + /// Initialize a new instance of a PSRule exception that is thrown when attempting to read configuration. + /// + protected ConfigurationException(SerializationInfo info, StreamingContext context) + : base(info, context) { } +} + +/// +/// A pipeline configuration exception. +/// +[Serializable] +public sealed class PipelineConfigurationException : ConfigurationException +{ + /// + /// Creates a pipeline configuration exception. + /// + public PipelineConfigurationException() + { + } + + /// + /// Creates a pipeline configuration exception. + /// + /// The name of the option that caused the exception. + /// The detail of the exception. + public PipelineConfigurationException(string optionName, string message) : base(message) + { + } + + /// + /// Creates a pipeline configuration exception. + /// + public PipelineConfigurationException(string message) : base(message) + { + } + + /// + /// Creates a pipeline configuration exception. + /// + public PipelineConfigurationException(string message, Exception innerException) : base(message, innerException) + { + } + + /// + /// Creates a pipeline configuration exception. + /// + /// The name of the option that caused the exception. + /// The detail of the exception. + /// A nested exception that caused the issue. + public PipelineConfigurationException(string optionName, string message, Exception innerException) : base(message, innerException) + { + } + + /// + private PipelineConfigurationException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + + /// + [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)] + public override void GetObjectData(SerializationInfo info, StreamingContext context) + { + if (info == null) + throw new ArgumentNullException(nameof(info)); + + base.GetObjectData(info, context); + } +} + +/// +/// A configuration parse exception. +/// +[Serializable] +public sealed class ConfigurationParseException : ConfigurationException +{ + /// + /// Creates a configuration parse exception. + /// + public ConfigurationParseException() + { + } + + /// + /// Creates a configuration parse exception. + /// + /// The path to the options file thay cause the exception. + /// The detail of the exception. + public ConfigurationParseException(string path, string message) + : base(message) + { + Path = path; + } + + /// + /// Creates a configuration parse exception. + /// + public ConfigurationParseException(string message) + : base(message) { } + + /// + /// Creates a configuration parse exception. + /// + public ConfigurationParseException(string message, Exception innerException) + : base(message, innerException) { } + + /// + /// Creates a configuration parse exception. + /// + /// The path to the options file thay cause the exception + /// The detail of the exception. + /// A nested exception that caused the issue. + public ConfigurationParseException(string path, string message, Exception innerException) + : base(message, innerException) + { + Path = path; + } + + /// + private ConfigurationParseException(SerializationInfo info, StreamingContext context) + : base(info, context) { } + + /// + [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)] + public override void GetObjectData(SerializationInfo info, StreamingContext context) + { + if (info == null) + throw new ArgumentNullException(nameof(info)); + + base.GetObjectData(info, context); + } + + /// + /// The path to the options file used. + /// + public string Path { get; } +} + +/// +/// An exception thrown by PSRule when the calling PowerShell environment should fail because one or more rules failed or errored. +/// +[Serializable] +public sealed class FailPipelineException : PipelineException +{ + /// + public FailPipelineException() + { + } + + /// + public FailPipelineException(string message) : base(message) + { + } + + /// + public FailPipelineException(string message, Exception innerException) : base(message, innerException) + { + } + + /// + private FailPipelineException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + + /// + [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)] + public override void GetObjectData(SerializationInfo info, StreamingContext context) + { + if (info == null) + throw new ArgumentNullException(nameof(info)); + + base.GetObjectData(info, context); + } +} + +/// +/// An exception thrown by PSRule when a runtime property or method is used outside of the intended scope. +/// Avoid using runtime variables outside of a PSRule pipeline. +/// +[Serializable] +public sealed class RuntimeScopeException : PipelineException +{ + /// + public RuntimeScopeException() + { + } + + /// + public RuntimeScopeException(string message) : base(message) + { + } + + /// + public RuntimeScopeException(string message, Exception innerException) : base(message, innerException) + { + } + + /// + private RuntimeScopeException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + + /// + [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)] + public override void GetObjectData(SerializationInfo info, StreamingContext context) + { + if (info == null) + throw new ArgumentNullException(nameof(info)); + + base.GetObjectData(info, context); } } diff --git a/src/PSRule/Pipeline/ExecutionScope.cs b/src/PSRule/Pipeline/ExecutionScope.cs index 71169db1f0..6e51f6afb3 100644 --- a/src/PSRule/Pipeline/ExecutionScope.cs +++ b/src/PSRule/Pipeline/ExecutionScope.cs @@ -1,30 +1,29 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -namespace PSRule.Pipeline +namespace PSRule.Pipeline; + +internal enum ExecutionScope { - internal enum ExecutionScope - { - None = 0, + None = 0, - /// - /// Execution is occuring at the script. This occurs during discovery. - /// - Script = 1, + /// + /// Execution is occuring at the script. This occurs during discovery. + /// + Script = 1, - /// - /// Execution is occuring in the main rule condition block. - /// - Condition = 2, + /// + /// Execution is occuring in the main rule condition block. + /// + Condition = 2, - /// - /// Execution is occuring in a rule precondition. - /// - Precondition = 3, + /// + /// Execution is occuring in a rule precondition. + /// + Precondition = 3, - /// - /// Execution is currently parsing YAML objects. - /// - Resource = 4 - } + /// + /// Execution is currently parsing YAML objects. + /// + Resource = 4 } diff --git a/src/PSRule/Pipeline/ExportBaselinePipelineBuilder.cs b/src/PSRule/Pipeline/ExportBaselinePipelineBuilder.cs index f4bd2e29d6..de5a805b81 100644 --- a/src/PSRule/Pipeline/ExportBaselinePipelineBuilder.cs +++ b/src/PSRule/Pipeline/ExportBaselinePipelineBuilder.cs @@ -4,55 +4,54 @@ using PSRule.Configuration; using PSRule.Definitions.Baselines; -namespace PSRule.Pipeline +namespace PSRule.Pipeline; + +internal sealed class ExportBaselinePipelineBuilder : PipelineBuilderBase { - internal sealed class ExportBaselinePipelineBuilder : PipelineBuilderBase + private string[] _Name; + + internal ExportBaselinePipelineBuilder(Source[] source, IHostContext hostContext) + : base(source, hostContext) { } + + /// + /// Filter returned baselines by name. + /// + public new void Name(string[] name) { - private string[] _Name; - - internal ExportBaselinePipelineBuilder(Source[] source, IHostContext hostContext) - : base(source, hostContext) { } - - /// - /// Filter returned baselines by name. - /// - public new void Name(string[] name) - { - if (name == null || name.Length == 0) - return; - - _Name = name; - } - - public override IPipelineBuilder Configure(PSRuleOption option) - { - if (option == null) - return this; - - Option.Baseline = new Options.BaselineOption(option.Baseline); - Option.Output.As = ResultFormat.Detail; - Option.Output.Culture = GetCulture(option.Output.Culture); - Option.Output.Format = option.Output.Format ?? OutputOption.Default.Format; - Option.Output.Encoding = option.Output.Encoding ?? OutputOption.Default.Encoding; - Option.Output.Path = option.Output.Path ?? OutputOption.Default.Path; - Option.Output.JsonIndent = NormalizeJsonIndentRange(option.Output.JsonIndent); + if (name == null || name.Length == 0) + return; + + _Name = name; + } + + public override IPipelineBuilder Configure(PSRuleOption option) + { + if (option == null) return this; - } - - public override IPipeline Build(IPipelineWriter writer = null) - { - var filter = new BaselineFilter(_Name); - return new GetBaselinePipeline( - pipeline: PrepareContext( - bindTargetName: null, - bindTargetType: null, - bindField: null - ), - source: Source, - reader: PrepareReader(), - writer: writer ?? PrepareWriter(), - filter: filter - ); - } + + Option.Baseline = new Options.BaselineOption(option.Baseline); + Option.Output.As = ResultFormat.Detail; + Option.Output.Culture = GetCulture(option.Output.Culture); + Option.Output.Format = option.Output.Format ?? OutputOption.Default.Format; + Option.Output.Encoding = option.Output.Encoding ?? OutputOption.Default.Encoding; + Option.Output.Path = option.Output.Path ?? OutputOption.Default.Path; + Option.Output.JsonIndent = NormalizeJsonIndentRange(option.Output.JsonIndent); + return this; + } + + public override IPipeline Build(IPipelineWriter writer = null) + { + var filter = new BaselineFilter(_Name); + return new GetBaselinePipeline( + pipeline: PrepareContext( + bindTargetName: null, + bindTargetType: null, + bindField: null + ), + source: Source, + reader: PrepareReader(), + writer: writer ?? PrepareWriter(), + filter: filter + ); } } diff --git a/src/PSRule/Pipeline/Formatters/AssertFormatter.cs b/src/PSRule/Pipeline/Formatters/AssertFormatter.cs index 3687836ae2..06e927f249 100644 --- a/src/PSRule/Pipeline/Formatters/AssertFormatter.cs +++ b/src/PSRule/Pipeline/Formatters/AssertFormatter.cs @@ -7,724 +7,723 @@ using PSRule.Resources; using PSRule.Rules; -namespace PSRule.Pipeline.Formatters +namespace PSRule.Pipeline.Formatters; + +internal interface IAssertFormatter : IPipelineWriter { - internal interface IAssertFormatter : IPipelineWriter - { - void Result(InvokeResult result); + void Result(InvokeResult result); - void Error(ErrorRecord errorRecord); + void Error(ErrorRecord errorRecord); - void Warning(WarningRecord warningRecord); + void Warning(WarningRecord warningRecord); - void End(int total, int fail, int error); - } + void End(int total, int fail, int error); +} - /// - /// Configures formatted output. - /// - internal sealed class TerminalSupport +/// +/// Configures formatted output. +/// +internal sealed class TerminalSupport +{ + public TerminalSupport(int indent) { - public TerminalSupport(int indent) - { - BodyIndent = new string(' ', indent); - MessageIdent = BodyIndent; - StartResultIndent = FormatterStrings.StartObjectPrefix; - SourceLocationPrefix = "| "; - SynopsisPrefix = FormatterStrings.SynopsisPrefix; - PassStatus = FormatterStrings.Result_Pass; - FailStatus = FormatterStrings.Result_Fail; - InformationStatus = FormatterStrings.Result_Information; - WarningStatus = FormatterStrings.Result_Warning; - ErrorStatus = FormatterStrings.Result_Error; - RecommendationHeading = FormatterStrings.Recommend; - RecommendationPrefix = "| "; - ReasonHeading = FormatterStrings.Reason; - ReasonItemPrefix = "| - "; - HelpHeading = FormatterStrings.Help; - HelpLinkPrefix = "| - "; - } + BodyIndent = new string(' ', indent); + MessageIdent = BodyIndent; + StartResultIndent = FormatterStrings.StartObjectPrefix; + SourceLocationPrefix = "| "; + SynopsisPrefix = FormatterStrings.SynopsisPrefix; + PassStatus = FormatterStrings.Result_Pass; + FailStatus = FormatterStrings.Result_Fail; + InformationStatus = FormatterStrings.Result_Information; + WarningStatus = FormatterStrings.Result_Warning; + ErrorStatus = FormatterStrings.Result_Error; + RecommendationHeading = FormatterStrings.Recommend; + RecommendationPrefix = "| "; + ReasonHeading = FormatterStrings.Reason; + ReasonItemPrefix = "| - "; + HelpHeading = FormatterStrings.Help; + HelpLinkPrefix = "| - "; + } - public string BodyIndent { get; } + public string BodyIndent { get; } - public string MessageIdent { get; internal set; } + public string MessageIdent { get; internal set; } - public string StartResultIndent { get; internal set; } + public string StartResultIndent { get; internal set; } - public ConsoleColor? StartResultForegroundColor { get; internal set; } + public ConsoleColor? StartResultForegroundColor { get; internal set; } - public string SourceLocationPrefix { get; internal set; } + public string SourceLocationPrefix { get; internal set; } - public ConsoleColor? SourceLocationForegroundColor { get; internal set; } + public ConsoleColor? SourceLocationForegroundColor { get; internal set; } - public string SynopsisPrefix { get; internal set; } + public string SynopsisPrefix { get; internal set; } - public ConsoleColor? SynopsisForegroundColor { get; internal set; } + public ConsoleColor? SynopsisForegroundColor { get; internal set; } - public string PassStatus { get; internal set; } + public string PassStatus { get; internal set; } - public ConsoleColor? PassForegroundColor { get; internal set; } + public ConsoleColor? PassForegroundColor { get; internal set; } - public ConsoleColor? PassBackgroundColor { get; internal set; } + public ConsoleColor? PassBackgroundColor { get; internal set; } - public ConsoleColor? PassStatusBackgroundColor { get; internal set; } + public ConsoleColor? PassStatusBackgroundColor { get; internal set; } - public ConsoleColor? PassStatusForegroundColor { get; internal set; } + public ConsoleColor? PassStatusForegroundColor { get; internal set; } - public string FailStatus { get; internal set; } + public string FailStatus { get; internal set; } - public ConsoleColor? FailForegroundColor { get; internal set; } + public ConsoleColor? FailForegroundColor { get; internal set; } - public ConsoleColor? FailBackgroundColor { get; internal set; } + public ConsoleColor? FailBackgroundColor { get; internal set; } - public ConsoleColor? FailStatusBackgroundColor { get; internal set; } + public ConsoleColor? FailStatusBackgroundColor { get; internal set; } - public ConsoleColor? FailStatusForegroundColor { get; internal set; } + public ConsoleColor? FailStatusForegroundColor { get; internal set; } - public string InformationStatus { get; internal set; } + public string InformationStatus { get; internal set; } - public string WarningStatus { get; internal set; } + public string WarningStatus { get; internal set; } - public ConsoleColor? WarningForegroundColor { get; internal set; } + public ConsoleColor? WarningForegroundColor { get; internal set; } - public ConsoleColor? WarningBackgroundColor { get; internal set; } + public ConsoleColor? WarningBackgroundColor { get; internal set; } - public ConsoleColor? WarningStatusBackgroundColor { get; internal set; } + public ConsoleColor? WarningStatusBackgroundColor { get; internal set; } - public ConsoleColor? WarningStatusForegroundColor { get; internal set; } + public ConsoleColor? WarningStatusForegroundColor { get; internal set; } - public string ErrorStatus { get; internal set; } + public string ErrorStatus { get; internal set; } - public ConsoleColor? ErrorForegroundColor { get; internal set; } + public ConsoleColor? ErrorForegroundColor { get; internal set; } - public ConsoleColor? ErrorBackgroundColor { get; internal set; } + public ConsoleColor? ErrorBackgroundColor { get; internal set; } - public ConsoleColor? ErrorStatusBackgroundColor { get; internal set; } + public ConsoleColor? ErrorStatusBackgroundColor { get; internal set; } - public ConsoleColor? ErrorStatusForegroundColor { get; internal set; } + public ConsoleColor? ErrorStatusForegroundColor { get; internal set; } - public ConsoleColor? BodyForegroundColor { get; internal set; } + public ConsoleColor? BodyForegroundColor { get; internal set; } - public string RecommendationHeading { get; internal set; } + public string RecommendationHeading { get; internal set; } - public string RecommendationPrefix { get; internal set; } + public string RecommendationPrefix { get; internal set; } - public string ReasonHeading { get; internal set; } + public string ReasonHeading { get; internal set; } - public string ReasonItemPrefix { get; internal set; } + public string ReasonItemPrefix { get; internal set; } - public string HelpHeading { get; internal set; } + public string HelpHeading { get; internal set; } - public string HelpLinkPrefix { get; internal set; } - } + public string HelpLinkPrefix { get; internal set; } +} - /// - /// A base class for a formatter. - /// - internal abstract class AssertFormatterBase : PipelineLoggerBase, IAssertFormatter - { - private const string OUTPUT_SEPARATOR_BAR = "----------------------------"; +/// +/// A base class for a formatter. +/// +internal abstract class AssertFormatterBase : PipelineLoggerBase, IAssertFormatter +{ + private const string OUTPUT_SEPARATOR_BAR = "----------------------------"; - protected readonly IPipelineWriter Writer; - protected readonly PSRuleOption Option; + protected readonly IPipelineWriter Writer; + protected readonly PSRuleOption Option; - private bool _UnbrokenContent; - private bool _UnbrokenInfo; - private bool _UnbrokenObject; + private bool _UnbrokenContent; + private bool _UnbrokenInfo; + private bool _UnbrokenObject; - private static readonly TerminalSupport DefaultTerminalSupport = new(4); + private static readonly TerminalSupport DefaultTerminalSupport = new(4); - protected AssertFormatterBase(Source[] source, IPipelineWriter writer, PSRuleOption option) - { - Writer = writer; - Option = option; - Banner(); - Source(source); - SupportLinks(source); - RepositoryInfo(); - } + protected AssertFormatterBase(Source[] source, IPipelineWriter writer, PSRuleOption option) + { + Writer = writer; + Option = option; + Banner(); + Source(source); + SupportLinks(source); + RepositoryInfo(); + } - #region IAssertFormatter + #region IAssertFormatter - public void Error(ErrorRecord errorRecord) - { - Error(errorRecord.Exception.Message); - } + public void Error(ErrorRecord errorRecord) + { + Error(errorRecord.Exception.Message); + } - public void Warning(WarningRecord warningRecord) - { - Warning(warningRecord.Message); - } + public void Warning(WarningRecord warningRecord) + { + Warning(warningRecord.Message); + } - public void Result(InvokeResult result) - { - if ((Option.Output.Outcome.Value & result.Outcome) != result.Outcome) - return; + public void Result(InvokeResult result) + { + if ((Option.Output.Outcome.Value & result.Outcome) != result.Outcome) + return; - StartResult(result, out var records); - for (var i = 0; i < records.Length; i++) - { - if (records[i].IsSuccess()) - Pass(records[i]); - else if (records[i].Outcome == RuleOutcome.Error) - FailWithError(records[i]); - else - Fail(records[i]); - } + StartResult(result, out var records); + for (var i = 0; i < records.Length; i++) + { + if (records[i].IsSuccess()) + Pass(records[i]); + else if (records[i].Outcome == RuleOutcome.Error) + FailWithError(records[i]); + else + Fail(records[i]); } + } - public void End(int total, int fail, int error) - { - if (Option.Output.Footer.GetValueOrDefault(FooterFormat.Default) != FooterFormat.None) - LineBreak(); + public void End(int total, int fail, int error) + { + if (Option.Output.Footer.GetValueOrDefault(FooterFormat.Default) != FooterFormat.None) + LineBreak(); - FooterRuleCount(total, fail, error); - FooterRunInfo(); - } + FooterRuleCount(total, fail, error); + FooterRunInfo(); + } - #endregion IAssertFormatter + #endregion IAssertFormatter - #region PipelineLoggerBase + #region PipelineLoggerBase - protected sealed override void DoWriteError(ErrorRecord errorRecord) - { - Error(errorRecord); - } + protected sealed override void DoWriteError(ErrorRecord errorRecord) + { + Error(errorRecord); + } - protected sealed override void DoWriteWarning(string message) - { - Warning(message); - } + protected sealed override void DoWriteWarning(string message) + { + Warning(message); + } - protected sealed override void DoWriteVerbose(string message) - { - Writer.WriteVerbose(message); - } + protected sealed override void DoWriteVerbose(string message) + { + Writer.WriteVerbose(message); + } - protected sealed override void DoWriteInformation(InformationRecord informationRecord) - { - Writer.WriteInformation(informationRecord); - } + protected sealed override void DoWriteInformation(InformationRecord informationRecord) + { + Writer.WriteInformation(informationRecord); + } - protected sealed override void DoWriteDebug(DebugRecord debugRecord) - { - Writer.WriteDebug(debugRecord); - } + protected sealed override void DoWriteDebug(DebugRecord debugRecord) + { + Writer.WriteDebug(debugRecord); + } - protected sealed override void DoWriteObject(object sendToPipeline, bool enumerateCollection) - { - Writer.WriteObject(sendToPipeline, enumerateCollection); - } + protected sealed override void DoWriteObject(object sendToPipeline, bool enumerateCollection) + { + Writer.WriteObject(sendToPipeline, enumerateCollection); + } - #endregion PipelineLoggerBase + #endregion PipelineLoggerBase - /// - /// Occurs when a rule passes. - /// - private void Pass(RuleRecord record) - { - if (Option.Output.As != ResultFormat.Detail || !Option.Output.Outcome.Value.HasFlag(RuleOutcome.Pass)) - return; - - BreakIfUnbrokenObject(); - BreakIfUnbrokenInfo(); - WriteStatus( - status: GetTerminalSupport().PassStatus, - statusIndent: GetTerminalSupport().BodyIndent, - statusForeground: GetTerminalSupport().PassStatusForegroundColor, - statusBackground: GetTerminalSupport().PassStatusBackgroundColor, - messageForeground: GetTerminalSupport().PassForegroundColor, - messageBackground: GetTerminalSupport().PassBackgroundColor, - message: record.RuleName, - suffix: record.Ref - ); - UnbrokenContent(); - } + /// + /// Occurs when a rule passes. + /// + private void Pass(RuleRecord record) + { + if (Option.Output.As != ResultFormat.Detail || !Option.Output.Outcome.Value.HasFlag(RuleOutcome.Pass)) + return; + + BreakIfUnbrokenObject(); + BreakIfUnbrokenInfo(); + WriteStatus( + status: GetTerminalSupport().PassStatus, + statusIndent: GetTerminalSupport().BodyIndent, + statusForeground: GetTerminalSupport().PassStatusForegroundColor, + statusBackground: GetTerminalSupport().PassStatusBackgroundColor, + messageForeground: GetTerminalSupport().PassForegroundColor, + messageBackground: GetTerminalSupport().PassBackgroundColor, + message: record.RuleName, + suffix: record.Ref + ); + UnbrokenContent(); + } - /// - /// Occurs when a rule fails. - /// - private void Fail(RuleRecord record) - { - if (!Option.Output.Outcome.Value.HasFlag(RuleOutcome.Fail)) - return; - - BreakIfUnbrokenObject(); - BreakIfUnbrokenInfo(); - WriteStatus( - status: GetTerminalSupport().FailStatus, - statusIndent: GetTerminalSupport().BodyIndent, - statusForeground: GetTerminalSupport().FailStatusForegroundColor, - statusBackground: GetTerminalSupport().FailStatusBackgroundColor, - messageForeground: GetTerminalSupport().FailForegroundColor, - messageBackground: GetTerminalSupport().FailBackgroundColor, - message: record.RuleName, - suffix: record.Ref - ); - FailDetail(record); - } + /// + /// Occurs when a rule fails. + /// + private void Fail(RuleRecord record) + { + if (!Option.Output.Outcome.Value.HasFlag(RuleOutcome.Fail)) + return; + + BreakIfUnbrokenObject(); + BreakIfUnbrokenInfo(); + WriteStatus( + status: GetTerminalSupport().FailStatus, + statusIndent: GetTerminalSupport().BodyIndent, + statusForeground: GetTerminalSupport().FailStatusForegroundColor, + statusBackground: GetTerminalSupport().FailStatusBackgroundColor, + messageForeground: GetTerminalSupport().FailForegroundColor, + messageBackground: GetTerminalSupport().FailBackgroundColor, + message: record.RuleName, + suffix: record.Ref + ); + FailDetail(record); + } - /// - /// Occurs when a rule raises an error. - /// - private void FailWithError(RuleRecord record) - { - if (!Option.Output.Outcome.Value.HasFlag(RuleOutcome.Error)) - return; - - BreakIfUnbrokenObject(); - BreakIfUnbrokenInfo(); - WriteStatus( - status: GetTerminalSupport().ErrorStatus, - statusIndent: GetTerminalSupport().BodyIndent, - statusForeground: GetTerminalSupport().ErrorStatusForegroundColor, - statusBackground: GetTerminalSupport().ErrorStatusBackgroundColor, - messageForeground: GetTerminalSupport().ErrorForegroundColor, - messageBackground: GetTerminalSupport().ErrorBackgroundColor, - message: record.RuleName, - suffix: record.Ref - ); - ErrorDetail(record); - UnbrokenContent(); - } + /// + /// Occurs when a rule raises an error. + /// + private void FailWithError(RuleRecord record) + { + if (!Option.Output.Outcome.Value.HasFlag(RuleOutcome.Error)) + return; + + BreakIfUnbrokenObject(); + BreakIfUnbrokenInfo(); + WriteStatus( + status: GetTerminalSupport().ErrorStatus, + statusIndent: GetTerminalSupport().BodyIndent, + statusForeground: GetTerminalSupport().ErrorStatusForegroundColor, + statusBackground: GetTerminalSupport().ErrorStatusBackgroundColor, + messageForeground: GetTerminalSupport().ErrorForegroundColor, + messageBackground: GetTerminalSupport().ErrorBackgroundColor, + message: record.RuleName, + suffix: record.Ref + ); + ErrorDetail(record); + UnbrokenContent(); + } - protected virtual void FailDetail(RuleRecord record) - { - WriteSourceLocation(record, shouldBreak: false); - WriteRecommendation(record); - WriteReason(record); - WriteHelp(record); - LineBreak(); - } + protected virtual void FailDetail(RuleRecord record) + { + WriteSourceLocation(record, shouldBreak: false); + WriteRecommendation(record); + WriteReason(record); + WriteHelp(record); + LineBreak(); + } - protected virtual void ErrorDetail(RuleRecord record) - { + protected virtual void ErrorDetail(RuleRecord record) + { - } + } - protected void Error(string message) - { - WriteErrorMessage(GetTerminalSupport().MessageIdent, message); - } + protected void Error(string message) + { + WriteErrorMessage(GetTerminalSupport().MessageIdent, message); + } - protected void Warning(string message) - { - WriteWarningMessage(GetTerminalSupport().MessageIdent, message); - } + protected void Warning(string message) + { + WriteWarningMessage(GetTerminalSupport().MessageIdent, message); + } - protected void Information(string message) - { - WriteInformationMessage(GetTerminalSupport().MessageIdent, message); - } + protected void Information(string message) + { + WriteInformationMessage(GetTerminalSupport().MessageIdent, message); + } - private void Banner() - { - if (!Option.Output.Banner.GetValueOrDefault(BannerFormat.Default).HasFlag(BannerFormat.Title)) - return; + private void Banner() + { + if (!Option.Output.Banner.GetValueOrDefault(BannerFormat.Default).HasFlag(BannerFormat.Title)) + return; - WriteLine(FormatterStrings.Banner.Replace("\\n", System.Environment.NewLine)); - LineBreak(); - } + WriteLine(FormatterStrings.Banner.Replace("\\n", System.Environment.NewLine)); + LineBreak(); + } - private void StartResult(InvokeResult result, out RuleRecord[] records) - { - records = result.AsRecord(); - if (records == null || records.Length == 0) - return; - - BreakIfUnbrokenContent(); - BreakIfUnbrokenInfo(); - WriteStartResult(result); - UnbrokenObject(); - } + private void StartResult(InvokeResult result, out RuleRecord[] records) + { + records = result.AsRecord(); + if (records == null || records.Length == 0) + return; + + BreakIfUnbrokenContent(); + BreakIfUnbrokenInfo(); + WriteStartResult(result); + UnbrokenObject(); + } - private void WriteStartResult(InvokeResult result) - { - WriteLine( - message: string.Concat( - GetTerminalSupport().StartResultIndent, - result.TargetName, - " : ", - result.TargetType, - " [", - result.Pass, - "/", - result.Total, - "]"), - forgroundColor: GetTerminalSupport().StartResultForegroundColor); - } + private void WriteStartResult(InvokeResult result) + { + WriteLine( + message: string.Concat( + GetTerminalSupport().StartResultIndent, + result.TargetName, + " : ", + result.TargetType, + " [", + result.Pass, + "/", + result.Total, + "]"), + forgroundColor: GetTerminalSupport().StartResultForegroundColor); + } - protected virtual TerminalSupport GetTerminalSupport() - { - return DefaultTerminalSupport; - } + protected virtual TerminalSupport GetTerminalSupport() + { + return DefaultTerminalSupport; + } - private void Source(Source[] source) - { - if (!Option.Output.Banner.GetValueOrDefault(BannerFormat.Default).HasFlag(BannerFormat.Source)) - return; + private void Source(Source[] source) + { + if (!Option.Output.Banner.GetValueOrDefault(BannerFormat.Default).HasFlag(BannerFormat.Source)) + return; - var version = Engine.GetVersion(); - if (!string.IsNullOrEmpty(version)) - WriteLineFormat(FormatterStrings.PSRuleVersion, version); + var version = Engine.GetVersion(); + if (!string.IsNullOrEmpty(version)) + WriteLineFormat(FormatterStrings.PSRuleVersion, version); - var list = new HashSet(StringComparer.OrdinalIgnoreCase); - for (var i = 0; source != null && i < source.Length; i++) + var list = new HashSet(StringComparer.OrdinalIgnoreCase); + for (var i = 0; source != null && i < source.Length; i++) + { + if (source[i].Module != null && !list.Contains(source[i].Module.Name)) { - if (source[i].Module != null && !list.Contains(source[i].Module.Name)) - { - WriteLineFormat(FormatterStrings.ModuleVersion, source[i].Module.Name, source[i].Module.Version); - list.Add(source[i].Module.Name); - } + WriteLineFormat(FormatterStrings.ModuleVersion, source[i].Module.Name, source[i].Module.Version); + list.Add(source[i].Module.Name); } - LineBreak(); } + LineBreak(); + } + + private void SupportLinks(Source[] source) + { + if (!Option.Output.Banner.GetValueOrDefault(BannerFormat.Default).HasFlag(BannerFormat.SupportLinks)) + return; - private void SupportLinks(Source[] source) + WriteLine(OUTPUT_SEPARATOR_BAR); + WriteLine(FormatterStrings.HelpDocs); + WriteLine(FormatterStrings.HelpContribute); + WriteLine(FormatterStrings.HelpIssues); + var list = new HashSet(StringComparer.OrdinalIgnoreCase); + for (var i = 0; source != null && i < source.Length; i++) { - if (!Option.Output.Banner.GetValueOrDefault(BannerFormat.Default).HasFlag(BannerFormat.SupportLinks)) - return; - - WriteLine(OUTPUT_SEPARATOR_BAR); - WriteLine(FormatterStrings.HelpDocs); - WriteLine(FormatterStrings.HelpContribute); - WriteLine(FormatterStrings.HelpIssues); - var list = new HashSet(StringComparer.OrdinalIgnoreCase); - for (var i = 0; source != null && i < source.Length; i++) + if (source[i].Module != null && !list.Contains(source[i].Module.Name) && !string.IsNullOrEmpty(source[i].Module.ProjectUri)) { - if (source[i].Module != null && !list.Contains(source[i].Module.Name) && !string.IsNullOrEmpty(source[i].Module.ProjectUri)) - { - WriteLineFormat(FormatterStrings.HelpModule, source[i].Module.Name, source[i].Module.ProjectUri); - list.Add(source[i].Module.Name); - } + WriteLineFormat(FormatterStrings.HelpModule, source[i].Module.Name, source[i].Module.ProjectUri); + list.Add(source[i].Module.Name); } - WriteLine(OUTPUT_SEPARATOR_BAR); - LineBreak(); } + WriteLine(OUTPUT_SEPARATOR_BAR); + LineBreak(); + } - private void RepositoryInfo() - { - if (!Option.Output.Banner.GetValueOrDefault(BannerFormat.Default).HasFlag(BannerFormat.RepositoryInfo)) - return; + private void RepositoryInfo() + { + if (!Option.Output.Banner.GetValueOrDefault(BannerFormat.Default).HasFlag(BannerFormat.RepositoryInfo)) + return; - var repository = Option.Repository.Url; - if (string.IsNullOrEmpty(repository)) - return; + var repository = Option.Repository.Url; + if (string.IsNullOrEmpty(repository)) + return; - WriteLineFormat(FormatterStrings.Repository_Url, repository); - if (GitHelper.TryHeadBranch(out var branch)) - WriteLineFormat(FormatterStrings.Repository_Branch, branch); + WriteLineFormat(FormatterStrings.Repository_Url, repository); + if (GitHelper.TryHeadBranch(out var branch)) + WriteLineFormat(FormatterStrings.Repository_Branch, branch); - if (GitHelper.TryRevision(out var revision)) - WriteLineFormat(FormatterStrings.Repository_Revision, revision); + if (GitHelper.TryRevision(out var revision)) + WriteLineFormat(FormatterStrings.Repository_Revision, revision); - if (!string.IsNullOrEmpty(branch) || !string.IsNullOrEmpty(revision)) - LineBreak(); - } + if (!string.IsNullOrEmpty(branch) || !string.IsNullOrEmpty(revision)) + LineBreak(); + } - protected static string GetErrorMessage(RuleRecord record) - { - return string.IsNullOrEmpty(record.Ref) ? - string.Format( - Thread.CurrentThread.CurrentCulture, - FormatterStrings.Result_ErrorDetail, - record.TargetName, - record.Info.Name, - record.Error.Message - ) : - string.Format( - Thread.CurrentThread.CurrentCulture, - FormatterStrings.Result_ErrorDetailWithRef, - record.TargetName, - record.Info.Name, - record.Error.Message, - record.Ref - ); - } + protected static string GetErrorMessage(RuleRecord record) + { + return string.IsNullOrEmpty(record.Ref) ? + string.Format( + Thread.CurrentThread.CurrentCulture, + FormatterStrings.Result_ErrorDetail, + record.TargetName, + record.Info.Name, + record.Error.Message + ) : + string.Format( + Thread.CurrentThread.CurrentCulture, + FormatterStrings.Result_ErrorDetailWithRef, + record.TargetName, + record.Info.Name, + record.Error.Message, + record.Ref + ); + } - protected static string GetFailMessage(RuleRecord record) - { - return string.IsNullOrEmpty(record.Ref) ? - string.Format( - Thread.CurrentThread.CurrentCulture, - FormatterStrings.Result_FailDetail, - record.TargetName, - record.Info.Name, - record.Info.Synopsis.Text - ) : - string.Format( - Thread.CurrentThread.CurrentCulture, - FormatterStrings.Result_FailDetailWithRef, - record.TargetName, - record.Info.Name, - record.Info.Synopsis.Text, - record.Ref - ); - } + protected static string GetFailMessage(RuleRecord record) + { + return string.IsNullOrEmpty(record.Ref) ? + string.Format( + Thread.CurrentThread.CurrentCulture, + FormatterStrings.Result_FailDetail, + record.TargetName, + record.Info.Name, + record.Info.Synopsis.Text + ) : + string.Format( + Thread.CurrentThread.CurrentCulture, + FormatterStrings.Result_FailDetailWithRef, + record.TargetName, + record.Info.Name, + record.Info.Synopsis.Text, + record.Ref + ); + } - private void FooterRuleCount(int total, int fail, int error) - { - if (!Option.Output.Footer.GetValueOrDefault(FooterFormat.Default).HasFlag(FooterFormat.RuleCount)) - return; + private void FooterRuleCount(int total, int fail, int error) + { + if (!Option.Output.Footer.GetValueOrDefault(FooterFormat.Default).HasFlag(FooterFormat.RuleCount)) + return; - WriteLineFormat(FormatterStrings.FooterRuleCount, total, fail, error); - } + WriteLineFormat(FormatterStrings.FooterRuleCount, total, fail, error); + } - private void FooterRunInfo() - { - if (PipelineContext.CurrentThread == null || !Option.Output.Footer.GetValueOrDefault(FooterFormat.Default).HasFlag(FooterFormat.RunInfo)) - return; + private void FooterRunInfo() + { + if (PipelineContext.CurrentThread == null || !Option.Output.Footer.GetValueOrDefault(FooterFormat.Default).HasFlag(FooterFormat.RunInfo)) + return; - var elapsed = PipelineContext.CurrentThread.RunTime.Elapsed; - WriteLineFormat(FormatterStrings.FooterRunInfo, PipelineContext.CurrentThread.RunId, elapsed.ToString("c", Thread.CurrentThread.CurrentCulture)); - } + var elapsed = PipelineContext.CurrentThread.RunTime.Elapsed; + WriteLineFormat(FormatterStrings.FooterRunInfo, PipelineContext.CurrentThread.RunId, elapsed.ToString("c", Thread.CurrentThread.CurrentCulture)); + } - protected void WriteStatus(string status, string statusIndent, ConsoleColor? statusForeground, ConsoleColor? statusBackground, ConsoleColor? messageForeground, ConsoleColor? messageBackground, string message, string suffix = null) + protected void WriteStatus(string status, string statusIndent, ConsoleColor? statusForeground, ConsoleColor? statusBackground, ConsoleColor? messageForeground, ConsoleColor? messageBackground, string message, string suffix = null) + { + var output = message; + if (statusForeground != null || statusBackground != null) { - var output = message; - if (statusForeground != null || statusBackground != null) - { - Writer.WriteHost(new HostInformationMessage { Message = statusIndent, NoNewLine = true }); - Writer.WriteHost(new HostInformationMessage - { - Message = status, - ForegroundColor = statusForeground, - BackgroundColor = statusBackground, - NoNewLine = true - }); - Writer.WriteHost(new HostInformationMessage { Message = " ", NoNewLine = true }); - output = string.IsNullOrEmpty(suffix) ? output : string.Concat(output, " (", suffix, ")"); - } - else - { - output = string.IsNullOrEmpty(suffix) ? string.Concat(status, output) : string.Concat(status, output, " (", suffix, ")"); ; - } + Writer.WriteHost(new HostInformationMessage { Message = statusIndent, NoNewLine = true }); Writer.WriteHost(new HostInformationMessage { - Message = output, - ForegroundColor = messageForeground, - BackgroundColor = messageBackground + Message = status, + ForegroundColor = statusForeground, + BackgroundColor = statusBackground, + NoNewLine = true }); + Writer.WriteHost(new HostInformationMessage { Message = " ", NoNewLine = true }); + output = string.IsNullOrEmpty(suffix) ? output : string.Concat(output, " (", suffix, ")"); } - - protected void WriteLine(string prefix, ConsoleColor? forgroundColor, string message, params object[] args) + else { - var output = args == null || args.Length == 0 ? message : string.Format(Thread.CurrentThread.CurrentCulture, message, args); - Writer.WriteHost(new HostInformationMessage { Message = string.Concat(prefix, output), ForegroundColor = forgroundColor }); + output = string.IsNullOrEmpty(suffix) ? string.Concat(status, output) : string.Concat(status, output, " (", suffix, ")"); ; } - - protected void WriteLine(string message, string prefix = null, ConsoleColor? forgroundColor = null) + Writer.WriteHost(new HostInformationMessage { - var output = string.IsNullOrEmpty(prefix) ? message : string.Concat(prefix, message); - Writer.WriteHost(new HostInformationMessage { Message = output, ForegroundColor = forgroundColor }); - } + Message = output, + ForegroundColor = messageForeground, + BackgroundColor = messageBackground + }); + } - protected void WriteIndentedLine(string message, string indent, string prefix = null, ConsoleColor? forgroundColor = null) - { - if (string.IsNullOrEmpty(message)) - return; + protected void WriteLine(string prefix, ConsoleColor? forgroundColor, string message, params object[] args) + { + var output = args == null || args.Length == 0 ? message : string.Format(Thread.CurrentThread.CurrentCulture, message, args); + Writer.WriteHost(new HostInformationMessage { Message = string.Concat(prefix, output), ForegroundColor = forgroundColor }); + } - var output = string.Concat(indent, prefix, message); - Writer.WriteHost(new HostInformationMessage { Message = output, ForegroundColor = forgroundColor }); - } + protected void WriteLine(string message, string prefix = null, ConsoleColor? forgroundColor = null) + { + var output = string.IsNullOrEmpty(prefix) ? message : string.Concat(prefix, message); + Writer.WriteHost(new HostInformationMessage { Message = output, ForegroundColor = forgroundColor }); + } - protected void WriteIndentedLines(string message, string indent, string prefix = null, ConsoleColor? forgroundColor = null) - { - if (string.IsNullOrEmpty(message)) - return; + protected void WriteIndentedLine(string message, string indent, string prefix = null, ConsoleColor? forgroundColor = null) + { + if (string.IsNullOrEmpty(message)) + return; - var lines = message.SplitSemantic(); - for (var i = 0; i < lines.Length; i++) - WriteIndentedLine(lines[i], indent, prefix, forgroundColor); - } + var output = string.Concat(indent, prefix, message); + Writer.WriteHost(new HostInformationMessage { Message = output, ForegroundColor = forgroundColor }); + } - protected void WriteLineFormat(string message, params object[] args) - { - WriteLine(string.Format(Thread.CurrentThread.CurrentCulture, message, args)); - } + protected void WriteIndentedLines(string message, string indent, string prefix = null, ConsoleColor? forgroundColor = null) + { + if (string.IsNullOrEmpty(message)) + return; - protected void WriteLines(string message, string prefix = null, ConsoleColor? forgroundColor = null) - { - if (string.IsNullOrEmpty(message)) - return; + var lines = message.SplitSemantic(); + for (var i = 0; i < lines.Length; i++) + WriteIndentedLine(lines[i], indent, prefix, forgroundColor); + } - var lines = message.Split(new string[] { System.Environment.NewLine }, StringSplitOptions.None); - for (var i = 0; i < lines.Length; i++) - WriteLine(lines[i], prefix, forgroundColor); - } + protected void WriteLineFormat(string message, params object[] args) + { + WriteLine(string.Format(Thread.CurrentThread.CurrentCulture, message, args)); + } - protected void LineBreak() - { - Writer.WriteHost(new HostInformationMessage() { Message = string.Empty }); - _UnbrokenContent = _UnbrokenInfo = _UnbrokenObject = false; - } + protected void WriteLines(string message, string prefix = null, ConsoleColor? forgroundColor = null) + { + if (string.IsNullOrEmpty(message)) + return; - protected void BreakIfUnbrokenInfo() - { - if (!_UnbrokenInfo) - return; + var lines = message.Split(new string[] { System.Environment.NewLine }, StringSplitOptions.None); + for (var i = 0; i < lines.Length; i++) + WriteLine(lines[i], prefix, forgroundColor); + } - LineBreak(); - } + protected void LineBreak() + { + Writer.WriteHost(new HostInformationMessage() { Message = string.Empty }); + _UnbrokenContent = _UnbrokenInfo = _UnbrokenObject = false; + } - protected void BreakIfUnbrokenContent() - { - if (!_UnbrokenContent) - return; + protected void BreakIfUnbrokenInfo() + { + if (!_UnbrokenInfo) + return; - LineBreak(); - } + LineBreak(); + } - protected void BreakIfUnbrokenObject() - { - if (!_UnbrokenObject) - return; + protected void BreakIfUnbrokenContent() + { + if (!_UnbrokenContent) + return; - LineBreak(); - } + LineBreak(); + } - protected void UnbrokenInfo() - { - _UnbrokenInfo = true; - } + protected void BreakIfUnbrokenObject() + { + if (!_UnbrokenObject) + return; - protected void UnbrokenContent() - { - _UnbrokenContent = true; - } + LineBreak(); + } - protected void UnbrokenObject() - { - _UnbrokenObject = true; - } + protected void UnbrokenInfo() + { + _UnbrokenInfo = true; + } - protected void WriteSourceLocation(RuleRecord record, bool shouldBreak = true) - { - if (record.Source != null && record.Source.Length > 0) - { - if (shouldBreak) - LineBreak(); - - for (var i = 0; i < record.Source.Length; i++) - WriteIndentedLine( - message: record.Source[i].ToString(FormatterStrings.SourceAt, useRelativePath: true), - indent: GetTerminalSupport().BodyIndent, - prefix: GetTerminalSupport().SourceLocationPrefix, - forgroundColor: GetTerminalSupport().SourceLocationForegroundColor - ); - } - } + protected void UnbrokenContent() + { + _UnbrokenContent = true; + } - protected void WriteSynopsis(IDetailedRuleResultV2 record, bool shouldBreak = true) - { - if (!record.Info.Synopsis.HasValue) - return; + protected void UnbrokenObject() + { + _UnbrokenObject = true; + } + protected void WriteSourceLocation(RuleRecord record, bool shouldBreak = true) + { + if (record.Source != null && record.Source.Length > 0) + { if (shouldBreak) LineBreak(); - WriteIndentedLine( - message: record.Info.Synopsis.Text, - indent: GetTerminalSupport().BodyIndent, - prefix: GetTerminalSupport().SynopsisPrefix, - forgroundColor: GetTerminalSupport().SynopsisForegroundColor - ); + for (var i = 0; i < record.Source.Length; i++) + WriteIndentedLine( + message: record.Source[i].ToString(FormatterStrings.SourceAt, useRelativePath: true), + indent: GetTerminalSupport().BodyIndent, + prefix: GetTerminalSupport().SourceLocationPrefix, + forgroundColor: GetTerminalSupport().SourceLocationForegroundColor + ); } + } - protected void WriteRecommendation(IDetailedRuleResultV2 record) - { - if (!record.Info.Recommendation.HasValue) - return; + protected void WriteSynopsis(IDetailedRuleResultV2 record, bool shouldBreak = true) + { + if (!record.Info.Synopsis.HasValue) + return; + if (shouldBreak) LineBreak(); - WriteLine(GetTerminalSupport().RecommendationHeading, forgroundColor: GetTerminalSupport().BodyForegroundColor); - WriteIndentedLines( - message: record.Info.Recommendation.Text, - indent: GetTerminalSupport().BodyIndent, - prefix: GetTerminalSupport().RecommendationPrefix, - forgroundColor: GetTerminalSupport().BodyForegroundColor - ); - } - protected void WriteReason(RuleRecord record) - { - if (record.Reason != null && record.Reason.Length > 0) - { - LineBreak(); - WriteLine(GetTerminalSupport().ReasonHeading, forgroundColor: GetTerminalSupport().BodyForegroundColor); - for (var i = 0; i < record.Reason.Length; i++) - { - WriteIndentedLine( - message: record.Reason[i], - indent: GetTerminalSupport().BodyIndent, - prefix: GetTerminalSupport().ReasonItemPrefix, - forgroundColor: GetTerminalSupport().BodyForegroundColor - ); - } - } - } + WriteIndentedLine( + message: record.Info.Synopsis.Text, + indent: GetTerminalSupport().BodyIndent, + prefix: GetTerminalSupport().SynopsisPrefix, + forgroundColor: GetTerminalSupport().SynopsisForegroundColor + ); + } - protected void WriteHelp(IDetailedRuleResultV2 record) + protected void WriteRecommendation(IDetailedRuleResultV2 record) + { + if (!record.Info.Recommendation.HasValue) + return; + + LineBreak(); + WriteLine(GetTerminalSupport().RecommendationHeading, forgroundColor: GetTerminalSupport().BodyForegroundColor); + WriteIndentedLines( + message: record.Info.Recommendation.Text, + indent: GetTerminalSupport().BodyIndent, + prefix: GetTerminalSupport().RecommendationPrefix, + forgroundColor: GetTerminalSupport().BodyForegroundColor + ); + } + + protected void WriteReason(RuleRecord record) + { + if (record.Reason != null && record.Reason.Length > 0) { - var link = record.Info?.GetOnlineHelpUri()?.ToString(); - if (!string.IsNullOrEmpty(link)) + LineBreak(); + WriteLine(GetTerminalSupport().ReasonHeading, forgroundColor: GetTerminalSupport().BodyForegroundColor); + for (var i = 0; i < record.Reason.Length; i++) { - LineBreak(); - WriteLine(GetTerminalSupport().HelpHeading, forgroundColor: GetTerminalSupport().BodyForegroundColor); WriteIndentedLine( - message: link, + message: record.Reason[i], indent: GetTerminalSupport().BodyIndent, - prefix: GetTerminalSupport().HelpLinkPrefix, + prefix: GetTerminalSupport().ReasonItemPrefix, forgroundColor: GetTerminalSupport().BodyForegroundColor ); } } + } - protected void WriteErrorMessage(string indent, string message) - { - BreakIfUnbrokenObject(); - BreakIfUnbrokenContent(); - WriteStatus( - status: GetTerminalSupport().ErrorStatus, - statusIndent: indent, - statusForeground: GetTerminalSupport().ErrorStatusForegroundColor, - statusBackground: GetTerminalSupport().ErrorStatusBackgroundColor, - messageForeground: GetTerminalSupport().ErrorForegroundColor, - messageBackground: GetTerminalSupport().ErrorBackgroundColor, - message: message); - UnbrokenInfo(); - } - - protected void WriteWarningMessage(string indent, string message) + protected void WriteHelp(IDetailedRuleResultV2 record) + { + var link = record.Info?.GetOnlineHelpUri()?.ToString(); + if (!string.IsNullOrEmpty(link)) { - BreakIfUnbrokenObject(); - BreakIfUnbrokenContent(); - WriteStatus( - status: GetTerminalSupport().WarningStatus, - statusIndent: indent, - statusForeground: GetTerminalSupport().WarningStatusForegroundColor, - statusBackground: GetTerminalSupport().WarningStatusBackgroundColor, - messageForeground: GetTerminalSupport().WarningForegroundColor, - messageBackground: GetTerminalSupport().WarningBackgroundColor, - message: message + LineBreak(); + WriteLine(GetTerminalSupport().HelpHeading, forgroundColor: GetTerminalSupport().BodyForegroundColor); + WriteIndentedLine( + message: link, + indent: GetTerminalSupport().BodyIndent, + prefix: GetTerminalSupport().HelpLinkPrefix, + forgroundColor: GetTerminalSupport().BodyForegroundColor ); - UnbrokenInfo(); } + } - protected void WriteInformationMessage(string indent, string message) - { - BreakIfUnbrokenObject(); - BreakIfUnbrokenContent(); - WriteStatus( - status: GetTerminalSupport().InformationStatus, - statusIndent: indent, - statusForeground: GetTerminalSupport().WarningStatusForegroundColor, - statusBackground: GetTerminalSupport().WarningStatusBackgroundColor, - messageForeground: GetTerminalSupport().WarningForegroundColor, - messageBackground: GetTerminalSupport().WarningBackgroundColor, - message: message - ); - UnbrokenInfo(); - } + protected void WriteErrorMessage(string indent, string message) + { + BreakIfUnbrokenObject(); + BreakIfUnbrokenContent(); + WriteStatus( + status: GetTerminalSupport().ErrorStatus, + statusIndent: indent, + statusForeground: GetTerminalSupport().ErrorStatusForegroundColor, + statusBackground: GetTerminalSupport().ErrorStatusBackgroundColor, + messageForeground: GetTerminalSupport().ErrorForegroundColor, + messageBackground: GetTerminalSupport().ErrorBackgroundColor, + message: message); + UnbrokenInfo(); + } + + protected void WriteWarningMessage(string indent, string message) + { + BreakIfUnbrokenObject(); + BreakIfUnbrokenContent(); + WriteStatus( + status: GetTerminalSupport().WarningStatus, + statusIndent: indent, + statusForeground: GetTerminalSupport().WarningStatusForegroundColor, + statusBackground: GetTerminalSupport().WarningStatusBackgroundColor, + messageForeground: GetTerminalSupport().WarningForegroundColor, + messageBackground: GetTerminalSupport().WarningBackgroundColor, + message: message + ); + UnbrokenInfo(); + } + + protected void WriteInformationMessage(string indent, string message) + { + BreakIfUnbrokenObject(); + BreakIfUnbrokenContent(); + WriteStatus( + status: GetTerminalSupport().InformationStatus, + statusIndent: indent, + statusForeground: GetTerminalSupport().WarningStatusForegroundColor, + statusBackground: GetTerminalSupport().WarningStatusBackgroundColor, + messageForeground: GetTerminalSupport().WarningForegroundColor, + messageBackground: GetTerminalSupport().WarningBackgroundColor, + message: message + ); + UnbrokenInfo(); } } diff --git a/src/PSRule/Pipeline/Formatters/AzurePipelinesFormatter.cs b/src/PSRule/Pipeline/Formatters/AzurePipelinesFormatter.cs index 39e480dd38..c6b1a17379 100644 --- a/src/PSRule/Pipeline/Formatters/AzurePipelinesFormatter.cs +++ b/src/PSRule/Pipeline/Formatters/AzurePipelinesFormatter.cs @@ -4,60 +4,59 @@ using PSRule.Configuration; using PSRule.Rules; -namespace PSRule.Pipeline.Formatters +namespace PSRule.Pipeline.Formatters; + +/// +/// Formatter for Azure Pipelines. +/// +internal sealed class AzurePipelinesFormatter : AssertFormatterBase, IAssertFormatter { - /// - /// Formatter for Azure Pipelines. - /// - internal sealed class AzurePipelinesFormatter : AssertFormatterBase, IAssertFormatter - { - // Available commands are defined here: https://docs.microsoft.com/en-us/azure/devops/pipelines/scripts/logging-commands - private const string MESSAGE_PREFIX_ERROR = "##vso[task.logissue type=error]"; - private const string MESSAGE_PREFIX_WARNING = "##vso[task.logissue type=warning]"; + // Available commands are defined here: https://docs.microsoft.com/en-us/azure/devops/pipelines/scripts/logging-commands + private const string MESSAGE_PREFIX_ERROR = "##vso[task.logissue type=error]"; + private const string MESSAGE_PREFIX_WARNING = "##vso[task.logissue type=warning]"; - private readonly TerminalSupport _TerminalSupport; + private readonly TerminalSupport _TerminalSupport; - internal AzurePipelinesFormatter(Source[] source, IPipelineWriter logger, PSRuleOption option) - : base(source, logger, option) - { - _TerminalSupport = new TerminalSupport(4) - { - MessageIdent = string.Empty, - ErrorStatus = MESSAGE_PREFIX_ERROR, - WarningStatus = MESSAGE_PREFIX_WARNING, - }; - } - - protected override TerminalSupport GetTerminalSupport() + internal AzurePipelinesFormatter(Source[] source, IPipelineWriter logger, PSRuleOption option) + : base(source, logger, option) + { + _TerminalSupport = new TerminalSupport(4) { - return _TerminalSupport; - } + MessageIdent = string.Empty, + ErrorStatus = MESSAGE_PREFIX_ERROR, + WarningStatus = MESSAGE_PREFIX_WARNING, + }; + } - protected override void ErrorDetail(RuleRecord record) - { - if (record.Error == null) - return; + protected override TerminalSupport GetTerminalSupport() + { + return _TerminalSupport; + } - LineBreak(); - Error(GetErrorMessage(record)); - LineBreak(); - WriteLine(record.Error.PositionMessage); - LineBreak(); - WriteLine(record.Error.ScriptStackTrace); - } + protected override void ErrorDetail(RuleRecord record) + { + if (record.Error == null) + return; + + LineBreak(); + Error(GetErrorMessage(record)); + LineBreak(); + WriteLine(record.Error.PositionMessage); + LineBreak(); + WriteLine(record.Error.ScriptStackTrace); + } - protected override void FailDetail(RuleRecord record) - { - base.FailDetail(record); - var message = GetFailMessage(record); - if (record.Level == Definitions.Rules.SeverityLevel.Error) - Error(message); + protected override void FailDetail(RuleRecord record) + { + base.FailDetail(record); + var message = GetFailMessage(record); + if (record.Level == Definitions.Rules.SeverityLevel.Error) + Error(message); - if (record.Level == Definitions.Rules.SeverityLevel.Warning) - Warning(message); + if (record.Level == Definitions.Rules.SeverityLevel.Warning) + Warning(message); - if (record.Level != Definitions.Rules.SeverityLevel.Information) - LineBreak(); - } + if (record.Level != Definitions.Rules.SeverityLevel.Information) + LineBreak(); } } diff --git a/src/PSRule/Pipeline/Formatters/ClientFormatter.cs b/src/PSRule/Pipeline/Formatters/ClientFormatter.cs index d9e35bc99f..ce1a477fd9 100644 --- a/src/PSRule/Pipeline/Formatters/ClientFormatter.cs +++ b/src/PSRule/Pipeline/Formatters/ClientFormatter.cs @@ -5,50 +5,49 @@ using PSRule.Resources; using PSRule.Rules; -namespace PSRule.Pipeline.Formatters +namespace PSRule.Pipeline.Formatters; + +/// +/// Client assert formatter. +/// +internal sealed class ClientFormatter : AssertFormatterBase, IAssertFormatter { - /// - /// Client assert formatter. - /// - internal sealed class ClientFormatter : AssertFormatterBase, IAssertFormatter - { - private readonly TerminalSupport _TerminalSupport; + private readonly TerminalSupport _TerminalSupport; - internal ClientFormatter(Source[] source, IPipelineWriter logger, PSRuleOption option) - : base(source, logger, option) + internal ClientFormatter(Source[] source, IPipelineWriter logger, PSRuleOption option) + : base(source, logger, option) + { + _TerminalSupport = new TerminalSupport(4) { - _TerminalSupport = new TerminalSupport(4) - { - StartResultForegroundColor = ConsoleColor.Green, - SourceLocationForegroundColor = ConsoleColor.Red, - SynopsisForegroundColor = ConsoleColor.Red, - ErrorForegroundColor = ConsoleColor.Red, - FailForegroundColor = ConsoleColor.Red, - PassForegroundColor = ConsoleColor.Green, - WarningForegroundColor = ConsoleColor.Yellow, - BodyForegroundColor = ConsoleColor.Cyan, - }; - } + StartResultForegroundColor = ConsoleColor.Green, + SourceLocationForegroundColor = ConsoleColor.Red, + SynopsisForegroundColor = ConsoleColor.Red, + ErrorForegroundColor = ConsoleColor.Red, + FailForegroundColor = ConsoleColor.Red, + PassForegroundColor = ConsoleColor.Green, + WarningForegroundColor = ConsoleColor.Yellow, + BodyForegroundColor = ConsoleColor.Cyan, + }; + } - protected override TerminalSupport GetTerminalSupport() - { - return _TerminalSupport; - } + protected override TerminalSupport GetTerminalSupport() + { + return _TerminalSupport; + } - protected override void ErrorDetail(RuleRecord record) - { - if (record.Error == null) - return; + protected override void ErrorDetail(RuleRecord record) + { + if (record.Error == null) + return; - LineBreak(); - WriteLine(FormatterStrings.Message, forgroundColor: GetTerminalSupport().ErrorForegroundColor); - WriteIndentedLine(record.Error.Message, GetTerminalSupport().BodyIndent, forgroundColor: GetTerminalSupport().ErrorForegroundColor); - LineBreak(); - WriteLine(FormatterStrings.Position, forgroundColor: GetTerminalSupport().BodyForegroundColor); - WriteIndentedLine(record.Error.PositionMessage, GetTerminalSupport().BodyIndent, forgroundColor: GetTerminalSupport().BodyForegroundColor); - LineBreak(); - WriteLine(FormatterStrings.StackTrace, forgroundColor: GetTerminalSupport().BodyForegroundColor); - WriteIndentedLine(record.Error.ScriptStackTrace, GetTerminalSupport().BodyIndent, forgroundColor: GetTerminalSupport().BodyForegroundColor); - } + LineBreak(); + WriteLine(FormatterStrings.Message, forgroundColor: GetTerminalSupport().ErrorForegroundColor); + WriteIndentedLine(record.Error.Message, GetTerminalSupport().BodyIndent, forgroundColor: GetTerminalSupport().ErrorForegroundColor); + LineBreak(); + WriteLine(FormatterStrings.Position, forgroundColor: GetTerminalSupport().BodyForegroundColor); + WriteIndentedLine(record.Error.PositionMessage, GetTerminalSupport().BodyIndent, forgroundColor: GetTerminalSupport().BodyForegroundColor); + LineBreak(); + WriteLine(FormatterStrings.StackTrace, forgroundColor: GetTerminalSupport().BodyForegroundColor); + WriteIndentedLine(record.Error.ScriptStackTrace, GetTerminalSupport().BodyIndent, forgroundColor: GetTerminalSupport().BodyForegroundColor); } } diff --git a/src/PSRule/Pipeline/Formatters/GitHubActionsFormatter.cs b/src/PSRule/Pipeline/Formatters/GitHubActionsFormatter.cs index 3c2bd3bdb0..e1baf01fa9 100644 --- a/src/PSRule/Pipeline/Formatters/GitHubActionsFormatter.cs +++ b/src/PSRule/Pipeline/Formatters/GitHubActionsFormatter.cs @@ -4,64 +4,63 @@ using PSRule.Configuration; using PSRule.Rules; -namespace PSRule.Pipeline.Formatters +namespace PSRule.Pipeline.Formatters; + +/// +/// Formatter for GitHub Actions. +/// +internal sealed class GitHubActionsFormatter : AssertFormatterBase, IAssertFormatter { - /// - /// Formatter for GitHub Actions. - /// - internal sealed class GitHubActionsFormatter : AssertFormatterBase, IAssertFormatter - { - // Available commands are defined here: https://docs.github.com/en/actions/learn-github-actions/workflow-commands-for-github-actions - private const string ERROR_COMMAND_NO_SOURCE = "::error::"; - private const string WARNING_COMMAND_NO_SOURCE = "::warning::"; - private const string NOTICE_COMMAND_NO_SOURCE = "::notice::"; + // Available commands are defined here: https://docs.github.com/en/actions/learn-github-actions/workflow-commands-for-github-actions + private const string ERROR_COMMAND_NO_SOURCE = "::error::"; + private const string WARNING_COMMAND_NO_SOURCE = "::warning::"; + private const string NOTICE_COMMAND_NO_SOURCE = "::notice::"; - private readonly TerminalSupport _TerminalSupport; + private readonly TerminalSupport _TerminalSupport; - internal GitHubActionsFormatter(Source[] source, IPipelineWriter logger, PSRuleOption option) - : base(source, logger, option) + internal GitHubActionsFormatter(Source[] source, IPipelineWriter logger, PSRuleOption option) + : base(source, logger, option) + { + _TerminalSupport = new TerminalSupport(4) { - _TerminalSupport = new TerminalSupport(4) - { - MessageIdent = string.Empty, - ErrorStatus = ERROR_COMMAND_NO_SOURCE, - WarningStatus = WARNING_COMMAND_NO_SOURCE, - InformationStatus = NOTICE_COMMAND_NO_SOURCE, - }; - } + MessageIdent = string.Empty, + ErrorStatus = ERROR_COMMAND_NO_SOURCE, + WarningStatus = WARNING_COMMAND_NO_SOURCE, + InformationStatus = NOTICE_COMMAND_NO_SOURCE, + }; + } - protected override TerminalSupport GetTerminalSupport() - { - return _TerminalSupport; - } + protected override TerminalSupport GetTerminalSupport() + { + return _TerminalSupport; + } - protected override void ErrorDetail(RuleRecord record) - { - if (record.Error == null) - return; + protected override void ErrorDetail(RuleRecord record) + { + if (record.Error == null) + return; - LineBreak(); - Error(GetErrorMessage(record)); - LineBreak(); - WriteLine(record.Error.PositionMessage); - LineBreak(); - WriteLine(record.Error.ScriptStackTrace); - } + LineBreak(); + Error(GetErrorMessage(record)); + LineBreak(); + WriteLine(record.Error.PositionMessage); + LineBreak(); + WriteLine(record.Error.ScriptStackTrace); + } - protected override void FailDetail(RuleRecord record) - { - base.FailDetail(record); - var message = GetFailMessage(record); - if (record.Level == Definitions.Rules.SeverityLevel.Error) - Error(message); + protected override void FailDetail(RuleRecord record) + { + base.FailDetail(record); + var message = GetFailMessage(record); + if (record.Level == Definitions.Rules.SeverityLevel.Error) + Error(message); - if (record.Level == Definitions.Rules.SeverityLevel.Warning) - Warning(message); + if (record.Level == Definitions.Rules.SeverityLevel.Warning) + Warning(message); - if (record.Level == Definitions.Rules.SeverityLevel.Information) - Information(message); + if (record.Level == Definitions.Rules.SeverityLevel.Information) + Information(message); - LineBreak(); - } + LineBreak(); } } diff --git a/src/PSRule/Pipeline/Formatters/PlainFormatter.cs b/src/PSRule/Pipeline/Formatters/PlainFormatter.cs index a0b2a62ba7..30d4efab6d 100644 --- a/src/PSRule/Pipeline/Formatters/PlainFormatter.cs +++ b/src/PSRule/Pipeline/Formatters/PlainFormatter.cs @@ -5,30 +5,29 @@ using PSRule.Resources; using PSRule.Rules; -namespace PSRule.Pipeline.Formatters +namespace PSRule.Pipeline.Formatters; + +/// +/// Plain text assert formatter. +/// +internal sealed class PlainFormatter : AssertFormatterBase, IAssertFormatter { - /// - /// Plain text assert formatter. - /// - internal sealed class PlainFormatter : AssertFormatterBase, IAssertFormatter - { - internal PlainFormatter(Source[] source, IPipelineWriter logger, PSRuleOption option) - : base(source, logger, option) { } + internal PlainFormatter(Source[] source, IPipelineWriter logger, PSRuleOption option) + : base(source, logger, option) { } - protected override void ErrorDetail(RuleRecord record) - { - if (record.Error == null) - return; + protected override void ErrorDetail(RuleRecord record) + { + if (record.Error == null) + return; - LineBreak(); - WriteLine(FormatterStrings.Message, forgroundColor: GetTerminalSupport().ErrorForegroundColor); - WriteIndentedLine(record.Error.Message, GetTerminalSupport().BodyIndent, forgroundColor: GetTerminalSupport().ErrorForegroundColor); - LineBreak(); - WriteLine(FormatterStrings.Position, forgroundColor: GetTerminalSupport().BodyForegroundColor); - WriteIndentedLine(record.Error.PositionMessage, GetTerminalSupport().BodyIndent, forgroundColor: GetTerminalSupport().BodyForegroundColor); - LineBreak(); - WriteLine(FormatterStrings.StackTrace, forgroundColor: GetTerminalSupport().BodyForegroundColor); - WriteIndentedLine(record.Error.ScriptStackTrace, GetTerminalSupport().BodyIndent, forgroundColor: GetTerminalSupport().BodyForegroundColor); - } + LineBreak(); + WriteLine(FormatterStrings.Message, forgroundColor: GetTerminalSupport().ErrorForegroundColor); + WriteIndentedLine(record.Error.Message, GetTerminalSupport().BodyIndent, forgroundColor: GetTerminalSupport().ErrorForegroundColor); + LineBreak(); + WriteLine(FormatterStrings.Position, forgroundColor: GetTerminalSupport().BodyForegroundColor); + WriteIndentedLine(record.Error.PositionMessage, GetTerminalSupport().BodyIndent, forgroundColor: GetTerminalSupport().BodyForegroundColor); + LineBreak(); + WriteLine(FormatterStrings.StackTrace, forgroundColor: GetTerminalSupport().BodyForegroundColor); + WriteIndentedLine(record.Error.ScriptStackTrace, GetTerminalSupport().BodyIndent, forgroundColor: GetTerminalSupport().BodyForegroundColor); } } diff --git a/src/PSRule/Pipeline/Formatters/VisualStudioCodeFormatter.cs b/src/PSRule/Pipeline/Formatters/VisualStudioCodeFormatter.cs index 96ec9ad517..7f6a0c7cfa 100644 --- a/src/PSRule/Pipeline/Formatters/VisualStudioCodeFormatter.cs +++ b/src/PSRule/Pipeline/Formatters/VisualStudioCodeFormatter.cs @@ -5,65 +5,64 @@ using PSRule.Resources; using PSRule.Rules; -namespace PSRule.Pipeline.Formatters +namespace PSRule.Pipeline.Formatters; + +/// +/// Visual Studio Code assert formatter. +/// +internal sealed class VisualStudioCodeFormatter : AssertFormatterBase, IAssertFormatter { - /// - /// Visual Studio Code assert formatter. - /// - internal sealed class VisualStudioCodeFormatter : AssertFormatterBase, IAssertFormatter - { - private readonly TerminalSupport _TerminalSupport; + private readonly TerminalSupport _TerminalSupport; - internal VisualStudioCodeFormatter(Source[] source, IPipelineWriter logger, PSRuleOption option) - : base(source, logger, option) + internal VisualStudioCodeFormatter(Source[] source, IPipelineWriter logger, PSRuleOption option) + : base(source, logger, option) + { + _TerminalSupport = new TerminalSupport(2) { - _TerminalSupport = new TerminalSupport(2) - { - StartResultIndent = FormatterStrings.VSCode_StartObjectPrefix, - StartResultForegroundColor = ConsoleColor.Green, - SourceLocationForegroundColor = ConsoleColor.Cyan, - SourceLocationPrefix = null, - SynopsisPrefix = null, - SynopsisForegroundColor = ConsoleColor.Cyan, - ErrorStatus = FormatterStrings.VSCode_Error, - ErrorForegroundColor = ConsoleColor.Red, - ErrorStatusForegroundColor = ConsoleColor.Black, - ErrorStatusBackgroundColor = ConsoleColor.Red, - FailStatus = FormatterStrings.VSCode_Fail, - FailForegroundColor = ConsoleColor.Red, - FailStatusForegroundColor = ConsoleColor.Black, - FailStatusBackgroundColor = ConsoleColor.Red, - PassStatus = FormatterStrings.VSCode_Pass, - PassForegroundColor = ConsoleColor.Green, - PassStatusForegroundColor = ConsoleColor.Black, - PassStatusBackgroundColor = ConsoleColor.Green, - WarningStatus = FormatterStrings.VSCode_Warning, - WarningForegroundColor = ConsoleColor.Yellow, - WarningStatusForegroundColor = ConsoleColor.Black, - WarningStatusBackgroundColor = ConsoleColor.Yellow, - BodyForegroundColor = ConsoleColor.White, - RecommendationHeading = FormatterStrings.VSCode_Recommend, - RecommendationPrefix = null, - ReasonHeading = FormatterStrings.VSCode_Reason, - ReasonItemPrefix = "- ", - HelpHeading = FormatterStrings.VSCode_Help, - HelpLinkPrefix = "- ", - }; - } + StartResultIndent = FormatterStrings.VSCode_StartObjectPrefix, + StartResultForegroundColor = ConsoleColor.Green, + SourceLocationForegroundColor = ConsoleColor.Cyan, + SourceLocationPrefix = null, + SynopsisPrefix = null, + SynopsisForegroundColor = ConsoleColor.Cyan, + ErrorStatus = FormatterStrings.VSCode_Error, + ErrorForegroundColor = ConsoleColor.Red, + ErrorStatusForegroundColor = ConsoleColor.Black, + ErrorStatusBackgroundColor = ConsoleColor.Red, + FailStatus = FormatterStrings.VSCode_Fail, + FailForegroundColor = ConsoleColor.Red, + FailStatusForegroundColor = ConsoleColor.Black, + FailStatusBackgroundColor = ConsoleColor.Red, + PassStatus = FormatterStrings.VSCode_Pass, + PassForegroundColor = ConsoleColor.Green, + PassStatusForegroundColor = ConsoleColor.Black, + PassStatusBackgroundColor = ConsoleColor.Green, + WarningStatus = FormatterStrings.VSCode_Warning, + WarningForegroundColor = ConsoleColor.Yellow, + WarningStatusForegroundColor = ConsoleColor.Black, + WarningStatusBackgroundColor = ConsoleColor.Yellow, + BodyForegroundColor = ConsoleColor.White, + RecommendationHeading = FormatterStrings.VSCode_Recommend, + RecommendationPrefix = null, + ReasonHeading = FormatterStrings.VSCode_Reason, + ReasonItemPrefix = "- ", + HelpHeading = FormatterStrings.VSCode_Help, + HelpLinkPrefix = "- ", + }; + } - protected override TerminalSupport GetTerminalSupport() - { - return _TerminalSupport; - } + protected override TerminalSupport GetTerminalSupport() + { + return _TerminalSupport; + } - protected override void FailDetail(RuleRecord record) - { - WriteSynopsis(record, shouldBreak: true); - WriteSourceLocation(record, shouldBreak: true); - WriteRecommendation(record); - WriteReason(record); - WriteHelp(record); - LineBreak(); - } + protected override void FailDetail(RuleRecord record) + { + WriteSynopsis(record, shouldBreak: true); + WriteSourceLocation(record, shouldBreak: true); + WriteRecommendation(record); + WriteReason(record); + WriteHelp(record); + LineBreak(); } } diff --git a/src/PSRule/Pipeline/GetBaselinePipeline.cs b/src/PSRule/Pipeline/GetBaselinePipeline.cs index 5b4c871635..1e7a664c86 100644 --- a/src/PSRule/Pipeline/GetBaselinePipeline.cs +++ b/src/PSRule/Pipeline/GetBaselinePipeline.cs @@ -5,33 +5,32 @@ using PSRule.Definitions.Baselines; using PSRule.Host; -namespace PSRule.Pipeline +namespace PSRule.Pipeline; + +internal sealed class GetBaselinePipeline : RulePipeline { - internal sealed class GetBaselinePipeline : RulePipeline - { - private readonly IResourceFilter _Filter; + private readonly IResourceFilter _Filter; - internal GetBaselinePipeline( - PipelineContext pipeline, - Source[] source, - PipelineReader reader, - IPipelineWriter writer, - IResourceFilter filter - ) - : base(pipeline, source, reader, writer) - { - _Filter = filter; - } + internal GetBaselinePipeline( + PipelineContext pipeline, + Source[] source, + PipelineReader reader, + IPipelineWriter writer, + IResourceFilter filter + ) + : base(pipeline, source, reader, writer) + { + _Filter = filter; + } - public override void End() - { - Writer.WriteObject(HostHelper.GetBaseline(Source, Context).Where(Match), true); - Writer.End(); - } + public override void End() + { + Writer.WriteObject(HostHelper.GetBaseline(Source, Context).Where(Match), true); + Writer.End(); + } - private bool Match(Baseline baseline) - { - return _Filter == null || _Filter.Match(baseline); - } + private bool Match(Baseline baseline) + { + return _Filter == null || _Filter.Match(baseline); } } diff --git a/src/PSRule/Pipeline/GetBaselinePipelineBuilder.cs b/src/PSRule/Pipeline/GetBaselinePipelineBuilder.cs index 765b5e6fc7..a8636ced86 100644 --- a/src/PSRule/Pipeline/GetBaselinePipelineBuilder.cs +++ b/src/PSRule/Pipeline/GetBaselinePipelineBuilder.cs @@ -4,60 +4,59 @@ using PSRule.Configuration; using PSRule.Definitions.Baselines; -namespace PSRule.Pipeline +namespace PSRule.Pipeline; + +internal sealed class GetBaselinePipelineBuilder : PipelineBuilderBase { - internal sealed class GetBaselinePipelineBuilder : PipelineBuilderBase + private string[] _Name; + + internal GetBaselinePipelineBuilder(Source[] source, IHostContext hostContext) + : base(source, hostContext) { } + + /// + /// Filter returned baselines by name. + /// + public new void Name(string[] name) + { + if (name == null || name.Length == 0) + return; + + _Name = name; + } + + public override IPipelineBuilder Configure(PSRuleOption option) { - private string[] _Name; - - internal GetBaselinePipelineBuilder(Source[] source, IHostContext hostContext) - : base(source, hostContext) { } - - /// - /// Filter returned baselines by name. - /// - public new void Name(string[] name) - { - if (name == null || name.Length == 0) - return; - - _Name = name; - } - - public override IPipelineBuilder Configure(PSRuleOption option) - { - if (option == null) - return this; - - Option.Baseline = new Options.BaselineOption(option.Baseline); - Option.Output.As = ResultFormat.Detail; - Option.Output.Culture = GetCulture(option.Output.Culture); - Option.Output.Format = SuppressFormat(option.Output.Format); - Option.Output.JsonIndent = NormalizeJsonIndentRange(option.Output.JsonIndent); + if (option == null) return this; - } - - public override IPipeline Build(IPipelineWriter writer = null) - { - var filter = new BaselineFilter(ResolveBaselineGroup(_Name)); - return new GetBaselinePipeline( - pipeline: PrepareContext( - bindTargetName: null, - bindTargetType: null, - bindField: null - ), - source: Source, - reader: PrepareReader(), - writer: writer ?? PrepareWriter(), - filter: filter - ); - } - - private static OutputFormat SuppressFormat(OutputFormat? format) - { - return !format.HasValue || - !(format == OutputFormat.Yaml || - format == OutputFormat.Json) ? OutputFormat.None : format.Value; - } + + Option.Baseline = new Options.BaselineOption(option.Baseline); + Option.Output.As = ResultFormat.Detail; + Option.Output.Culture = GetCulture(option.Output.Culture); + Option.Output.Format = SuppressFormat(option.Output.Format); + Option.Output.JsonIndent = NormalizeJsonIndentRange(option.Output.JsonIndent); + return this; + } + + public override IPipeline Build(IPipelineWriter writer = null) + { + var filter = new BaselineFilter(ResolveBaselineGroup(_Name)); + return new GetBaselinePipeline( + pipeline: PrepareContext( + bindTargetName: null, + bindTargetType: null, + bindField: null + ), + source: Source, + reader: PrepareReader(), + writer: writer ?? PrepareWriter(), + filter: filter + ); + } + + private static OutputFormat SuppressFormat(OutputFormat? format) + { + return !format.HasValue || + !(format == OutputFormat.Yaml || + format == OutputFormat.Json) ? OutputFormat.None : format.Value; } } diff --git a/src/PSRule/Pipeline/GetRuleHelpPipeline.cs b/src/PSRule/Pipeline/GetRuleHelpPipeline.cs index 15f555a6e7..4d5bc3f268 100644 --- a/src/PSRule/Pipeline/GetRuleHelpPipeline.cs +++ b/src/PSRule/Pipeline/GetRuleHelpPipeline.cs @@ -9,182 +9,181 @@ using PSRule.Resources; using PSRule.Rules; -namespace PSRule.Pipeline +namespace PSRule.Pipeline; + +/// +/// A helper to build a pipeline for getting help from rules. +/// +public interface IHelpPipelineBuilder : IPipelineBuilder { /// - /// A helper to build a pipeline for getting help from rules. + /// Get the full help output for a rule. + /// + void Full(); + + /// + /// Open or show online help for a rule if it exists. + /// + void Online(); + + /// + /// Filter by name. /// - public interface IHelpPipelineBuilder : IPipelineBuilder + void Name(string[] name); +} + +internal sealed class GetRuleHelpPipelineBuilder : PipelineBuilderBase, IHelpPipelineBuilder +{ + private bool _Full; + private bool _Online; + + internal GetRuleHelpPipelineBuilder(Source[] source, IHostContext hostContext) + : base(source, hostContext) { } + + /// + public override IPipelineBuilder Configure(PSRuleOption option) { - /// - /// Get the full help output for a rule. - /// - void Full(); - - /// - /// Open or show online help for a rule if it exists. - /// - void Online(); - - /// - /// Filter by name. - /// - void Name(string[] name); + if (option == null) + return this; + + Option.Execution = GetExecutionOption(option.Execution); + Option.Output.Culture = GetCulture(option.Output.Culture); + + if (option.Rule != null) + Option.Rule = new RuleOption(option.Rule); + + return this; } - internal sealed class GetRuleHelpPipelineBuilder : PipelineBuilderBase, IHelpPipelineBuilder + /// + public void Full() { - private bool _Full; - private bool _Online; + _Full = true; + } - internal GetRuleHelpPipelineBuilder(Source[] source, IHostContext hostContext) - : base(source, hostContext) { } + /// + public void Online() + { + _Online = true; + } - /// - public override IPipelineBuilder Configure(PSRuleOption option) - { - if (option == null) - return this; + /// + public override IPipeline Build(IPipelineWriter writer = null) + { + return new GetRuleHelpPipeline( + pipeline: PrepareContext( + bindTargetName: null, + bindTargetType: null, + bindField: null), + source: Source, + reader: PrepareReader(), + writer: writer ?? PrepareWriter()); + } - Option.Execution = GetExecutionOption(option.Execution); - Option.Output.Culture = GetCulture(option.Output.Culture); + private sealed class HelpWriter : PipelineWriter + { + private const string OUTPUT_TYPENAME_FULL = "PSRule.Rules.RuleHelpInfo+Full"; + private const string OUTPUT_TYPENAME_COLLECTION = "PSRule.Rules.RuleHelpInfo+Collection"; - if (option.Rule != null) - Option.Rule = new RuleOption(option.Rule); + private readonly LanguageMode _LanguageMode; + private readonly bool _InSession; + private readonly bool _ShouldOutput; + private readonly string _TypeName; - return this; + internal HelpWriter(PipelineWriter inner, PSRuleOption option, ShouldProcess shouldProcess, LanguageMode languageMode, bool inSession, bool online, bool full) + : base(inner, option, shouldProcess) + { + _LanguageMode = languageMode; + _InSession = inSession; + _ShouldOutput = !online; + _TypeName = full ? OUTPUT_TYPENAME_FULL : null; } - /// - public void Full() + public override void WriteObject(object sendToPipeline, bool enumerateCollection) { - _Full = true; + if (sendToPipeline is not RuleHelpInfo[] result) + { + base.WriteObject(sendToPipeline, enumerateCollection); + return; + } + if (result.Length == 1) + { + if (_ShouldOutput || !TryLaunchBrowser(result[0].GetOnlineHelpUri())) + WriteHelpInfo(result[0], _TypeName); + + return; + } + + for (var i = 0; i < result.Length; i++) + WriteHelpInfo(result[i], OUTPUT_TYPENAME_COLLECTION); } - /// - public void Online() + private bool TryLaunchBrowser(Uri uri) { - _Online = true; + return uri == null || TryProcess(uri.OriginalString) || TryConstrained(uri.OriginalString); } - /// - public override IPipeline Build(IPipelineWriter writer = null) + private bool TryConstrained(string uri) { - return new GetRuleHelpPipeline( - pipeline: PrepareContext( - bindTargetName: null, - bindTargetType: null, - bindField: null), - source: Source, - reader: PrepareReader(), - writer: writer ?? PrepareWriter()); + base.WriteObject(string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.LaunchBrowser, uri), false); + return true; } - private sealed class HelpWriter : PipelineWriter + private bool TryProcess(string uri) { - private const string OUTPUT_TYPENAME_FULL = "PSRule.Rules.RuleHelpInfo+Full"; - private const string OUTPUT_TYPENAME_COLLECTION = "PSRule.Rules.RuleHelpInfo+Collection"; - - private readonly LanguageMode _LanguageMode; - private readonly bool _InSession; - private readonly bool _ShouldOutput; - private readonly string _TypeName; - - internal HelpWriter(PipelineWriter inner, PSRuleOption option, ShouldProcess shouldProcess, LanguageMode languageMode, bool inSession, bool online, bool full) - : base(inner, option, shouldProcess) - { - _LanguageMode = languageMode; - _InSession = inSession; - _ShouldOutput = !online; - _TypeName = full ? OUTPUT_TYPENAME_FULL : null; - } + if (_LanguageMode == LanguageMode.ConstrainedLanguage || _InSession) + return false; - public override void WriteObject(object sendToPipeline, bool enumerateCollection) + var browser = new Process(); + try { - if (sendToPipeline is not RuleHelpInfo[] result) - { - base.WriteObject(sendToPipeline, enumerateCollection); - return; - } - if (result.Length == 1) - { - if (_ShouldOutput || !TryLaunchBrowser(result[0].GetOnlineHelpUri())) - WriteHelpInfo(result[0], _TypeName); - - return; - } - - for (var i = 0; i < result.Length; i++) - WriteHelpInfo(result[i], OUTPUT_TYPENAME_COLLECTION); + browser.StartInfo.FileName = uri; + browser.StartInfo.UseShellExecute = true; + return browser.Start(); } - - private bool TryLaunchBrowser(Uri uri) - { - return uri == null || TryProcess(uri.OriginalString) || TryConstrained(uri.OriginalString); - } - - private bool TryConstrained(string uri) - { - base.WriteObject(string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.LaunchBrowser, uri), false); - return true; - } - - private bool TryProcess(string uri) + finally { - if (_LanguageMode == LanguageMode.ConstrainedLanguage || _InSession) - return false; - - var browser = new Process(); - try - { - browser.StartInfo.FileName = uri; - browser.StartInfo.UseShellExecute = true; - return browser.Start(); - } - finally - { - browser.Dispose(); - } + browser.Dispose(); } + } - private void WriteHelpInfo(object o, string typeName) + private void WriteHelpInfo(object o, string typeName) + { + if (typeName == null) { - if (typeName == null) - { - base.WriteObject(o, false); - return; - } - var pso = PSObject.AsPSObject(o); - pso.TypeNames.Insert(0, typeName); - base.WriteObject(pso, false); + base.WriteObject(o, false); + return; } + var pso = PSObject.AsPSObject(o); + pso.TypeNames.Insert(0, typeName); + base.WriteObject(pso, false); } + } - protected override PipelineWriter PrepareWriter() - { - return new HelpWriter( - inner: GetOutput(), - option: Option, - shouldProcess: ShouldProcess, - languageMode: Option.Execution.LanguageMode.GetValueOrDefault(ExecutionOption.Default.LanguageMode.Value), - inSession: InSession, - online: _Online, - full: _Full - ); - } + protected override PipelineWriter PrepareWriter() + { + return new HelpWriter( + inner: GetOutput(), + option: Option, + shouldProcess: ShouldProcess, + languageMode: Option.Execution.LanguageMode.GetValueOrDefault(ExecutionOption.Default.LanguageMode.Value), + inSession: InSession, + online: _Online, + full: _Full + ); } +} - internal sealed class GetRuleHelpPipeline : RulePipeline, IPipeline +internal sealed class GetRuleHelpPipeline : RulePipeline, IPipeline +{ + internal GetRuleHelpPipeline(PipelineContext pipeline, Source[] source, PipelineReader reader, IPipelineWriter writer) + : base(pipeline, source, reader, writer) { - internal GetRuleHelpPipeline(PipelineContext pipeline, Source[] source, PipelineReader reader, IPipelineWriter writer) - : base(pipeline, source, reader, writer) - { - // Do nothing - } + // Do nothing + } - public override void End() - { - Writer.WriteObject(HostHelper.GetRuleHelp(Source, Context), true); - } + public override void End() + { + Writer.WriteObject(HostHelper.GetRuleHelp(Source, Context), true); } } diff --git a/src/PSRule/Pipeline/GetRulePipeline.cs b/src/PSRule/Pipeline/GetRulePipeline.cs index 7dcbfc4be1..b045c2622a 100644 --- a/src/PSRule/Pipeline/GetRulePipeline.cs +++ b/src/PSRule/Pipeline/GetRulePipeline.cs @@ -3,28 +3,27 @@ using PSRule.Host; -namespace PSRule.Pipeline +namespace PSRule.Pipeline; + +internal sealed class GetRulePipeline : RulePipeline, IPipeline { - internal sealed class GetRulePipeline : RulePipeline, IPipeline - { - private readonly bool _IncludeDependencies; + private readonly bool _IncludeDependencies; - internal GetRulePipeline( - PipelineContext pipeline, - Source[] source, - PipelineReader reader, - IPipelineWriter writer, - bool includeDependencies - ) - : base(pipeline, source, reader, writer) - { - _IncludeDependencies = includeDependencies; - } + internal GetRulePipeline( + PipelineContext pipeline, + Source[] source, + PipelineReader reader, + IPipelineWriter writer, + bool includeDependencies + ) + : base(pipeline, source, reader, writer) + { + _IncludeDependencies = includeDependencies; + } - public override void End() - { - Writer.WriteObject(HostHelper.GetRule(Source, Context, _IncludeDependencies), true); - Writer.End(); - } + public override void End() + { + Writer.WriteObject(HostHelper.GetRule(Source, Context, _IncludeDependencies), true); + Writer.End(); } } diff --git a/src/PSRule/Pipeline/GetRulePipelineBuilder.cs b/src/PSRule/Pipeline/GetRulePipelineBuilder.cs index 5ac72e6430..13228b6cb4 100644 --- a/src/PSRule/Pipeline/GetRulePipelineBuilder.cs +++ b/src/PSRule/Pipeline/GetRulePipelineBuilder.cs @@ -3,77 +3,76 @@ using PSRule.Configuration; -namespace PSRule.Pipeline +namespace PSRule.Pipeline; + +/// +/// A helper to build a get pipeline. +/// +public interface IGetPipelineBuilder : IPipelineBuilder { /// - /// A helper to build a get pipeline. + /// Determines if the returned rules also include rule dependencies. /// - public interface IGetPipelineBuilder : IPipelineBuilder - { - /// - /// Determines if the returned rules also include rule dependencies. - /// - void IncludeDependencies(); - } + void IncludeDependencies(); +} - /// - /// A helper to construct a get pipeline. - /// - internal sealed class GetRulePipelineBuilder : PipelineBuilderBase, IGetPipelineBuilder - { - private bool _IncludeDependencies; +/// +/// A helper to construct a get pipeline. +/// +internal sealed class GetRulePipelineBuilder : PipelineBuilderBase, IGetPipelineBuilder +{ + private bool _IncludeDependencies; - internal GetRulePipelineBuilder(Source[] source, IHostContext hostContext) - : base(source, hostContext) { } + internal GetRulePipelineBuilder(Source[] source, IHostContext hostContext) + : base(source, hostContext) { } - /// - public override IPipelineBuilder Configure(PSRuleOption option) - { - if (option == null) - return this; + /// + public override IPipelineBuilder Configure(PSRuleOption option) + { + if (option == null) + return this; - Option.Baseline = new Options.BaselineOption(option.Baseline); - Option.Execution = GetExecutionOption(option.Execution); - Option.Output.Culture = GetCulture(option.Output.Culture); - Option.Output.Format = SuppressFormat(option.Output.Format); - Option.Requires = new RequiresOption(option.Requires); - Option.Output.JsonIndent = NormalizeJsonIndentRange(option.Output.JsonIndent); - if (option.Rule != null) - Option.Rule = new RuleOption(option.Rule); + Option.Baseline = new Options.BaselineOption(option.Baseline); + Option.Execution = GetExecutionOption(option.Execution); + Option.Output.Culture = GetCulture(option.Output.Culture); + Option.Output.Format = SuppressFormat(option.Output.Format); + Option.Requires = new RequiresOption(option.Requires); + Option.Output.JsonIndent = NormalizeJsonIndentRange(option.Output.JsonIndent); + if (option.Rule != null) + Option.Rule = new RuleOption(option.Rule); - return this; - } + return this; + } - /// - public void IncludeDependencies() - { - _IncludeDependencies = true; - } + /// + public void IncludeDependencies() + { + _IncludeDependencies = true; + } - /// - public override IPipeline Build(IPipelineWriter writer = null) - { - return !RequireModules() || !RequireSources() - ? null - : (IPipeline)new GetRulePipeline( - pipeline: PrepareContext( - bindTargetName: null, - bindTargetType: null, - bindField: null - ), - source: Source, - reader: PrepareReader(), - writer: writer ?? PrepareWriter(), - includeDependencies: _IncludeDependencies - ); - } + /// + public override IPipeline Build(IPipelineWriter writer = null) + { + return !RequireModules() || !RequireSources() + ? null + : (IPipeline)new GetRulePipeline( + pipeline: PrepareContext( + bindTargetName: null, + bindTargetType: null, + bindField: null + ), + source: Source, + reader: PrepareReader(), + writer: writer ?? PrepareWriter(), + includeDependencies: _IncludeDependencies + ); + } - private static OutputFormat SuppressFormat(OutputFormat? format) - { - return !format.HasValue || - !(format == OutputFormat.Wide || - format == OutputFormat.Json || - format == OutputFormat.Yaml) ? OutputFormat.None : format.Value; - } + private static OutputFormat SuppressFormat(OutputFormat? format) + { + return !format.HasValue || + !(format == OutputFormat.Wide || + format == OutputFormat.Json || + format == OutputFormat.Yaml) ? OutputFormat.None : format.Value; } } diff --git a/src/PSRule/Pipeline/GetTargetPipeline.cs b/src/PSRule/Pipeline/GetTargetPipeline.cs index 199c739cc7..34723d4ea1 100644 --- a/src/PSRule/Pipeline/GetTargetPipeline.cs +++ b/src/PSRule/Pipeline/GetTargetPipeline.cs @@ -4,151 +4,150 @@ using System.Management.Automation; using PSRule.Configuration; -namespace PSRule.Pipeline +namespace PSRule.Pipeline; + +/// +/// A helper to build a pipeline to return target objects. +/// +public interface IGetTargetPipelineBuilder : IPipelineBuilder { /// - /// A helper to build a pipeline to return target objects. + /// Specifies a path for reading input objects from disk. /// - public interface IGetTargetPipelineBuilder : IPipelineBuilder + void InputPath(string[] path); +} + +/// +/// A helper to construct the pipeline for Assert-PSRule. +/// +internal sealed class GetTargetPipelineBuilder : PipelineBuilderBase, IGetTargetPipelineBuilder +{ + private InputPathBuilder _InputPath; + + internal GetTargetPipelineBuilder(Source[] source, IHostContext hostContext) + : base(source, hostContext) { - /// - /// Specifies a path for reading input objects from disk. - /// - void InputPath(string[] path); + _InputPath = null; } - /// - /// A helper to construct the pipeline for Assert-PSRule. - /// - internal sealed class GetTargetPipelineBuilder : PipelineBuilderBase, IGetTargetPipelineBuilder + /// + public override IPipelineBuilder Configure(PSRuleOption option) { - private InputPathBuilder _InputPath; - - internal GetTargetPipelineBuilder(Source[] source, IHostContext hostContext) - : base(source, hostContext) - { - _InputPath = null; - } + if (option == null) + return this; - /// - public override IPipelineBuilder Configure(PSRuleOption option) - { - if (option == null) - return this; + base.Configure(option); - base.Configure(option); + Option.Output = new OutputOption(); + Option.Output.Culture = GetCulture(option.Output.Culture); - Option.Output = new OutputOption(); - Option.Output.Culture = GetCulture(option.Output.Culture); + ConfigureBinding(option); + Option.Requires = new RequiresOption(option.Requires); - ConfigureBinding(option); - Option.Requires = new RequiresOption(option.Requires); + return this; + } - return this; - } + /// + public void InputPath(string[] path) + { + if (path == null || path.Length == 0) + return; - /// - public void InputPath(string[] path) + PathFilter required = null; + if (TryChangedFiles(out var files)) { - if (path == null || path.Length == 0) - return; + required = PathFilter.Create(Environment.GetWorkingPath(), path); + path = files; + } - PathFilter required = null; - if (TryChangedFiles(out var files)) - { - required = PathFilter.Create(Environment.GetWorkingPath(), path); - path = files; - } + var builder = new InputPathBuilder(GetOutput(), Environment.GetWorkingPath(), "*", GetInputFilter(), required); + builder.Add(path); + _InputPath = builder; + } - var builder = new InputPathBuilder(GetOutput(), Environment.GetWorkingPath(), "*", GetInputFilter(), required); - builder.Add(path); - _InputPath = builder; - } + /// + public override IPipeline Build(IPipelineWriter writer = null) + { + return new GetTargetPipeline(PrepareContext(null, null, null), PrepareReader(), writer ?? PrepareWriter()); + } - /// - public override IPipeline Build(IPipelineWriter writer = null) + /// + protected override PipelineReader PrepareReader() + { + if (!string.IsNullOrEmpty(Option.Input.ObjectPath)) { - return new GetTargetPipeline(PrepareContext(null, null, null), PrepareReader(), writer ?? PrepareWriter()); + AddVisitTargetObjectAction((sourceObject, next) => + { + return PipelineReceiverActions.ReadObjectPath(sourceObject, next, Option.Input.ObjectPath, true); + }); } - /// - protected override PipelineReader PrepareReader() + if (Option.Input.Format == InputFormat.Yaml) { - if (!string.IsNullOrEmpty(Option.Input.ObjectPath)) - { - AddVisitTargetObjectAction((sourceObject, next) => - { - return PipelineReceiverActions.ReadObjectPath(sourceObject, next, Option.Input.ObjectPath, true); - }); - } - - if (Option.Input.Format == InputFormat.Yaml) + AddVisitTargetObjectAction((sourceObject, next) => { - AddVisitTargetObjectAction((sourceObject, next) => - { - return PipelineReceiverActions.ConvertFromYaml(sourceObject, next); - }); - } - else if (Option.Input.Format == InputFormat.Json) + return PipelineReceiverActions.ConvertFromYaml(sourceObject, next); + }); + } + else if (Option.Input.Format == InputFormat.Json) + { + AddVisitTargetObjectAction((sourceObject, next) => { - AddVisitTargetObjectAction((sourceObject, next) => - { - return PipelineReceiverActions.ConvertFromJson(sourceObject, next); - }); - } - else if (Option.Input.Format == InputFormat.Markdown) + return PipelineReceiverActions.ConvertFromJson(sourceObject, next); + }); + } + else if (Option.Input.Format == InputFormat.Markdown) + { + AddVisitTargetObjectAction((sourceObject, next) => { - AddVisitTargetObjectAction((sourceObject, next) => - { - return PipelineReceiverActions.ConvertFromMarkdown(sourceObject, next); - }); - } - else if (Option.Input.Format == InputFormat.PowerShellData) + return PipelineReceiverActions.ConvertFromMarkdown(sourceObject, next); + }); + } + else if (Option.Input.Format == InputFormat.PowerShellData) + { + AddVisitTargetObjectAction((sourceObject, next) => { - AddVisitTargetObjectAction((sourceObject, next) => - { - return PipelineReceiverActions.ConvertFromPowerShellData(sourceObject, next); - }); - } - else if (Option.Input.Format == InputFormat.File) + return PipelineReceiverActions.ConvertFromPowerShellData(sourceObject, next); + }); + } + else if (Option.Input.Format == InputFormat.File) + { + AddVisitTargetObjectAction((sourceObject, next) => { - AddVisitTargetObjectAction((sourceObject, next) => - { - return PipelineReceiverActions.ConvertFromGitHead(sourceObject, next); - }); - } - else if (Option.Input.Format == InputFormat.Detect && _InputPath != null) + return PipelineReceiverActions.ConvertFromGitHead(sourceObject, next); + }); + } + else if (Option.Input.Format == InputFormat.Detect && _InputPath != null) + { + AddVisitTargetObjectAction((sourceObject, next) => { - AddVisitTargetObjectAction((sourceObject, next) => - { - return PipelineReceiverActions.DetectInputFormat(sourceObject, next); - }); - } - return new PipelineReader(VisitTargetObject, _InputPath, GetInputObjectSourceFilter()); + return PipelineReceiverActions.DetectInputFormat(sourceObject, next); + }); } + return new PipelineReader(VisitTargetObject, _InputPath, GetInputObjectSourceFilter()); } +} - /// - /// A pipeline that gets target objects through the pipeline. - /// - internal sealed class GetTargetPipeline : RulePipeline - { - internal GetTargetPipeline(PipelineContext context, PipelineReader reader, IPipelineWriter writer) - : base(context, null, reader, writer) { } +/// +/// A pipeline that gets target objects through the pipeline. +/// +internal sealed class GetTargetPipeline : RulePipeline +{ + internal GetTargetPipeline(PipelineContext context, PipelineReader reader, IPipelineWriter writer) + : base(context, null, reader, writer) { } - public override void Process(PSObject sourceObject) + public override void Process(PSObject sourceObject) + { + try { - try - { - Reader.Enqueue(sourceObject); - while (Reader.TryDequeue(out var next)) - Writer.WriteObject(next.Value, false); - } - catch (Exception) - { - End(); - throw; - } + Reader.Enqueue(sourceObject); + while (Reader.TryDequeue(out var next)) + Writer.WriteObject(next.Value, false); + } + catch (Exception) + { + End(); + throw; } } } diff --git a/src/PSRule/Pipeline/HostContext.cs b/src/PSRule/Pipeline/HostContext.cs index 5bc49983df..ebc41e5daa 100644 --- a/src/PSRule/Pipeline/HostContext.cs +++ b/src/PSRule/Pipeline/HostContext.cs @@ -4,301 +4,300 @@ using System.Management.Automation; using PSRule.Definitions; -namespace PSRule.Pipeline +namespace PSRule.Pipeline; + +/// +/// A host context for handling input and output emitted from the pipeline. +/// +public interface IHostContext { /// - /// A host context for handling input and output emitted from the pipeline. + /// Determines if the pipeline is executing in a remote PowerShell session. + /// + bool InSession { get; } + + /// + /// Determines if the pipeline encountered any errors. + /// + bool HadErrors { get; } + + /// + /// Get the value of a PowerShell preference variable. + /// + ActionPreference GetPreferenceVariable(string variableName); + + /// + /// Get the value of a named variable. + /// + T GetVariable(string variableName); + + /// + /// Set the value of a named variable. + /// + void SetVariable(string variableName, T value); + + /// + /// Handle an error reported by the pipeline. + /// + void Error(ErrorRecord errorRecord); + + /// + /// Handle a warning reported by the pipeline. + /// + void Warning(string text); + + /// + /// Handle an informational record reported by the pipeline. + /// + void Information(InformationRecord informationRecord); + + /// + /// Handle a verbose message reported by the pipeline. + /// + void Verbose(string text); + + /// + /// Handle a debug message reported by the pipeline. + /// + void Debug(string text); + + /// + /// Handle an object emitted from the pipeline. + /// + void Object(object sendToPipeline, bool enumerateCollection); + + /// + /// Determines if a destructive action such as overwriting a file should be processed. + /// + bool ShouldProcess(string target, string action); + + /// + /// Get the current working path. /// - public interface IHostContext + string GetWorkingPath(); +} + +internal static class HostContextExtensions +{ + private const string ErrorPreference = "ErrorActionPreference"; + private const string WarningPreference = "WarningPreference"; + private const string InformationPreference = "InformationPreference"; + private const string VerbosePreference = "VerbosePreference"; + private const string DebugPreference = "DebugPreference"; + private const string AutoLoadingPreference = "PSModuleAutoLoadingPreference"; + + public static ActionPreference GetErrorPreference(this IHostContext hostContext) { - /// - /// Determines if the pipeline is executing in a remote PowerShell session. - /// - bool InSession { get; } - - /// - /// Determines if the pipeline encountered any errors. - /// - bool HadErrors { get; } - - /// - /// Get the value of a PowerShell preference variable. - /// - ActionPreference GetPreferenceVariable(string variableName); - - /// - /// Get the value of a named variable. - /// - T GetVariable(string variableName); - - /// - /// Set the value of a named variable. - /// - void SetVariable(string variableName, T value); - - /// - /// Handle an error reported by the pipeline. - /// - void Error(ErrorRecord errorRecord); - - /// - /// Handle a warning reported by the pipeline. - /// - void Warning(string text); - - /// - /// Handle an informational record reported by the pipeline. - /// - void Information(InformationRecord informationRecord); - - /// - /// Handle a verbose message reported by the pipeline. - /// - void Verbose(string text); - - /// - /// Handle a debug message reported by the pipeline. - /// - void Debug(string text); - - /// - /// Handle an object emitted from the pipeline. - /// - void Object(object sendToPipeline, bool enumerateCollection); - - /// - /// Determines if a destructive action such as overwriting a file should be processed. - /// - bool ShouldProcess(string target, string action); - - /// - /// Get the current working path. - /// - string GetWorkingPath(); + return hostContext.GetPreferenceVariable(ErrorPreference); } - internal static class HostContextExtensions + public static ActionPreference GetWarningPreference(this IHostContext hostContext) { - private const string ErrorPreference = "ErrorActionPreference"; - private const string WarningPreference = "WarningPreference"; - private const string InformationPreference = "InformationPreference"; - private const string VerbosePreference = "VerbosePreference"; - private const string DebugPreference = "DebugPreference"; - private const string AutoLoadingPreference = "PSModuleAutoLoadingPreference"; - - public static ActionPreference GetErrorPreference(this IHostContext hostContext) - { - return hostContext.GetPreferenceVariable(ErrorPreference); - } - - public static ActionPreference GetWarningPreference(this IHostContext hostContext) - { - return hostContext.GetPreferenceVariable(WarningPreference); - } - - public static ActionPreference GetInformationPreference(this IHostContext hostContext) - { - return hostContext.GetPreferenceVariable(InformationPreference); - } - - public static ActionPreference GetVerbosePreference(this IHostContext hostContext) - { - return hostContext.GetPreferenceVariable(VerbosePreference); - } - - public static ActionPreference GetDebugPreference(this IHostContext hostContext) - { - return hostContext.GetPreferenceVariable(DebugPreference); - } - - public static PSModuleAutoLoadingPreference GetAutoLoadingPreference(this IHostContext hostContext) - { - return hostContext.GetVariable(AutoLoadingPreference); - } + return hostContext.GetPreferenceVariable(WarningPreference); } - /// - /// A base class for custom host context instances. - /// - public abstract class HostContext : IHostContext + public static ActionPreference GetInformationPreference(this IHostContext hostContext) { - private const string ErrorPreference = "ErrorActionPreference"; - private const string WarningPreference = "WarningPreference"; + return hostContext.GetPreferenceVariable(InformationPreference); + } - /// - public virtual bool InSession => false; + public static ActionPreference GetVerbosePreference(this IHostContext hostContext) + { + return hostContext.GetPreferenceVariable(VerbosePreference); + } + + public static ActionPreference GetDebugPreference(this IHostContext hostContext) + { + return hostContext.GetPreferenceVariable(DebugPreference); + } + + public static PSModuleAutoLoadingPreference GetAutoLoadingPreference(this IHostContext hostContext) + { + return hostContext.GetVariable(AutoLoadingPreference); + } +} + +/// +/// A base class for custom host context instances. +/// +public abstract class HostContext : IHostContext +{ + private const string ErrorPreference = "ErrorActionPreference"; + private const string WarningPreference = "WarningPreference"; - /// - public virtual bool HadErrors { get; protected set; } + /// + public virtual bool InSession => false; - /// - public virtual void Debug(string text) - { + /// + public virtual bool HadErrors { get; protected set; } - } + /// + public virtual void Debug(string text) + { - /// - public virtual void Error(ErrorRecord errorRecord) - { - HadErrors = true; - } + } + + /// + public virtual void Error(ErrorRecord errorRecord) + { + HadErrors = true; + } - /// - public virtual ActionPreference GetPreferenceVariable(string variableName) - { - return variableName == ErrorPreference || - variableName == WarningPreference ? ActionPreference.Continue : ActionPreference.Ignore; - } + /// + public virtual ActionPreference GetPreferenceVariable(string variableName) + { + return variableName == ErrorPreference || + variableName == WarningPreference ? ActionPreference.Continue : ActionPreference.Ignore; + } - /// - public virtual T GetVariable(string variableName) - { - return default; - } + /// + public virtual T GetVariable(string variableName) + { + return default; + } - /// - public virtual void Information(InformationRecord informationRecord) - { + /// + public virtual void Information(InformationRecord informationRecord) + { - } + } - /// - public virtual void Object(object sendToPipeline, bool enumerateCollection) - { - if (sendToPipeline is IResultRecord record) - Record(record); - //else if (enumerateCollection) - // foreach (var item in record) - } + /// + public virtual void Object(object sendToPipeline, bool enumerateCollection) + { + if (sendToPipeline is IResultRecord record) + Record(record); + //else if (enumerateCollection) + // foreach (var item in record) + } - /// - public virtual void SetVariable(string variableName, T value) - { + /// + public virtual void SetVariable(string variableName, T value) + { - } + } - /// - public abstract bool ShouldProcess(string target, string action); + /// + public abstract bool ShouldProcess(string target, string action); - /// - public virtual void Verbose(string text) - { + /// + public virtual void Verbose(string text) + { - } + } - /// - public virtual void Warning(string text) - { + /// + public virtual void Warning(string text) + { - } + } - /// - /// Handle record objects emitted from the pipeline. - /// - public virtual void Record(IResultRecord record) - { + /// + /// Handle record objects emitted from the pipeline. + /// + public virtual void Record(IResultRecord record) + { - } + } - /// - public virtual string GetWorkingPath() - { - return Directory.GetCurrentDirectory(); - } + /// + public virtual string GetWorkingPath() + { + return Directory.GetCurrentDirectory(); } +} + +/// +/// The host context used for PowerShell-based pipelines. +/// +public sealed class PSHostContext : IHostContext +{ + internal readonly PSCmdlet CmdletContext; + internal readonly EngineIntrinsics ExecutionContext; + + /// + public bool InSession { get; } + + /// + public bool HadErrors { get; private set; } /// - /// The host context used for PowerShell-based pipelines. + /// Create an instance of a PowerShell-based host context. /// - public sealed class PSHostContext : IHostContext + public PSHostContext(PSCmdlet commandRuntime, EngineIntrinsics executionContext) + { + InSession = false; + CmdletContext = commandRuntime; + ExecutionContext = executionContext; + InSession = executionContext != null && executionContext.SessionState.PSVariable.GetValue("PSSenderInfo") != null; + } + + /// + public ActionPreference GetPreferenceVariable(string variableName) + { + return ExecutionContext == null + ? ActionPreference.SilentlyContinue + : (ActionPreference)ExecutionContext.SessionState.PSVariable.GetValue(variableName); + } + + /// + public T GetVariable(string variableName) + { + return ExecutionContext == null ? default : (T)ExecutionContext.SessionState.PSVariable.GetValue(variableName); + } + + /// + public void SetVariable(string variableName, T value) + { + CmdletContext.SessionState.PSVariable.Set(variableName, value); + } + + /// + public bool ShouldProcess(string target, string action) + { + return CmdletContext == null || CmdletContext.ShouldProcess(target, action); + } + + /// + public void Error(ErrorRecord errorRecord) + { + CmdletContext.WriteError(errorRecord); + HadErrors = true; + } + + /// + public void Warning(string text) + { + CmdletContext.WriteWarning(text); + } + + /// + public void Information(InformationRecord informationRecord) + { + CmdletContext.WriteInformation(informationRecord); + } + + /// + public void Verbose(string text) + { + CmdletContext.WriteVerbose(text); + } + + /// + public void Debug(string text) + { + CmdletContext.WriteDebug(text); + } + + /// + public void Object(object sendToPipeline, bool enumerateCollection) + { + CmdletContext.WriteObject(sendToPipeline, enumerateCollection); + } + + /// + public string GetWorkingPath() { - internal readonly PSCmdlet CmdletContext; - internal readonly EngineIntrinsics ExecutionContext; - - /// - public bool InSession { get; } - - /// - public bool HadErrors { get; private set; } - - /// - /// Create an instance of a PowerShell-based host context. - /// - public PSHostContext(PSCmdlet commandRuntime, EngineIntrinsics executionContext) - { - InSession = false; - CmdletContext = commandRuntime; - ExecutionContext = executionContext; - InSession = executionContext != null && executionContext.SessionState.PSVariable.GetValue("PSSenderInfo") != null; - } - - /// - public ActionPreference GetPreferenceVariable(string variableName) - { - return ExecutionContext == null - ? ActionPreference.SilentlyContinue - : (ActionPreference)ExecutionContext.SessionState.PSVariable.GetValue(variableName); - } - - /// - public T GetVariable(string variableName) - { - return ExecutionContext == null ? default : (T)ExecutionContext.SessionState.PSVariable.GetValue(variableName); - } - - /// - public void SetVariable(string variableName, T value) - { - CmdletContext.SessionState.PSVariable.Set(variableName, value); - } - - /// - public bool ShouldProcess(string target, string action) - { - return CmdletContext == null || CmdletContext.ShouldProcess(target, action); - } - - /// - public void Error(ErrorRecord errorRecord) - { - CmdletContext.WriteError(errorRecord); - HadErrors = true; - } - - /// - public void Warning(string text) - { - CmdletContext.WriteWarning(text); - } - - /// - public void Information(InformationRecord informationRecord) - { - CmdletContext.WriteInformation(informationRecord); - } - - /// - public void Verbose(string text) - { - CmdletContext.WriteVerbose(text); - } - - /// - public void Debug(string text) - { - CmdletContext.WriteDebug(text); - } - - /// - public void Object(object sendToPipeline, bool enumerateCollection) - { - CmdletContext.WriteObject(sendToPipeline, enumerateCollection); - } - - /// - public string GetWorkingPath() - { - return ExecutionContext.SessionState.Path.CurrentFileSystemLocation.Path; - } + return ExecutionContext.SessionState.Path.CurrentFileSystemLocation.Path; } } diff --git a/src/PSRule/Pipeline/InputPathBuilder.cs b/src/PSRule/Pipeline/InputPathBuilder.cs index 7e3bf3e501..cace5fb033 100644 --- a/src/PSRule/Pipeline/InputPathBuilder.cs +++ b/src/PSRule/Pipeline/InputPathBuilder.cs @@ -1,11 +1,10 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -namespace PSRule.Pipeline +namespace PSRule.Pipeline; + +internal sealed class InputPathBuilder : PathBuilder { - internal sealed class InputPathBuilder : PathBuilder - { - public InputPathBuilder(IPipelineWriter logger, string basePath, string searchPattern, PathFilter filter, PathFilter required) - : base(logger, basePath, searchPattern, filter, required) { } - } + public InputPathBuilder(IPipelineWriter logger, string basePath, string searchPattern, PathFilter filter, PathFilter required) + : base(logger, basePath, searchPattern, filter, required) { } } diff --git a/src/PSRule/Pipeline/InvokePipelineBuilder.cs b/src/PSRule/Pipeline/InvokePipelineBuilder.cs index d78121f90c..6554ec9c69 100644 --- a/src/PSRule/Pipeline/InvokePipelineBuilder.cs +++ b/src/PSRule/Pipeline/InvokePipelineBuilder.cs @@ -4,213 +4,212 @@ using PSRule.Configuration; using PSRule.Host; -namespace PSRule.Pipeline +namespace PSRule.Pipeline; + +/// +/// A helper to build a pipeline for executing rules and conventions within a PSRule sandbox. +/// +public interface IInvokePipelineBuilder : IPipelineBuilder { /// - /// A helper to build a pipeline for executing rules and conventions within a PSRule sandbox. + /// Configures paths that will be scanned for input. + /// + /// An array of relative or absolute path specs to be scanned. Directories will be recursively scanned for all files not excluded matching the file path spec. + void InputPath(string[] path); + + /// + /// Configures a variable that will recieve all results in addition to the host context. + /// + /// The name of the variable to set. + void ResultVariable(string variableName); + + /// + /// Unblocks PowerShell sources from trusted publishers that originate from an Internet zone. /// - public interface IInvokePipelineBuilder : IPipelineBuilder + /// The trusted publisher to unblock. + void UnblockPublisher(string publisher); +} + +internal abstract class InvokePipelineBuilderBase : PipelineBuilderBase, IInvokePipelineBuilder +{ + protected InputPathBuilder _InputPath; + protected string _ResultVariableName; + + private List _TrustedPublishers; + + protected InvokePipelineBuilderBase(Source[] source, IHostContext hostContext) + : base(source, hostContext) { - /// - /// Configures paths that will be scanned for input. - /// - /// An array of relative or absolute path specs to be scanned. Directories will be recursively scanned for all files not excluded matching the file path spec. - void InputPath(string[] path); - - /// - /// Configures a variable that will recieve all results in addition to the host context. - /// - /// The name of the variable to set. - void ResultVariable(string variableName); - - /// - /// Unblocks PowerShell sources from trusted publishers that originate from an Internet zone. - /// - /// The trusted publisher to unblock. - void UnblockPublisher(string publisher); + _InputPath = null; } - internal abstract class InvokePipelineBuilderBase : PipelineBuilderBase, IInvokePipelineBuilder + public void InputPath(string[] path) { - protected InputPathBuilder _InputPath; - protected string _ResultVariableName; - - private List _TrustedPublishers; + if (path == null || path.Length == 0) + return; - protected InvokePipelineBuilderBase(Source[] source, IHostContext hostContext) - : base(source, hostContext) + PathFilter required = null; + if (TryChangedFiles(out var files)) { - _InputPath = null; + required = PathFilter.Create(Environment.GetWorkingPath(), path); + path = files; } - public void InputPath(string[] path) - { - if (path == null || path.Length == 0) - return; - - PathFilter required = null; - if (TryChangedFiles(out var files)) - { - required = PathFilter.Create(Environment.GetWorkingPath(), path); - path = files; - } + var builder = new InputPathBuilder(PrepareWriter(), Environment.GetWorkingPath(), "*", GetInputFilter(), required); + builder.Add(path); + _InputPath = builder; + } - var builder = new InputPathBuilder(PrepareWriter(), Environment.GetWorkingPath(), "*", GetInputFilter(), required); - builder.Add(path); - _InputPath = builder; - } + public void ResultVariable(string variableName) + { + _ResultVariableName = variableName; + } - public void ResultVariable(string variableName) - { - _ResultVariableName = variableName; - } + public void UnblockPublisher(string publisher) + { + if (System.Environment.OSVersion.Platform != PlatformID.Win32NT) + return; - public void UnblockPublisher(string publisher) - { - if (System.Environment.OSVersion.Platform != PlatformID.Win32NT) - return; + _TrustedPublishers ??= new List(); + _TrustedPublishers.Add(publisher); + } - _TrustedPublishers ??= new List(); - _TrustedPublishers.Add(publisher); - } + public override IPipelineBuilder Configure(PSRuleOption option) + { + if (option == null) + return this; - public override IPipelineBuilder Configure(PSRuleOption option) - { - if (option == null) - return this; + base.Configure(option); - base.Configure(option); + Option.Logging.RuleFail = option.Logging.RuleFail ?? LoggingOption.Default.RuleFail; + Option.Logging.RulePass = option.Logging.RulePass ?? LoggingOption.Default.RulePass; + Option.Logging.LimitVerbose = option.Logging.LimitVerbose; + Option.Logging.LimitDebug = option.Logging.LimitDebug; - Option.Logging.RuleFail = option.Logging.RuleFail ?? LoggingOption.Default.RuleFail; - Option.Logging.RulePass = option.Logging.RulePass ?? LoggingOption.Default.RulePass; - Option.Logging.LimitVerbose = option.Logging.LimitVerbose; - Option.Logging.LimitDebug = option.Logging.LimitDebug; + Option.Output.As = option.Output.As ?? OutputOption.Default.As; + Option.Output.Culture = GetCulture(option.Output.Culture); + Option.Output.Encoding = option.Output.Encoding ?? OutputOption.Default.Encoding; + Option.Output.Format = option.Output.Format ?? OutputOption.Default.Format; + Option.Output.Path = option.Output.Path ?? OutputOption.Default.Path; + Option.Output.JsonIndent = NormalizeJsonIndentRange(option.Output.JsonIndent); - Option.Output.As = option.Output.As ?? OutputOption.Default.As; - Option.Output.Culture = GetCulture(option.Output.Culture); - Option.Output.Encoding = option.Output.Encoding ?? OutputOption.Default.Encoding; - Option.Output.Format = option.Output.Format ?? OutputOption.Default.Format; - Option.Output.Path = option.Output.Path ?? OutputOption.Default.Path; - Option.Output.JsonIndent = NormalizeJsonIndentRange(option.Output.JsonIndent); + if (option.Rule != null) + Option.Rule = new RuleOption(option.Rule); - if (option.Rule != null) - Option.Rule = new RuleOption(option.Rule); + if (option.Configuration != null) + Option.Configuration = new ConfigurationOption(option.Configuration); - if (option.Configuration != null) - Option.Configuration = new ConfigurationOption(option.Configuration); + ConfigureBinding(option); + Option.Requires = new RequiresOption(option.Requires); + if (option.Suppression.Count > 0) + Option.Suppression = new SuppressionOption(option.Suppression); - ConfigureBinding(option); - Option.Requires = new RequiresOption(option.Requires); - if (option.Suppression.Count > 0) - Option.Suppression = new SuppressionOption(option.Suppression); + return this; + } - return this; - } + public override IPipeline Build(IPipelineWriter writer = null) + { + writer ??= PrepareWriter(); + Unblock(writer); + return !RequireModules() || !RequireSources() + ? null + : (IPipeline)new InvokeRulePipeline(PrepareContext(BindTargetNameHook, BindTargetTypeHook, BindFieldHook), Source, writer, Option.Output.Outcome.Value); + } - public override IPipeline Build(IPipelineWriter writer = null) - { - writer ??= PrepareWriter(); - Unblock(writer); - return !RequireModules() || !RequireSources() - ? null - : (IPipeline)new InvokeRulePipeline(PrepareContext(BindTargetNameHook, BindTargetTypeHook, BindFieldHook), Source, writer, Option.Output.Outcome.Value); - } + protected void Unblock(IPipelineWriter writer) + { + if (Source == null || Source.Length == 0 || + _TrustedPublishers == null || _TrustedPublishers.Count == 0 || + System.Environment.OSVersion.Platform != PlatformID.Win32NT) + return; - protected void Unblock(IPipelineWriter writer) + var files = new List(); + for (var i = 0; i < Source.Length; i++) { - if (Source == null || Source.Length == 0 || - _TrustedPublishers == null || _TrustedPublishers.Count == 0 || - System.Environment.OSVersion.Platform != PlatformID.Win32NT) - return; - - var files = new List(); - for (var i = 0; i < Source.Length; i++) + for (var j = 0; j < Source[i].File.Length; j++) { - for (var j = 0; j < Source[i].File.Length; j++) - { - if (Source[i].File[j].Type == SourceType.Script && IsBlocked(Source[i].File[j].Path)) - files.Add(Source[i].File[j].Path); - } + if (Source[i].File[j].Type == SourceType.Script && IsBlocked(Source[i].File[j].Path)) + files.Add(Source[i].File[j].Path); } - if (files.Count > 0) - HostHelper.UnblockFile(writer, _TrustedPublishers.ToArray(), files.ToArray()); } + if (files.Count > 0) + HostHelper.UnblockFile(writer, _TrustedPublishers.ToArray(), files.ToArray()); + } - private static bool IsBlocked(string path) + private static bool IsBlocked(string path) + { + try { - try - { - var zone = File.ReadLines(string.Concat(path, ":Zone.Identifier")).FirstOrDefault(s => s.StartsWith("ZoneId=")); - return zone != null; - } - catch - { - return false; - } + var zone = File.ReadLines(string.Concat(path, ":Zone.Identifier")).FirstOrDefault(s => s.StartsWith("ZoneId=")); + return zone != null; + } + catch + { + return false; } + } - protected override PipelineReader PrepareReader() + protected override PipelineReader PrepareReader() + { + if (!string.IsNullOrEmpty(Option.Input.ObjectPath)) { - if (!string.IsNullOrEmpty(Option.Input.ObjectPath)) + AddVisitTargetObjectAction((sourceObject, next) => { - AddVisitTargetObjectAction((sourceObject, next) => - { - return PipelineReceiverActions.ReadObjectPath(sourceObject, next, Option.Input.ObjectPath, true); - }); - } + return PipelineReceiverActions.ReadObjectPath(sourceObject, next, Option.Input.ObjectPath, true); + }); + } - if (Option.Input.Format == InputFormat.Yaml) + if (Option.Input.Format == InputFormat.Yaml) + { + AddVisitTargetObjectAction((sourceObject, next) => { - AddVisitTargetObjectAction((sourceObject, next) => - { - return PipelineReceiverActions.ConvertFromYaml(sourceObject, next); - }); - } - else if (Option.Input.Format == InputFormat.Json) + return PipelineReceiverActions.ConvertFromYaml(sourceObject, next); + }); + } + else if (Option.Input.Format == InputFormat.Json) + { + AddVisitTargetObjectAction((sourceObject, next) => { - AddVisitTargetObjectAction((sourceObject, next) => - { - return PipelineReceiverActions.ConvertFromJson(sourceObject, next); - }); - } - else if (Option.Input.Format == InputFormat.Markdown) + return PipelineReceiverActions.ConvertFromJson(sourceObject, next); + }); + } + else if (Option.Input.Format == InputFormat.Markdown) + { + AddVisitTargetObjectAction((sourceObject, next) => { - AddVisitTargetObjectAction((sourceObject, next) => - { - return PipelineReceiverActions.ConvertFromMarkdown(sourceObject, next); - }); - } - else if (Option.Input.Format == InputFormat.PowerShellData) + return PipelineReceiverActions.ConvertFromMarkdown(sourceObject, next); + }); + } + else if (Option.Input.Format == InputFormat.PowerShellData) + { + AddVisitTargetObjectAction((sourceObject, next) => { - AddVisitTargetObjectAction((sourceObject, next) => - { - return PipelineReceiverActions.ConvertFromPowerShellData(sourceObject, next); - }); - } - else if (Option.Input.Format == InputFormat.File) + return PipelineReceiverActions.ConvertFromPowerShellData(sourceObject, next); + }); + } + else if (Option.Input.Format == InputFormat.File) + { + AddVisitTargetObjectAction((sourceObject, next) => { - AddVisitTargetObjectAction((sourceObject, next) => - { - return PipelineReceiverActions.ConvertFromGitHead(sourceObject, next); - }); - } - else if (Option.Input.Format == InputFormat.Detect && _InputPath != null) + return PipelineReceiverActions.ConvertFromGitHead(sourceObject, next); + }); + } + else if (Option.Input.Format == InputFormat.Detect && _InputPath != null) + { + AddVisitTargetObjectAction((sourceObject, next) => { - AddVisitTargetObjectAction((sourceObject, next) => - { - return PipelineReceiverActions.DetectInputFormat(sourceObject, next); - }); - } - return new PipelineReader(VisitTargetObject, _InputPath, GetInputObjectSourceFilter()); + return PipelineReceiverActions.DetectInputFormat(sourceObject, next); + }); } + return new PipelineReader(VisitTargetObject, _InputPath, GetInputObjectSourceFilter()); } +} - /// - /// A helper to construct the pipeline for Invoke-PSRule. - /// - internal sealed class InvokeRulePipelineBuilder : InvokePipelineBuilderBase - { - internal InvokeRulePipelineBuilder(Source[] source, IHostContext hostContext) - : base(source, hostContext) { } - } +/// +/// A helper to construct the pipeline for Invoke-PSRule. +/// +internal sealed class InvokeRulePipelineBuilder : InvokePipelineBuilderBase +{ + internal InvokeRulePipelineBuilder(Source[] source, IHostContext hostContext) + : base(source, hostContext) { } } diff --git a/src/PSRule/Pipeline/InvokeResult.cs b/src/PSRule/Pipeline/InvokeResult.cs index 1ced61d474..a95a27b132 100644 --- a/src/PSRule/Pipeline/InvokeResult.cs +++ b/src/PSRule/Pipeline/InvokeResult.cs @@ -4,136 +4,135 @@ using PSRule.Definitions.Rules; using PSRule.Rules; -namespace PSRule.Pipeline +namespace PSRule.Pipeline; + +/// +/// A result for a target object. +/// +public sealed class InvokeResult { + private readonly List _Record; + private RuleOutcome _Outcome; + private SeverityLevel _Level; + private long _Time; + private int _Total; + private int _Error; + private int _Pass; + private int _Fail; + + internal InvokeResult() + { + _Record = new List(); + _Time = 0; + _Total = 0; + _Error = 0; + _Pass = 0; + _Fail = 0; + } + + /// + /// The execution time of all rules in milliseconds. + /// + internal long Time => _Time; + + /// + /// The total number of rule records. + /// + internal int Total => _Total; + + /// + /// The number of rule records with a error result. + /// + internal int Error => _Error; + + /// + /// The number of rule records with a fail result. + /// + internal int Fail => _Fail; + + /// + /// The number of rules records with a pass result. + /// + internal int Pass => _Pass; + + /// + /// The worst outcome of all rule records. + /// + public RuleOutcome Outcome => _Outcome; + /// - /// A result for a target object. + /// The highest severity level of all rule records. /// - public sealed class InvokeResult + public SeverityLevel Level => _Level; + + internal string TargetName { - private readonly List _Record; - private RuleOutcome _Outcome; - private SeverityLevel _Level; - private long _Time; - private int _Total; - private int _Error; - private int _Pass; - private int _Fail; - - internal InvokeResult() + get { - _Record = new List(); - _Time = 0; - _Total = 0; - _Error = 0; - _Pass = 0; - _Fail = 0; + return IsEmptyRecord() ? null : _Record[0].TargetName; } + } - /// - /// The execution time of all rules in milliseconds. - /// - internal long Time => _Time; - - /// - /// The total number of rule records. - /// - internal int Total => _Total; - - /// - /// The number of rule records with a error result. - /// - internal int Error => _Error; - - /// - /// The number of rule records with a fail result. - /// - internal int Fail => _Fail; - - /// - /// The number of rules records with a pass result. - /// - internal int Pass => _Pass; - - /// - /// The worst outcome of all rule records. - /// - public RuleOutcome Outcome => _Outcome; - - /// - /// The highest severity level of all rule records. - /// - public SeverityLevel Level => _Level; - - internal string TargetName + internal string TargetType + { + get { - get - { - return IsEmptyRecord() ? null : _Record[0].TargetName; - } + return IsEmptyRecord() ? null : _Record[0].TargetType; } + } - internal string TargetType - { - get - { - return IsEmptyRecord() ? null : _Record[0].TargetType; - } - } + private bool IsEmptyRecord() + { + return _Record == null || _Record.Count == 0; + } - private bool IsEmptyRecord() - { - return _Record == null || _Record.Count == 0; - } + /// + /// Get the individual records for the target object. + /// + /// Returns an enumeration of RuleRecords. + public RuleRecord[] AsRecord() + { + return _Record.ToArray(); + } - /// - /// Get the individual records for the target object. - /// - /// Returns an enumeration of RuleRecords. - public RuleRecord[] AsRecord() - { - return _Record.ToArray(); - } + /// + /// Get an overall pass or fail for the target object. + /// + /// Returns true if object passed and false if object failed. + public bool IsSuccess() + { + return _Outcome == RuleOutcome.Pass || _Outcome == RuleOutcome.None; + } - /// - /// Get an overall pass or fail for the target object. - /// - /// Returns true if object passed and false if object failed. - public bool IsSuccess() - { - return _Outcome == RuleOutcome.Pass || _Outcome == RuleOutcome.None; - } + /// + /// Determines of the target object was processed. + /// + public bool IsProcessed() + { + return _Outcome == RuleOutcome.Pass || _Outcome == RuleOutcome.Fail || _Outcome == RuleOutcome.Error; + } - /// - /// Determines of the target object was processed. - /// - public bool IsProcessed() - { - return _Outcome == RuleOutcome.Pass || _Outcome == RuleOutcome.Fail || _Outcome == RuleOutcome.Error; - } + /// + /// Add a record to the result. + /// + /// The record after processing a rule. + internal void Add(RuleRecord ruleRecord) + { + _Outcome = ruleRecord.Outcome.GetWorstCase(_Outcome); + _Time += ruleRecord.Time; + _Total++; + + if (ruleRecord.Outcome == RuleOutcome.Pass) + _Pass++; + + if (ruleRecord.Outcome == RuleOutcome.Error) + _Error++; - /// - /// Add a record to the result. - /// - /// The record after processing a rule. - internal void Add(RuleRecord ruleRecord) + if (ruleRecord.Outcome == RuleOutcome.Fail) { - _Outcome = ruleRecord.Outcome.GetWorstCase(_Outcome); - _Time += ruleRecord.Time; - _Total++; - - if (ruleRecord.Outcome == RuleOutcome.Pass) - _Pass++; - - if (ruleRecord.Outcome == RuleOutcome.Error) - _Error++; - - if (ruleRecord.Outcome == RuleOutcome.Fail) - { - _Fail++; - _Level = _Level.GetWorstCase(ruleRecord.Level); - } - _Record.Add(ruleRecord); + _Fail++; + _Level = _Level.GetWorstCase(ruleRecord.Level); } + _Record.Add(ruleRecord); } } diff --git a/src/PSRule/Pipeline/InvokeRulePipeline.cs b/src/PSRule/Pipeline/InvokeRulePipeline.cs index 4a4e12427b..0f1cedccdd 100644 --- a/src/PSRule/Pipeline/InvokeRulePipeline.cs +++ b/src/PSRule/Pipeline/InvokeRulePipeline.cs @@ -7,220 +7,219 @@ using PSRule.Host; using PSRule.Rules; -namespace PSRule.Pipeline +namespace PSRule.Pipeline; + +internal sealed class InvokeRulePipeline : RulePipeline, IPipeline { - internal sealed class InvokeRulePipeline : RulePipeline, IPipeline - { - private readonly RuleOutcome _Outcome; - private readonly DependencyGraph _RuleGraph; + private readonly RuleOutcome _Outcome; + private readonly DependencyGraph _RuleGraph; - // A per rule summary of rules that have been processed and the outcome - private readonly Dictionary _Summary; + // A per rule summary of rules that have been processed and the outcome + private readonly Dictionary _Summary; - private readonly bool _IsSummary; - private readonly SuppressionFilter _SuppressionFilter; - private readonly SuppressionFilter _SuppressionGroupFilter; - private readonly List _Completed; + private readonly bool _IsSummary; + private readonly SuppressionFilter _SuppressionFilter; + private readonly SuppressionFilter _SuppressionGroupFilter; + private readonly List _Completed; - // Track whether Dispose has been called. - private bool _Disposed; + // Track whether Dispose has been called. + private bool _Disposed; - internal InvokeRulePipeline(PipelineContext context, Source[] source, IPipelineWriter writer, RuleOutcome outcome) - : base(context, source, context.Reader, writer) - { - _RuleGraph = HostHelper.GetRuleBlockGraph(Source, Context); - RuleCount = _RuleGraph.Count; - if (RuleCount == 0) - Context.WarnRuleNotFound(); - - _Outcome = outcome; - _IsSummary = context.Option.Output.As.Value == ResultFormat.Summary; - _Summary = _IsSummary ? new Dictionary() : null; - var allRuleBlocks = _RuleGraph.GetAll(); - var resourceIndex = new ResourceIndex(allRuleBlocks); - _SuppressionFilter = new SuppressionFilter(Context, context.Option.Suppression, resourceIndex); - _SuppressionGroupFilter = new SuppressionFilter(Pipeline.SuppressionGroup, resourceIndex); - - _Completed = new List(); - } + internal InvokeRulePipeline(PipelineContext context, Source[] source, IPipelineWriter writer, RuleOutcome outcome) + : base(context, source, context.Reader, writer) + { + _RuleGraph = HostHelper.GetRuleBlockGraph(Source, Context); + RuleCount = _RuleGraph.Count; + if (RuleCount == 0) + Context.WarnRuleNotFound(); + + _Outcome = outcome; + _IsSummary = context.Option.Output.As.Value == ResultFormat.Summary; + _Summary = _IsSummary ? new Dictionary() : null; + var allRuleBlocks = _RuleGraph.GetAll(); + var resourceIndex = new ResourceIndex(allRuleBlocks); + _SuppressionFilter = new SuppressionFilter(Context, context.Option.Suppression, resourceIndex); + _SuppressionGroupFilter = new SuppressionFilter(Pipeline.SuppressionGroup, resourceIndex); + + _Completed = new List(); + } - public int RuleCount { get; private set; } + public int RuleCount { get; private set; } - /// - public override void Process(PSObject sourceObject) + /// + public override void Process(PSObject sourceObject) + { + try { - try + Reader.Enqueue(sourceObject); + while (Reader.TryDequeue(out var next)) { - Reader.Enqueue(sourceObject); - while (Reader.TryDequeue(out var next)) - { - var result = ProcessTargetObject(next); - _Completed.Add(result); - Writer.WriteObject(result, false); - } - } - catch (Exception) - { - End(); - throw; + var result = ProcessTargetObject(next); + _Completed.Add(result); + Writer.WriteObject(result, false); } } + catch (Exception) + { + End(); + throw; + } + } - /// - public override void End() + /// + public override void End() + { + if (_Completed.Count > 0) { - if (_Completed.Count > 0) - { - var completed = _Completed.ToArray(); - _Completed.Clear(); - Context.End(completed); - } + var completed = _Completed.ToArray(); + _Completed.Clear(); + Context.End(completed); + } - if (_IsSummary) - Writer.WriteObject(_Summary.Values.Where(r => _Outcome == RuleOutcome.All || (r.Outcome & _Outcome) > 0).ToArray(), true); + if (_IsSummary) + Writer.WriteObject(_Summary.Values.Where(r => _Outcome == RuleOutcome.All || (r.Outcome & _Outcome) > 0).ToArray(), true); - Writer.End(); - } + Writer.End(); + } - private InvokeResult ProcessTargetObject(TargetObject targetObject) + private InvokeResult ProcessTargetObject(TargetObject targetObject) + { + try { - try + Context.EnterTargetObject(targetObject); + var result = new InvokeResult(); + var ruleCounter = 0; + var suppressedRuleCounter = 0; + var suppressionGroupCounter = new Dictionary(new ISuppressionInfoComparer()); + + // Process rule blocks ordered by dependency graph + foreach (var ruleBlockTarget in _RuleGraph.GetSingleTarget()) { - Context.EnterTargetObject(targetObject); - var result = new InvokeResult(); - var ruleCounter = 0; - var suppressedRuleCounter = 0; - var suppressionGroupCounter = new Dictionary(new ISuppressionInfoComparer()); - - // Process rule blocks ordered by dependency graph - foreach (var ruleBlockTarget in _RuleGraph.GetSingleTarget()) + // Enter rule block scope + Context.EnterLanguageScope(ruleBlockTarget.Value.Source); + var ruleRecord = Context.EnterRuleBlock(ruleBlock: ruleBlockTarget.Value); + ruleCounter++; + + try { - // Enter rule block scope - Context.EnterLanguageScope(ruleBlockTarget.Value.Source); - var ruleRecord = Context.EnterRuleBlock(ruleBlock: ruleBlockTarget.Value); - ruleCounter++; + if (Context.Binding.ShouldFilter) + continue; - try + // Check if dependency failed + if (ruleBlockTarget.Skipped) { - if (Context.Binding.ShouldFilter) - continue; - - // Check if dependency failed - if (ruleBlockTarget.Skipped) - { - ruleRecord.OutcomeReason = RuleOutcomeReason.DependencyFail; - } - // Check for suppression - else if (_SuppressionFilter.Match(id: ruleBlockTarget.Value.Id, targetName: ruleRecord.TargetName)) - { - ruleRecord.OutcomeReason = RuleOutcomeReason.Suppressed; - suppressedRuleCounter++; - - if (!_IsSummary) - Context.RuleSuppressed(ruleId: ruleRecord.RuleId); - } - // Check for suppression group - else if (_SuppressionGroupFilter.TrySuppressionGroup(ruleId: ruleRecord.RuleId, targetObject, out var suppression)) - { - ruleRecord.OutcomeReason = RuleOutcomeReason.Suppressed; - if (!_IsSummary) - Context.RuleSuppressionGroup(ruleId: ruleRecord.RuleId, suppression); - else - suppressionGroupCounter[suppression] = suppressionGroupCounter.TryGetValue(suppression, out var count) ? ++count : 1; - } - else - { - HostHelper.InvokeRuleBlock(context: Context, ruleBlock: ruleBlockTarget.Value, ruleRecord: ruleRecord); - if (ruleRecord.OutcomeReason == RuleOutcomeReason.PreconditionFail) - ruleCounter--; - } - - // Report outcome to dependency graph - if (ruleRecord.Outcome == RuleOutcome.Pass) - { - ruleBlockTarget.Pass(); - Context.Pass(); - } - else if (ruleRecord.Outcome == RuleOutcome.Fail) - { - Result.HadFailures = true; - ruleBlockTarget.Fail(); - Context.Fail(); - } - else if (ruleRecord.Outcome == RuleOutcome.Error) - { - Result.HadErrors = true; - ruleBlockTarget.Fail(); - } - - AddToSummary(ruleBlock: ruleBlockTarget.Value, outcome: ruleRecord.Outcome); + ruleRecord.OutcomeReason = RuleOutcomeReason.DependencyFail; } - finally + // Check for suppression + else if (_SuppressionFilter.Match(id: ruleBlockTarget.Value.Id, targetName: ruleRecord.TargetName)) { - // Exit rule block scope - Context.ExitRuleBlock(); - if (ShouldOutput(ruleRecord.Outcome)) - result.Add(ruleRecord); + ruleRecord.OutcomeReason = RuleOutcomeReason.Suppressed; + suppressedRuleCounter++; - Context.ExitLanguageScope(ruleBlockTarget.Value.Source); + if (!_IsSummary) + Context.RuleSuppressed(ruleId: ruleRecord.RuleId); + } + // Check for suppression group + else if (_SuppressionGroupFilter.TrySuppressionGroup(ruleId: ruleRecord.RuleId, targetObject, out var suppression)) + { + ruleRecord.OutcomeReason = RuleOutcomeReason.Suppressed; + if (!_IsSummary) + Context.RuleSuppressionGroup(ruleId: ruleRecord.RuleId, suppression); + else + suppressionGroupCounter[suppression] = suppressionGroupCounter.TryGetValue(suppression, out var count) ? ++count : 1; + } + else + { + HostHelper.InvokeRuleBlock(context: Context, ruleBlock: ruleBlockTarget.Value, ruleRecord: ruleRecord); + if (ruleRecord.OutcomeReason == RuleOutcomeReason.PreconditionFail) + ruleCounter--; } - } - if (ruleCounter == 0) - Context.WarnObjectNotProcessed(); + // Report outcome to dependency graph + if (ruleRecord.Outcome == RuleOutcome.Pass) + { + ruleBlockTarget.Pass(); + Context.Pass(); + } + else if (ruleRecord.Outcome == RuleOutcome.Fail) + { + Result.HadFailures = true; + ruleBlockTarget.Fail(); + Context.Fail(); + } + else if (ruleRecord.Outcome == RuleOutcome.Error) + { + Result.HadErrors = true; + ruleBlockTarget.Fail(); + } - if (_IsSummary) + AddToSummary(ruleBlock: ruleBlockTarget.Value, outcome: ruleRecord.Outcome); + } + finally { - if (suppressedRuleCounter > 0) - Context.WarnRuleCountSuppressed(ruleCount: suppressedRuleCounter); + // Exit rule block scope + Context.ExitRuleBlock(); + if (ShouldOutput(ruleRecord.Outcome)) + result.Add(ruleRecord); - foreach (var keyValuePair in suppressionGroupCounter) - Context.RuleSuppressionGroupCount(suppression: keyValuePair.Key, count: keyValuePair.Value); + Context.ExitLanguageScope(ruleBlockTarget.Value.Source); } - return result; } - finally + + if (ruleCounter == 0) + Context.WarnObjectNotProcessed(); + + if (_IsSummary) { - Context.ExitTargetObject(); + if (suppressedRuleCounter > 0) + Context.WarnRuleCountSuppressed(ruleCount: suppressedRuleCounter); + + foreach (var keyValuePair in suppressionGroupCounter) + Context.RuleSuppressionGroupCount(suppression: keyValuePair.Key, count: keyValuePair.Value); } + return result; } - - private bool ShouldOutput(RuleOutcome outcome) + finally { - return _Outcome == RuleOutcome.All || (outcome & _Outcome) > 0; + Context.ExitTargetObject(); } + } - /// - /// Add rule result to summary. - /// - private void AddToSummary(RuleBlock ruleBlock, RuleOutcome outcome) - { - if (!_IsSummary || ruleBlock == null) - return; + private bool ShouldOutput(RuleOutcome outcome) + { + return _Outcome == RuleOutcome.All || (outcome & _Outcome) > 0; + } - if (!_Summary.TryGetValue(ruleBlock.Id.Value, out var s)) - { - s = new RuleSummaryRecord( - ruleId: ruleBlock.Id.Value, - ruleName: ruleBlock.Name, - tag: ruleBlock.Tag, - info: ruleBlock.Info - ); - _Summary.Add(ruleBlock.Id.Value, s); - } - s.Add(outcome); + /// + /// Add rule result to summary. + /// + private void AddToSummary(RuleBlock ruleBlock, RuleOutcome outcome) + { + if (!_IsSummary || ruleBlock == null) + return; + + if (!_Summary.TryGetValue(ruleBlock.Id.Value, out var s)) + { + s = new RuleSummaryRecord( + ruleId: ruleBlock.Id.Value, + ruleName: ruleBlock.Name, + tag: ruleBlock.Tag, + info: ruleBlock.Info + ); + _Summary.Add(ruleBlock.Id.Value, s); } + s.Add(outcome); + } - protected override void Dispose(bool disposing) + protected override void Dispose(bool disposing) + { + if (!_Disposed) { - if (!_Disposed) - { - if (disposing) - _RuleGraph.Dispose(); + if (disposing) + _RuleGraph.Dispose(); - _Disposed = true; - } - base.Dispose(disposing); + _Disposed = true; } + base.Dispose(disposing); } } diff --git a/src/PSRule/Pipeline/ModulePathComparer.cs b/src/PSRule/Pipeline/ModulePathComparer.cs index af0b270b60..14ea16f624 100644 --- a/src/PSRule/Pipeline/ModulePathComparer.cs +++ b/src/PSRule/Pipeline/ModulePathComparer.cs @@ -3,22 +3,21 @@ using PSRule.Data; -namespace PSRule.Pipeline +namespace PSRule.Pipeline; + +/// +/// Sort paths by versions in decending order. Use orginal order when versions are compariable. +/// +internal sealed class ModulePathComparer : IComparer { - /// - /// Sort paths by versions in decending order. Use orginal order when versions are compariable. - /// - internal sealed class ModulePathComparer : IComparer + /// + public int Compare(string x, string y) { - /// - public int Compare(string x, string y) - { - var x_name = Path.GetFileName(x); - var y_name = Path.GetFileName(y); - if (!SemanticVersion.TryParseVersion(x_name, out var x_version)) - return SemanticVersion.TryParseVersion(y_name, out _) ? 1 : 0; + var x_name = Path.GetFileName(x); + var y_name = Path.GetFileName(y); + if (!SemanticVersion.TryParseVersion(x_name, out var x_version)) + return SemanticVersion.TryParseVersion(y_name, out _) ? 1 : 0; - return !SemanticVersion.TryParseVersion(y_name, out var y_version) ? -1 : y_version.CompareTo(x_version); - } + return !SemanticVersion.TryParseVersion(y_name, out var y_version) ? -1 : y_version.CompareTo(x_version); } } diff --git a/src/PSRule/Pipeline/OptionContext.cs b/src/PSRule/Pipeline/OptionContext.cs index f773726aa0..f4ade6d621 100644 --- a/src/PSRule/Pipeline/OptionContext.cs +++ b/src/PSRule/Pipeline/OptionContext.cs @@ -5,70 +5,69 @@ using PSRule.Definitions; using PSRule.Options; -namespace PSRule.Pipeline +namespace PSRule.Pipeline; + +internal sealed class OptionContext { - internal sealed class OptionContext - { - private ConventionOption _Convention; - private List _ConventionOrder; + private ConventionOption _Convention; + private List _ConventionOrder; - public OptionContext() - { + public OptionContext() + { - } + } - public Options.BaselineOption Baseline { get; set; } + public Options.BaselineOption Baseline { get; set; } - public BindingOption Binding { get; set; } + public BindingOption Binding { get; set; } - public ConfigurationOption Configuration { get; set; } + public ConfigurationOption Configuration { get; set; } - public ConventionOption Convention + public ConventionOption Convention + { + get { - get - { - return _Convention; - } - set - { - _Convention = value; - _ConventionOrder = null; - } + return _Convention; } + set + { + _Convention = value; + _ConventionOrder = null; + } + } - public ExecutionOption Execution { get; set; } + public ExecutionOption Execution { get; set; } - public IncludeOption Include { get; set; } + public IncludeOption Include { get; set; } - public InputOption Input { get; set; } + public InputOption Input { get; set; } - public LoggingOption Logging { get; set; } + public LoggingOption Logging { get; set; } - public OutputOption Output { get; set; } + public OutputOption Output { get; set; } - public RepositoryOption Repository { get; set; } + public RepositoryOption Repository { get; set; } - public RequiresOption Requires { get; set; } + public RequiresOption Requires { get; set; } - public RuleOption Rule { get; set; } + public RuleOption Rule { get; set; } - public SuppressionOption Suppression { get; set; } + public SuppressionOption Suppression { get; set; } - public IResourceFilter ConventionFilter { get; set; } + public IResourceFilter ConventionFilter { get; set; } - public IResourceFilter RuleFilter { get; set; } + public IResourceFilter RuleFilter { get; set; } - internal int GetConventionOrder(IConvention convention) - { - if (Convention?.Include == null || Convention.Include.Length == 0) - return -1; + 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); + _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; - } + return index > -1 ? index : int.MaxValue; } } diff --git a/src/PSRule/Pipeline/OptionContextBuilder.cs b/src/PSRule/Pipeline/OptionContextBuilder.cs index 34e3fcb7a5..c3cc8177f9 100644 --- a/src/PSRule/Pipeline/OptionContextBuilder.cs +++ b/src/PSRule/Pipeline/OptionContextBuilder.cs @@ -11,213 +11,212 @@ using PSRule.Options; using PSRule.Runtime; -namespace PSRule.Pipeline +namespace PSRule.Pipeline; + +/// +/// A helper to create an . +/// +internal sealed class OptionContextBuilder { + 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); + } + /// - /// A helper to create an . + /// Create a builder with parameter and workspace options set. /// - internal sealed class OptionContextBuilder + /// The workspace options. + /// A list of rule identifiers to include set by parameters. If not set all rules that meet filters are included. + /// 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) { - private readonly Dictionary _ModuleBaselineScope; - private readonly List _Scopes; - private readonly OptionScopeComparer _Comparer; - private readonly string[] _DefaultCulture; - private readonly List _ConventionOrder; + Workspace(option); + } - 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); - } + /// + /// Build an . + /// + internal OptionContext Build(string languageScope) + { + languageScope = LanguageScope.Normalize(languageScope); + var context = new OptionContext(); - /// - /// Create a builder with parameter and workspace options set. - /// - /// The workspace options. - /// A list of rule identifiers to include set by parameters. If not set all rules that meet filters are included. - /// 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) - { - Workspace(option); - } + _Scopes.Sort(_Comparer); - /// - /// Build an . - /// - internal OptionContext Build(string languageScope) + for (var i = 0; i < _Scopes.Count; i++) { - 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; + 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; - public bool ContainsBaseline(string baselineId) + context.RuleFilter = GetRuleFilter(context.Rule); + context.ConventionFilter = GetConventionFilter(languageScope, _Scopes); + context.Convention = new ConventionOption { - return _ModuleBaselineScope.ContainsKey(baselineId); - } + Include = GetConventions(_Scopes) + }; + return context; + } - /// - /// Check for any obsolete resources and log warnings. - /// - internal void CheckObsolete(ILogger logger) - { - if (logger == null) - return; + public bool ContainsBaseline(string baselineId) + { + return _ModuleBaselineScope.ContainsKey(baselineId); + } - foreach (var kv in _ModuleBaselineScope) - { - if (kv.Value) - logger.WarnResourceObsolete(ResourceKind.Baseline, kv.Key); - } - } + /// + /// Check for any obsolete resources and log warnings. + /// + internal void CheckObsolete(ILogger logger) + { + if (logger == null) + return; - private void Parameter(string[] ruleInclude, Hashtable ruleTag, string[] conventionInclude) + foreach (var kv in _ModuleBaselineScope) { - _Scopes.Add(OptionScope.FromParameters(ruleInclude, ruleTag, conventionInclude)); + if (kv.Value) + logger.WarnResourceObsolete(ResourceKind.Baseline, kv.Key); } + } - internal void Workspace(PSRuleOption option) - { - _Scopes.Add(OptionScope.FromWorkspace(option)); - } + private void Parameter(string[] ruleInclude, Hashtable ruleTag, string[] conventionInclude) + { + _Scopes.Add(OptionScope.FromParameters(ruleInclude, ruleTag, conventionInclude)); + } - 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 Workspace(PSRuleOption option) + { + _Scopes.Add(OptionScope.FromWorkspace(option)); + } - internal void ModuleConfig(string module, ModuleConfigV1Spec spec) - { - _Scopes.Add(OptionScope.FromModuleConfig(module, spec)); - } + 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)); + } - private static bool ShouldCombine(string languageScope, OptionScope optionScope) - { - return optionScope.LanguageScope == LanguageScope.STANDALONE_SCOPENAME || optionScope.LanguageScope == languageScope || optionScope.Type == ScopeType.Explicit; - } + internal void ModuleConfig(string module, ModuleConfigV1Spec spec) + { + _Scopes.Add(OptionScope.FromModuleConfig(module, spec)); + } - /// - /// Combine the specified with an existing . - /// - private static void Combine(OptionContext context, OptionScope optionScope) - { - 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 bool ShouldCombine(string languageScope, OptionScope optionScope) + { + return optionScope.LanguageScope == LanguageScope.STANDALONE_SCOPENAME || optionScope.LanguageScope == languageScope || optionScope.Type == ScopeType.Explicit; + } - 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); - } + /// + /// Combine the specified with an existing . + /// + private static void Combine(OptionContext context, OptionScope optionScope) + { + 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 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]); + 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); + } - //for (var i = 0; _WorkspaceConfig?.Convention?.Include != null && i < _WorkspaceConfig.Convention.Include.Length; i++) - // include.Add(_WorkspaceConfig.Convention.Include[i]); + 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; _ModuleConfig?.Convention?.Include != null && i < _ModuleConfig.Convention.Include.Length; i++) - // include.Add(ResourceHelper.GetIdString(_ModuleConfig.LanguageScope, _ModuleConfig.Convention.Include[i])); + //for (var i = 0; _WorkspaceConfig?.Convention?.Include != null && i < _WorkspaceConfig.Convention.Include.Length; i++) + // include.Add(_WorkspaceConfig.Convention.Include[i]); - return new ConventionFilter(GetConventions(scopes)); - } + //for (var i = 0; _ModuleConfig?.Convention?.Include != null && i < _ModuleConfig.Convention.Include.Length; i++) + // include.Add(ResourceHelper.GetIdString(_ModuleConfig.LanguageScope, _ModuleConfig.Convention.Include[i])); - private static string[] GetConventions(List scopes) + return new ConventionFilter(GetConventions(scopes)); + } + + private static string[] GetConventions(List scopes) + { + var include = new List(); + for (var i = 0; i < scopes.Count; i++) { - 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++) { - 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); + if (scopes[i].Type == ScopeType.Module) + add[j] = ResourceHelper.GetIdString(scopes[i].LanguageScope, add[j]); } - return include.ToArray(); + include.AddUnique(add); } + return include.ToArray(); + } - private bool? GetIncludeLocal(List scopes) + private bool? GetIncludeLocal(List scopes) + { + for (var i = 0; i < scopes.Count; i++) { - 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; + if (scopes[i].Type == ScopeType.Workspace && scopes[i].Rule != null && scopes[i].Rule.IncludeLocal.HasValue) + return scopes[i].Rule.IncludeLocal.Value; } + return null; + } - private static string[] GetDefaultCulture() - { - var result = new List(); - var set = new HashSet(); + 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(); + // 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 index a1db55e311..58abe492d4 100644 --- a/src/PSRule/Pipeline/OptionScope.cs +++ b/src/PSRule/Pipeline/OptionScope.cs @@ -8,165 +8,164 @@ using PSRule.Definitions.ModuleConfigs; using PSRule.Options; -namespace PSRule.Pipeline +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 { - internal enum ScopeType + public readonly ScopeType Type; + public readonly string LanguageScope; + + private OptionScope(ScopeType type, string languageScope) { - /// - /// 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 + Type = type; + LanguageScope = Runtime.LanguageScope.Normalize(languageScope); } - internal class OptionScope - { - public readonly ScopeType Type; - public readonly string LanguageScope; + public Options.BaselineOption Baseline { get; set; } - private OptionScope(ScopeType type, string languageScope) - { - Type = type; - LanguageScope = Runtime.LanguageScope.Normalize(languageScope); - } + public BindingOption Binding { get; set; } - public Options.BaselineOption Baseline { get; set; } + public ConfigurationOption Configuration { get; set; } - public BindingOption Binding { get; set; } + public ConventionOption Convention { get; set; } - public ConfigurationOption Configuration { get; set; } + public ExecutionOption Execution { get; set; } - public ConventionOption Convention { get; set; } + public IncludeOption Include { get; set; } - public ExecutionOption Execution { get; set; } + public InputOption Input { get; set; } - public IncludeOption Include { get; set; } + public LoggingOption Logging { get; set; } - public InputOption Input { get; set; } + public OutputOption Output { get; set; } - public LoggingOption Logging { get; set; } + public RepositoryOption Repository { get; set; } - public OutputOption Output { get; set; } + public RequiresOption Requires { get; set; } - public RepositoryOption Repository { get; set; } + public RuleOption Rule { get; set; } - public RequiresOption Requires { get; set; } + public SuppressionOption Suppression { get; set; } - public RuleOption Rule { get; set; } + public static OptionScope FromParameters(string[] ruleInclude, Hashtable ruleTag, string[] conventionInclude) + { + bool? includeLocal = ruleInclude == null && ruleTag == null ? null : false; - public SuppressionOption Suppression { get; set; } + return new OptionScope(ScopeType.Parameter, null) + { + Rule = new RuleOption + { + Include = ruleInclude, + Tag = ruleTag, + IncludeLocal = includeLocal, + }, + Convention = new ConventionOption + { + Include = conventionInclude + } + }; + } - public static OptionScope FromParameters(string[] ruleInclude, Hashtable ruleTag, string[] conventionInclude) + public static OptionScope FromWorkspace(PSRuleOption option) + { + return new OptionScope(ScopeType.Workspace, null) { - bool? includeLocal = ruleInclude == null && ruleTag == null ? null : false; + 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 + }; + } - 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) + public static OptionScope FromModuleConfig(string module, ModuleConfigV1Spec spec) + { + return new OptionScope(ScopeType.Module, module) { - return new OptionScope(ScopeType.Workspace, null) + Binding = spec.Binding, + Configuration = spec.Configuration, + Convention = ApplyScope(module, spec.Convention), + Output = new OutputOption { - 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) + Culture = spec.Output?.Culture + }, + Rule = new RuleOption { - 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) + Baseline = spec.Rule?.Baseline + } + }; + } + + internal static OptionScope FromBaseline(ScopeType type, string baselineId, string module, BaselineSpec spec, bool obsolete) + { + return new OptionScope(type, module) { - return new OptionScope(type, module) + Binding = spec.Binding, + Configuration = spec.Configuration, + Rule = new RuleOption { - 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; + Include = spec.Rule?.Include, + Exclude = spec.Rule?.Exclude, + Tag = spec.Rule?.Tag, + Labels = spec.Rule?.Labels, + IncludeLocal = type == ScopeType.Explicit ? false : null + } + }; + } - for (var i = 0; i < include.Length; i++) - include[i] = ResourceHelper.GetIdString(scope, include[i]); + private static string[] GetConventions(string scope, string[] include) + { + if (include == null || include.Length == 0) + return null; - return include; - } + for (var i = 0; i < include.Length; i++) + include[i] = ResourceHelper.GetIdString(scope, include[i]); - private static ConventionOption ApplyScope(string scope, ConventionOption option) - { - if (option == null || option.Include == null || option.Include.Length == 0) - return option; + return include; + } - option.Include = GetConventions(scope, option.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 index 70b4080d6c..329fd67725 100644 --- a/src/PSRule/Pipeline/OptionScopeComparer.cs +++ b/src/PSRule/Pipeline/OptionScopeComparer.cs @@ -1,20 +1,19 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -namespace PSRule.Pipeline +namespace PSRule.Pipeline; + +/// +/// A comparer to sort based on precedence. +/// +internal sealed class OptionScopeComparer : IComparer { - /// - /// A comparer to sort based on precedence. - /// - internal sealed class OptionScopeComparer : IComparer + public int Compare(OptionScope x, OptionScope y) { - 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; - } + 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/Output/CsvOutputWriter.cs b/src/PSRule/Pipeline/Output/CsvOutputWriter.cs index c14856d094..d3ac2dfad5 100644 --- a/src/PSRule/Pipeline/Output/CsvOutputWriter.cs +++ b/src/PSRule/Pipeline/Output/CsvOutputWriter.cs @@ -7,138 +7,137 @@ using PSRule.Resources; using PSRule.Rules; -namespace PSRule.Pipeline.Output +namespace PSRule.Pipeline.Output; + +internal sealed class CsvOutputWriter : SerializationOutputWriter { - internal sealed class CsvOutputWriter : SerializationOutputWriter + private const char COMMA = ','; + private const char QUOTE = '"'; + + private readonly StringBuilder _Builder; + + internal CsvOutputWriter(PipelineWriter inner, PSRuleOption option, ShouldProcess shouldProcess) + : base(inner, option, shouldProcess) { - private const char COMMA = ','; - private const char QUOTE = '"'; + _Builder = new StringBuilder(); + } - private readonly StringBuilder _Builder; + protected override string Serialize(object[] o) + { + WriteHeader(); + if (Option.Output.As == ResultFormat.Detail) + WriteDetail(o); + else + WriteSummary(o); - internal CsvOutputWriter(PipelineWriter inner, PSRuleOption option, ShouldProcess shouldProcess) - : base(inner, option, shouldProcess) - { - _Builder = new StringBuilder(); - } + return _Builder.ToString(); + } - protected override string Serialize(object[] o) - { - WriteHeader(); - if (Option.Output.As == ResultFormat.Detail) - WriteDetail(o); - else - WriteSummary(o); + private void WriteSummary(object[] o) + { + for (var i = 0; i < o.Length; i++) + if (o[i] is RuleSummaryRecord record) + VisitSummaryRecord(record); + } - return _Builder.ToString(); - } + private void WriteDetail(object[] o) + { + for (var i = 0; i < o.Length; i++) + if (o[i] is RuleRecord record) + VisitRecord(record); + } - private void WriteSummary(object[] o) - { - for (var i = 0; i < o.Length; i++) - if (o[i] is RuleSummaryRecord record) - VisitSummaryRecord(record); - } + private void WriteHeader() + { + _Builder.Append(ViewStrings.RuleName); + _Builder.Append(COMMA); - private void WriteDetail(object[] o) + if (Option.Output.As == ResultFormat.Summary) { - for (var i = 0; i < o.Length; i++) - if (o[i] is RuleRecord record) - VisitRecord(record); + _Builder.Append(ViewStrings.Pass); + _Builder.Append(COMMA); + _Builder.Append(ViewStrings.Fail); } - - private void WriteHeader() + else { - _Builder.Append(ViewStrings.RuleName); + _Builder.Append(ViewStrings.TargetName); _Builder.Append(COMMA); - - if (Option.Output.As == ResultFormat.Summary) - { - _Builder.Append(ViewStrings.Pass); - _Builder.Append(COMMA); - _Builder.Append(ViewStrings.Fail); - } - else - { - _Builder.Append(ViewStrings.TargetName); - _Builder.Append(COMMA); - _Builder.Append(ViewStrings.TargetType); - } - - _Builder.Append(COMMA); - _Builder.Append(ViewStrings.Outcome); - - if (Option.Output.As == ResultFormat.Detail) - { - _Builder.Append(COMMA); - _Builder.Append(ViewStrings.OutcomeReason); - } - - _Builder.Append(COMMA); - _Builder.Append(ViewStrings.Synopsis); - _Builder.Append(COMMA); - _Builder.Append(ViewStrings.Recommendation); - _Builder.Append(System.Environment.NewLine); + _Builder.Append(ViewStrings.TargetType); } - private void VisitRecord(RuleRecord record) - { - if (record == null) - return; + _Builder.Append(COMMA); + _Builder.Append(ViewStrings.Outcome); - WriteColumn(record.RuleName); - _Builder.Append(COMMA); - WriteColumn(record.TargetName); - _Builder.Append(COMMA); - WriteColumn(record.TargetType); - _Builder.Append(COMMA); - WriteColumn(record.Outcome.ToString()); - _Builder.Append(COMMA); - WriteColumn(record.OutcomeReason.ToString()); - _Builder.Append(COMMA); - WriteColumn(record.Info.Synopsis); + if (Option.Output.As == ResultFormat.Detail) + { _Builder.Append(COMMA); - WriteColumn(record.Info.Recommendation); - _Builder.Append(System.Environment.NewLine); + _Builder.Append(ViewStrings.OutcomeReason); } - private void VisitSummaryRecord(RuleSummaryRecord record) - { - if (record == null) - return; + _Builder.Append(COMMA); + _Builder.Append(ViewStrings.Synopsis); + _Builder.Append(COMMA); + _Builder.Append(ViewStrings.Recommendation); + _Builder.Append(System.Environment.NewLine); + } - WriteColumn(record.RuleName); - _Builder.Append(COMMA); - _Builder.Append(record.Pass); - _Builder.Append(COMMA); - _Builder.Append(record.Fail); - _Builder.Append(COMMA); - WriteColumn(record.Outcome.ToString()); - _Builder.Append(COMMA); - WriteColumn(record.Info.Synopsis); - _Builder.Append(COMMA); - WriteColumn(record.Info.Recommendation); - _Builder.Append(System.Environment.NewLine); - } + private void VisitRecord(RuleRecord record) + { + if (record == null) + return; + + WriteColumn(record.RuleName); + _Builder.Append(COMMA); + WriteColumn(record.TargetName); + _Builder.Append(COMMA); + WriteColumn(record.TargetType); + _Builder.Append(COMMA); + WriteColumn(record.Outcome.ToString()); + _Builder.Append(COMMA); + WriteColumn(record.OutcomeReason.ToString()); + _Builder.Append(COMMA); + WriteColumn(record.Info.Synopsis); + _Builder.Append(COMMA); + WriteColumn(record.Info.Recommendation); + _Builder.Append(System.Environment.NewLine); + } - private void WriteColumn(string value) - { - if (string.IsNullOrEmpty(value)) - return; + private void VisitSummaryRecord(RuleSummaryRecord record) + { + if (record == null) + return; + + WriteColumn(record.RuleName); + _Builder.Append(COMMA); + _Builder.Append(record.Pass); + _Builder.Append(COMMA); + _Builder.Append(record.Fail); + _Builder.Append(COMMA); + WriteColumn(record.Outcome.ToString()); + _Builder.Append(COMMA); + WriteColumn(record.Info.Synopsis); + _Builder.Append(COMMA); + WriteColumn(record.Info.Recommendation); + _Builder.Append(System.Environment.NewLine); + } - _Builder.Append(QUOTE); - _Builder.Append(value.Replace("\"", "\"\"")); - _Builder.Append(QUOTE); - } + private void WriteColumn(string value) + { + if (string.IsNullOrEmpty(value)) + return; - private void WriteColumn(InfoString value) - { - if (!value.HasValue) - return; + _Builder.Append(QUOTE); + _Builder.Append(value.Replace("\"", "\"\"")); + _Builder.Append(QUOTE); + } - _Builder.Append(QUOTE); - _Builder.Append(value.Text.Replace("\"", "\"\"")); - _Builder.Append(QUOTE); - } + private void WriteColumn(InfoString value) + { + if (!value.HasValue) + return; + + _Builder.Append(QUOTE); + _Builder.Append(value.Text.Replace("\"", "\"\"")); + _Builder.Append(QUOTE); } } diff --git a/src/PSRule/Pipeline/Output/FileOutputWriter.cs b/src/PSRule/Pipeline/Output/FileOutputWriter.cs index e84f3e576b..91bf4f195f 100644 --- a/src/PSRule/Pipeline/Output/FileOutputWriter.cs +++ b/src/PSRule/Pipeline/Output/FileOutputWriter.cs @@ -6,59 +6,58 @@ using PSRule.Configuration; using PSRule.Resources; -namespace PSRule.Pipeline.Output +namespace PSRule.Pipeline.Output; + +/// +/// An output writer that writes output to disk. +/// +internal sealed class FileOutputWriter : PipelineWriter { - /// - /// An output writer that writes output to disk. - /// - internal sealed class FileOutputWriter : PipelineWriter + private const string Source = "PSRule"; + + private readonly Encoding _Encoding; + private readonly string _Path; + private readonly bool _WriteHost; + + internal FileOutputWriter(PipelineWriter inner, PSRuleOption option, Encoding encoding, string path, ShouldProcess shouldProcess, bool writeHost) + : base(inner, option, shouldProcess) { - private const string Source = "PSRule"; + _Encoding = encoding; + _Path = path; + _WriteHost = writeHost; + } - private readonly Encoding _Encoding; - private readonly string _Path; - private readonly bool _WriteHost; + public override void WriteObject(object sendToPipeline, bool enumerateCollection) + { + WriteToFile(sendToPipeline); + } - internal FileOutputWriter(PipelineWriter inner, PSRuleOption option, Encoding encoding, string path, ShouldProcess shouldProcess, bool writeHost) - : base(inner, option, shouldProcess) + private void WriteToFile(object o) + { + var rootedPath = Environment.GetRootedPath(_Path); + if (CreateFile(rootedPath)) { - _Encoding = encoding; - _Path = path; - _WriteHost = writeHost; + File.WriteAllText(path: rootedPath, contents: o.ToString(), encoding: _Encoding); + InfoOutputPath(rootedPath); } + } - public override void WriteObject(object sendToPipeline, bool enumerateCollection) - { - WriteToFile(sendToPipeline); - } + private void InfoOutputPath(string rootedPath) + { + if (!Option.Output.Footer.GetValueOrDefault(OutputOption.Default.Footer.Value).HasFlag(FooterFormat.OutputFile)) + return; - private void WriteToFile(object o) + var message = string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.InfoOutputPath, rootedPath); + if (_WriteHost) { - var rootedPath = Environment.GetRootedPath(_Path); - if (CreateFile(rootedPath)) + WriteHost(new HostInformationMessage { - File.WriteAllText(path: rootedPath, contents: o.ToString(), encoding: _Encoding); - InfoOutputPath(rootedPath); - } + Message = message + }); } - - private void InfoOutputPath(string rootedPath) + else { - if (!Option.Output.Footer.GetValueOrDefault(OutputOption.Default.Footer.Value).HasFlag(FooterFormat.OutputFile)) - return; - - var message = string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.InfoOutputPath, rootedPath); - if (_WriteHost) - { - WriteHost(new HostInformationMessage - { - Message = message - }); - } - else - { - WriteInformation(new InformationRecord(message, Source)); - } + WriteInformation(new InformationRecord(message, Source)); } } } diff --git a/src/PSRule/Pipeline/Output/HostPipelineWriter.cs b/src/PSRule/Pipeline/Output/HostPipelineWriter.cs index c9c7d89957..e6cdacadb0 100644 --- a/src/PSRule/Pipeline/Output/HostPipelineWriter.cs +++ b/src/PSRule/Pipeline/Output/HostPipelineWriter.cs @@ -5,216 +5,215 @@ using PSRule.Configuration; using PSRule.Rules; -namespace PSRule.Pipeline.Output +namespace PSRule.Pipeline.Output; + +/// +/// An output writer that returns output to the host PowerShell runspace. +/// +internal sealed class HostPipelineWriter : PipelineWriter { - /// - /// An output writer that returns output to the host PowerShell runspace. - /// - internal sealed class HostPipelineWriter : PipelineWriter - { - private const string Source = "PSRule"; - private const string HostTag = "PSHOST"; + private const string Source = "PSRule"; + private const string HostTag = "PSHOST"; - private Action _OnWriteWarning; - private Action _OnWriteVerbose; - private Action _OnWriteError; - private Action _OnWriteInformation; - private Action _OnWriteDebug; - internal Action OnWriteObject; + private Action _OnWriteWarning; + private Action _OnWriteVerbose; + private Action _OnWriteError; + private Action _OnWriteInformation; + private Action _OnWriteDebug; + internal Action OnWriteObject; - private bool _LogError; - private bool _LogWarning; - private bool _LogVerbose; - private bool _LogInformation; - private bool _LogDebug; + private bool _LogError; + private bool _LogWarning; + private bool _LogVerbose; + private bool _LogInformation; + private bool _LogDebug; - private HashSet _VerboseFilter; - private HashSet _DebugFilter; + private HashSet _VerboseFilter; + private HashSet _DebugFilter; - private string _ScopeName; + private string _ScopeName; - internal HostPipelineWriter(IHostContext hostContext, PSRuleOption option, ShouldProcess shouldProcess) - : base(null, option, shouldProcess) + internal HostPipelineWriter(IHostContext hostContext, PSRuleOption option, ShouldProcess shouldProcess) + : base(null, option, shouldProcess) + { + if (hostContext != null) { - if (hostContext != null) - { - UseCommandRuntime(hostContext); - UseExecutionContext(hostContext); - } + UseCommandRuntime(hostContext); + UseExecutionContext(hostContext); } + } - public override void Begin() - { - if (Option.Logging.LimitVerbose != null && Option.Logging.LimitVerbose.Length > 0) - _VerboseFilter = new HashSet(Option.Logging.LimitVerbose); + public override void Begin() + { + if (Option.Logging.LimitVerbose != null && Option.Logging.LimitVerbose.Length > 0) + _VerboseFilter = new HashSet(Option.Logging.LimitVerbose); - if (Option.Logging.LimitDebug != null && Option.Logging.LimitDebug.Length > 0) - _DebugFilter = new HashSet(Option.Logging.LimitDebug); - } + if (Option.Logging.LimitDebug != null && Option.Logging.LimitDebug.Length > 0) + _DebugFilter = new HashSet(Option.Logging.LimitDebug); + } - private void UseCommandRuntime(IHostContext hostContext) - { - if (hostContext == null) - return; - - _OnWriteVerbose = hostContext.Verbose; - _OnWriteWarning = hostContext.Warning; - _OnWriteError = hostContext.Error; - _OnWriteInformation = hostContext.Information; - _OnWriteDebug = hostContext.Debug; - OnWriteObject = hostContext.Object; - } + private void UseCommandRuntime(IHostContext hostContext) + { + if (hostContext == null) + return; + + _OnWriteVerbose = hostContext.Verbose; + _OnWriteWarning = hostContext.Warning; + _OnWriteError = hostContext.Error; + _OnWriteInformation = hostContext.Information; + _OnWriteDebug = hostContext.Debug; + OnWriteObject = hostContext.Object; + } - private void UseExecutionContext(IHostContext hostContext) - { - if (hostContext == null) - return; - - _LogError = GetPreferenceVariable(hostContext, ErrorPreference); - _LogWarning = GetPreferenceVariable(hostContext, WarningPreference); - _LogVerbose = GetPreferenceVariable(hostContext, VerbosePreference); - _LogInformation = GetPreferenceVariable(hostContext, InformationPreference); - _LogDebug = GetPreferenceVariable(hostContext, DebugPreference); - } + private void UseExecutionContext(IHostContext hostContext) + { + if (hostContext == null) + return; + + _LogError = GetPreferenceVariable(hostContext, ErrorPreference); + _LogWarning = GetPreferenceVariable(hostContext, WarningPreference); + _LogVerbose = GetPreferenceVariable(hostContext, VerbosePreference); + _LogInformation = GetPreferenceVariable(hostContext, InformationPreference); + _LogDebug = GetPreferenceVariable(hostContext, DebugPreference); + } - private static bool GetPreferenceVariable(IHostContext hostContext, string variableName) - { - var preference = hostContext.GetPreferenceVariable(variableName); - return preference != ActionPreference.Ignore && !(preference == ActionPreference.SilentlyContinue && ( - variableName == VerbosePreference || - variableName == DebugPreference) - ); - } + private static bool GetPreferenceVariable(IHostContext hostContext, string variableName) + { + var preference = hostContext.GetPreferenceVariable(variableName); + return preference != ActionPreference.Ignore && !(preference == ActionPreference.SilentlyContinue && ( + variableName == VerbosePreference || + variableName == DebugPreference) + ); + } - #region Internal logging methods + #region Internal logging methods - /// - /// Core methods to hand off to logger. - /// - /// A valid PowerShell error record. - public override void WriteError(ErrorRecord errorRecord) - { - if (_OnWriteError == null || !ShouldWriteError()) - return; + /// + /// Core methods to hand off to logger. + /// + /// A valid PowerShell error record. + public override void WriteError(ErrorRecord errorRecord) + { + if (_OnWriteError == null || !ShouldWriteError()) + return; - _OnWriteError(errorRecord); - } + _OnWriteError(errorRecord); + } - /// - /// Core method to hand off verbose messages to logger. - /// - /// A message to log. - public override void WriteVerbose(string message) - { - if (_OnWriteVerbose == null || !ShouldWriteVerbose()) - return; + /// + /// Core method to hand off verbose messages to logger. + /// + /// A message to log. + public override void WriteVerbose(string message) + { + if (_OnWriteVerbose == null || !ShouldWriteVerbose()) + return; - _OnWriteVerbose(message); - } + _OnWriteVerbose(message); + } - /// - /// Core method to hand off warning messages to logger. - /// - /// A message to log - public override void WriteWarning(string message) - { - if (_OnWriteWarning == null || !ShouldWriteWarning()) - return; + /// + /// Core method to hand off warning messages to logger. + /// + /// A message to log + public override void WriteWarning(string message) + { + if (_OnWriteWarning == null || !ShouldWriteWarning()) + return; - _OnWriteWarning(message); - } + _OnWriteWarning(message); + } - /// - /// Core method to hand off information messages to logger. - /// - public override void WriteInformation(InformationRecord informationRecord) - { - if (_OnWriteInformation == null || !ShouldWriteInformation()) - return; + /// + /// Core method to hand off information messages to logger. + /// + public override void WriteInformation(InformationRecord informationRecord) + { + if (_OnWriteInformation == null || !ShouldWriteInformation()) + return; - _OnWriteInformation(informationRecord); - } + _OnWriteInformation(informationRecord); + } - /// - /// Core method to hand off debug messages to logger. - /// - public override void WriteDebug(string text, params object[] args) - { - if (_OnWriteDebug == null || string.IsNullOrEmpty(text) || !ShouldWriteDebug()) - return; + /// + /// Core method to hand off debug messages to logger. + /// + public override void WriteDebug(string text, params object[] args) + { + if (_OnWriteDebug == null || string.IsNullOrEmpty(text) || !ShouldWriteDebug()) + return; - text = args == null || args.Length == 0 ? text : string.Format(Thread.CurrentThread.CurrentCulture, text, args); - _OnWriteDebug(text); - } + text = args == null || args.Length == 0 ? text : string.Format(Thread.CurrentThread.CurrentCulture, text, args); + _OnWriteDebug(text); + } - public override void WriteObject(object sendToPipeline, bool enumerateCollection) - { - if (OnWriteObject == null || (sendToPipeline is InvokeResult && Option.Output.As == ResultFormat.Summary)) - return; + public override void WriteObject(object sendToPipeline, bool enumerateCollection) + { + if (OnWriteObject == null || (sendToPipeline is InvokeResult && Option.Output.As == ResultFormat.Summary)) + return; - if (sendToPipeline is InvokeResult result) - ProcessRecord(result.AsRecord()); - else - OnWriteObject(sendToPipeline, enumerateCollection); - } + if (sendToPipeline is InvokeResult result) + ProcessRecord(result.AsRecord()); + else + OnWriteObject(sendToPipeline, enumerateCollection); + } - public override void WriteHost(HostInformationMessage info) - { - if (_OnWriteInformation == null) - return; + public override void WriteHost(HostInformationMessage info) + { + if (_OnWriteInformation == null) + return; - var record = new InformationRecord(info, Source); - record.Tags.Add(HostTag); - _OnWriteInformation(record); - } + var record = new InformationRecord(info, Source); + record.Tags.Add(HostTag); + _OnWriteInformation(record); + } - public override bool ShouldWriteVerbose() - { - return _LogVerbose && (_VerboseFilter == null || _ScopeName == null || _VerboseFilter.Contains(_ScopeName)); - } + public override bool ShouldWriteVerbose() + { + return _LogVerbose && (_VerboseFilter == null || _ScopeName == null || _VerboseFilter.Contains(_ScopeName)); + } - public override bool ShouldWriteInformation() - { - return _LogInformation; - } + public override bool ShouldWriteInformation() + { + return _LogInformation; + } - public override bool ShouldWriteDebug() - { - return _LogDebug && (_DebugFilter == null || _ScopeName == null || _DebugFilter.Contains(_ScopeName)); - } + public override bool ShouldWriteDebug() + { + return _LogDebug && (_DebugFilter == null || _ScopeName == null || _DebugFilter.Contains(_ScopeName)); + } - public override bool ShouldWriteError() - { - return _LogError; - } + public override bool ShouldWriteError() + { + return _LogError; + } - public override bool ShouldWriteWarning() - { - return _LogWarning; - } + public override bool ShouldWriteWarning() + { + return _LogWarning; + } - public override void EnterScope(string scopeName) - { - _ScopeName = scopeName; - } + public override void EnterScope(string scopeName) + { + _ScopeName = scopeName; + } - public override void ExitScope() - { - _ScopeName = null; - } + public override void ExitScope() + { + _ScopeName = null; + } - #endregion Internal logging methods + #endregion Internal logging methods + + private void ProcessRecord(RuleRecord[] records) + { + if (records == null || records.Length == 0) + return; - private void ProcessRecord(RuleRecord[] records) + for (var i = 0; i < records.Length; i++) { - if (records == null || records.Length == 0) - return; - - for (var i = 0; i < records.Length; i++) - { - OnWriteObject(records[i], false); - WriteErrorInfo(records[i]); - } + OnWriteObject(records[i], false); + WriteErrorInfo(records[i]); } } } diff --git a/src/PSRule/Pipeline/Output/JobSummaryWriter.cs b/src/PSRule/Pipeline/Output/JobSummaryWriter.cs index d7eb66b881..38e6682162 100644 --- a/src/PSRule/Pipeline/Output/JobSummaryWriter.cs +++ b/src/PSRule/Pipeline/Output/JobSummaryWriter.cs @@ -6,235 +6,234 @@ using PSRule.Resources; using PSRule.Rules; -namespace PSRule.Pipeline.Output +namespace PSRule.Pipeline.Output; + +/// +/// Define an pipeline writer to write a job summary to disk. +/// +internal sealed class JobSummaryWriter : ResultOutputWriter { - /// - /// Define an pipeline writer to write a job summary to disk. - /// - internal sealed class JobSummaryWriter : ResultOutputWriter - { - private const string TICK = "✔️"; - private const string CROSS = "❌"; - private const string QUESTION = "❔"; - private const string HEADER_H1 = "# "; - private const string HEADER_H2 = "## "; - - private readonly string _OutputPath; - private readonly Encoding _Encoding; - private readonly JobSummaryFormat _JobSummary; - private readonly Source[] _Source; - - private Stream _Stream; - private StreamWriter _Writer; - private bool _IsDisposed; - - public JobSummaryWriter(IPipelineWriter inner, PSRuleOption option, ShouldProcess shouldProcess, string outputPath = null, Stream stream = null, Source[] source = null) - : base(inner, option, shouldProcess) - { - _OutputPath = outputPath ?? Environment.GetRootedPath(Option.Output.JobSummaryPath); - _Encoding = option.Output.GetEncoding(); - _JobSummary = JobSummaryFormat.Default; - _Stream = stream; - _Source = source; - - if (Option.Output.As == ResultFormat.Summary && inner != null) - inner.WriteError(new PipelineConfigurationException("Output.As", PSRuleResources.PSR0002), "PSRule.Output.AsOutputSerialization", System.Management.Automation.ErrorCategory.InvalidOperation); - } + private const string TICK = "✔️"; + private const string CROSS = "❌"; + private const string QUESTION = "❔"; + private const string HEADER_H1 = "# "; + private const string HEADER_H2 = "## "; + + private readonly string _OutputPath; + private readonly Encoding _Encoding; + private readonly JobSummaryFormat _JobSummary; + private readonly Source[] _Source; + + private Stream _Stream; + private StreamWriter _Writer; + private bool _IsDisposed; + + public JobSummaryWriter(IPipelineWriter inner, PSRuleOption option, ShouldProcess shouldProcess, string outputPath = null, Stream stream = null, Source[] source = null) + : base(inner, option, shouldProcess) + { + _OutputPath = outputPath ?? Environment.GetRootedPath(Option.Output.JobSummaryPath); + _Encoding = option.Output.GetEncoding(); + _JobSummary = JobSummaryFormat.Default; + _Stream = stream; + _Source = source; + + if (Option.Output.As == ResultFormat.Summary && inner != null) + inner.WriteError(new PipelineConfigurationException("Output.As", PSRuleResources.PSR0002), "PSRule.Output.AsOutputSerialization", System.Management.Automation.ErrorCategory.InvalidOperation); + } - public override void Begin() - { - Open(); - base.Begin(); - } + public override void Begin() + { + Open(); + base.Begin(); + } - public sealed override void End() - { - Flush(); - base.End(); - } + public sealed override void End() + { + Flush(); + base.End(); + } - #region Helper methods + #region Helper methods - private void Open() - { - if (string.IsNullOrEmpty(_OutputPath) || _IsDisposed || !CreateFile(_OutputPath)) - return; + private void Open() + { + if (string.IsNullOrEmpty(_OutputPath) || _IsDisposed || !CreateFile(_OutputPath)) + return; - _Stream ??= new FileStream(_OutputPath, FileMode.Create, FileAccess.ReadWrite, FileShare.Read); - _Writer = new StreamWriter(_Stream, _Encoding, 2048, false); - } + _Stream ??= new FileStream(_OutputPath, FileMode.Create, FileAccess.ReadWrite, FileShare.Read); + _Writer = new StreamWriter(_Stream, _Encoding, 2048, false); + } - private void Source() - { - if (!_JobSummary.HasFlag(JobSummaryFormat.Source) || _Source == null || _Source.Length == 0) - return; + private void Source() + { + if (!_JobSummary.HasFlag(JobSummaryFormat.Source) || _Source == null || _Source.Length == 0) + return; - H2(Summaries.JobSummary_Source); - WriteLine(Summaries.JobSummary_IncludedModules); - WriteLine(); + H2(Summaries.JobSummary_Source); + WriteLine(Summaries.JobSummary_IncludedModules); + WriteLine(); - WriteLine($"{Summaries.JobSummary_Module} | {Summaries.JobSummary_Version}"); - WriteLine("------ | --------"); + WriteLine($"{Summaries.JobSummary_Module} | {Summaries.JobSummary_Version}"); + WriteLine("------ | --------"); - var version = Engine.GetVersion(); - if (!string.IsNullOrEmpty(version)) - WriteLine($"PSRule | v{version}"); + var version = Engine.GetVersion(); + if (!string.IsNullOrEmpty(version)) + WriteLine($"PSRule | v{version}"); - var list = new HashSet(StringComparer.OrdinalIgnoreCase); - for (var i = 0; i < _Source.Length; i++) + var list = new HashSet(StringComparer.OrdinalIgnoreCase); + for (var i = 0; i < _Source.Length; i++) + { + if (_Source[i].Module != null && !list.Contains(_Source[i].Module.Name)) { - if (_Source[i].Module != null && !list.Contains(_Source[i].Module.Name)) - { - var projectLink = string.IsNullOrEmpty(_Source[i].Module.ProjectUri) ? _Source[i].Module.Name : $"[{_Source[i].Module.Name}]({_Source[i].Module.ProjectUri})"; - WriteLine($"{projectLink} | v{_Source[i].Module.Version}"); - list.Add(_Source[i].Module.Name); - } + var projectLink = string.IsNullOrEmpty(_Source[i].Module.ProjectUri) ? _Source[i].Module.Name : $"[{_Source[i].Module.Name}]({_Source[i].Module.ProjectUri})"; + WriteLine($"{projectLink} | v{_Source[i].Module.Version}"); + list.Add(_Source[i].Module.Name); } - WriteLine(); } + WriteLine(); + } - private void H1(string text) - { - if (string.IsNullOrEmpty(text)) - return; - - WriteLine(string.Concat(HEADER_H1, text)); - WriteLine(); - } + private void H1(string text) + { + if (string.IsNullOrEmpty(text)) + return; - private void H2(string text) - { - if (string.IsNullOrEmpty(text)) - return; + WriteLine(string.Concat(HEADER_H1, text)); + WriteLine(); + } - WriteLine(string.Concat(HEADER_H2, text)); - WriteLine(); - } + private void H2(string text) + { + if (string.IsNullOrEmpty(text)) + return; - private void WriteLine(string text = null) - { - if (_Writer == null || _IsDisposed) - return; + WriteLine(string.Concat(HEADER_H2, text)); + WriteLine(); + } - _Writer.WriteLine(text ?? string.Empty); - } + private void WriteLine(string text = null) + { + if (_Writer == null || _IsDisposed) + return; - private void WriteLine(string text, params object[] args) - { - if (_Writer == null || _IsDisposed) - return; + _Writer.WriteLine(text ?? string.Empty); + } - _Writer.WriteLine(text ?? string.Empty, args); - } + private void WriteLine(string text, params object[] args) + { + if (_Writer == null || _IsDisposed) + return; - private void Complete() - { - var results = GetResults(); - H1(Summaries.JobSummary_Title); - FinalResult(results); - Source(); - Analysis(results); - } + _Writer.WriteLine(text ?? string.Empty, args); + } - private void Analysis(InvokeResult[] o) - { - if (o == null || o.Length == 0) - return; + private void Complete() + { + var results = GetResults(); + H1(Summaries.JobSummary_Title); + FinalResult(results); + Source(); + Analysis(results); + } - H2(Summaries.JobSummary_Analysis); - var rows = o.SelectMany(r => r.AsRecord()).Where(r => r.Outcome == RuleOutcome.Fail || r.Outcome == RuleOutcome.Error).ToArray(); - if (rows.Length > 0) - { - WriteLine(Summaries.JobSummary_AnalysisResults); - WriteLine(); - WriteLine($"{Summaries.JobSummary_Name} | {Summaries.JobSummary_TargetName} | {Summaries.JobSummary_Synopsis}"); - WriteLine("---- | ----------- | --------"); - } - else - { - WriteLine(Summaries.JobSummary_AnalysisEmpty); - } - for (var i = 0; i < rows.Length; i++) - WriteAnalysisRow(rows[i]); - } + private void Analysis(InvokeResult[] o) + { + if (o == null || o.Length == 0) + return; - private void WriteAnalysisRow(RuleRecord record) + H2(Summaries.JobSummary_Analysis); + var rows = o.SelectMany(r => r.AsRecord()).Where(r => r.Outcome == RuleOutcome.Fail || r.Outcome == RuleOutcome.Error).ToArray(); + if (rows.Length > 0) { - var link = record.Info.GetOnlineHelpUrl(); - var name = link != null ? $"[{record.RuleName}]({link})" : record.RuleName; - WriteLine($"{name} | {record.TargetName} | {record.Info.Synopsis.Markdown}"); + WriteLine(Summaries.JobSummary_AnalysisResults); + WriteLine(); + WriteLine($"{Summaries.JobSummary_Name} | {Summaries.JobSummary_TargetName} | {Summaries.JobSummary_Synopsis}"); + WriteLine("---- | ----------- | --------"); } - - private void FinalResult(InvokeResult[] o) + else { - var count = o?.Length; - var overall = RuleOutcome.None; - var pass = 0; - var fail = 0; - var total = 0; - var error = 0; - for (var i = 0; o != null && i < o.Length; i++) - { - overall = o[i].Outcome.GetWorstCase(overall); - pass += o[i].Pass; - fail += o[i].Fail; - error += o[i].Error; - total += o[i].Total; - } - var elapsed = PipelineContext.CurrentThread?.RunTime.Elapsed ?? TimeSpan.FromSeconds(0); - WriteLine(Summaries.JobSummary_FinalResultMessage, - OutcomeEmoji(overall), - Enum.GetName(typeof(RuleOutcome), overall), - pass + fail + error, - count, - elapsed - ); - WriteLine(); + WriteLine(Summaries.JobSummary_AnalysisEmpty); } + for (var i = 0; i < rows.Length; i++) + WriteAnalysisRow(rows[i]); + } + + private void WriteAnalysisRow(RuleRecord record) + { + var link = record.Info.GetOnlineHelpUrl(); + var name = link != null ? $"[{record.RuleName}]({link})" : record.RuleName; + WriteLine($"{name} | {record.TargetName} | {record.Info.Synopsis.Markdown}"); + } - private static string OutcomeEmoji(RuleOutcome outcome) + private void FinalResult(InvokeResult[] o) + { + var count = o?.Length; + var overall = RuleOutcome.None; + var pass = 0; + var fail = 0; + var total = 0; + var error = 0; + for (var i = 0; o != null && i < o.Length; i++) + { + overall = o[i].Outcome.GetWorstCase(overall); + pass += o[i].Pass; + fail += o[i].Fail; + error += o[i].Error; + total += o[i].Total; + } + var elapsed = PipelineContext.CurrentThread?.RunTime.Elapsed ?? TimeSpan.FromSeconds(0); + WriteLine(Summaries.JobSummary_FinalResultMessage, + OutcomeEmoji(overall), + Enum.GetName(typeof(RuleOutcome), overall), + pass + fail + error, + count, + elapsed + ); + WriteLine(); + } + + private static string OutcomeEmoji(RuleOutcome outcome) + { + switch (outcome) { - switch (outcome) - { - case RuleOutcome.Pass: - return TICK; + case RuleOutcome.Pass: + return TICK; - case RuleOutcome.Error: - case RuleOutcome.Fail: - return CROSS; + case RuleOutcome.Error: + case RuleOutcome.Fail: + return CROSS; - default: - return QUESTION; - } + default: + return QUESTION; } + } - protected override void Flush() - { - if (_Writer == null || _IsDisposed) - return; + protected override void Flush() + { + if (_Writer == null || _IsDisposed) + return; - Complete(); - _Writer.Flush(); - } + Complete(); + _Writer.Flush(); + } - #endregion Helper methods + #endregion Helper methods - #region IDisposable + #region IDisposable - protected override void Dispose(bool disposing) + protected override void Dispose(bool disposing) + { + if (!_IsDisposed) { - if (!_IsDisposed) + if (disposing) { - if (disposing) - { - _Writer?.Dispose(); - _Stream?.Dispose(); - } - _IsDisposed = true; + _Writer?.Dispose(); + _Stream?.Dispose(); } - base.Dispose(disposing); + _IsDisposed = true; } - - #endregion IDisposable + base.Dispose(disposing); } + + #endregion IDisposable } diff --git a/src/PSRule/Pipeline/Output/JsonOutputWriter.cs b/src/PSRule/Pipeline/Output/JsonOutputWriter.cs index 99b1ba4e94..5e417e4e7c 100644 --- a/src/PSRule/Pipeline/Output/JsonOutputWriter.cs +++ b/src/PSRule/Pipeline/Output/JsonOutputWriter.cs @@ -6,54 +6,53 @@ using PSRule.Configuration; using PSRule.Definitions.Baselines; -namespace PSRule.Pipeline.Output +namespace PSRule.Pipeline.Output; + +internal sealed class JsonOutputWriter : SerializationOutputWriter { - internal sealed class JsonOutputWriter : SerializationOutputWriter + internal JsonOutputWriter(PipelineWriter inner, PSRuleOption option, ShouldProcess shouldProcess) + : base(inner, option, shouldProcess) { } + + protected override string Serialize(object[] o) + { + return ToJson(o, Option.Output.JsonIndent); + } + + internal static string ToJson(object[] o, int? jsonIndent) { - internal JsonOutputWriter(PipelineWriter inner, PSRuleOption option, ShouldProcess shouldProcess) - : base(inner, option, shouldProcess) { } + using var stringWriter = new StringWriter(); + using var jsonTextWriter = new JsonCommentWriter(stringWriter); - protected override string Serialize(object[] o) + var jsonSerializer = new JsonSerializer { - return ToJson(o, Option.Output.JsonIndent); + NullValueHandling = NullValueHandling.Ignore + }; + + var outputJsonIndent = jsonIndent ?? 0; + if (outputJsonIndent > 0) + { + jsonSerializer.Formatting = Formatting.Indented; + jsonTextWriter.Indentation = outputJsonIndent; } - internal static string ToJson(object[] o, int? jsonIndent) + jsonSerializer.ContractResolver = new OrderedPropertiesContractResolver(); + jsonSerializer.Converters.Add(new ErrorCategoryJsonConverter()); + jsonSerializer.Converters.Add(new PSObjectJsonConverter()); + jsonSerializer.Converters.Add(new StringEnumConverter()); + jsonSerializer.Converters.Add(new ResourceIdConverter()); + + // To avoid writing baselines with an extra outer array + // We can serialize the first object which has all the baselines + if (o[0] is IEnumerable baselines) { - using var stringWriter = new StringWriter(); - using var jsonTextWriter = new JsonCommentWriter(stringWriter); - - var jsonSerializer = new JsonSerializer - { - NullValueHandling = NullValueHandling.Ignore - }; - - var outputJsonIndent = jsonIndent ?? 0; - if (outputJsonIndent > 0) - { - jsonSerializer.Formatting = Formatting.Indented; - jsonTextWriter.Indentation = outputJsonIndent; - } - - jsonSerializer.ContractResolver = new OrderedPropertiesContractResolver(); - jsonSerializer.Converters.Add(new ErrorCategoryJsonConverter()); - jsonSerializer.Converters.Add(new PSObjectJsonConverter()); - jsonSerializer.Converters.Add(new StringEnumConverter()); - jsonSerializer.Converters.Add(new ResourceIdConverter()); - - // To avoid writing baselines with an extra outer array - // We can serialize the first object which has all the baselines - if (o[0] is IEnumerable baselines) - { - jsonSerializer.Converters.Add(new BaselineJsonConverter()); - jsonSerializer.Serialize(jsonTextWriter, baselines); - } - else - { - jsonSerializer.Serialize(jsonTextWriter, o); - } - - return stringWriter.ToString(); + jsonSerializer.Converters.Add(new BaselineJsonConverter()); + jsonSerializer.Serialize(jsonTextWriter, baselines); } + else + { + jsonSerializer.Serialize(jsonTextWriter, o); + } + + return stringWriter.ToString(); } } diff --git a/src/PSRule/Pipeline/Output/MarkdownOutputWriter.cs b/src/PSRule/Pipeline/Output/MarkdownOutputWriter.cs index 17dd451668..febcb47efe 100644 --- a/src/PSRule/Pipeline/Output/MarkdownOutputWriter.cs +++ b/src/PSRule/Pipeline/Output/MarkdownOutputWriter.cs @@ -7,259 +7,258 @@ using PSRule.Resources; using PSRule.Rules; -namespace PSRule.Pipeline.Output +namespace PSRule.Pipeline.Output; + +internal sealed class MarkdownOutputWriter : SerializationOutputWriter { - internal sealed class MarkdownOutputWriter : SerializationOutputWriter - { - private const char Hash = '#'; - private const char Space = ' '; + private const char Hash = '#'; + private const char Space = ' '; - private readonly StringBuilder _Builder; + private readonly StringBuilder _Builder; - private bool _LineBreak = true; + private bool _LineBreak = true; - internal MarkdownOutputWriter(PipelineWriter inner, PSRuleOption option, ShouldProcess shouldProcess) - : base(inner, option, shouldProcess) - { - _Builder = new StringBuilder(); - } + internal MarkdownOutputWriter(PipelineWriter inner, PSRuleOption option, ShouldProcess shouldProcess) + : base(inner, option, shouldProcess) + { + _Builder = new StringBuilder(); + } - public override void WriteObject(object sendToPipeline, bool enumerateCollection) - { - if (sendToPipeline is not InvokeResult result) - return; + public override void WriteObject(object sendToPipeline, bool enumerateCollection) + { + if (sendToPipeline is not InvokeResult result) + return; - Add(result); - } + Add(result); + } - protected override string Serialize(InvokeResult[] o) - { - if (Option.Output.As == ResultFormat.Detail) - AsDetail(o); - else - AsSummary(o); + protected override string Serialize(InvokeResult[] o) + { + if (Option.Output.As == ResultFormat.Detail) + AsDetail(o); + else + AsSummary(o); - return _Builder.ToString(); - } + return _Builder.ToString(); + } - private void AsDetail(InvokeResult[] o) - { - Header(); - for (var i = 0; i < o.Length; i++) - if (o[i].Total > 0) - DetailResult(o[i]); - } + private void AsDetail(InvokeResult[] o) + { + Header(); + for (var i = 0; i < o.Length; i++) + if (o[i].Total > 0) + DetailResult(o[i]); + } - private void AsSummary(InvokeResult[] o) + private void AsSummary(InvokeResult[] o) + { + Header(); + var ruleGroup = o.SelectMany(result => result.AsRecord()).GroupBy(record => record.RuleId).ToArray(); + for (var i = 0; i < ruleGroup.Length; i++) { - Header(); - var ruleGroup = o.SelectMany(result => result.AsRecord()).GroupBy(record => record.RuleId).ToArray(); - for (var i = 0; i < ruleGroup.Length; i++) - { - RuleSummary(ruleGroup[i].ToArray()); - } + RuleSummary(ruleGroup[i].ToArray()); } + } - private void DetailResult(InvokeResult invokeResult) - { - var records = invokeResult.AsRecord(); + private void DetailResult(InvokeResult invokeResult) + { + var records = invokeResult.AsRecord(); - Section(2, records[0].TargetName, " : ", records[0].TargetType); - LineBreak(); + Section(2, records[0].TargetName, " : ", records[0].TargetType); + LineBreak(); - for (var i = 0; i < records.Length; i++) + for (var i = 0; i < records.Length; i++) + { + CheckedItem(records[i].IsSuccess(), records[i].RuleName); + if (!records[i].IsSuccess()) { - CheckedItem(records[i].IsSuccess(), records[i].RuleName); - if (!records[i].IsSuccess()) - { - LineBreak(); - AppendLine(records[i].Recommendation); - LineBreak(); - } + LineBreak(); + AppendLine(records[i].Recommendation); + LineBreak(); } } + } - private void CheckedItem(bool ticked, string text) - { - if (ticked) - AppendLine("- [X] ", text); - else - AppendLine("- [ ] ", text); - } + private void CheckedItem(bool ticked, string text) + { + if (ticked) + AppendLine("- [X] ", text); + else + AppendLine("- [ ] ", text); + } - private void RuleSummary(IDetailedRuleResultV2[] records) + private void RuleSummary(IDetailedRuleResultV2[] records) + { + if (records.Length == 0) + return; + + Section(2, records[0].Info.DisplayName); + LineBreak(); + AppendLine("> ", records[0].Info.Name); + LineBreak(); + AppendLine(records[0].Info.Synopsis); + LineBreak(); + AppendLine(records[0].Info.Description); + LineBreak(); + + // Info block + AppendLine("```yaml"); + foreach (var key in records[0].Info.Annotations.Keys) { - if (records.Length == 0) - return; - - Section(2, records[0].Info.DisplayName); - LineBreak(); - AppendLine("> ", records[0].Info.Name); - LineBreak(); - AppendLine(records[0].Info.Synopsis); - LineBreak(); - AppendLine(records[0].Info.Description); - LineBreak(); - - // Info block - AppendLine("```yaml"); - foreach (var key in records[0].Info.Annotations.Keys) + if (key.ToString() != "online version") { - if (key.ToString() != "online version") - { - AppendLine(key.ToString(), ": ", records[0].Info.Annotations[key].ToString()); - } + AppendLine(key.ToString(), ": ", records[0].Info.Annotations[key].ToString()); } - AppendLine("```"); - LineBreak(); + } + AppendLine("```"); + LineBreak(); - // Add recommendation - AppendLine("**", ReportStrings.Markdown_Recommendation, "**:"); - LineBreak(); - AppendLine(records[0].Info.Recommendation); - LineBreak(); + // Add recommendation + AppendLine("**", ReportStrings.Markdown_Recommendation, "**:"); + LineBreak(); + AppendLine(records[0].Info.Recommendation); + LineBreak(); - // Add links - if (records[0]?.Info?.Links?.Length > 0) + // Add links + if (records[0]?.Info?.Links?.Length > 0) + { + AppendLine("**", ReportStrings.Markdown_Links, "**:"); + LineBreak(); + for (var i = 0; i < records[0].Info.Links.Length; i++) { - AppendLine("**", ReportStrings.Markdown_Links, "**:"); - LineBreak(); - for (var i = 0; i < records[0].Info.Links.Length; i++) - { - Link(records[0].Info.Links[i]); - } + Link(records[0].Info.Links[i]); } - LineBreak(); - - // Get padding - var padding = new int[3] { 0, 0, 0 }; - GetColumnPadding(padding, ReportStrings.Markdown_TargetName, ReportStrings.Markdown_TargetType, ReportStrings.Markdown_Outcome); - for (var i = 0; i < records.Length; i++) - GetColumnPadding(padding, records[i].TargetName, records[i].TargetType, records[i].Outcome.ToString()); - - // Build results table - LineBreak(); - AppendLine("**", ReportStrings.Markdown_Results, "**:"); - LineBreak(); - AppendLine(ReportStrings.Markdown_ResultText); - LineBreak(); - AppendColumn(ReportStrings.Markdown_TargetName, Space, padding[0]); - Append(" | "); - AppendColumn(ReportStrings.Markdown_TargetType, Space, padding[1]); - Append(" | "); - AppendColumn(ReportStrings.Markdown_Outcome, Space, padding[2]); - LineEnd(); - AppendColumn('-', ReportStrings.Markdown_TargetName.Length, Space, padding[0]); + } + LineBreak(); + + // Get padding + var padding = new int[3] { 0, 0, 0 }; + GetColumnPadding(padding, ReportStrings.Markdown_TargetName, ReportStrings.Markdown_TargetType, ReportStrings.Markdown_Outcome); + for (var i = 0; i < records.Length; i++) + GetColumnPadding(padding, records[i].TargetName, records[i].TargetType, records[i].Outcome.ToString()); + + // Build results table + LineBreak(); + AppendLine("**", ReportStrings.Markdown_Results, "**:"); + LineBreak(); + AppendLine(ReportStrings.Markdown_ResultText); + LineBreak(); + AppendColumn(ReportStrings.Markdown_TargetName, Space, padding[0]); + Append(" | "); + AppendColumn(ReportStrings.Markdown_TargetType, Space, padding[1]); + Append(" | "); + AppendColumn(ReportStrings.Markdown_Outcome, Space, padding[2]); + LineEnd(); + AppendColumn('-', ReportStrings.Markdown_TargetName.Length, Space, padding[0]); + Append(" | "); + AppendColumn('-', ReportStrings.Markdown_TargetType.Length, Space, padding[1]); + Append(" | "); + Append('-', ReportStrings.Markdown_Outcome.Length); + LineEnd(); + for (var i = 0; i < records.Length; i++) + { + AppendColumn(records[i].TargetName, Space, padding[0]); Append(" | "); - AppendColumn('-', ReportStrings.Markdown_TargetType.Length, Space, padding[1]); + AppendColumn(records[i].TargetType, Space, padding[1]); Append(" | "); - Append('-', ReportStrings.Markdown_Outcome.Length); + Append(records[i].Outcome.ToString()); LineEnd(); - for (var i = 0; i < records.Length; i++) - { - AppendColumn(records[i].TargetName, Space, padding[0]); - Append(" | "); - AppendColumn(records[i].TargetType, Space, padding[1]); - Append(" | "); - Append(records[i].Outcome.ToString()); - LineEnd(); - } } + } - private void Link(Link link) - { - AppendLine("- [", link.Name, "](", link.Uri, ")"); - } + private void Link(Link link) + { + AppendLine("- [", link.Name, "](", link.Uri, ")"); + } - private static void GetColumnPadding(int[] padding, string targetName, string targetType, string outcome) - { - if (targetName.Length > padding[0]) - padding[0] = targetName.Length; + private static void GetColumnPadding(int[] padding, string targetName, string targetType, string outcome) + { + if (targetName.Length > padding[0]) + padding[0] = targetName.Length; - if (targetType.Length > padding[1]) - padding[1] = targetType.Length; + if (targetType.Length > padding[1]) + padding[1] = targetType.Length; - if (outcome.Length > padding[2]) - padding[2] = outcome.Length; - } + if (outcome.Length > padding[2]) + padding[2] = outcome.Length; + } - private void Header() - { - Section(1, "PSRule"); - } + private void Header() + { + Section(1, "PSRule"); + } - private void Section(int depth, params string[] name) - { - LineBreak(); - Append(Hash, depth); - Append(Space); - AppendLine(name); - } + private void Section(int depth, params string[] name) + { + LineBreak(); + Append(Hash, depth); + Append(Space); + AppendLine(name); + } - private void Append(string text) - { - _LineBreak = false; - _Builder.Append(text); - } + private void Append(string text) + { + _LineBreak = false; + _Builder.Append(text); + } - private void Append(char c, int count = 1) - { - _LineBreak = false; - _Builder.Append(c, count); - } + private void Append(char c, int count = 1) + { + _LineBreak = false; + _Builder.Append(c, count); + } - private void AppendColumn(string s, char padding, int width) - { - Append(s); - if (s.Length < width) - _Builder.Append(padding, width - s.Length); - } + private void AppendColumn(string s, char padding, int width) + { + Append(s); + if (s.Length < width) + _Builder.Append(padding, width - s.Length); + } - private void AppendColumn(char c, int count, char padding, int width) - { - Append(c, count); - if (count < width) - _Builder.Append(padding, width - count); - } + private void AppendColumn(char c, int count, char padding, int width) + { + Append(c, count); + if (count < width) + _Builder.Append(padding, width - count); + } - private void AppendLine(InfoString text) - { - AppendLine(text.Text); - } + private void AppendLine(InfoString text) + { + AppendLine(text.Text); + } - private void AppendLine(params string[] text) - { - if (text == null || text.Length == 0) - return; + private void AppendLine(params string[] text) + { + if (text == null || text.Length == 0) + return; - var hasContent = false; - for (var i = 0; i < text.Length; i++) + var hasContent = false; + for (var i = 0; i < text.Length; i++) + { + if (!string.IsNullOrEmpty(text[i])) { - if (!string.IsNullOrEmpty(text[i])) - { - Append(text[i]); - hasContent = true; - } + Append(text[i]); + hasContent = true; } - if (!hasContent) - return; - - _LineBreak = false; - LineEnd(); } + if (!hasContent) + return; - private void LineEnd() - { - _Builder.AppendLine(); - } + _LineBreak = false; + LineEnd(); + } - private void LineBreak() - { - if (_LineBreak) - return; + private void LineEnd() + { + _Builder.AppendLine(); + } - _Builder.AppendLine(); - _LineBreak = true; - } + private void LineBreak() + { + if (_LineBreak) + return; + + _Builder.AppendLine(); + _LineBreak = true; } } diff --git a/src/PSRule/Pipeline/Output/NUnit3OutputWriter.cs b/src/PSRule/Pipeline/Output/NUnit3OutputWriter.cs index 3c2064bd5d..b47452c0ab 100644 --- a/src/PSRule/Pipeline/Output/NUnit3OutputWriter.cs +++ b/src/PSRule/Pipeline/Output/NUnit3OutputWriter.cs @@ -7,221 +7,220 @@ using PSRule.Resources; using PSRule.Rules; -namespace PSRule.Pipeline.Output +namespace PSRule.Pipeline.Output; + +internal sealed class NUnit3OutputWriter : SerializationOutputWriter { - internal sealed class NUnit3OutputWriter : SerializationOutputWriter - { - internal NUnit3OutputWriter(PipelineWriter inner, PSRuleOption option, ShouldProcess shouldProcess) - : base(inner, option, shouldProcess) { } + internal NUnit3OutputWriter(PipelineWriter inner, PSRuleOption option, ShouldProcess shouldProcess) + : base(inner, option, shouldProcess) { } - public override void WriteObject(object sendToPipeline, bool enumerateCollection) - { - if (sendToPipeline is not InvokeResult result) - return; + public override void WriteObject(object sendToPipeline, bool enumerateCollection) + { + if (sendToPipeline is not InvokeResult result) + return; - Add(result); - } + Add(result); + } - protected override string Serialize(InvokeResult[] o) + protected override string Serialize(InvokeResult[] o) + { + var settings = new XmlWriterSettings { - var settings = new XmlWriterSettings - { - Encoding = Encoding.UTF8, // Consider using: Option.Output.GetEncoding() - // Consider using: Indent = true, - }; - using var writer = new OutputStringWriter(Option); - using var xml = XmlWriter.Create(writer, settings); - xml.WriteStartDocument(standalone: false); - - float time = o.Sum(r => r.Time); - var total = o.Sum(r => r.Total); - var error = o.Sum(r => r.Error); - var fail = o.Sum(r => r.Fail); - - xml.WriteStartElement("test-results"); - xml.WriteAttributeString("xsi", "noNamespaceSchemaLocation", "http://www.w3.org/2001/XMLSchema-instance", "nunit_schema_2.5.xsd"); - xml.WriteAttributeString("name", "PSRule"); - xml.WriteAttributeString("total", total.ToString()); - xml.WriteAttributeString("errors", error.ToString()); - xml.WriteAttributeString("failures", fail.ToString()); - xml.WriteAttributeString("not-run", "0"); - xml.WriteAttributeString("inconclusive", "0"); - xml.WriteAttributeString("ignored", "0"); - xml.WriteAttributeString("skipped", "0"); - xml.WriteAttributeString("invalid", "0"); - xml.WriteAttributeString("date", DateTime.UtcNow.ToString("yyyy-MM-dd", Thread.CurrentThread.CurrentCulture)); - xml.WriteAttributeString("time", TimeSpan.FromMilliseconds(time).ToString()); - - xml.WriteStartElement("environment"); - xml.WriteAttributeString("user", System.Environment.UserName); - xml.WriteAttributeString("machine-name", System.Environment.MachineName); - xml.WriteAttributeString("cwd", Environment.GetWorkingPath()); - xml.WriteAttributeString("user-domain", System.Environment.UserDomainName); - xml.WriteAttributeString("platform", System.Environment.OSVersion.Platform.ToString()); - xml.WriteAttributeString("nunit-version", "2.5.8.0"); - xml.WriteAttributeString("os-version", System.Environment.OSVersion.Version.ToString()); - xml.WriteAttributeString("clr-version", System.Environment.Version.ToString()); - xml.WriteEndElement(); - - xml.WriteStartElement("culture-info"); - xml.WriteAttributeString("current-culture", Thread.CurrentThread.CurrentCulture.ToString()); - xml.WriteAttributeString("current-uiculture", Thread.CurrentThread.CurrentUICulture.ToString()); - xml.WriteEndElement(); - - foreach (var result in o) - { - if (result.Total == 0) - continue; - - var records = result.AsRecord(); - var testCases = records - .Select(r => new TestCase( - name: string.Concat(r.TargetName, " -- ", r.RuleName), - description: r.Info.Synopsis.Text, - success: r.IsSuccess(), - executed: r.IsProcessed(), - time: r.Time, - failureMessage: FailureMessage(r), - scriptStackTrace: r.Error?.ScriptStackTrace - )) - .ToArray(); - var failedCount = testCases.Count(r => !r.Success); - var fixture = new TestFixture( - name: records[0].TargetName, - description: "", - success: result.IsSuccess(), - executed: result.IsProcessed(), - time: result.Time, - asserts: failedCount, - testCases: testCases - ); - VisitFixture(xml, fixture); - } - xml.WriteEndElement(); - xml.WriteEndDocument(); - xml.Flush(); - return writer.ToString(); - } - - private static void VisitFixture(XmlWriter xml, TestFixture fixture) + Encoding = Encoding.UTF8, // Consider using: Option.Output.GetEncoding() + // Consider using: Indent = true, + }; + using var writer = new OutputStringWriter(Option); + using var xml = XmlWriter.Create(writer, settings); + xml.WriteStartDocument(standalone: false); + + float time = o.Sum(r => r.Time); + var total = o.Sum(r => r.Total); + var error = o.Sum(r => r.Error); + var fail = o.Sum(r => r.Fail); + + xml.WriteStartElement("test-results"); + xml.WriteAttributeString("xsi", "noNamespaceSchemaLocation", "http://www.w3.org/2001/XMLSchema-instance", "nunit_schema_2.5.xsd"); + xml.WriteAttributeString("name", "PSRule"); + xml.WriteAttributeString("total", total.ToString()); + xml.WriteAttributeString("errors", error.ToString()); + xml.WriteAttributeString("failures", fail.ToString()); + xml.WriteAttributeString("not-run", "0"); + xml.WriteAttributeString("inconclusive", "0"); + xml.WriteAttributeString("ignored", "0"); + xml.WriteAttributeString("skipped", "0"); + xml.WriteAttributeString("invalid", "0"); + xml.WriteAttributeString("date", DateTime.UtcNow.ToString("yyyy-MM-dd", Thread.CurrentThread.CurrentCulture)); + xml.WriteAttributeString("time", TimeSpan.FromMilliseconds(time).ToString()); + + xml.WriteStartElement("environment"); + xml.WriteAttributeString("user", System.Environment.UserName); + xml.WriteAttributeString("machine-name", System.Environment.MachineName); + xml.WriteAttributeString("cwd", Environment.GetWorkingPath()); + xml.WriteAttributeString("user-domain", System.Environment.UserDomainName); + xml.WriteAttributeString("platform", System.Environment.OSVersion.Platform.ToString()); + xml.WriteAttributeString("nunit-version", "2.5.8.0"); + xml.WriteAttributeString("os-version", System.Environment.OSVersion.Version.ToString()); + xml.WriteAttributeString("clr-version", System.Environment.Version.ToString()); + xml.WriteEndElement(); + + xml.WriteStartElement("culture-info"); + xml.WriteAttributeString("current-culture", Thread.CurrentThread.CurrentCulture.ToString()); + xml.WriteAttributeString("current-uiculture", Thread.CurrentThread.CurrentUICulture.ToString()); + xml.WriteEndElement(); + + foreach (var result in o) { - xml.WriteStartElement("test-suite"); - xml.WriteAttributeString("type", "TestFixture"); - xml.WriteAttributeString("name", fixture.Name); - xml.WriteAttributeString("executed", fixture.Executed.ToString()); - xml.WriteAttributeString("result", fixture.Success ? "Success" : "Failure"); - xml.WriteAttributeString("success", fixture.Success.ToString()); - - xml.WriteAttributeString("time", fixture.Time.ToString(Thread.CurrentThread.CurrentCulture)); - xml.WriteAttributeString("asserts", fixture.Asserts.ToString()); - xml.WriteAttributeString("description", fixture.Description); - - xml.WriteStartElement("results"); - foreach (var testCase in fixture.Results) - VisitTestCase(xml, testCase); - - xml.WriteEndElement(); - xml.WriteEndElement(); + if (result.Total == 0) + continue; + + var records = result.AsRecord(); + var testCases = records + .Select(r => new TestCase( + name: string.Concat(r.TargetName, " -- ", r.RuleName), + description: r.Info.Synopsis.Text, + success: r.IsSuccess(), + executed: r.IsProcessed(), + time: r.Time, + failureMessage: FailureMessage(r), + scriptStackTrace: r.Error?.ScriptStackTrace + )) + .ToArray(); + var failedCount = testCases.Count(r => !r.Success); + var fixture = new TestFixture( + name: records[0].TargetName, + description: "", + success: result.IsSuccess(), + executed: result.IsProcessed(), + time: result.Time, + asserts: failedCount, + testCases: testCases + ); + VisitFixture(xml, fixture); } + xml.WriteEndElement(); + xml.WriteEndDocument(); + xml.Flush(); + return writer.ToString(); + } + + private static void VisitFixture(XmlWriter xml, TestFixture fixture) + { + xml.WriteStartElement("test-suite"); + xml.WriteAttributeString("type", "TestFixture"); + xml.WriteAttributeString("name", fixture.Name); + xml.WriteAttributeString("executed", fixture.Executed.ToString()); + xml.WriteAttributeString("result", fixture.Success ? "Success" : "Failure"); + xml.WriteAttributeString("success", fixture.Success.ToString()); + + xml.WriteAttributeString("time", fixture.Time.ToString(Thread.CurrentThread.CurrentCulture)); + xml.WriteAttributeString("asserts", fixture.Asserts.ToString()); + xml.WriteAttributeString("description", fixture.Description); + + xml.WriteStartElement("results"); + foreach (var testCase in fixture.Results) + VisitTestCase(xml, testCase); + + xml.WriteEndElement(); + xml.WriteEndElement(); + } - private static void VisitTestCase(XmlWriter xml, TestCase testCase) + private static void VisitTestCase(XmlWriter xml, TestCase testCase) + { + xml.WriteStartElement("test-case"); + xml.WriteAttributeString("description", testCase.Description); + xml.WriteAttributeString("name", testCase.Name); + xml.WriteAttributeString("time", testCase.Time.ToString(Thread.CurrentThread.CurrentCulture)); + xml.WriteAttributeString("asserts", "0"); + xml.WriteAttributeString("success", testCase.Success.ToString()); + xml.WriteAttributeString("result", testCase.Success ? "Success" : "Failure"); + xml.WriteAttributeString("executed", testCase.Executed.ToString()); + if (!testCase.Success) { - xml.WriteStartElement("test-case"); - xml.WriteAttributeString("description", testCase.Description); - xml.WriteAttributeString("name", testCase.Name); - xml.WriteAttributeString("time", testCase.Time.ToString(Thread.CurrentThread.CurrentCulture)); - xml.WriteAttributeString("asserts", "0"); - xml.WriteAttributeString("success", testCase.Success.ToString()); - xml.WriteAttributeString("result", testCase.Success ? "Success" : "Failure"); - xml.WriteAttributeString("executed", testCase.Executed.ToString()); - if (!testCase.Success) - { - xml.WriteStartElement("failure"); + xml.WriteStartElement("failure"); - xml.WriteStartElement("message"); - xml.WriteCData(testCase.FailureMessage); - xml.WriteEndElement(); + xml.WriteStartElement("message"); + xml.WriteCData(testCase.FailureMessage); + xml.WriteEndElement(); - xml.WriteStartElement("stack-trace"); - xml.WriteCData(testCase.ScriptStackTrace); - xml.WriteEndElement(); + xml.WriteStartElement("stack-trace"); + xml.WriteCData(testCase.ScriptStackTrace); + xml.WriteEndElement(); - xml.WriteEndElement(); - } xml.WriteEndElement(); } + xml.WriteEndElement(); + } - private string FailureMessage(RuleRecord record) + private string FailureMessage(RuleRecord record) + { + var useMarkdown = Option.Output.Style == OutputStyle.AzurePipelines; + var sb = new StringBuilder(); + sb.AppendLine(record.Recommendation); + var link = record.Info.GetOnlineHelpUri(); + if (useMarkdown && link != null) + sb.AppendLine(string.Format(Thread.CurrentThread.CurrentCulture, ReportStrings.NUnit_DetailsLink, link)); + + if (record.Reason != null && record.Reason.Length > 0) { - var useMarkdown = Option.Output.Style == OutputStyle.AzurePipelines; - var sb = new StringBuilder(); - sb.AppendLine(record.Recommendation); - var link = record.Info.GetOnlineHelpUri(); - if (useMarkdown && link != null) - sb.AppendLine(string.Format(Thread.CurrentThread.CurrentCulture, ReportStrings.NUnit_DetailsLink, link)); - - if (record.Reason != null && record.Reason.Length > 0) + sb.AppendLine(); + sb.AppendLine(ReportStrings.NUnit_ReportedReasons); + sb.AppendLine(); + for (var i = 0; i < record.Reason.Length; i++) { - sb.AppendLine(); - sb.AppendLine(ReportStrings.NUnit_ReportedReasons); - sb.AppendLine(); - for (var i = 0; i < record.Reason.Length; i++) + sb.Append("- "); + if (useMarkdown) { - sb.Append("- "); - if (useMarkdown) - { - sb.AppendMarkdownText(record.Reason[i]); - sb.AppendLine(); - } - else - sb.AppendLine(record.Reason[i]); + sb.AppendMarkdownText(record.Reason[i]); + sb.AppendLine(); } + else + sb.AppendLine(record.Reason[i]); } - return sb.ToString(); } + return sb.ToString(); + } - private sealed class TestFixture + private sealed class TestFixture + { + public readonly string Name; + public readonly string Description; + public readonly bool Success; + public readonly bool Executed; + public readonly float Time; + public readonly int Asserts; + public readonly TestCase[] Results; + + public TestFixture(string name, string description, bool success, bool executed, long time, int asserts, TestCase[] testCases) { - public readonly string Name; - public readonly string Description; - public readonly bool Success; - public readonly bool Executed; - public readonly float Time; - public readonly int Asserts; - public readonly TestCase[] Results; - - public TestFixture(string name, string description, bool success, bool executed, long time, int asserts, TestCase[] testCases) - { - Name = name; - Description = description; - Success = success; - Executed = executed; - Time = time / 1000f; - Asserts = asserts; - Results = testCases; - } + Name = name; + Description = description; + Success = success; + Executed = executed; + Time = time / 1000f; + Asserts = asserts; + Results = testCases; } + } - private sealed class TestCase + private sealed class TestCase + { + public readonly string Name; + public readonly string Description; + public readonly bool Success; + public readonly bool Executed; + public readonly float Time; + public readonly string FailureMessage; + public readonly string ScriptStackTrace; + + public TestCase(string name, string description, bool success, bool executed, long time, string failureMessage, string scriptStackTrace) { - public readonly string Name; - public readonly string Description; - public readonly bool Success; - public readonly bool Executed; - public readonly float Time; - public readonly string FailureMessage; - public readonly string ScriptStackTrace; - - public TestCase(string name, string description, bool success, bool executed, long time, string failureMessage, string scriptStackTrace) - { - Name = name; - Description = description; - Success = success; - Executed = executed; - Time = time / 1000f; - FailureMessage = failureMessage; - ScriptStackTrace = scriptStackTrace; - } + Name = name; + Description = description; + Success = success; + Executed = executed; + Time = time / 1000f; + FailureMessage = failureMessage; + ScriptStackTrace = scriptStackTrace; } } } diff --git a/src/PSRule/Pipeline/Output/OutputStringWriter.cs b/src/PSRule/Pipeline/Output/OutputStringWriter.cs index 5ad81605b6..659dcea8c0 100644 --- a/src/PSRule/Pipeline/Output/OutputStringWriter.cs +++ b/src/PSRule/Pipeline/Output/OutputStringWriter.cs @@ -4,17 +4,16 @@ using System.Text; using PSRule.Configuration; -namespace PSRule.Pipeline.Output -{ - internal sealed class OutputStringWriter : StringWriter - { - private readonly Encoding _Encoding; +namespace PSRule.Pipeline.Output; - public OutputStringWriter(PSRuleOption option) - { - _Encoding = option.Output.GetEncoding(); - } +internal sealed class OutputStringWriter : StringWriter +{ + private readonly Encoding _Encoding; - public override Encoding Encoding => _Encoding; + public OutputStringWriter(PSRuleOption option) + { + _Encoding = option.Output.GetEncoding(); } + + public override Encoding Encoding => _Encoding; } diff --git a/src/PSRule/Pipeline/Output/SarifOutputWriter.cs b/src/PSRule/Pipeline/Output/SarifOutputWriter.cs index 65e2557e40..2740502680 100644 --- a/src/PSRule/Pipeline/Output/SarifOutputWriter.cs +++ b/src/PSRule/Pipeline/Output/SarifOutputWriter.cs @@ -10,370 +10,368 @@ using PSRule.Resources; using PSRule.Rules; -namespace PSRule.Pipeline.Output +namespace PSRule.Pipeline.Output; + +internal sealed class SarifBuilder { - internal sealed class SarifBuilder + private const string TOOL_NAME = "PSRule"; + private const string TOOL_ORG = "Microsoft Corporation"; + private const string TOOL_GUID = "0130215d-58eb-4887-b6fa-31ed02500569"; + private const string RECOMMENDATION_MESSAGE_ID = "recommendation"; + private const string LOCATION_KIND_OBJECT = "object"; + private const string LOCATION_ID_REPOROOT = "REPOROOT"; + + private readonly Run _Run; + private readonly Dictionary _Rules; + private readonly Dictionary _Extensions; + + public SarifBuilder(Source[] source, PSRuleOption option) { - private const string TOOL_NAME = "PSRule"; - private const string TOOL_ORG = "Microsoft Corporation"; - private const string TOOL_GUID = "0130215d-58eb-4887-b6fa-31ed02500569"; - private const string RECOMMENDATION_MESSAGE_ID = "recommendation"; - private const string LOCATION_KIND_OBJECT = "object"; - private const string LOCATION_ID_REPOROOT = "REPOROOT"; - - private readonly Run _Run; - private readonly Dictionary _Rules; - private readonly Dictionary _Extensions; - - public SarifBuilder(Source[] source, PSRuleOption option) + _Rules = new Dictionary(); + _Extensions = new Dictionary(); + _Run = new Run { - _Rules = new Dictionary(); - _Extensions = new Dictionary(); - _Run = new Run - { - Tool = GetTool(source), - Results = new List(), - Invocations = GetInvocation(), - AutomationDetails = GetAutomationDetails(), - OriginalUriBaseIds = GetBaseIds(), - VersionControlProvenance = GetVersionControl(option.Repository), - }; - } + Tool = GetTool(source), + Results = new List(), + Invocations = GetInvocation(), + AutomationDetails = GetAutomationDetails(), + OriginalUriBaseIds = GetBaseIds(), + VersionControlProvenance = GetVersionControl(option.Repository), + }; + } - /// - /// Get information from version control system. - /// - /// - /// https://docs.oasis-open.org/sarif/sarif/v2.1.0/os/sarif-v2.1.0-os.html#_Toc34317497 - /// - private static IList GetVersionControl(RepositoryOption option) + /// + /// Get information from version control system. + /// + /// + /// https://docs.oasis-open.org/sarif/sarif/v2.1.0/os/sarif-v2.1.0-os.html#_Toc34317497 + /// + private static IList GetVersionControl(RepositoryOption option) + { + var repository = option.Url; + return new List() { - var repository = option.Url; - return new List() - { - new VersionControlDetails + new() { + RepositoryUri = !string.IsNullOrEmpty(repository) ? new Uri(repository) : null, + RevisionId = !string.IsNullOrEmpty(repository) && GitHelper.TryRevision(out var revision) ? revision : null, + Branch = !string.IsNullOrEmpty(repository) && GitHelper.TryHeadBranch(out var branch) ? branch : null, + MappedTo = new ArtifactLocation { - RepositoryUri = !string.IsNullOrEmpty(repository) ? new Uri(repository) : null, - RevisionId = !string.IsNullOrEmpty(repository) && GitHelper.TryRevision(out var revision) ? revision : null, - Branch = !string.IsNullOrEmpty(repository) && GitHelper.TryHeadBranch(out var branch) ? branch : null, - MappedTo = new ArtifactLocation - { - UriBaseId = LOCATION_ID_REPOROOT - }, - } - }; - } + UriBaseId = LOCATION_ID_REPOROOT + }, + } + }; + } - private static IDictionary GetBaseIds() + private static IDictionary GetBaseIds() + { + return new Dictionary(1) { - return new Dictionary(1) { + LOCATION_ID_REPOROOT, + new ArtifactLocation { - LOCATION_ID_REPOROOT, - new ArtifactLocation - { - Description = GetMessage(ReportStrings.SARIF_REPOROOT_Description), - } + Description = GetMessage(ReportStrings.SARIF_REPOROOT_Description), } - }; - } - - public SarifLog Build() - { - var log = new SarifLog - { - Runs = new List(1), - }; - log.Runs.Add(_Run); - return log; - } + } + }; + } - public void Add(RuleRecord record) + public SarifLog Build() + { + var log = new SarifLog { - if (record == null) - return; + Runs = new List(1), + }; + log.Runs.Add(_Run); + return log; + } - var rule = GetRule(record); - var result = new Result - { - Rule = rule, - Kind = GetKind(record), - Level = GetLevel(record), - Message = new Message { Text = record.Recommendation }, - Locations = GetLocations(record), - }; + public void Add(RuleRecord record) + { + if (record == null) + return; - // SARIF2004: Use the RuleId property instead of Rule for standalone rules. - if (rule.ToolComponent.Guid == TOOL_GUID) - { - result.RuleId = rule.Id; - result.Rule = null; - } - _Run.Results.Add(result); + var rule = GetRule(record); + var result = new Result + { + Rule = rule, + Kind = GetKind(record), + Level = GetLevel(record), + Message = new Message { Text = record.Recommendation }, + Locations = GetLocations(record), + }; + + // SARIF2004: Use the RuleId property instead of Rule for standalone rules. + if (rule.ToolComponent.Guid == TOOL_GUID) + { + result.RuleId = rule.Id; + result.Rule = null; } + _Run.Results.Add(result); + } - private ReportingDescriptorReference GetRule(RuleRecord record) - { - var id = record.Ref ?? record.RuleId; - if (!_Rules.TryGetValue(id, out var descriptorReference)) - descriptorReference = AddRule(record, id); + private ReportingDescriptorReference GetRule(RuleRecord record) + { + var id = record.Ref ?? record.RuleId; + if (!_Rules.TryGetValue(id, out var descriptorReference)) + descriptorReference = AddRule(record, id); - return descriptorReference; - } + return descriptorReference; + } - private ReportingDescriptorReference AddRule(RuleRecord record, string id) - { - if (string.IsNullOrEmpty(record.Info.ModuleName) || !_Extensions.TryGetValue(record.Info.ModuleName, out var toolComponent)) - toolComponent = _Run.Tool.Driver; + private ReportingDescriptorReference AddRule(RuleRecord record, string id) + { + if (string.IsNullOrEmpty(record.Info.ModuleName) || !_Extensions.TryGetValue(record.Info.ModuleName, out var toolComponent)) + toolComponent = _Run.Tool.Driver; - // Add the rule to the component - var descriptor = new ReportingDescriptor + // Add the rule to the component + var descriptor = new ReportingDescriptor + { + Id = id, + Name = record.RuleName, + ShortDescription = GetMessageString(record.Info.Synopsis), + HelpUri = record.Info.GetOnlineHelpUri(), + FullDescription = GetMessageString(record.Info.Description), + MessageStrings = GetMessageStrings(record), + DefaultConfiguration = new ReportingConfiguration { - Id = id, - Name = record.RuleName, - ShortDescription = GetMessageString(record.Info.Synopsis), - HelpUri = record.Info.GetOnlineHelpUri(), - FullDescription = GetMessageString(record.Info.Description), - MessageStrings = GetMessageStrings(record), - DefaultConfiguration = new ReportingConfiguration - { - Enabled = true, - Level = GetLevel(record), - } - }; - toolComponent.Rules.Add(descriptor); + Enabled = true, + Level = GetLevel(record), + } + }; + toolComponent.Rules.Add(descriptor); - // Create a reference to the rule - var descriptorReference = new ReportingDescriptorReference + // Create a reference to the rule + var descriptorReference = new ReportingDescriptorReference + { + Id = descriptor.Id, + ToolComponent = new ToolComponentReference { - Id = descriptor.Id, - ToolComponent = new ToolComponentReference - { - Guid = toolComponent.Guid, - Name = toolComponent.Name, - Index = _Run.Tool.Extensions == null ? -1 : _Run.Tool.Extensions.IndexOf(toolComponent), - } - }; - _Rules.Add(id, descriptorReference); - return descriptorReference; - } + Guid = toolComponent.Guid, + Name = toolComponent.Name, + Index = _Run.Tool.Extensions == null ? -1 : _Run.Tool.Extensions.IndexOf(toolComponent), + } + }; + _Rules.Add(id, descriptorReference); + return descriptorReference; + } - private static RunAutomationDetails GetAutomationDetails() + private static RunAutomationDetails GetAutomationDetails() + { + return PipelineContext.CurrentThread == null ? null : new RunAutomationDetails { - return PipelineContext.CurrentThread == null ? null : new RunAutomationDetails - { - Id = PipelineContext.CurrentThread.RunId, - }; - } + Id = PipelineContext.CurrentThread.RunId, + }; + } - private static IList GetInvocation() + private static IList GetInvocation() + { + var result = new List(1); + var invocation = new Invocation { - var result = new List(1); - var invocation = new Invocation - { - }; - result.Add(invocation); - return result; - } + }; + result.Add(invocation); + return result; + } - private static Message GetMessage(string text) + private static Message GetMessage(string text) + { + return new Message { - return new Message - { - Text = text - }; - } + Text = text + }; + } - private static MultiformatMessageString GetMessageString(InfoString text) + private static MultiformatMessageString GetMessageString(InfoString text) + { + return new MultiformatMessageString { - return new MultiformatMessageString - { - Text = text.Text - }; - } + Text = text.Text + }; + } - private static MultiformatMessageString GetMessageString(string text) + private static MultiformatMessageString GetMessageString(string text) + { + return new MultiformatMessageString { - return new MultiformatMessageString - { - Text = text - }; - } + Text = text + }; + } - private static IDictionary GetMessageStrings(RuleRecord record) + private static IDictionary GetMessageStrings(RuleRecord record) + { + return new Dictionary(1) { - return new Dictionary(1) { + RECOMMENDATION_MESSAGE_ID, + new MultiformatMessageString { - RECOMMENDATION_MESSAGE_ID, - new MultiformatMessageString - { - Text = record.Recommendation - } + Text = record.Recommendation } - }; - } + } + }; + } - private static IList GetLocations(RuleRecord record) - { - if (!record.HasSource()) - return null; + private static IList GetLocations(RuleRecord record) + { + if (!record.HasSource()) + return null; - var result = new List(record.Source.Length); - for (var i = 0; i < record.Source.Length; i++) + var result = new List(record.Source.Length); + for (var i = 0; i < record.Source.Length; i++) + { + result.Add(new Location { - result.Add(new Location + PhysicalLocation = GetPhysicalLocation(record.Source[i]), + LogicalLocation = new LogicalLocation { - PhysicalLocation = GetPhysicalLocation(record.Source[i]), - LogicalLocation = new LogicalLocation - { - Name = record.TargetName, - FullyQualifiedName = string.Concat(record.TargetType, "/", record.TargetName), - Kind = LOCATION_KIND_OBJECT, - } - }); - } - return result; + Name = record.TargetName, + FullyQualifiedName = string.Concat(record.TargetType, "/", record.TargetName), + Kind = LOCATION_KIND_OBJECT, + } + }); } + return result; + } - private static PhysicalLocation GetPhysicalLocation(TargetSourceInfo info) + private static PhysicalLocation GetPhysicalLocation(TargetSourceInfo info) + { + var region = new Region { - var region = new Region - { - StartLine = info.Line ?? 1, - StartColumn = info.Position ?? 0, - }; - var location = new PhysicalLocation + StartLine = info.Line ?? 1, + StartColumn = info.Position ?? 0, + }; + var location = new PhysicalLocation + { + ArtifactLocation = new ArtifactLocation { - ArtifactLocation = new ArtifactLocation - { - Uri = new Uri(info.GetPath(useRelativePath: true), UriKind.Relative), - UriBaseId = LOCATION_ID_REPOROOT, - }, - Region = region, - }; - return location; - } + Uri = new Uri(info.GetPath(useRelativePath: true), UriKind.Relative), + UriBaseId = LOCATION_ID_REPOROOT, + }, + Region = region, + }; + return location; + } - private static ResultKind GetKind(RuleRecord record) - { - if (record.Outcome == RuleOutcome.Pass) - return ResultKind.Pass; + private static ResultKind GetKind(RuleRecord record) + { + if (record.Outcome == RuleOutcome.Pass) + return ResultKind.Pass; - if (record.Outcome == RuleOutcome.Fail) - return ResultKind.Fail; + if (record.Outcome == RuleOutcome.Fail) + return ResultKind.Fail; - return record.Outcome == RuleOutcome.Error || - record.Outcome == RuleOutcome.None && record.OutcomeReason == RuleOutcomeReason.Inconclusive ? - ResultKind.Open : - ResultKind.None; - } + return record.Outcome == RuleOutcome.Error || + record.Outcome == RuleOutcome.None && record.OutcomeReason == RuleOutcomeReason.Inconclusive ? + ResultKind.Open : + ResultKind.None; + } - private static FailureLevel GetLevel(RuleRecord record) - { - if (record.Outcome != RuleOutcome.Fail) - return FailureLevel.None; + private static FailureLevel GetLevel(RuleRecord record) + { + if (record.Outcome != RuleOutcome.Fail) + return FailureLevel.None; - if (record.Level == SeverityLevel.Error) - return FailureLevel.Error; + if (record.Level == SeverityLevel.Error) + return FailureLevel.Error; - return record.Level == SeverityLevel.Warning ? FailureLevel.Warning : FailureLevel.Note; - } + return record.Level == SeverityLevel.Warning ? FailureLevel.Warning : FailureLevel.Note; + } - private Tool GetTool(Source[] source) + private Tool GetTool(Source[] source) + { + var version = Engine.GetVersion(); + return new Tool { - var version = Engine.GetVersion(); - return new Tool + Driver = new ToolComponent { - Driver = new ToolComponent - { - Name = TOOL_NAME, - SemanticVersion = version, - Organization = TOOL_ORG, - Guid = TOOL_GUID, - Rules = new List(), - InformationUri = new Uri("https://aka.ms/ps-rule", UriKind.Absolute), - }, - Extensions = GetExtensions(source), - }; - } + Name = TOOL_NAME, + SemanticVersion = version, + Organization = TOOL_ORG, + Guid = TOOL_GUID, + Rules = new List(), + InformationUri = new Uri("https://aka.ms/ps-rule", UriKind.Absolute), + }, + Extensions = GetExtensions(source), + }; + } - private IList GetExtensions(Source[] source) - { - if (source == null || source.Length == 0) - return null; + private IList GetExtensions(Source[] source) + { + if (source == null || source.Length == 0) + return null; - var result = new List(); - for (var i = 0; i < source.Length; i++) + var result = new List(); + for (var i = 0; i < source.Length; i++) + { + if (source[i].Module != null && !_Extensions.ContainsKey(source[i].Module.Name)) { - if (source[i].Module != null && !_Extensions.ContainsKey(source[i].Module.Name)) + var extension = new ToolComponent { - var extension = new ToolComponent + Name = source[i].Module.Name, + Version = source[i].Module.Version, + Guid = source[i].Module.Guid, + AssociatedComponent = new ToolComponentReference { - Name = source[i].Module.Name, - Version = source[i].Module.Version, - Guid = source[i].Module.Guid, - AssociatedComponent = new ToolComponentReference - { - Name = TOOL_NAME, - }, - InformationUri = new Uri(source[i].Module.ProjectUri, UriKind.Absolute), - Organization = source[i].Module.CompanyName, - Rules = new List(), - }; - _Extensions.Add(extension.Name, extension); - result.Add(extension); - } + Name = TOOL_NAME, + }, + InformationUri = new Uri(source[i].Module.ProjectUri, UriKind.Absolute), + Organization = source[i].Module.CompanyName, + Rules = new List(), + }; + _Extensions.Add(extension.Name, extension); + result.Add(extension); } - return result.Count > 0 ? result : null; } + return result.Count > 0 ? result : null; } +} - internal sealed class SarifOutputWriter : SerializationOutputWriter - { - private readonly SarifBuilder _Builder; - private readonly Encoding _Encoding; - private readonly bool _ReportAll; +internal sealed class SarifOutputWriter : SerializationOutputWriter +{ + private readonly SarifBuilder _Builder; + private readonly Encoding _Encoding; + private readonly bool _ReportAll; - internal SarifOutputWriter(Source[] source, PipelineWriter inner, PSRuleOption option, ShouldProcess shouldProcess) - : base(inner, option, shouldProcess) - { - _Builder = new SarifBuilder(source, option); - _Encoding = option.Output.GetEncoding(); - _ReportAll = !option.Output.SarifProblemsOnly.GetValueOrDefault(OutputOption.Default.SarifProblemsOnly.Value); - } + internal SarifOutputWriter(Source[] source, PipelineWriter inner, PSRuleOption option, ShouldProcess shouldProcess) + : base(inner, option, shouldProcess) + { + _Builder = new SarifBuilder(source, option); + _Encoding = option.Output.GetEncoding(); + _ReportAll = !option.Output.SarifProblemsOnly.GetValueOrDefault(OutputOption.Default.SarifProblemsOnly.Value); + } - public override void WriteObject(object sendToPipeline, bool enumerateCollection) - { - if (sendToPipeline is not InvokeResult result) - return; + public override void WriteObject(object sendToPipeline, bool enumerateCollection) + { + if (sendToPipeline is not InvokeResult result) + return; - Add(result); - } + Add(result); + } - protected override string Serialize(InvokeResult[] o) + protected override string Serialize(InvokeResult[] o) + { + for (var i = 0; o != null && i < o.Length; i++) { - for (var i = 0; o != null && i < o.Length; i++) - { - var records = o[i].AsRecord(); - for (var j = 0; j < records.Length; j++) - if (ShouldReport(records[j])) - _Builder.Add(records[j]); - } - var log = _Builder.Build(); - using var stream = new MemoryStream(); - using var writer = new StreamWriter(stream, _Encoding, bufferSize: 1024, leaveOpen: true); - log.Save(writer); - stream.Seek(0, SeekOrigin.Begin); - using var reader = new StreamReader(stream); - return reader.ReadToEnd(); + var records = o[i].AsRecord(); + for (var j = 0; j < records.Length; j++) + if (ShouldReport(records[j])) + _Builder.Add(records[j]); } + var log = _Builder.Build(); + using var stream = new MemoryStream(); + using var writer = new StreamWriter(stream, _Encoding, bufferSize: 1024, leaveOpen: true); + log.Save(writer); + stream.Seek(0, SeekOrigin.Begin); + using var reader = new StreamReader(stream); + return reader.ReadToEnd(); + } - private bool ShouldReport(RuleRecord record) - { - return _ReportAll || - (record.Outcome & RuleOutcome.Problem) != RuleOutcome.None; - } + private bool ShouldReport(RuleRecord record) + { + return _ReportAll || + (record.Outcome & RuleOutcome.Problem) != RuleOutcome.None; } } diff --git a/src/PSRule/Pipeline/Output/WideOutputWriter.cs b/src/PSRule/Pipeline/Output/WideOutputWriter.cs index 0af8c03fe8..a377b1bc68 100644 --- a/src/PSRule/Pipeline/Output/WideOutputWriter.cs +++ b/src/PSRule/Pipeline/Output/WideOutputWriter.cs @@ -6,34 +6,33 @@ using PSRule.Definitions.Rules; using PSRule.Rules; -namespace PSRule.Pipeline.Output +namespace PSRule.Pipeline.Output; + +internal sealed class WideOutputWriter : PipelineWriter { - internal sealed class WideOutputWriter : PipelineWriter - { - internal WideOutputWriter(PipelineWriter inner, PSRuleOption option, ShouldProcess shouldProcess) - : base(inner, option, shouldProcess) { } + internal WideOutputWriter(PipelineWriter inner, PSRuleOption option, ShouldProcess shouldProcess) + : base(inner, option, shouldProcess) { } - public override void WriteObject(object sendToPipeline, bool enumerateCollection) - { - if (sendToPipeline is InvokeResult result) - WriteWideObject(result.AsRecord()); - else if (sendToPipeline is IEnumerable rules) - WriteWideObject(rules); - else - base.WriteObject(sendToPipeline, enumerateCollection); - } + public override void WriteObject(object sendToPipeline, bool enumerateCollection) + { + if (sendToPipeline is InvokeResult result) + WriteWideObject(result.AsRecord()); + else if (sendToPipeline is IEnumerable rules) + WriteWideObject(rules); + else + base.WriteObject(sendToPipeline, enumerateCollection); + } - private void WriteWideObject(IEnumerable collection) + private void WriteWideObject(IEnumerable collection) + { + var typeName = string.Concat(typeof(T).FullName, "+Wide"); + foreach (var item in collection) { - var typeName = string.Concat(typeof(T).FullName, "+Wide"); - foreach (var item in collection) - { - var o = PSObject.AsPSObject(item); - o.TypeNames.Insert(0, typeName); - base.WriteObject(o, false); - if (item is RuleRecord record) - WriteErrorInfo(record); - } + var o = PSObject.AsPSObject(item); + o.TypeNames.Insert(0, typeName); + base.WriteObject(o, false); + if (item is RuleRecord record) + WriteErrorInfo(record); } } } diff --git a/src/PSRule/Pipeline/Output/YamlOutputWriter.cs b/src/PSRule/Pipeline/Output/YamlOutputWriter.cs index 2191d33717..e6106444f9 100644 --- a/src/PSRule/Pipeline/Output/YamlOutputWriter.cs +++ b/src/PSRule/Pipeline/Output/YamlOutputWriter.cs @@ -8,51 +8,50 @@ using YamlDotNet.Serialization; using YamlDotNet.Serialization.NamingConventions; -namespace PSRule.Pipeline.Output +namespace PSRule.Pipeline.Output; + +internal sealed class YamlOutputWriter : SerializationOutputWriter { - internal sealed class YamlOutputWriter : SerializationOutputWriter - { - internal YamlOutputWriter(PipelineWriter inner, PSRuleOption option, ShouldProcess shouldProcess) - : base(inner, option, shouldProcess) { } + internal YamlOutputWriter(PipelineWriter inner, PSRuleOption option, ShouldProcess shouldProcess) + : base(inner, option, shouldProcess) { } - protected override string Serialize(object[] o) - { - return o[0] is IEnumerable baselines ? ToBaselineYaml(baselines) : ToYaml(o); - } + protected override string Serialize(object[] o) + { + return o[0] is IEnumerable baselines ? ToBaselineYaml(baselines) : ToYaml(o); + } - internal static string ToYaml(object[] o) - { - var s = new SerializerBuilder() - .DisableAliases() - .WithTypeInspector(f => new FieldYamlTypeInspector()) - .WithTypeInspector(inspector => new OrderedPropertiesTypeInspector(inspector)) - .WithTypeConverter(new PSObjectYamlTypeConverter()) - .WithTypeConverter(new FieldMapYamlTypeConverter()) - .WithTypeConverter(new InfoStringYamlTypeConverter()) - .WithNamingConvention(CamelCaseNamingConvention.Instance) - .ConfigureDefaultValuesHandling(DefaultValuesHandling.OmitDefaults) - .Build(); - - return s.Serialize(o); - } + internal static string ToYaml(object[] o) + { + var s = new SerializerBuilder() + .DisableAliases() + .WithTypeInspector(f => new FieldYamlTypeInspector()) + .WithTypeInspector(inspector => new OrderedPropertiesTypeInspector(inspector)) + .WithTypeConverter(new PSObjectYamlTypeConverter()) + .WithTypeConverter(new FieldMapYamlTypeConverter()) + .WithTypeConverter(new InfoStringYamlTypeConverter()) + .WithNamingConvention(CamelCaseNamingConvention.Instance) + .ConfigureDefaultValuesHandling(DefaultValuesHandling.OmitDefaults) + .Build(); + + return s.Serialize(o); + } - internal static string ToBaselineYaml(IEnumerable baselines) - { - using var output = new StringWriter(); - var emitter = new Emitter(output, bestIndent: 2, bestWidth: int.MaxValue, isCanonical: false); + internal static string ToBaselineYaml(IEnumerable baselines) + { + using var output = new StringWriter(); + var emitter = new Emitter(output, bestIndent: 2, bestWidth: int.MaxValue, isCanonical: false); - emitter.Emit(new StreamStart()); + emitter.Emit(new StreamStart()); - foreach (var baseline in baselines) - { - emitter.Emit(new DocumentStart()); - BaselineYamlSerializationMapper.MapBaseline(emitter, baseline); - emitter.Emit(new DocumentEnd(isImplicit: true)); - } + foreach (var baseline in baselines) + { + emitter.Emit(new DocumentStart()); + BaselineYamlSerializationMapper.MapBaseline(emitter, baseline); + emitter.Emit(new DocumentEnd(isImplicit: true)); + } - emitter.Emit(new StreamEnd()); + emitter.Emit(new StreamEnd()); - return output.ToString(); - } + return output.ToString(); } } diff --git a/src/PSRule/Pipeline/PathBuilder.cs b/src/PSRule/Pipeline/PathBuilder.cs index d402099197..07c5f459c2 100644 --- a/src/PSRule/Pipeline/PathBuilder.cs +++ b/src/PSRule/Pipeline/PathBuilder.cs @@ -5,259 +5,258 @@ using System.Management.Automation; using PSRule.Data; -namespace PSRule.Pipeline -{ - //public interface IPathBuilder - //{ - // void Add(string path); +namespace PSRule.Pipeline; + +//public interface IPathBuilder +//{ +// void Add(string path); - // void Add(FileInfo[] fileInfo); +// void Add(FileInfo[] fileInfo); - // void Add(PathInfo[] pathInfo); +// void Add(PathInfo[] pathInfo); - // InputFileInfo[] Build(); - //} +// InputFileInfo[] Build(); +//} - internal abstract class PathBuilder +internal abstract class PathBuilder +{ + // Path separators + private const char Slash = '/'; + private const char BackSlash = '\\'; + + private const char Dot = '.'; + private const string CurrentPath = "."; + private const string RecursiveSearchOperator = "**"; + + private static readonly char[] PathLiteralStopCharacters = new char[] { '*', '[', '?' }; + private static readonly char[] PathSeparatorCharacters = new char[] { '\\', '/' }; + + private readonly IPipelineWriter _Logger; + private readonly List _Files; + private readonly HashSet _Paths; + private readonly string _BasePath; + private readonly string _DefaultSearchPattern; + private readonly PathFilter _GlobalFilter; + private readonly PathFilter _Required; + + protected PathBuilder(IPipelineWriter logger, string basePath, string searchPattern, PathFilter filter, PathFilter required) { - // Path separators - private const char Slash = '/'; - private const char BackSlash = '\\'; - - private const char Dot = '.'; - private const string CurrentPath = "."; - private const string RecursiveSearchOperator = "**"; - - private static readonly char[] PathLiteralStopCharacters = new char[] { '*', '[', '?' }; - private static readonly char[] PathSeparatorCharacters = new char[] { '\\', '/' }; - - private readonly IPipelineWriter _Logger; - private readonly List _Files; - private readonly HashSet _Paths; - private readonly string _BasePath; - private readonly string _DefaultSearchPattern; - private readonly PathFilter _GlobalFilter; - private readonly PathFilter _Required; - - protected PathBuilder(IPipelineWriter logger, string basePath, string searchPattern, PathFilter filter, PathFilter required) - { - _Logger = logger; - _Files = new List(); - _Paths = new HashSet(); - _BasePath = NormalizePath(Environment.GetRootedBasePath(basePath)); - _DefaultSearchPattern = searchPattern; - _GlobalFilter = filter; - _Required = required; - } + _Logger = logger; + _Files = new List(); + _Paths = new HashSet(); + _BasePath = NormalizePath(Environment.GetRootedBasePath(basePath)); + _DefaultSearchPattern = searchPattern; + _GlobalFilter = filter; + _Required = required; + } - /// - /// The number of files found. - /// - public int Count => _Files.Count; + /// + /// The number of files found. + /// + public int Count => _Files.Count; - /// - /// Add an array of paths to the builder. - /// - public void Add(string[] path) - { - if (path == null || path.Length == 0) - return; + /// + /// Add an array of paths to the builder. + /// + public void Add(string[] path) + { + if (path == null || path.Length == 0) + return; - for (var i = 0; i < path.Length; i++) - Add(path[i]); - } + for (var i = 0; i < path.Length; i++) + Add(path[i]); + } - /// - /// Add a path to the builder. - /// - public void Add(string path) - { - if (string.IsNullOrEmpty(path)) - return; - - try - { - FindFiles(path); - } - catch (Exception ex) - { - _Logger.ErrorReadInputFailed(path, ex); - } - } + /// + /// Add a path to the builder. + /// + public void Add(string path) + { + if (string.IsNullOrEmpty(path)) + return; - public InputFileInfo[] Build() + try { - try - { - return _Files.ToArray(); - } - finally - { - _Files.Clear(); - _Paths.Clear(); - } + FindFiles(path); } - - private void FindFiles(string path) + catch (Exception ex) { - if (TryUrl(path) || TryPath(path, out path)) - return; - - var pathLiteral = GetSearchParameters(path, out var searchPattern, out var searchOption, out var filter); - var files = Directory.EnumerateFiles(pathLiteral, searchPattern, searchOption); - foreach (var file in files) - if (ShouldInclude(file, filter)) - AddFile(file); + _Logger.ErrorReadInputFailed(path, ex); } + } - private bool TryUrl(string path) + public InputFileInfo[] Build() + { + try { - if (!path.IsURL()) - return false; - - AddFile(path); - return true; + return _Files.ToArray(); } - - private bool TryPath(string path, out string normalPath) + finally { - normalPath = path; - if (path.IndexOfAny(PathLiteralStopCharacters) > -1) - return false; - - var rootedPath = GetRootedPath(path); - if (Directory.Exists(rootedPath) || path == CurrentPath) - { - if (IsBasePath(rootedPath)) - normalPath = CurrentPath; - - return false; - } - if (!File.Exists(rootedPath)) - { - ErrorNotFound(path); - return false; - } - AddFile(rootedPath); - return true; + _Files.Clear(); + _Paths.Clear(); } + } - private bool IsBasePath(string path) - { - path = IsSeparator(path[path.Length - 1]) ? path : string.Concat(path, Path.DirectorySeparatorChar); - return NormalizePath(path) == _BasePath; - } + private void FindFiles(string path) + { + if (TryUrl(path) || TryPath(path, out path)) + return; + + var pathLiteral = GetSearchParameters(path, out var searchPattern, out var searchOption, out var filter); + var files = Directory.EnumerateFiles(pathLiteral, searchPattern, searchOption); + foreach (var file in files) + if (ShouldInclude(file, filter)) + AddFile(file); + } - private void ErrorNotFound(string path) - { - if (_Logger == null) - return; + private bool TryUrl(string path) + { + if (!path.IsURL()) + return false; - _Logger.WriteError(new ErrorRecord(new FileNotFoundException(), "PSRule.PathBuilder.ErrorNotFound", ErrorCategory.ObjectNotFound, path)); - } + AddFile(path); + return true; + } + + private bool TryPath(string path, out string normalPath) + { + normalPath = path; + if (path.IndexOfAny(PathLiteralStopCharacters) > -1) + return false; - private void AddFile(string path) + var rootedPath = GetRootedPath(path); + if (Directory.Exists(rootedPath) || path == CurrentPath) { - if (_Paths.Contains(path)) - return; + if (IsBasePath(rootedPath)) + normalPath = CurrentPath; - _Files.Add(new InputFileInfo(_BasePath, path)); - _Paths.Add(path); + return false; } - - private string GetRootedPath(string path) + if (!File.Exists(rootedPath)) { - return Path.IsPathRooted(path) ? path : Path.GetFullPath(Path.Combine(_BasePath, path)); + ErrorNotFound(path); + return false; } + AddFile(rootedPath); + return true; + } - /// - /// Split a search path into components based on wildcards. - /// - private string GetSearchParameters(string path, out string searchPattern, out SearchOption searchOption, out PathFilter filter) - { - searchOption = SearchOption.AllDirectories; - var pathLiteral = TrimPath(path, out var relativeAnchor); + private bool IsBasePath(string path) + { + path = IsSeparator(path[path.Length - 1]) ? path : string.Concat(path, Path.DirectorySeparatorChar); + return NormalizePath(path) == _BasePath; + } + + private void ErrorNotFound(string path) + { + if (_Logger == null) + return; + + _Logger.WriteError(new ErrorRecord(new FileNotFoundException(), "PSRule.PathBuilder.ErrorNotFound", ErrorCategory.ObjectNotFound, path)); + } - if (TryFilter(pathLiteral, out searchPattern, out filter)) - return _BasePath; + private void AddFile(string path) + { + if (_Paths.Contains(path)) + return; - pathLiteral = SplitSearchPath(pathLiteral, out searchPattern); - if ((relativeAnchor || !string.IsNullOrEmpty(pathLiteral)) && !string.IsNullOrEmpty(searchPattern)) - searchOption = SearchOption.TopDirectoryOnly; + _Files.Add(new InputFileInfo(_BasePath, path)); + _Paths.Add(path); + } - if (string.IsNullOrEmpty(searchPattern)) - searchPattern = _DefaultSearchPattern; + private string GetRootedPath(string path) + { + return Path.IsPathRooted(path) ? path : Path.GetFullPath(Path.Combine(_BasePath, path)); + } - return GetRootedPath(pathLiteral); - } + /// + /// Split a search path into components based on wildcards. + /// + private string GetSearchParameters(string path, out string searchPattern, out SearchOption searchOption, out PathFilter filter) + { + searchOption = SearchOption.AllDirectories; + var pathLiteral = TrimPath(path, out var relativeAnchor); - private static string SplitSearchPath(string path, out string searchPattern) - { - // Find the index of the first expression character i.e. out/modules/**/file - var stopIndex = path.IndexOfAny(PathLiteralStopCharacters); + if (TryFilter(pathLiteral, out searchPattern, out filter)) + return _BasePath; - // Track back to the separator before any expression characters - var literalSeparator = stopIndex > -1 ? path.LastIndexOfAny(PathSeparatorCharacters, stopIndex) + 1 : path.LastIndexOfAny(PathSeparatorCharacters) + 1; - searchPattern = path.Substring(literalSeparator, path.Length - literalSeparator); - return path.Substring(0, literalSeparator); - } + pathLiteral = SplitSearchPath(pathLiteral, out searchPattern); + if ((relativeAnchor || !string.IsNullOrEmpty(pathLiteral)) && !string.IsNullOrEmpty(searchPattern)) + searchOption = SearchOption.TopDirectoryOnly; - private bool TryFilter(string path, out string searchPattern, out PathFilter filter) - { - searchPattern = null; - filter = null; - if (UseSimpleSearch(path)) - return false; - - filter = PathFilter.Create(_BasePath, path); - var patternSeparator = path.LastIndexOfAny(PathSeparatorCharacters) + 1; - searchPattern = path.Substring(patternSeparator, path.Length - patternSeparator); - return true; - } + if (string.IsNullOrEmpty(searchPattern)) + searchPattern = _DefaultSearchPattern; - /// - /// Remove leading ./ or .\ characters indicating a relative path anchor. - /// - /// The path to trim. - /// Returns true when a relative path anchor was present. - /// Return a clean path without the relative path anchor. - private static string TrimPath(string path, out bool relativeAnchor) - { - relativeAnchor = false; - if (path.Length >= 2 && path[0] == Dot && IsSeparator(path[1])) - { - relativeAnchor = true; - return path.Remove(0, 2); - } - return path; - } + return GetRootedPath(pathLiteral); + } - private bool ShouldInclude(string file, PathFilter filter) - { - return (filter == null || filter.Match(file)) && - (_Required == null || _Required.Match(file)) && - (_GlobalFilter == null || _GlobalFilter.Match(file)); - } + private static string SplitSearchPath(string path, out string searchPattern) + { + // Find the index of the first expression character i.e. out/modules/**/file + var stopIndex = path.IndexOfAny(PathLiteralStopCharacters); - [DebuggerStepThrough] - private static bool IsSeparator(char c) - { - return c == Slash || c == BackSlash; - } + // Track back to the separator before any expression characters + var literalSeparator = stopIndex > -1 ? path.LastIndexOfAny(PathSeparatorCharacters, stopIndex) + 1 : path.LastIndexOfAny(PathSeparatorCharacters) + 1; + searchPattern = path.Substring(literalSeparator, path.Length - literalSeparator); + return path.Substring(0, literalSeparator); + } - /// - /// Determines if a simple search can be used. - /// - [DebuggerStepThrough] - private static bool UseSimpleSearch(string s) - { - return s.IndexOf(RecursiveSearchOperator, System.StringComparison.OrdinalIgnoreCase) == -1; - } + private bool TryFilter(string path, out string searchPattern, out PathFilter filter) + { + searchPattern = null; + filter = null; + if (UseSimpleSearch(path)) + return false; + + filter = PathFilter.Create(_BasePath, path); + var patternSeparator = path.LastIndexOfAny(PathSeparatorCharacters) + 1; + searchPattern = path.Substring(patternSeparator, path.Length - patternSeparator); + return true; + } - [DebuggerStepThrough] - private static string NormalizePath(string path) + /// + /// Remove leading ./ or .\ characters indicating a relative path anchor. + /// + /// The path to trim. + /// Returns true when a relative path anchor was present. + /// Return a clean path without the relative path anchor. + private static string TrimPath(string path, out bool relativeAnchor) + { + relativeAnchor = false; + if (path.Length >= 2 && path[0] == Dot && IsSeparator(path[1])) { - return path.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); + relativeAnchor = true; + return path.Remove(0, 2); } + return path; + } + + private bool ShouldInclude(string file, PathFilter filter) + { + return (filter == null || filter.Match(file)) && + (_Required == null || _Required.Match(file)) && + (_GlobalFilter == null || _GlobalFilter.Match(file)); + } + + [DebuggerStepThrough] + private static bool IsSeparator(char c) + { + return c == Slash || c == BackSlash; + } + + /// + /// Determines if a simple search can be used. + /// + [DebuggerStepThrough] + private static bool UseSimpleSearch(string s) + { + return s.IndexOf(RecursiveSearchOperator, System.StringComparison.OrdinalIgnoreCase) == -1; + } + + [DebuggerStepThrough] + private static string NormalizePath(string path) + { + return path.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); } } diff --git a/src/PSRule/Pipeline/PathFilter.cs b/src/PSRule/Pipeline/PathFilter.cs index b7488d3f6f..af729c3539 100644 --- a/src/PSRule/Pipeline/PathFilter.cs +++ b/src/PSRule/Pipeline/PathFilter.cs @@ -3,484 +3,483 @@ using System.Diagnostics; -namespace PSRule.Pipeline +namespace PSRule.Pipeline; + +internal sealed class PathFilterBuilder { - internal sealed class PathFilterBuilder + private const string GitIgnoreFileName = ".gitignore"; + + private static readonly string[] CommonFiles = new string[] { - private const string GitIgnoreFileName = ".gitignore"; + "README.md", + ".DS_Store", + ".gitignore", + ".gitattributes", + ".gitmodules", + "LICENSE", + "LICENSE.txt", + "CODE_OF_CONDUCT.md", + "CONTRIBUTING.md", + "SECURITY.md", + "SUPPORT.md", + ".vscode/*.json", + ".vscode/*.code-snippets", + ".github/**/*.md", + ".github/CODEOWNERS", + ".pipelines/**/*.yml", + ".pipelines/**/*.yaml", + ".azure-pipelines/**/*.yml", + ".azure-pipelines/**/*.yaml", + ".azuredevops/*.md" + }; + + private readonly string _BasePath; + private readonly List _Expressions; + private readonly bool _MatchResult; + + private PathFilterBuilder(string basePath, string[] expressions, bool matchResult, bool ignoreGitPath, bool ignoreRepositoryCommon) + { + _BasePath = basePath; + _Expressions = expressions == null || expressions.Length == 0 ? new List() : new List(expressions); + _MatchResult = matchResult; + if (ignoreRepositoryCommon) + _Expressions.InsertRange(0, CommonFiles); + + if (ignoreGitPath) + _Expressions.Add(".git/"); + } - private static readonly string[] CommonFiles = new string[] - { - "README.md", - ".DS_Store", - ".gitignore", - ".gitattributes", - ".gitmodules", - "LICENSE", - "LICENSE.txt", - "CODE_OF_CONDUCT.md", - "CONTRIBUTING.md", - "SECURITY.md", - "SUPPORT.md", - ".vscode/*.json", - ".vscode/*.code-snippets", - ".github/**/*.md", - ".github/CODEOWNERS", - ".pipelines/**/*.yml", - ".pipelines/**/*.yaml", - ".azure-pipelines/**/*.yml", - ".azure-pipelines/**/*.yaml", - ".azuredevops/*.md" - }; - - private readonly string _BasePath; - private readonly List _Expressions; - private readonly bool _MatchResult; - - private PathFilterBuilder(string basePath, string[] expressions, bool matchResult, bool ignoreGitPath, bool ignoreRepositoryCommon) + internal static PathFilterBuilder Create(string basePath, string[] expressions, bool ignoreGitPath, bool ignoreRepositoryCommon) + { + return new PathFilterBuilder(basePath, expressions, false, ignoreGitPath, ignoreRepositoryCommon); + } + + internal void UseGitIgnore(string basePath = null) + { + _Expressions.Add("!.git/HEAD"); + ReadFile(Path.Combine(basePath ?? _BasePath, GitIgnoreFileName)); + } + + internal PathFilter Build() + { + return PathFilter.Create(_BasePath, _Expressions.ToArray(), _MatchResult); + } + + private void ReadFile(string filePath) + { + if (File.Exists(filePath)) + _Expressions.AddRange(File.ReadAllLines(filePath)); + } +} + +/// +/// Filters paths based on predefined rules. +/// +internal sealed class PathFilter +{ + // Path separators + private const char Slash = '/'; + private const char BackSlash = '\\'; + + // Operators + private const char Asterix = '*'; // Match multiple characters except '/' + private const char Question = '?'; // Match any character except '/' + private const char Hash = '#'; // Comment + private const char Exclamation = '!'; // Include a previously excluded path + + private readonly string _BasePath; + private readonly PathFilterExpression[] _Expression; + private readonly bool _MatchResult; + + private PathFilter(string basePath, PathFilterExpression[] expression, bool matchResult) + { + _BasePath = NormalDirectoryPath(basePath); + _Expression = expression ?? Array.Empty(); + _MatchResult = matchResult; + } + + #region PathStream + + [DebuggerDisplay("Path = '{_Path}', Position = {_Position}, Current = '{_Current}'")] + private sealed class PathStream + { + private readonly string _Path; + private int _Position; + private char _Current; + + public PathStream(string path) { - _BasePath = basePath; - _Expressions = expressions == null || expressions.Length == 0 ? new List() : new List(expressions); - _MatchResult = matchResult; - if (ignoreRepositoryCommon) - _Expressions.InsertRange(0, CommonFiles); - - if (ignoreGitPath) - _Expressions.Add(".git/"); + _Path = path; + Reset(); } - internal static PathFilterBuilder Create(string basePath, string[] expressions, bool ignoreGitPath, bool ignoreRepositoryCommon) + /// + /// Resets the cursor to the start of the path stream. + /// + public void Reset() { - return new PathFilterBuilder(basePath, expressions, false, ignoreGitPath, ignoreRepositoryCommon); + _Position = -1; + _Current = char.MinValue; + if (_Path[0] == Exclamation) + Next(); } - internal void UseGitIgnore(string basePath = null) + /// + /// Move to the next character. + /// + public bool Next() { - _Expressions.Add("!.git/HEAD"); - ReadFile(Path.Combine(basePath ?? _BasePath, GitIgnoreFileName)); + _Position++; + if (_Position >= _Path.Length) + { + _Current = char.MinValue; + return false; + } + _Current = _Path[_Position]; + return true; } - internal PathFilter Build() + public bool TryMatch(PathStream other, int offset) { - return PathFilter.Create(_BasePath, _Expressions.ToArray(), _MatchResult); + return other.Peak(offset, out var c) && IsMatch(c); } - private void ReadFile(string filePath) + public bool IsUnmatchedSingle(PathStream other, int offset) { - if (File.Exists(filePath)) - _Expressions.AddRange(File.ReadAllLines(filePath)); + return other.Peak(offset, out var c) && IsWilcardQ(c) && other.Peak(offset + 1, out var cnext) && IsMatch(cnext); } - } - /// - /// Filters paths based on predefined rules. - /// - internal sealed class PathFilter - { - // Path separators - private const char Slash = '/'; - private const char BackSlash = '\\'; - - // Operators - private const char Asterix = '*'; // Match multiple characters except '/' - private const char Question = '?'; // Match any character except '/' - private const char Hash = '#'; // Comment - private const char Exclamation = '!'; // Include a previously excluded path - - private readonly string _BasePath; - private readonly PathFilterExpression[] _Expression; - private readonly bool _MatchResult; - - private PathFilter(string basePath, PathFilterExpression[] expression, bool matchResult) + private bool IsMatch(char c) { - _BasePath = NormalDirectoryPath(basePath); - _Expression = expression ?? Array.Empty(); - _MatchResult = matchResult; + return _Current == c || + (IsSeparator(_Current) && IsSeparator(c)) || + (!IsSeparator(_Current) && IsWilcardQ(c)); } - #region PathStream - - [DebuggerDisplay("Path = '{_Path}', Position = {_Position}, Current = '{_Current}'")] - private sealed class PathStream + /// + /// Determine if the current character sequence is ** or **/. + /// + public bool IsAnyMatchEnding(int offset = 0) { - private readonly string _Path; - private int _Position; - private char _Current; + if (!IsWildcardAA(offset)) + return false; - public PathStream(string path) - { - _Path = path; - Reset(); - } + var pos = _Position + offset; - /// - /// Resets the cursor to the start of the path stream. - /// - public void Reset() - { - _Position = -1; - _Current = char.MinValue; - if (_Path[0] == Exclamation) - Next(); - } - - /// - /// Move to the next character. - /// - public bool Next() - { - _Position++; - if (_Position >= _Path.Length) - { - _Current = char.MinValue; - return false; - } - _Current = _Path[_Position]; + // Ends in ** + if (pos + 1 == _Path.Length - 1) return true; - } - - public bool TryMatch(PathStream other, int offset) - { - return other.Peak(offset, out var c) && IsMatch(c); - } - public bool IsUnmatchedSingle(PathStream other, int offset) - { - return other.Peak(offset, out var c) && IsWilcardQ(c) && other.Peak(offset + 1, out var cnext) && IsMatch(cnext); - } + // Ends in **/ + return pos + 2 == _Path.Length - 1 && IsSeparator(_Path[pos + 2]); + } - private bool IsMatch(char c) - { - return _Current == c || - (IsSeparator(_Current) && IsSeparator(c)) || - (!IsSeparator(_Current) && IsWilcardQ(c)); - } + public bool SkipMatchAA() + { + if (!IsWildcardAA()) + return false; - /// - /// Determine if the current character sequence is ** or **/. - /// - public bool IsAnyMatchEnding(int offset = 0) - { - if (!IsWildcardAA(offset)) - return false; + Skip(2); // Skip ** + SkipSeparator(); // Skip **/ + SkipMatchAA(); // Skip **/**/ + return true; + } - var pos = _Position + offset; + public bool SkipMatchA() + { + if (!IsWildardA()) + return false; - // Ends in ** - if (pos + 1 == _Path.Length - 1) - return true; + Skip(1); + return true; + } - // Ends in **/ - return pos + 2 == _Path.Length - 1 && IsSeparator(_Path[pos + 2]); - } + private bool Skip(int count) + { + if (count > 1) + _Position += count - 1; - public bool SkipMatchAA() - { - if (!IsWildcardAA()) - return false; + return Next(); + } - Skip(2); // Skip ** - SkipSeparator(); // Skip **/ - SkipMatchAA(); // Skip **/**/ - return true; - } + [DebuggerStepThrough] + private void SkipSeparator() + { + if (IsSeparator(_Current)) + Next(); + } - public bool SkipMatchA() - { - if (!IsWildardA()) - return false; + /// + /// Determine if the current character sequence is **. + /// + [DebuggerStepThrough] + private bool IsWildcardAA(int offset = 0) + { + var pos = _Position + offset; + return pos + 1 < _Path.Length && _Path[pos] == Asterix && _Path[pos + 1] == Asterix; + } - Skip(1); - return true; - } + [DebuggerStepThrough] + private bool IsWildardA(int offset = 0) + { + var pos = _Position + offset; + return pos < _Path.Length && _Path[pos] == Asterix; + } - private bool Skip(int count) - { - if (count > 1) - _Position += count - 1; + [DebuggerStepThrough] + private static bool IsWilcardQ(char c) + { + return c == Question; + } - return Next(); - } + [DebuggerStepThrough] + private static bool IsSeparator(char c) + { + return c == Slash || c == BackSlash; + } - [DebuggerStepThrough] - private void SkipSeparator() + /// + /// Match ** + /// + public bool TryMatchAA(PathStream other, int start) + { + var offset = start; + do { - if (IsSeparator(_Current)) - Next(); - } + if (IsUnmatchedSingle(other, offset)) + offset++; - /// - /// Determine if the current character sequence is **. - /// - [DebuggerStepThrough] - private bool IsWildcardAA(int offset = 0) - { - var pos = _Position + offset; - return pos + 1 < _Path.Length && _Path[pos] == Asterix && _Path[pos + 1] == Asterix; - } + // Determine if fully matched to end or the next any match + if (other.IsWildcardAA(offset)) + { + other.Skip(offset); + return true; + } + else if (other.IsWildardA(offset) && TryMatchA(other, offset + 1)) + { + return true; + } - [DebuggerStepThrough] - private bool IsWildardA(int offset = 0) - { - var pos = _Position + offset; - return pos < _Path.Length && _Path[pos] == Asterix; - } + //(IsSingleWildcard(c) && other.Peak(offset + 1, out char cnext) && IsMatch(cnext)) + //if (TryMatchCharacter(other, offset)) + //{ - [DebuggerStepThrough] - private static bool IsWilcardQ(char c) - { - return c == Question; - } + //} + // Try to match the remaining + if (TryMatch(other, offset)) + { + offset++; + } + else + { + offset = start; + } - [DebuggerStepThrough] - private static bool IsSeparator(char c) - { - return c == Slash || c == BackSlash; - } + if (offset + other._Position >= other._Path.Length) + { + other.Skip(offset); + return true; + } + } while (Next()); + return false; + } - /// - /// Match ** - /// - public bool TryMatchAA(PathStream other, int start) + /// + /// Match * + /// + public bool TryMatchA(PathStream other, int start) + { + var offset = start; + do { - var offset = start; - do + // Determine if fully matched to end or the next any match + if (other.IsWildcardAA(offset)) { - if (IsUnmatchedSingle(other, offset)) - offset++; - - // Determine if fully matched to end or the next any match - if (other.IsWildcardAA(offset)) - { - other.Skip(offset); - return true; - } - else if (other.IsWildardA(offset) && TryMatchA(other, offset + 1)) - { - return true; - } - - //(IsSingleWildcard(c) && other.Peak(offset + 1, out char cnext) && IsMatch(cnext)) - //if (TryMatchCharacter(other, offset)) - //{ - - //} - // Try to match the remaining - if (TryMatch(other, offset)) - { - offset++; - } - else - { - offset = start; - } - - if (offset + other._Position >= other._Path.Length) - { - other.Skip(offset); - return true; - } - } while (Next()); - return false; - } + other.Skip(offset); + return true; + } + //if (other.IsCharacterMatch(offset)) - /// - /// Match * - /// - public bool TryMatchA(PathStream other, int start) - { - var offset = start; - do + // Try to match the remaining + if (TryMatch(other, offset)) { - // Determine if fully matched to end or the next any match - if (other.IsWildcardAA(offset)) - { - other.Skip(offset); - return true; - } - //if (other.IsCharacterMatch(offset)) - - // Try to match the remaining - if (TryMatch(other, offset)) - { - offset++; - } - else - { - offset = start; - } - - if (offset + other._Position >= other._Path.Length) - { - Next(); - other.Skip(offset); - return true; - } - } while (Next()); - return false; - } + offset++; + } + else + { + offset = start; + } - private bool Peak(int offset, out char c) - { - if (offset + _Position >= _Path.Length) + if (offset + other._Position >= other._Path.Length) { - c = char.MinValue; - return false; + Next(); + other.Skip(offset); + return true; } - c = _Path[offset + _Position]; - return true; - } + } while (Next()); + return false; } - #endregion PathStream - - #region PathFilterExpression - - [DebuggerDisplay("{_Expression}")] - private sealed class PathFilterExpression + private bool Peak(int offset, out char c) { - private readonly PathStream _Expression; - private readonly bool _Include; - - private PathFilterExpression(string expression, bool include) + if (offset + _Position >= _Path.Length) { - _Expression = new PathStream(expression); - _Include = include; + c = char.MinValue; + return false; } + c = _Path[offset + _Position]; + return true; + } + } - public static PathFilterExpression Create(string expression) - { - if (string.IsNullOrEmpty(expression)) - throw new ArgumentNullException(nameof(expression)); - - var actualInclude = true; - if (expression[0] == Exclamation) - actualInclude = false; - - return new PathFilterExpression(expression, actualInclude); - } + #endregion PathStream - /// - /// Determine if the path matches the expression. - /// - public void Match(string path, ref bool include) - { - // Only process if the result would change - if (_Include == include || !Match(path)) - return; + #region PathFilterExpression - include = !include; - } + [DebuggerDisplay("{_Expression}")] + private sealed class PathFilterExpression + { + private readonly PathStream _Expression; + private readonly bool _Include; - /// - /// Determines if the path matches the expression. - /// - private bool Match(string path) - { - _Expression.Reset(); - var stream = new PathStream(path); - while (stream.Next() && _Expression.Next()) - { - // Match characters - if (stream.TryMatch(_Expression, 0)) - continue; - - // Skip ? when zero characters are being matched - else if (stream.IsUnmatchedSingle(_Expression, 0)) - _Expression.Next(); - - // Match ending wildcards e.g. src/** or src/**/ - else if (_Expression.IsAnyMatchEnding()) - break; - - // Match ending with depth e.g. src/**/bin/ - else if (_Expression.SkipMatchAA()) - { - if (!stream.TryMatchAA(_Expression, 0)) - return false; - } - - // Match wildcard * - else if (_Expression.SkipMatchA()) - { - if (!stream.TryMatchA(_Expression, 0)) - return false; - } - - else return false; - } - return true; - } + private PathFilterExpression(string expression, bool include) + { + _Expression = new PathStream(expression); + _Include = include; } - #endregion PathFilterExpression + public static PathFilterExpression Create(string expression) + { + if (string.IsNullOrEmpty(expression)) + throw new ArgumentNullException(nameof(expression)); - #region Public methods + var actualInclude = true; + if (expression[0] == Exclamation) + actualInclude = false; - public static PathFilter Create(string basePath, string expression, bool matchResult = true) - { - return !ShouldSkipExpression(expression) - ? new PathFilter( - basePath, - new PathFilterExpression[] { PathFilterExpression.Create(expression) }, - matchResult) - : new PathFilter(basePath, null, matchResult); + return new PathFilterExpression(expression, actualInclude); } - public static PathFilter Create(string basePath, string[] expression, bool matchResult = true) + /// + /// Determine if the path matches the expression. + /// + public void Match(string path, ref bool include) { - var result = new List(expression.Length); - for (var i = 0; i < expression.Length; i++) - if (!ShouldSkipExpression(expression[i])) - result.Add(PathFilterExpression.Create(expression[i])); - - return result.Count == 0 - ? new PathFilter(basePath, null, matchResult) - : new PathFilter(basePath, result.ToArray(), matchResult); + // Only process if the result would change + if (_Include == include || !Match(path)) + return; + + include = !include; } /// - /// Determine if the specific path is matched. + /// Determines if the path matches the expression. /// - /// The path to evaluate. - public bool Match(string path) + private bool Match(string path) { - var start = 0; - - // Check if path is within base path - if (Path.IsPathRooted(path)) + _Expression.Reset(); + var stream = new PathStream(path); + while (stream.Next() && _Expression.Next()) { - if (!path.StartsWith(_BasePath)) - return !_MatchResult; + // Match characters + if (stream.TryMatch(_Expression, 0)) + continue; - start = _BasePath.Length; - } - var cleanPath = start > 0 ? path.Remove(0, start) : path; + // Skip ? when zero characters are being matched + else if (stream.IsUnmatchedSingle(_Expression, 0)) + _Expression.Next(); - // Include unless excluded - var result = false; + // Match ending wildcards e.g. src/** or src/**/ + else if (_Expression.IsAnyMatchEnding()) + break; - // Compare expressions - for (var i = 0; i < _Expression.Length; i++) - _Expression[i].Match(cleanPath, ref result); + // Match ending with depth e.g. src/**/bin/ + else if (_Expression.SkipMatchAA()) + { + if (!stream.TryMatchAA(_Expression, 0)) + return false; + } + + // Match wildcard * + else if (_Expression.SkipMatchA()) + { + if (!stream.TryMatchA(_Expression, 0)) + return false; + } - // Flip the result if a match should return false - return _MatchResult ? result : !result; + else return false; + } + return true; } + } - #endregion Public methods + #endregion PathFilterExpression - private static bool ShouldSkipExpression(string expression) - { - return string.IsNullOrEmpty(expression) || expression[0] == Hash; - } + #region Public methods + + public static PathFilter Create(string basePath, string expression, bool matchResult = true) + { + return !ShouldSkipExpression(expression) + ? new PathFilter( + basePath, + new PathFilterExpression[] { PathFilterExpression.Create(expression) }, + matchResult) + : new PathFilter(basePath, null, matchResult); + } + + public static PathFilter Create(string basePath, string[] expression, bool matchResult = true) + { + var result = new List(expression.Length); + for (var i = 0; i < expression.Length; i++) + if (!ShouldSkipExpression(expression[i])) + result.Add(PathFilterExpression.Create(expression[i])); + + return result.Count == 0 + ? new PathFilter(basePath, null, matchResult) + : new PathFilter(basePath, result.ToArray(), matchResult); + } + + /// + /// Determine if the specific path is matched. + /// + /// The path to evaluate. + public bool Match(string path) + { + var start = 0; - private static string NormalDirectoryPath(string path) + // Check if path is within base path + if (Path.IsPathRooted(path)) { - var c = path[path.Length - 1]; - return c == Path.DirectorySeparatorChar || c == Path.AltDirectorySeparatorChar - ? path - : string.Concat(path, Path.DirectorySeparatorChar); + if (!path.StartsWith(_BasePath)) + return !_MatchResult; + + start = _BasePath.Length; } + var cleanPath = start > 0 ? path.Remove(0, start) : path; + + // Include unless excluded + var result = false; + + // Compare expressions + for (var i = 0; i < _Expression.Length; i++) + _Expression[i].Match(cleanPath, ref result); + + // Flip the result if a match should return false + return _MatchResult ? result : !result; + } + + #endregion Public methods + + private static bool ShouldSkipExpression(string expression) + { + return string.IsNullOrEmpty(expression) || expression[0] == Hash; + } + + private static string NormalDirectoryPath(string path) + { + var c = path[path.Length - 1]; + return c == Path.DirectorySeparatorChar || c == Path.AltDirectorySeparatorChar + ? path + : string.Concat(path, Path.DirectorySeparatorChar); } } diff --git a/src/PSRule/Pipeline/PipelineBuilder.cs b/src/PSRule/Pipeline/PipelineBuilder.cs index ad7b2d60a4..31ab502335 100644 --- a/src/PSRule/Pipeline/PipelineBuilder.cs +++ b/src/PSRule/Pipeline/PipelineBuilder.cs @@ -12,682 +12,681 @@ using PSRule.Pipeline.Output; using PSRule.Resources; -namespace PSRule.Pipeline +namespace PSRule.Pipeline; + +/// +/// A helper to create a PowerShell-based pipeline for running PSRule. +/// +public static class PipelineBuilder { /// - /// A helper to create a PowerShell-based pipeline for running PSRule. + /// Create a builder for an Assert pipeline. + /// Used by Assert-PSRule. /// - public static class PipelineBuilder - { - /// - /// Create a builder for an Assert pipeline. - /// Used by Assert-PSRule. - /// - /// - /// Assert pipelines process objects with rules and produce text-based output suitable for output to a CI pipeline. - /// - /// An array of sources. - /// Options that configure PSRule. - /// An implementation of a host context that will receive output and results. - /// A builder object to configure the pipeline. - public static IInvokePipelineBuilder Assert(Source[] source, PSRuleOption option, IHostContext hostContext) - { - var pipeline = new AssertPipelineBuilder(source, hostContext); - pipeline.Configure(option); - return pipeline; - } - - /// - /// Create a builder for an Invoke pipeline. - /// Used by Invoke-PSRule. - /// - /// - /// Invoke pipelines process objects and produce records indicating the outcome of each rule. - /// - /// An array of sources. - /// Options that configure PSRule. - /// An implementation of a host context that will receive output and results. - /// A builder object to configure the pipeline. - public static IInvokePipelineBuilder Invoke(Source[] source, PSRuleOption option, IHostContext hostContext) - { - var pipeline = new InvokeRulePipelineBuilder(source, hostContext); - pipeline.Configure(option); - return pipeline; - } + /// + /// Assert pipelines process objects with rules and produce text-based output suitable for output to a CI pipeline. + /// + /// An array of sources. + /// Options that configure PSRule. + /// An implementation of a host context that will receive output and results. + /// A builder object to configure the pipeline. + public static IInvokePipelineBuilder Assert(Source[] source, PSRuleOption option, IHostContext hostContext) + { + var pipeline = new AssertPipelineBuilder(source, hostContext); + pipeline.Configure(option); + return pipeline; + } - /// - /// Create a builder for a Test pipeline. - /// Used by Test-PSRule. - /// - /// - /// Test pipelines process objects and true or false the outcome of each rule. - /// - /// An array of sources. - /// Options that configure PSRule. - /// An implementation of a host context that will receive output and results. - /// A builder object to configure the pipeline. - public static IInvokePipelineBuilder Test(Source[] source, PSRuleOption option, IHostContext hostContext) - { - var pipeline = new TestPipelineBuilder(source, hostContext); - pipeline.Configure(option); - return pipeline; - } + /// + /// Create a builder for an Invoke pipeline. + /// Used by Invoke-PSRule. + /// + /// + /// Invoke pipelines process objects and produce records indicating the outcome of each rule. + /// + /// An array of sources. + /// Options that configure PSRule. + /// An implementation of a host context that will receive output and results. + /// A builder object to configure the pipeline. + public static IInvokePipelineBuilder Invoke(Source[] source, PSRuleOption option, IHostContext hostContext) + { + var pipeline = new InvokeRulePipelineBuilder(source, hostContext); + pipeline.Configure(option); + return pipeline; + } - /// - /// Create a builder for a Get pipeline. - /// Used by Get-PSRule. - /// - /// - /// Get pipelines list rules that are discovered by PSRule either in modules or as standalone rules. - /// - /// An array of sources. - /// Options that configure PSRule. - /// An implementation of a host context that will receive output and results. - /// A builder object to configure the pipeline. - public static IGetPipelineBuilder Get(Source[] source, PSRuleOption option, IHostContext hostContext) - { - var pipeline = new GetRulePipelineBuilder(source, hostContext); - pipeline.Configure(option); - return pipeline; - } + /// + /// Create a builder for a Test pipeline. + /// Used by Test-PSRule. + /// + /// + /// Test pipelines process objects and true or false the outcome of each rule. + /// + /// An array of sources. + /// Options that configure PSRule. + /// An implementation of a host context that will receive output and results. + /// A builder object to configure the pipeline. + public static IInvokePipelineBuilder Test(Source[] source, PSRuleOption option, IHostContext hostContext) + { + var pipeline = new TestPipelineBuilder(source, hostContext); + pipeline.Configure(option); + return pipeline; + } - /// - /// Create a builder for a help pipeline. - /// Used by Get-PSRuleHelp. - /// - /// - /// Gets command lines help content for all or specific rules. - /// - /// An array of sources. - /// Options that configure PSRule. - /// An implementation of a host context that will receive output and results. - /// A builder object to configure the pipeline. - public static IHelpPipelineBuilder GetHelp(Source[] source, PSRuleOption option, IHostContext hostContext) - { - var pipeline = new GetRuleHelpPipelineBuilder(source, hostContext); - pipeline.Configure(option); - return pipeline; - } + /// + /// Create a builder for a Get pipeline. + /// Used by Get-PSRule. + /// + /// + /// Get pipelines list rules that are discovered by PSRule either in modules or as standalone rules. + /// + /// An array of sources. + /// Options that configure PSRule. + /// An implementation of a host context that will receive output and results. + /// A builder object to configure the pipeline. + public static IGetPipelineBuilder Get(Source[] source, PSRuleOption option, IHostContext hostContext) + { + var pipeline = new GetRulePipelineBuilder(source, hostContext); + pipeline.Configure(option); + return pipeline; + } - /// - /// Create a builder to define a list of rule sources. - /// - /// >Options that configure PSRule. - /// >An implementation of a host context that will receive output and results. - /// A builder object to configure the source pipeline. - public static ISourcePipelineBuilder RuleSource(PSRuleOption option, IHostContext hostContext) - { - var pipeline = new SourcePipelineBuilder(hostContext, option); - return pipeline; - } + /// + /// Create a builder for a help pipeline. + /// Used by Get-PSRuleHelp. + /// + /// + /// Gets command lines help content for all or specific rules. + /// + /// An array of sources. + /// Options that configure PSRule. + /// An implementation of a host context that will receive output and results. + /// A builder object to configure the pipeline. + public static IHelpPipelineBuilder GetHelp(Source[] source, PSRuleOption option, IHostContext hostContext) + { + var pipeline = new GetRuleHelpPipelineBuilder(source, hostContext); + pipeline.Configure(option); + return pipeline; + } - /// - /// Create a builder for a get baseline pipeline. - /// Used by Get-PSRuleBaseline. - /// - /// An array of sources. - /// Options that configure PSRule. - /// An implementation of a host context that will receive output and results. - /// A builder object to configure the pipeline. - public static IPipelineBuilder GetBaseline(Source[] source, PSRuleOption option, IHostContext hostContext) - { - var pipeline = new GetBaselinePipelineBuilder(source, hostContext); - pipeline.Configure(option); - return pipeline; - } + /// + /// Create a builder to define a list of rule sources. + /// + /// >Options that configure PSRule. + /// >An implementation of a host context that will receive output and results. + /// A builder object to configure the source pipeline. + public static ISourcePipelineBuilder RuleSource(PSRuleOption option, IHostContext hostContext) + { + var pipeline = new SourcePipelineBuilder(hostContext, option); + return pipeline; + } - /// - /// Create a builder for an export baseline pipeline. - /// Used by Export-PSRuleBaseline. - /// - /// An array of sources. - /// Options that configure PSRule. - /// An implementation of a host context that will receive output and results. - /// A builder object to configure the pipeline. - public static IPipelineBuilder ExportBaseline(Source[] source, PSRuleOption option, IHostContext hostContext) - { - var pipeline = new ExportBaselinePipelineBuilder(source, hostContext); - pipeline.Configure(option); - return pipeline; - } + /// + /// Create a builder for a get baseline pipeline. + /// Used by Get-PSRuleBaseline. + /// + /// An array of sources. + /// Options that configure PSRule. + /// An implementation of a host context that will receive output and results. + /// A builder object to configure the pipeline. + public static IPipelineBuilder GetBaseline(Source[] source, PSRuleOption option, IHostContext hostContext) + { + var pipeline = new GetBaselinePipelineBuilder(source, hostContext); + pipeline.Configure(option); + return pipeline; + } - /// - /// Create a builder for a target pipeline. - /// Used by Get-PSRuleTarget. - /// - /// Options that configure PSRule. - /// An implementation of a host context that will receive output and results. - /// A builder object to configure the pipeline. - public static IGetTargetPipelineBuilder GetTarget(PSRuleOption option, IHostContext hostContext) - { - var pipeline = new GetTargetPipelineBuilder(null, hostContext); - pipeline.Configure(option); - return pipeline; - } + /// + /// Create a builder for an export baseline pipeline. + /// Used by Export-PSRuleBaseline. + /// + /// An array of sources. + /// Options that configure PSRule. + /// An implementation of a host context that will receive output and results. + /// A builder object to configure the pipeline. + public static IPipelineBuilder ExportBaseline(Source[] source, PSRuleOption option, IHostContext hostContext) + { + var pipeline = new ExportBaselinePipelineBuilder(source, hostContext); + pipeline.Configure(option); + return pipeline; } /// - /// A helper to build a PSRule pipeline. + /// Create a builder for a target pipeline. + /// Used by Get-PSRuleTarget. /// - public interface IPipelineBuilder + /// Options that configure PSRule. + /// An implementation of a host context that will receive output and results. + /// A builder object to configure the pipeline. + public static IGetTargetPipelineBuilder GetTarget(PSRuleOption option, IHostContext hostContext) { - /// - /// Configure the pipeline with options. - /// - IPipelineBuilder Configure(PSRuleOption option); + var pipeline = new GetTargetPipelineBuilder(null, hostContext); + pipeline.Configure(option); + return pipeline; + } +} - /// - /// Configure the pipeline to use a specific baseline. - /// - /// A baseline option or the name of a baseline. - void Baseline(Configuration.BaselineOption baseline); +/// +/// A helper to build a PSRule pipeline. +/// +public interface IPipelineBuilder +{ + /// + /// Configure the pipeline with options. + /// + IPipelineBuilder Configure(PSRuleOption option); - /// - /// Build the pipeline. - /// - /// Optionally specify a custom writer which will handle output processing. - IPipeline Build(IPipelineWriter writer = null); - } + /// + /// Configure the pipeline to use a specific baseline. + /// + /// A baseline option or the name of a baseline. + void Baseline(Configuration.BaselineOption baseline); /// - /// An instance of a PSRule pipeline. + /// Build the pipeline. /// - public interface IPipeline : IDisposable - { - /// - /// Get the pipeline result. - /// - IPipelineResult Result { get; } + /// Optionally specify a custom writer which will handle output processing. + IPipeline Build(IPipelineWriter writer = null); +} + +/// +/// An instance of a PSRule pipeline. +/// +public interface IPipeline : IDisposable +{ + /// + /// Get the pipeline result. + /// + IPipelineResult Result { get; } - /// - /// Initialize the pipeline and results. Call this method once prior to calling Process. - /// - void Begin(); + /// + /// Initialize the pipeline and results. Call this method once prior to calling Process. + /// + void Begin(); - /// - /// Process an object through the pipeline. Each object will be processed by rules that apply based on pre-conditions. - /// - /// The object to process. - void Process(PSObject sourceObject); + /// + /// Process an object through the pipeline. Each object will be processed by rules that apply based on pre-conditions. + /// + /// The object to process. + void Process(PSObject sourceObject); - /// - /// Clean up and flush pipeline results. Call this method once after processing any objects through the pipeline. - /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1716:Identifiers should not match keywords", Justification = "Matches PowerShell pipeline.")] - void End(); - } + /// + /// Clean up and flush pipeline results. Call this method once after processing any objects through the pipeline. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1716:Identifiers should not match keywords", Justification = "Matches PowerShell pipeline.")] + void End(); +} +/// +/// A result from the pipeline. +/// +public interface IPipelineResult +{ /// - /// A result from the pipeline. + /// Determines if any errors were reported. /// - public interface IPipelineResult - { - /// - /// Determines if any errors were reported. - /// - public bool HadErrors { get; } + public bool HadErrors { get; } - /// - /// Determines if an failures were reported. - /// - public bool HadFailures { get; } - } + /// + /// Determines if an failures were reported. + /// + public bool HadFailures { get; } +} + +internal sealed class DefaultPipelineResult : IPipelineResult +{ + private readonly IPipelineWriter _Writer; + private bool _HadErrors; + private bool _HadFailures; - internal sealed class DefaultPipelineResult : IPipelineResult + public DefaultPipelineResult(IPipelineWriter writer) { - private readonly IPipelineWriter _Writer; - private bool _HadErrors; - private bool _HadFailures; + _Writer = writer; + } - public DefaultPipelineResult(IPipelineWriter writer) + /// + public bool HadErrors + { + get { - _Writer = writer; + return _HadErrors || (_Writer != null && _Writer.HadErrors); } - - /// - public bool HadErrors + set { - get - { - return _HadErrors || (_Writer != null && _Writer.HadErrors); - } - set - { - _HadErrors = value; - } + _HadErrors = value; } + } - /// - public bool HadFailures + /// + public bool HadFailures + { + get { - get - { - return _HadFailures || (_Writer != null && _Writer.HadFailures); - } - set - { - _HadFailures = value; - } + return _HadFailures || (_Writer != null && _Writer.HadFailures); + } + set + { + _HadFailures = value; } } +} - internal abstract class PipelineBuilderBase : IPipelineBuilder - { - private const string ENGINE_MODULE_NAME = "PSRule"; +internal abstract class PipelineBuilderBase : IPipelineBuilder +{ + private const string ENGINE_MODULE_NAME = "PSRule"; - protected readonly PSRuleOption Option; - protected readonly Source[] Source; - protected readonly IHostContext HostContext; - protected BindTargetMethod BindTargetNameHook; - protected BindTargetMethod BindTargetTypeHook; - protected BindTargetMethod BindFieldHook; - protected VisitTargetObject VisitTargetObject; + protected readonly PSRuleOption Option; + protected readonly Source[] Source; + protected readonly IHostContext HostContext; + protected BindTargetMethod BindTargetNameHook; + protected BindTargetMethod BindTargetTypeHook; + protected BindTargetMethod BindFieldHook; + protected VisitTargetObject VisitTargetObject; - private string[] _Include; - private Hashtable _Tag; - private Configuration.BaselineOption _Baseline; - private string[] _Convention; - private PathFilter _InputFilter; - private PipelineWriter _Writer; + private string[] _Include; + private Hashtable _Tag; + private Configuration.BaselineOption _Baseline; + private string[] _Convention; + private PathFilter _InputFilter; + private PipelineWriter _Writer; - private readonly HostPipelineWriter _Output; + private readonly HostPipelineWriter _Output; - private const int MIN_JSON_INDENT = 0; - private const int MAX_JSON_INDENT = 4; + private const int MIN_JSON_INDENT = 0; + private const int MAX_JSON_INDENT = 4; - protected PipelineBuilderBase(Source[] source, IHostContext hostContext) - { - Option = new PSRuleOption(); - Source = source; - _Output = new HostPipelineWriter(hostContext, Option, ShouldProcess); - HostContext = hostContext; - BindTargetNameHook = PipelineHookActions.BindTargetName; - BindTargetTypeHook = PipelineHookActions.BindTargetType; - BindFieldHook = PipelineHookActions.BindField; - VisitTargetObject = PipelineReceiverActions.PassThru; - } + protected PipelineBuilderBase(Source[] source, IHostContext hostContext) + { + Option = new PSRuleOption(); + Source = source; + _Output = new HostPipelineWriter(hostContext, Option, ShouldProcess); + HostContext = hostContext; + BindTargetNameHook = PipelineHookActions.BindTargetName; + BindTargetTypeHook = PipelineHookActions.BindTargetType; + BindFieldHook = PipelineHookActions.BindField; + VisitTargetObject = PipelineReceiverActions.PassThru; + } - /// - /// Determines if the pipeline is executing in a remote PowerShell session. - /// - public bool InSession => HostContext != null && HostContext.InSession; + /// + /// Determines if the pipeline is executing in a remote PowerShell session. + /// + public bool InSession => HostContext != null && HostContext.InSession; - /// - public void Name(string[] name) - { - if (name == null || name.Length == 0) - return; + /// + public void Name(string[] name) + { + if (name == null || name.Length == 0) + return; - _Include = name; - } + _Include = name; + } - /// - public void Tag(Hashtable tag) - { - if (tag == null || tag.Count == 0) - return; + /// + public void Tag(Hashtable tag) + { + if (tag == null || tag.Count == 0) + return; - _Tag = tag; - } + _Tag = tag; + } - /// - public void Convention(string[] convention) - { - if (convention == null || convention.Length == 0) - return; + /// + public void Convention(string[] convention) + { + if (convention == null || convention.Length == 0) + return; - _Convention = convention; - } + _Convention = convention; + } - /// - public virtual IPipelineBuilder Configure(PSRuleOption option) - { - if (option == null) - return this; - - Option.Baseline = new Options.BaselineOption(option.Baseline); - Option.Binding = new BindingOption(option.Binding); - Option.Convention = new ConventionOption(option.Convention); - Option.Execution = GetExecutionOption(option.Execution); - Option.Input = new InputOption(option.Input); - Option.Input.Format ??= InputOption.Default.Format; - Option.Output = new OutputOption(option.Output); - Option.Output.Outcome ??= OutputOption.Default.Outcome; - Option.Output.Banner ??= OutputOption.Default.Banner; - Option.Output.Style = GetStyle(option.Output.Style ?? OutputOption.Default.Style.Value); - Option.Repository = GetRepository(option.Repository); + /// + public virtual IPipelineBuilder Configure(PSRuleOption option) + { + if (option == null) return this; - } - /// - public abstract IPipeline Build(IPipelineWriter writer = null); + Option.Baseline = new Options.BaselineOption(option.Baseline); + Option.Binding = new BindingOption(option.Binding); + Option.Convention = new ConventionOption(option.Convention); + Option.Execution = GetExecutionOption(option.Execution); + Option.Input = new InputOption(option.Input); + Option.Input.Format ??= InputOption.Default.Format; + Option.Output = new OutputOption(option.Output); + Option.Output.Outcome ??= OutputOption.Default.Outcome; + Option.Output.Banner ??= OutputOption.Default.Banner; + Option.Output.Style = GetStyle(option.Output.Style ?? OutputOption.Default.Style.Value); + Option.Repository = GetRepository(option.Repository); + return this; + } - /// - public void Baseline(Configuration.BaselineOption baseline) - { - if (baseline == null) - return; + /// + public abstract IPipeline Build(IPipelineWriter writer = null); - _Baseline = baseline; - } + /// + public void Baseline(Configuration.BaselineOption baseline) + { + if (baseline == null) + return; - /// - /// Require correct module versions for pipeline execution. - /// - protected bool RequireModules() + _Baseline = baseline; + } + + /// + /// Require correct module versions for pipeline execution. + /// + protected bool RequireModules() + { + var result = true; + if (Option.Requires.TryGetValue(ENGINE_MODULE_NAME, out var requiredVersion)) { - var result = true; - if (Option.Requires.TryGetValue(ENGINE_MODULE_NAME, out var requiredVersion)) - { - var engineVersion = Engine.GetVersion(); - if (GuardModuleVersion(ENGINE_MODULE_NAME, engineVersion, requiredVersion)) - result = false; - } - for (var i = 0; Source != null && i < Source.Length; i++) - { - if (Source[i].Module != null && Option.Requires.TryGetValue(Source[i].Module.Name, out requiredVersion)) - { - if (GuardModuleVersion(Source[i].Module.Name, Source[i].Module.Version, requiredVersion)) - result = false; - } - } - return result; + var engineVersion = Engine.GetVersion(); + if (GuardModuleVersion(ENGINE_MODULE_NAME, engineVersion, requiredVersion)) + result = false; } - - /// - /// Require sources for pipeline execution. - /// - protected bool RequireSources() + for (var i = 0; Source != null && i < Source.Length; i++) { - if (Source == null || Source.Length == 0) + if (Source[i].Module != null && Option.Requires.TryGetValue(Source[i].Module.Name, out requiredVersion)) { - PrepareWriter().WarnRulePathNotFound(); - return false; + if (GuardModuleVersion(Source[i].Module.Name, Source[i].Module.Version, requiredVersion)) + result = false; } - return true; } + return result; + } - private bool GuardModuleVersion(string moduleName, string moduleVersion, string requiredVersion) + /// + /// Require sources for pipeline execution. + /// + protected bool RequireSources() + { + if (Source == null || Source.Length == 0) { - if (!TryModuleVersion(moduleVersion, requiredVersion)) - { - var writer = PrepareWriter(); - writer.ErrorRequiredVersionMismatch(moduleName, moduleVersion, requiredVersion); - writer.End(); - return true; - } + PrepareWriter().WarnRulePathNotFound(); return false; } + return true; + } - private static bool TryModuleVersion(string moduleVersion, string requiredVersion) + private bool GuardModuleVersion(string moduleName, string moduleVersion, string requiredVersion) + { + if (!TryModuleVersion(moduleVersion, requiredVersion)) { - return SemanticVersion.TryParseVersion(moduleVersion, out var version) && - SemanticVersion.TryParseConstraint(requiredVersion, out var constraint) && - constraint.Equals(version); + var writer = PrepareWriter(); + writer.ErrorRequiredVersionMismatch(moduleName, moduleVersion, requiredVersion); + writer.End(); + return true; } + return false; + } - protected PipelineContext PrepareContext(BindTargetMethod bindTargetName, BindTargetMethod bindTargetType, BindTargetMethod bindField) - { - var unresolved = new List(); - if (_Baseline is Configuration.BaselineOption.BaselineRef baselineRef) - unresolved.Add(new BaselineRef(ResolveBaselineGroup(baselineRef.Name), ScopeType.Explicit)); - - return PipelineContext.New( - option: Option, - hostContext: HostContext, - reader: PrepareReader(), - bindTargetName: bindTargetName, - bindTargetType: bindTargetType, - bindField: bindField, - optionBuilder: GetOptionBuilder(), - unresolved: unresolved - ); - } + private static bool TryModuleVersion(string moduleVersion, string requiredVersion) + { + return SemanticVersion.TryParseVersion(moduleVersion, out var version) && + SemanticVersion.TryParseConstraint(requiredVersion, out var constraint) && + constraint.Equals(version); + } - protected string[] ResolveBaselineGroup(string[] name) - { - for (var i = 0; name != null && i < name.Length; i++) - name[i] = ResolveBaselineGroup(name[i]); + protected PipelineContext PrepareContext(BindTargetMethod bindTargetName, BindTargetMethod bindTargetType, BindTargetMethod bindField) + { + var unresolved = new List(); + if (_Baseline is Configuration.BaselineOption.BaselineRef baselineRef) + unresolved.Add(new BaselineRef(ResolveBaselineGroup(baselineRef.Name), ScopeType.Explicit)); + + return PipelineContext.New( + option: Option, + hostContext: HostContext, + reader: PrepareReader(), + bindTargetName: bindTargetName, + bindTargetType: bindTargetType, + bindField: bindField, + optionBuilder: GetOptionBuilder(), + unresolved: unresolved + ); + } - return name; - } + protected string[] ResolveBaselineGroup(string[] name) + { + for (var i = 0; name != null && i < name.Length; i++) + name[i] = ResolveBaselineGroup(name[i]); - protected string ResolveBaselineGroup(string name) - { - if (name == null || name.Length < 2 || !name.StartsWith("@") || - Option == null || Option.Baseline == null || Option.Baseline.Group == null || - Option.Baseline.Group.Count == 0) - return name; + return name; + } - var key = name.Substring(1); - if (!Option.Baseline.Group.TryGetValue(key, out var baselines) || baselines.Length == 0) - throw new PipelineConfigurationException("Baseline.Group", string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.PSR0003, key)); + protected string ResolveBaselineGroup(string name) + { + if (name == null || name.Length < 2 || !name.StartsWith("@") || + Option == null || Option.Baseline == null || Option.Baseline.Group == null || + Option.Baseline.Group.Count == 0) + return name; - var writer = PrepareWriter(); - writer.WriteVerbose($"Using baseline group '{key}': {baselines[0]}"); - return baselines[0]; - } + var key = name.Substring(1); + if (!Option.Baseline.Group.TryGetValue(key, out var baselines) || baselines.Length == 0) + throw new PipelineConfigurationException("Baseline.Group", string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.PSR0003, key)); - protected virtual PipelineReader PrepareReader() - { - return new PipelineReader(null, null, GetInputObjectSourceFilter()); - } + var writer = PrepareWriter(); + writer.WriteVerbose($"Using baseline group '{key}': {baselines[0]}"); + return baselines[0]; + } - protected virtual PipelineWriter PrepareWriter() - { - if (_Writer != null) - return _Writer; + protected virtual PipelineReader PrepareReader() + { + return new PipelineReader(null, null, GetInputObjectSourceFilter()); + } - var output = GetOutput(); - _Writer = Option.Output.Format switch - { - OutputFormat.Csv => new CsvOutputWriter(output, Option, ShouldProcess), - OutputFormat.Json => new JsonOutputWriter(output, Option, ShouldProcess), - OutputFormat.NUnit3 => new NUnit3OutputWriter(output, Option, ShouldProcess), - OutputFormat.Yaml => new YamlOutputWriter(output, Option, ShouldProcess), - OutputFormat.Markdown => new MarkdownOutputWriter(output, Option, ShouldProcess), - OutputFormat.Wide => new WideOutputWriter(output, Option, ShouldProcess), - OutputFormat.Sarif => new SarifOutputWriter(Source, output, Option, ShouldProcess), - _ => output, - }; + protected virtual PipelineWriter PrepareWriter() + { + if (_Writer != null) return _Writer; - } - protected virtual PipelineWriter GetOutput(bool writeHost = false) - { - // Redirect to file instead - return !string.IsNullOrEmpty(Option.Output.Path) - ? new FileOutputWriter( - inner: _Output, - option: Option, - encoding: Option.Output.GetEncoding(), - path: Option.Output.Path, - shouldProcess: HostContext.ShouldProcess, - writeHost: writeHost - ) - : (PipelineWriter)_Output; - } + var output = GetOutput(); + _Writer = Option.Output.Format switch + { + OutputFormat.Csv => new CsvOutputWriter(output, Option, ShouldProcess), + OutputFormat.Json => new JsonOutputWriter(output, Option, ShouldProcess), + OutputFormat.NUnit3 => new NUnit3OutputWriter(output, Option, ShouldProcess), + OutputFormat.Yaml => new YamlOutputWriter(output, Option, ShouldProcess), + OutputFormat.Markdown => new MarkdownOutputWriter(output, Option, ShouldProcess), + OutputFormat.Wide => new WideOutputWriter(output, Option, ShouldProcess), + OutputFormat.Sarif => new SarifOutputWriter(Source, output, Option, ShouldProcess), + _ => output, + }; + return _Writer; + } + + protected virtual PipelineWriter GetOutput(bool writeHost = false) + { + // Redirect to file instead + return !string.IsNullOrEmpty(Option.Output.Path) + ? new FileOutputWriter( + inner: _Output, + option: Option, + encoding: Option.Output.GetEncoding(), + path: Option.Output.Path, + shouldProcess: HostContext.ShouldProcess, + writeHost: writeHost + ) + : (PipelineWriter)_Output; + } - protected static string[] GetCulture(string[] culture) + protected static string[] GetCulture(string[] culture) + { + var result = new List(); + var parent = new List(); + var set = new HashSet(); + for (var i = 0; culture != null && i < culture.Length; i++) { - var result = new List(); - var parent = new List(); - var set = new HashSet(); - for (var i = 0; culture != null && i < culture.Length; i++) + var c = CultureInfo.CreateSpecificCulture(culture[i]); + if (!set.Contains(c.Name)) { - var c = CultureInfo.CreateSpecificCulture(culture[i]); - if (!set.Contains(c.Name)) - { - result.Add(c.Name); - set.Add(c.Name); - } - for (var p = c.Parent; !string.IsNullOrEmpty(p.Name); p = p.Parent) + result.Add(c.Name); + set.Add(c.Name); + } + for (var p = c.Parent; !string.IsNullOrEmpty(p.Name); p = p.Parent) + { + if (!set.Contains(p.Name)) { - if (!set.Contains(p.Name)) - { - parent.Add(p.Name); - set.Add(p.Name); - } + parent.Add(p.Name); + set.Add(p.Name); } } - if (parent.Count > 0) - result.AddRange(parent); - - return result.Count == 0 ? null : result.ToArray(); } + if (parent.Count > 0) + result.AddRange(parent); - protected static RepositoryOption GetRepository(RepositoryOption option) - { - var result = new RepositoryOption(option); - if (string.IsNullOrEmpty(result.Url) && GitHelper.TryRepository(out var url)) - result.Url = url; + return result.Count == 0 ? null : result.ToArray(); + } - if (string.IsNullOrEmpty(result.BaseRef) && GitHelper.TryBaseRef(out var baseRef)) - result.BaseRef = baseRef; + protected static RepositoryOption GetRepository(RepositoryOption option) + { + var result = new RepositoryOption(option); + if (string.IsNullOrEmpty(result.Url) && GitHelper.TryRepository(out var url)) + result.Url = url; - return result; - } + if (string.IsNullOrEmpty(result.BaseRef) && GitHelper.TryBaseRef(out var baseRef)) + result.BaseRef = baseRef; - /// - /// Coalesce execution options with defaults. - /// - protected static ExecutionOption GetExecutionOption(ExecutionOption option) - { - var result = ExecutionOption.Combine(option, ExecutionOption.Default); - result.DuplicateResourceId = result.DuplicateResourceId == ExecutionActionPreference.None ? ExecutionOption.Default.DuplicateResourceId.Value : result.DuplicateResourceId; - result.SuppressionGroupExpired = result.SuppressionGroupExpired == ExecutionActionPreference.None ? ExecutionOption.Default.SuppressionGroupExpired.Value : result.SuppressionGroupExpired; - return result; - } + return result; + } - protected PathFilter GetInputObjectSourceFilter() - { - return Option.Input.IgnoreObjectSource.GetValueOrDefault(InputOption.Default.IgnoreObjectSource.Value) ? GetInputFilter() : null; - } + /// + /// Coalesce execution options with defaults. + /// + protected static ExecutionOption GetExecutionOption(ExecutionOption option) + { + var result = ExecutionOption.Combine(option, ExecutionOption.Default); + result.DuplicateResourceId = result.DuplicateResourceId == ExecutionActionPreference.None ? ExecutionOption.Default.DuplicateResourceId.Value : result.DuplicateResourceId; + result.SuppressionGroupExpired = result.SuppressionGroupExpired == ExecutionActionPreference.None ? ExecutionOption.Default.SuppressionGroupExpired.Value : result.SuppressionGroupExpired; + return result; + } - protected PathFilter GetInputFilter() - { - if (_InputFilter == null) - { - var basePath = Environment.GetWorkingPath(); - var ignoreGitPath = Option.Input.IgnoreGitPath ?? InputOption.Default.IgnoreGitPath.Value; - var ignoreRepositoryCommon = Option.Input.IgnoreRepositoryCommon ?? InputOption.Default.IgnoreRepositoryCommon.Value; - var builder = PathFilterBuilder.Create(basePath, Option.Input.PathIgnore, ignoreGitPath, ignoreRepositoryCommon); - if (Option.Input.Format == InputFormat.File) - builder.UseGitIgnore(); - - _InputFilter = builder.Build(); - } - return _InputFilter; - } + protected PathFilter GetInputObjectSourceFilter() + { + return Option.Input.IgnoreObjectSource.GetValueOrDefault(InputOption.Default.IgnoreObjectSource.Value) ? GetInputFilter() : null; + } - private OptionContextBuilder GetOptionBuilder() + protected PathFilter GetInputFilter() + { + if (_InputFilter == null) { - return new OptionContextBuilder(Option, _Include, _Tag, _Convention); - } + var basePath = Environment.GetWorkingPath(); + var ignoreGitPath = Option.Input.IgnoreGitPath ?? InputOption.Default.IgnoreGitPath.Value; + var ignoreRepositoryCommon = Option.Input.IgnoreRepositoryCommon ?? InputOption.Default.IgnoreRepositoryCommon.Value; + var builder = PathFilterBuilder.Create(basePath, Option.Input.PathIgnore, ignoreGitPath, ignoreRepositoryCommon); + if (Option.Input.Format == InputFormat.File) + builder.UseGitIgnore(); - protected void ConfigureBinding(PSRuleOption option) - { - if (option.Pipeline.BindTargetName != null && option.Pipeline.BindTargetName.Count > 0) - { - // Do not allow custom binding functions to be used with constrained language mode - if (Option.Execution.LanguageMode == LanguageMode.ConstrainedLanguage) - throw new PipelineConfigurationException(optionName: "BindTargetName", message: PSRuleResources.ConstrainedTargetBinding); + _InputFilter = builder.Build(); + } + return _InputFilter; + } - foreach (var action in option.Pipeline.BindTargetName) - BindTargetNameHook = AddBindTargetAction(action, BindTargetNameHook); - } + private OptionContextBuilder GetOptionBuilder() + { + return new OptionContextBuilder(Option, _Include, _Tag, _Convention); + } - if (option.Pipeline.BindTargetType != null && option.Pipeline.BindTargetType.Count > 0) - { - // Do not allow custom binding functions to be used with constrained language mode - if (Option.Execution.LanguageMode == LanguageMode.ConstrainedLanguage) - throw new PipelineConfigurationException(optionName: "BindTargetType", message: PSRuleResources.ConstrainedTargetBinding); + protected void ConfigureBinding(PSRuleOption option) + { + if (option.Pipeline.BindTargetName != null && option.Pipeline.BindTargetName.Count > 0) + { + // Do not allow custom binding functions to be used with constrained language mode + if (Option.Execution.LanguageMode == LanguageMode.ConstrainedLanguage) + throw new PipelineConfigurationException(optionName: "BindTargetName", message: PSRuleResources.ConstrainedTargetBinding); - foreach (var action in option.Pipeline.BindTargetType) - BindTargetTypeHook = AddBindTargetAction(action, BindTargetTypeHook); - } + foreach (var action in option.Pipeline.BindTargetName) + BindTargetNameHook = AddBindTargetAction(action, BindTargetNameHook); } - private static BindTargetMethod AddBindTargetAction(BindTargetFunc action, BindTargetMethod previous) + if (option.Pipeline.BindTargetType != null && option.Pipeline.BindTargetType.Count > 0) { - // Nest the previous write action in the new supplied action - // Execution chain will be: action -> previous -> previous..n - return (string[] propertyNames, bool caseSensitive, bool preferTargetInfo, object targetObject, out string path) => - { - return action(propertyNames, caseSensitive, preferTargetInfo, targetObject, previous, out path); - }; + // Do not allow custom binding functions to be used with constrained language mode + if (Option.Execution.LanguageMode == LanguageMode.ConstrainedLanguage) + throw new PipelineConfigurationException(optionName: "BindTargetType", message: PSRuleResources.ConstrainedTargetBinding); + + foreach (var action in option.Pipeline.BindTargetType) + BindTargetTypeHook = AddBindTargetAction(action, BindTargetTypeHook); } + } - private static BindTargetMethod AddBindTargetAction(BindTargetName action, BindTargetMethod previous) + private static BindTargetMethod AddBindTargetAction(BindTargetFunc action, BindTargetMethod previous) + { + // Nest the previous write action in the new supplied action + // Execution chain will be: action -> previous -> previous..n + return (string[] propertyNames, bool caseSensitive, bool preferTargetInfo, object targetObject, out string path) => { - return AddBindTargetAction((string[] propertyNames, bool caseSensitive, bool preferTargetInfo, object targetObject, BindTargetMethod next, out string path) => - { - path = null; - var targetType = action(targetObject); - return string.IsNullOrEmpty(targetType) ? next(propertyNames, caseSensitive, preferTargetInfo, targetObject, out path) : targetType; - }, previous); - } + return action(propertyNames, caseSensitive, preferTargetInfo, targetObject, previous, out path); + }; + } - protected void AddVisitTargetObjectAction(VisitTargetObjectAction action) + private static BindTargetMethod AddBindTargetAction(BindTargetName action, BindTargetMethod previous) + { + return AddBindTargetAction((string[] propertyNames, bool caseSensitive, bool preferTargetInfo, object targetObject, BindTargetMethod next, out string path) => { - // Nest the previous write action in the new supplied action - // Execution chain will be: action -> previous -> previous..n - var previous = VisitTargetObject; - VisitTargetObject = (targetObject) => action(targetObject, previous); - } + path = null; + var targetType = action(targetObject); + return string.IsNullOrEmpty(targetType) ? next(propertyNames, caseSensitive, preferTargetInfo, targetObject, out path) : targetType; + }, previous); + } + + protected void AddVisitTargetObjectAction(VisitTargetObjectAction action) + { + // Nest the previous write action in the new supplied action + // Execution chain will be: action -> previous -> previous..n + var previous = VisitTargetObject; + VisitTargetObject = (targetObject) => action(targetObject, previous); + } - /// - /// Normalizes JSON indent range between minimum 0 and maximum 4. - /// - /// - /// The number of characters to indent. - protected static int NormalizeJsonIndentRange(int? jsonIndent) + /// + /// Normalizes JSON indent range between minimum 0 and maximum 4. + /// + /// + /// The number of characters to indent. + protected static int NormalizeJsonIndentRange(int? jsonIndent) + { + if (jsonIndent.HasValue) { - if (jsonIndent.HasValue) - { - if (jsonIndent < MIN_JSON_INDENT) - return MIN_JSON_INDENT; + if (jsonIndent < MIN_JSON_INDENT) + return MIN_JSON_INDENT; - else if (jsonIndent > MAX_JSON_INDENT) - return MAX_JSON_INDENT; + else if (jsonIndent > MAX_JSON_INDENT) + return MAX_JSON_INDENT; - return jsonIndent.Value; - } - return MIN_JSON_INDENT; + return jsonIndent.Value; } + return MIN_JSON_INDENT; + } - protected bool TryChangedFiles(out string[] files) - { - files = null; - if (!Option.Input.IgnoreUnchangedPath.GetValueOrDefault(InputOption.Default.IgnoreUnchangedPath.Value) || - !GitHelper.TryGetChangedFiles(Option.Repository.BaseRef, "d", null, out files)) - return false; + protected bool TryChangedFiles(out string[] files) + { + files = null; + if (!Option.Input.IgnoreUnchangedPath.GetValueOrDefault(InputOption.Default.IgnoreUnchangedPath.Value) || + !GitHelper.TryGetChangedFiles(Option.Repository.BaseRef, "d", null, out files)) + return false; - for (var i = 0; i < files.Length; i++) - HostContext.Verbose(string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.UsingChangedFile, files[i])); + for (var i = 0; i < files.Length; i++) + HostContext.Verbose(string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.UsingChangedFile, files[i])); - return true; - } + return true; + } - protected bool ShouldProcess(string target, string action) - { - return HostContext == null || HostContext.ShouldProcess(target, action); - } + protected bool ShouldProcess(string target, string action) + { + return HostContext == null || HostContext.ShouldProcess(target, action); + } - protected static OutputStyle GetStyle(OutputStyle style) - { - if (style != OutputStyle.Detect) - return style; + protected static OutputStyle GetStyle(OutputStyle style) + { + if (style != OutputStyle.Detect) + return style; - if (Environment.IsAzurePipelines()) - return OutputStyle.AzurePipelines; + if (Environment.IsAzurePipelines()) + return OutputStyle.AzurePipelines; - if (Environment.IsGitHubActions()) - return OutputStyle.GitHubActions; + if (Environment.IsGitHubActions()) + return OutputStyle.GitHubActions; - return Environment.IsVisualStudioCode() ? - OutputStyle.VisualStudioCode : - OutputStyle.Client; - } + return Environment.IsVisualStudioCode() ? + OutputStyle.VisualStudioCode : + OutputStyle.Client; } } diff --git a/src/PSRule/Pipeline/PipelineContext.cs b/src/PSRule/Pipeline/PipelineContext.cs index f4611e8c29..1e20e2418f 100644 --- a/src/PSRule/Pipeline/PipelineContext.cs +++ b/src/PSRule/Pipeline/PipelineContext.cs @@ -19,325 +19,324 @@ using PSRule.Runtime; using PSRule.Runtime.ObjectPath; -namespace PSRule.Pipeline +namespace PSRule.Pipeline; + +internal sealed class PipelineContext : IDisposable, IBindingContext { - internal sealed class PipelineContext : IDisposable, IBindingContext - { - private const string ErrorPreference = "ErrorActionPreference"; - private const string WarningPreference = "WarningPreference"; - private const string VerbosePreference = "VerbosePreference"; - private const string DebugPreference = "DebugPreference"; + private const string ErrorPreference = "ErrorActionPreference"; + private const string WarningPreference = "WarningPreference"; + private const string VerbosePreference = "VerbosePreference"; + private const string DebugPreference = "DebugPreference"; - [ThreadStatic] - internal static PipelineContext CurrentThread; + [ThreadStatic] + internal static PipelineContext CurrentThread; - private readonly OptionContextBuilder _OptionBuilder; + private readonly OptionContextBuilder _OptionBuilder; - // Configuration parameters - private readonly IList _Unresolved; - private readonly LanguageMode _LanguageMode; - private readonly Dictionary _PathExpressionCache; - private readonly List _TrackedIssues; + // Configuration parameters + private readonly IList _Unresolved; + private readonly LanguageMode _LanguageMode; + private readonly Dictionary _PathExpressionCache; + private readonly List _TrackedIssues; - // Objects kept for caching and disposal - private Runspace _Runspace; + // Objects kept for caching and disposal + private Runspace _Runspace; - // Track whether Dispose has been called. - private bool _Disposed; + // Track whether Dispose has been called. + private bool _Disposed; - internal PSRuleOption Option; + internal PSRuleOption Option; - internal readonly Dictionary LocalizedDataCache; - internal readonly Dictionary ExpressionCache; - internal readonly Dictionary ContentCache; - internal readonly Dictionary Selector; - internal readonly List SuppressionGroup; - internal readonly IHostContext HostContext; - internal readonly PipelineReader Reader; - internal readonly BindTargetMethod BindTargetName; - internal readonly BindTargetMethod BindTargetType; - internal readonly BindTargetMethod BindField; - internal readonly string RunId; + internal readonly Dictionary LocalizedDataCache; + internal readonly Dictionary ExpressionCache; + internal readonly Dictionary ContentCache; + internal readonly Dictionary Selector; + internal readonly List SuppressionGroup; + internal readonly IHostContext HostContext; + internal readonly PipelineReader Reader; + internal readonly BindTargetMethod BindTargetName; + internal readonly BindTargetMethod BindTargetType; + internal readonly BindTargetMethod BindField; + internal readonly string RunId; - internal readonly Stopwatch RunTime; + internal readonly Stopwatch RunTime; - private OptionContext _DefaultOptionContext; + private OptionContext _DefaultOptionContext; - public System.Security.Cryptography.HashAlgorithm ObjectHashAlgorithm { get; } + public System.Security.Cryptography.HashAlgorithm ObjectHashAlgorithm { get; } - 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; - BindTargetName = bindTargetName; - BindTargetType = bindTargetType; - BindField = bindField; - _LanguageMode = option.Execution.LanguageMode ?? ExecutionOption.Default.LanguageMode.Value; - _PathExpressionCache = new Dictionary(); - LocalizedDataCache = new Dictionary(); - ExpressionCache = new Dictionary(); - ContentCache = new Dictionary(); - Selector = new Dictionary(); - SuppressionGroup = new List(); - _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); - } + 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; + BindTargetName = bindTargetName; + BindTargetType = bindTargetType; + BindField = bindField; + _LanguageMode = option.Execution.LanguageMode ?? ExecutionOption.Default.LanguageMode.Value; + _PathExpressionCache = new Dictionary(); + LocalizedDataCache = new Dictionary(); + ExpressionCache = new Dictionary(); + ContentCache = new Dictionary(); + Selector = new Dictionary(); + SuppressionGroup = new List(); + _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, OptionContextBuilder optionBuilder, 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, optionBuilder, unresolved); + CurrentThread = context; + return context; + } + + internal sealed class SourceScope + { + public readonly SourceFile File; + + public SourceScope(SourceFile source) { - var context = new PipelineContext(option, hostContext, reader, bindTargetName, bindTargetType, bindField, optionBuilder, unresolved); - CurrentThread = context; - return context; + File = source; } - internal sealed class SourceScope + public string[] SourceContentCache { - public readonly SourceFile File; - - public SourceScope(SourceFile source) + get { - File = source; - } - - public string[] SourceContentCache - { - get - { - return System.IO.File.ReadAllLines(File.Path, Encoding.UTF8); - } + return System.IO.File.ReadAllLines(File.Path, Encoding.UTF8); } } + } + + internal enum ResourceIssueType + { + Unknown + } - internal enum ResourceIssueType + internal sealed class ResourceIssue + { + public ResourceIssue(ResourceKind kind, string id, ResourceIssueType issue) { - Unknown + Kind = kind; + Id = id; + Issue = issue; } - internal sealed class ResourceIssue - { - public ResourceIssue(ResourceKind kind, string id, ResourceIssueType issue) - { - Kind = kind; - Id = id; - Issue = issue; - } + public ResourceKind Kind { get; } - public ResourceKind Kind { get; } + public string Id { get; } - public string Id { get; } + public ResourceIssueType Issue { get; } + } - public ResourceIssueType Issue { get; } + internal Runspace GetRunspace() + { + if (_Runspace == null) + { + var initialSessionState = Option.Execution.InitialSessionState.GetValueOrDefault(ExecutionOption.Default.InitialSessionState.Value); + var state = HostState.CreateSessionState(initialSessionState); + state.LanguageMode = _LanguageMode == LanguageMode.FullLanguage ? PSLanguageMode.FullLanguage : PSLanguageMode.ConstrainedLanguage; + + _Runspace = RunspaceFactory.CreateRunspace(state); + Runspace.DefaultRunspace ??= _Runspace; + + _Runspace.Open(); + _Runspace.SessionStateProxy.PSVariable.Set(new PSRuleVariable()); + _Runspace.SessionStateProxy.PSVariable.Set(new RuleVariable()); + _Runspace.SessionStateProxy.PSVariable.Set(new LocalizedDataVariable()); + _Runspace.SessionStateProxy.PSVariable.Set(new AssertVariable()); + _Runspace.SessionStateProxy.PSVariable.Set(new TargetObjectVariable()); + _Runspace.SessionStateProxy.PSVariable.Set(new ConfigurationVariable()); + _Runspace.SessionStateProxy.PSVariable.Set(ErrorPreference, ActionPreference.Continue); + _Runspace.SessionStateProxy.PSVariable.Set(WarningPreference, ActionPreference.Continue); + _Runspace.SessionStateProxy.PSVariable.Set(VerbosePreference, ActionPreference.Continue); + _Runspace.SessionStateProxy.PSVariable.Set(DebugPreference, ActionPreference.Continue); + _Runspace.SessionStateProxy.Path.SetLocation(Environment.GetWorkingPath()); } + return _Runspace; + } - internal Runspace GetRunspace() + internal void Import(RunspaceContext context, IResource resource) + { + TrackIssue(resource); + if (TryBaseline(resource, out var baseline) && TryBaselineRef(resource.Id, out var baselineRef)) { - if (_Runspace == null) - { - var initialSessionState = Option.Execution.InitialSessionState.GetValueOrDefault(ExecutionOption.Default.InitialSessionState.Value); - var state = HostState.CreateSessionState(initialSessionState); - state.LanguageMode = _LanguageMode == LanguageMode.FullLanguage ? PSLanguageMode.FullLanguage : PSLanguageMode.ConstrainedLanguage; - - _Runspace = RunspaceFactory.CreateRunspace(state); - Runspace.DefaultRunspace ??= _Runspace; - - _Runspace.Open(); - _Runspace.SessionStateProxy.PSVariable.Set(new PSRuleVariable()); - _Runspace.SessionStateProxy.PSVariable.Set(new RuleVariable()); - _Runspace.SessionStateProxy.PSVariable.Set(new LocalizedDataVariable()); - _Runspace.SessionStateProxy.PSVariable.Set(new AssertVariable()); - _Runspace.SessionStateProxy.PSVariable.Set(new TargetObjectVariable()); - _Runspace.SessionStateProxy.PSVariable.Set(new ConfigurationVariable()); - _Runspace.SessionStateProxy.PSVariable.Set(ErrorPreference, ActionPreference.Continue); - _Runspace.SessionStateProxy.PSVariable.Set(WarningPreference, ActionPreference.Continue); - _Runspace.SessionStateProxy.PSVariable.Set(VerbosePreference, ActionPreference.Continue); - _Runspace.SessionStateProxy.PSVariable.Set(DebugPreference, ActionPreference.Continue); - _Runspace.SessionStateProxy.Path.SetLocation(Environment.GetWorkingPath()); - } - return _Runspace; + RemoveBaselineRef(resource.Id); + _OptionBuilder.Baseline(baselineRef.Type, baseline.BaselineId, resource.Source.Module, baseline.Spec, baseline.Obsolete); } - - internal void Import(RunspaceContext context, IResource resource) + else if (resource.Kind == ResourceKind.Selector && resource is SelectorV1 selector) + Selector[selector.Id.Value] = new SelectorVisitor(context, selector.Id, selector.Source, selector.Spec.If); + else if (TryModuleConfig(resource, out var moduleConfig)) { - TrackIssue(resource); - if (TryBaseline(resource, out var baseline) && TryBaselineRef(resource.Id, out var baselineRef)) + if (!string.IsNullOrEmpty(moduleConfig?.Spec?.Rule?.Baseline)) { - RemoveBaselineRef(resource.Id); - _OptionBuilder.Baseline(baselineRef.Type, baseline.BaselineId, resource.Source.Module, baseline.Spec, baseline.Obsolete); + var baselineId = ResourceHelper.GetIdString(moduleConfig.Source.Module, moduleConfig.Spec.Rule.Baseline); + if (!_OptionBuilder.ContainsBaseline(baselineId)) + _Unresolved.Add(new BaselineRef(baselineId, ScopeType.Baseline)); } - else if (resource.Kind == ResourceKind.Selector && resource is SelectorV1 selector) - Selector[selector.Id.Value] = new SelectorVisitor(context, selector.Id, selector.Source, selector.Spec.If); - else if (TryModuleConfig(resource, out var moduleConfig)) + _OptionBuilder.ModuleConfig(resource.Source.Module, moduleConfig?.Spec); + } + else if (resource.Kind == ResourceKind.SuppressionGroup && resource is SuppressionGroupV1 suppressionGroup) + { + if (!suppressionGroup.Spec.ExpiresOn.HasValue || suppressionGroup.Spec.ExpiresOn.Value > DateTime.UtcNow) { - if (!string.IsNullOrEmpty(moduleConfig?.Spec?.Rule?.Baseline)) - { - var baselineId = ResourceHelper.GetIdString(moduleConfig.Source.Module, moduleConfig.Spec.Rule.Baseline); - if (!_OptionBuilder.ContainsBaseline(baselineId)) - _Unresolved.Add(new BaselineRef(baselineId, ScopeType.Baseline)); - } - _OptionBuilder.ModuleConfig(resource.Source.Module, moduleConfig?.Spec); + SuppressionGroup.Add(new SuppressionGroupVisitor( + context: context, + id: suppressionGroup.Id, + source: suppressionGroup.Source, + spec: suppressionGroup.Spec, + info: suppressionGroup.Info + )); } - else if (resource.Kind == ResourceKind.SuppressionGroup && resource is SuppressionGroupV1 suppressionGroup) + else { - if (!suppressionGroup.Spec.ExpiresOn.HasValue || suppressionGroup.Spec.ExpiresOn.Value > DateTime.UtcNow) - { - SuppressionGroup.Add(new SuppressionGroupVisitor( - context: context, - id: suppressionGroup.Id, - source: suppressionGroup.Source, - spec: suppressionGroup.Spec, - info: suppressionGroup.Info - )); - } - else - { - context.SuppressionGroupExpired(suppressionGroup.Id); - } + context.SuppressionGroupExpired(suppressionGroup.Id); } } + } - private void TrackIssue(IResource resource) - { - //if (resource.TryValidateResourceAnnotation()) - // _TrackedIssues.Add(new ResourceIssue(resource.Kind, resource.Id, ResourceIssueType.MissingApiVersion)); - } + private void TrackIssue(IResource resource) + { + //if (resource.TryValidateResourceAnnotation()) + // _TrackedIssues.Add(new ResourceIssue(resource.Kind, resource.Id, ResourceIssueType.MissingApiVersion)); + } - private bool TryBaselineRef(ResourceId resourceId, out BaselineRef baselineRef) - { - baselineRef = null; - var r = _Unresolved.FirstOrDefault(i => ResourceIdEqualityComparer.IdEquals(i.Id, resourceId.Value)); - if (r is not BaselineRef br) - return false; + private bool TryBaselineRef(ResourceId resourceId, out BaselineRef baselineRef) + { + baselineRef = null; + var r = _Unresolved.FirstOrDefault(i => ResourceIdEqualityComparer.IdEquals(i.Id, resourceId.Value)); + if (r is not BaselineRef br) + return false; - baselineRef = br; - return true; - } + baselineRef = br; + return true; + } - private void RemoveBaselineRef(ResourceId resourceId) + private void RemoveBaselineRef(ResourceId resourceId) + { + foreach (var r in _Unresolved.ToArray()) { - foreach (var r in _Unresolved.ToArray()) - { - if (ResourceIdEqualityComparer.IdEquals(r.Id, resourceId.Value)) - _Unresolved.Remove(r); - } + if (ResourceIdEqualityComparer.IdEquals(r.Id, resourceId.Value)) + _Unresolved.Remove(r); } + } - private static bool TryBaseline(IResource resource, out Baseline baseline) + private static bool TryBaseline(IResource resource, out Baseline baseline) + { + baseline = null; + if (resource.Kind == ResourceKind.Baseline && resource is Baseline result) { - baseline = null; - if (resource.Kind == ResourceKind.Baseline && resource is Baseline result) - { - baseline = result; - return true; - } - return false; + baseline = result; + return true; } + return false; + } - private static bool TryModuleConfig(IResource resource, out ModuleConfigV1 moduleConfig) + private static bool TryModuleConfig(IResource resource, out ModuleConfigV1 moduleConfig) + { + moduleConfig = null; + if (resource.Kind == ResourceKind.ModuleConfig && + !string.IsNullOrEmpty(resource.Source.Module) && + StringComparer.OrdinalIgnoreCase.Equals(resource.Source.Module, resource.Name) && + resource is ModuleConfigV1 result) { - moduleConfig = null; - if (resource.Kind == ResourceKind.ModuleConfig && - !string.IsNullOrEmpty(resource.Source.Module) && - StringComparer.OrdinalIgnoreCase.Equals(resource.Source.Module, resource.Name) && - resource is ModuleConfigV1 result) - { - moduleConfig = result; - return true; - } - return false; + moduleConfig = result; + return true; } + return false; + } - internal void Begin(RunspaceContext runspaceContext) - { - ReportUnresolved(runspaceContext); - ReportIssue(runspaceContext); - _DefaultOptionContext = _OptionBuilder.Build(null); - _OptionBuilder.CheckObsolete(runspaceContext); - } + internal void Begin(RunspaceContext runspaceContext) + { + ReportUnresolved(runspaceContext); + ReportIssue(runspaceContext); + _DefaultOptionContext = _OptionBuilder.Build(null); + _OptionBuilder.CheckObsolete(runspaceContext); + } - internal void UpdateLanguageScope(ILanguageScope languageScope) - { - var context = _OptionBuilder.Build(languageScope.Name); - languageScope.Configure(context); - } + internal void UpdateLanguageScope(ILanguageScope languageScope) + { + var context = _OptionBuilder.Build(languageScope.Name); + languageScope.Configure(context); + } - internal int GetConventionOrder(IConvention x) - { - return _DefaultOptionContext.GetConventionOrder(x); - } + internal int GetConventionOrder(IConvention x) + { + return _DefaultOptionContext.GetConventionOrder(x); + } - private void ReportUnresolved(RunspaceContext runspaceContext) - { - foreach (var unresolved in _Unresolved) - runspaceContext.ErrorResourceUnresolved(unresolved.Kind, unresolved.Id); + private void ReportUnresolved(RunspaceContext runspaceContext) + { + foreach (var unresolved in _Unresolved) + runspaceContext.ErrorResourceUnresolved(unresolved.Kind, unresolved.Id); - if (_Unresolved.Count > 0) - throw new PipelineBuilderException(PSRuleResources.ErrorPipelineException); - } + if (_Unresolved.Count > 0) + throw new PipelineBuilderException(PSRuleResources.ErrorPipelineException); + } - /// - /// Report any tracked issues. - /// - private void ReportIssue(RunspaceContext runspaceContext) - { - //for (var i = 0; _TrackedIssues != null && i < _TrackedIssues.Count; i++) - //if (_TrackedIssues[i].Issue == ResourceIssueType.MissingApiVersion) - // runspaceContext.WarnMissingApiVersion(_TrackedIssues[i].Kind, _TrackedIssues[i].Id); - } + /// + /// Report any tracked issues. + /// + private void ReportIssue(RunspaceContext runspaceContext) + { + //for (var i = 0; _TrackedIssues != null && i < _TrackedIssues.Count; i++) + //if (_TrackedIssues[i].Issue == ResourceIssueType.MissingApiVersion) + // runspaceContext.WarnMissingApiVersion(_TrackedIssues[i].Kind, _TrackedIssues[i].Id); + } - private static System.Security.Cryptography.HashAlgorithm GetHashAlgorithm(Options.HashAlgorithm hashAlgorithm) - { - if (hashAlgorithm == Options.HashAlgorithm.SHA256) - return SHA256.Create(); + private static System.Security.Cryptography.HashAlgorithm GetHashAlgorithm(Options.HashAlgorithm hashAlgorithm) + { + if (hashAlgorithm == Options.HashAlgorithm.SHA256) + return SHA256.Create(); - return hashAlgorithm == Options.HashAlgorithm.SHA384 ? SHA384.Create() : SHA512.Create(); - } + return hashAlgorithm == Options.HashAlgorithm.SHA384 ? SHA384.Create() : SHA512.Create(); + } - #region IBindingContext + #region IBindingContext - public bool GetPathExpression(string path, out PathExpression expression) - { - return _PathExpressionCache.TryGetValue(path, out expression); - } + public bool GetPathExpression(string path, out PathExpression expression) + { + return _PathExpressionCache.TryGetValue(path, out expression); + } - public void CachePathExpression(string path, PathExpression expression) - { - _PathExpressionCache[path] = expression; - } + public void CachePathExpression(string path, PathExpression expression) + { + _PathExpressionCache[path] = expression; + } - #endregion IBindingContext + #endregion IBindingContext - #region IDisposable + #region IDisposable - public void Dispose() - { - Dispose(true); - } + public void Dispose() + { + Dispose(true); + } - private void Dispose(bool disposing) + private void Dispose(bool disposing) + { + if (!_Disposed) { - if (!_Disposed) + if (disposing) { - if (disposing) - { - ObjectHashAlgorithm?.Dispose(); - _Runspace?.Dispose(); - _PathExpressionCache.Clear(); - LocalizedDataCache.Clear(); - ExpressionCache.Clear(); - ContentCache.Clear(); - RunTime.Stop(); - } - _Disposed = true; + ObjectHashAlgorithm?.Dispose(); + _Runspace?.Dispose(); + _PathExpressionCache.Clear(); + LocalizedDataCache.Clear(); + ExpressionCache.Clear(); + ContentCache.Clear(); + RunTime.Stop(); } + _Disposed = true; } - - #endregion IDisposable } + + #endregion IDisposable } diff --git a/src/PSRule/Pipeline/PipelineExtensions.cs b/src/PSRule/Pipeline/PipelineExtensions.cs index 09fa4fd58a..4fc1b6f536 100644 --- a/src/PSRule/Pipeline/PipelineExtensions.cs +++ b/src/PSRule/Pipeline/PipelineExtensions.cs @@ -3,24 +3,23 @@ using System.Management.Automation; -namespace PSRule.Pipeline +namespace PSRule.Pipeline; + +/// +/// Extension methods for the PSRule pipelines. +/// +public static class PipelineExtensions { /// - /// Extension methods for the PSRule pipelines. + /// Process an object through the pipeline. Each object will be processed by rules that apply based on pre-conditions. /// - public static class PipelineExtensions + /// An instance of a PSRule pipeline. + /// The object to process. + public static void Process(this IPipeline pipeline, object sourceObject) { - /// - /// Process an object through the pipeline. Each object will be processed by rules that apply based on pre-conditions. - /// - /// An instance of a PSRule pipeline. - /// The object to process. - public static void Process(this IPipeline pipeline, object sourceObject) - { - if (pipeline == null) - return; + if (pipeline == null) + return; - pipeline.Process(PSObject.AsPSObject(sourceObject)); - } + pipeline.Process(PSObject.AsPSObject(sourceObject)); } } diff --git a/src/PSRule/Pipeline/PipelineHookActions.cs b/src/PSRule/Pipeline/PipelineHookActions.cs index 1eb4c34a2d..82e0e1d608 100644 --- a/src/PSRule/Pipeline/PipelineHookActions.cs +++ b/src/PSRule/Pipeline/PipelineHookActions.cs @@ -9,222 +9,221 @@ using PSRule.Data; using PSRule.Runtime; -namespace PSRule.Pipeline +namespace PSRule.Pipeline; + +internal delegate bool ShouldProcess(string target, string action); + +/// +/// Define built-in binding hooks. +/// +internal static class PipelineHookActions { - internal delegate bool ShouldProcess(string target, string action); + private const string Property_TargetName = "TargetName"; + private const string Property_Name = "Name"; - /// - /// Define built-in binding hooks. - /// - internal static class PipelineHookActions + [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0046:Convert to conditional expression", Justification = "Avoid nested conditional expressions that increase complexity.")] + public static string BindTargetName(string[] propertyNames, bool caseSensitive, bool preferTargetInfo, object targetObject, out string path) { - private const string Property_TargetName = "TargetName"; - private const string Property_Name = "Name"; - - [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0046:Convert to conditional expression", Justification = "Avoid nested conditional expressions that increase complexity.")] - public static string BindTargetName(string[] propertyNames, bool caseSensitive, bool preferTargetInfo, object targetObject, out string path) - { - path = null; - if (targetObject == null) - return null; + path = null; + if (targetObject == null) + return null; - if (preferTargetInfo && TryGetInfoTargetName(targetObject, out var targetName)) - return targetName; + if (preferTargetInfo && TryGetInfoTargetName(targetObject, out var targetName)) + return targetName; - if (propertyNames != null) - return propertyNames.Any(n => n.Contains('.')) - ? NestedTargetPropertyBinding(propertyNames, caseSensitive, targetObject, DefaultTargetNameBinding, out path) - : CustomTargetPropertyBinding(propertyNames, caseSensitive, targetObject, DefaultTargetNameBinding, out path); + if (propertyNames != null) + return propertyNames.Any(n => n.Contains('.')) + ? NestedTargetPropertyBinding(propertyNames, caseSensitive, targetObject, DefaultTargetNameBinding, out path) + : CustomTargetPropertyBinding(propertyNames, caseSensitive, targetObject, DefaultTargetNameBinding, out path); - return DefaultTargetNameBinding(targetObject); - } + return DefaultTargetNameBinding(targetObject); + } - [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0046:Convert to conditional expression", Justification = "Avoid nested conditional expressions that increase complexity.")] - public static string BindTargetType(string[] propertyNames, bool caseSensitive, bool preferTargetInfo, object targetObject, out string path) - { - path = null; - if (targetObject == null) - return null; + [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0046:Convert to conditional expression", Justification = "Avoid nested conditional expressions that increase complexity.")] + public static string BindTargetType(string[] propertyNames, bool caseSensitive, bool preferTargetInfo, object targetObject, out string path) + { + path = null; + if (targetObject == null) + return null; - if (preferTargetInfo && TryGetInfoTargetType(targetObject, out var targetType)) - return targetType; + if (preferTargetInfo && TryGetInfoTargetType(targetObject, out var targetType)) + return targetType; - if (propertyNames != null) - return propertyNames.Any(n => n.Contains('.')) - ? NestedTargetPropertyBinding(propertyNames, caseSensitive, targetObject, DefaultTargetTypeBinding, out path) - : CustomTargetPropertyBinding(propertyNames, caseSensitive, targetObject, DefaultTargetTypeBinding, out path); + if (propertyNames != null) + return propertyNames.Any(n => n.Contains('.')) + ? NestedTargetPropertyBinding(propertyNames, caseSensitive, targetObject, DefaultTargetTypeBinding, out path) + : CustomTargetPropertyBinding(propertyNames, caseSensitive, targetObject, DefaultTargetTypeBinding, out path); - return DefaultTargetTypeBinding(targetObject); - } + return DefaultTargetTypeBinding(targetObject); + } - [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "Parameter preferTargetInfo is required for matching the delegate type.")] - [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0046:Convert to conditional expression", Justification = "Avoid nested conditional expressions that increase complexity.")] - public static string BindField(string[] propertyNames, bool caseSensitive, bool preferTargetInfo, object targetObject, out string path) - { - path = null; - if (targetObject == null) - return null; + [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "Parameter preferTargetInfo is required for matching the delegate type.")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0046:Convert to conditional expression", Justification = "Avoid nested conditional expressions that increase complexity.")] + public static string BindField(string[] propertyNames, bool caseSensitive, bool preferTargetInfo, object targetObject, out string path) + { + path = null; + if (targetObject == null) + return null; - if (propertyNames != null) - return propertyNames.Any(n => n.Contains('.')) - ? NestedTargetPropertyBinding(propertyNames, caseSensitive, targetObject, DefaultFieldBinding, out path) - : CustomTargetPropertyBinding(propertyNames, caseSensitive, targetObject, DefaultFieldBinding, out path); + if (propertyNames != null) + return propertyNames.Any(n => n.Contains('.')) + ? NestedTargetPropertyBinding(propertyNames, caseSensitive, targetObject, DefaultFieldBinding, out path) + : CustomTargetPropertyBinding(propertyNames, caseSensitive, targetObject, DefaultFieldBinding, out path); - return DefaultFieldBinding(targetObject); - } + return DefaultFieldBinding(targetObject); + } - /// - /// Get the TargetName of the object by looking for a TargetName or Name property. - /// - /// A PSObject to bind. - /// The TargetName of the object. - private static string DefaultTargetNameBinding(object targetObject) - { - return TryGetInfoTargetName(targetObject, out var targetName) || - TryGetTargetName(targetObject, propertyName: Property_TargetName, targetName: out targetName) || - TryGetTargetName(targetObject, propertyName: Property_Name, targetName: out targetName) - ? targetName - : GetUnboundObjectTargetName(targetObject); - } + /// + /// Get the TargetName of the object by looking for a TargetName or Name property. + /// + /// A PSObject to bind. + /// The TargetName of the object. + private static string DefaultTargetNameBinding(object targetObject) + { + return TryGetInfoTargetName(targetObject, out var targetName) || + TryGetTargetName(targetObject, propertyName: Property_TargetName, targetName: out targetName) || + TryGetTargetName(targetObject, propertyName: Property_Name, targetName: out targetName) + ? targetName + : GetUnboundObjectTargetName(targetObject); + } - /// - /// Get the TargetName of the object by using any of the specified property names. - /// - /// One or more property names to use to bind TargetName. - /// Determines if binding properties are case-sensitive. - /// A PSObject to bind. - /// The next delegate function to check if all of the property names can not be found. - /// The object path that was used for binding. - /// The TargetName of the object. - private static string CustomTargetPropertyBinding(string[] propertyNames, bool caseSensitive, object targetObject, BindTargetName next, out string path) + /// + /// Get the TargetName of the object by using any of the specified property names. + /// + /// One or more property names to use to bind TargetName. + /// Determines if binding properties are case-sensitive. + /// A PSObject to bind. + /// The next delegate function to check if all of the property names can not be found. + /// The object path that was used for binding. + /// The TargetName of the object. + private static string CustomTargetPropertyBinding(string[] propertyNames, bool caseSensitive, object targetObject, BindTargetName next, out string path) + { + path = null; + string targetName = null; + for (var i = 0; i < propertyNames.Length && targetName == null; i++) { - path = null; - string targetName = null; - for (var i = 0; i < propertyNames.Length && targetName == null; i++) - { - targetName = ValueAsString(targetObject, propertyName: propertyNames[i], caseSensitive: caseSensitive); - if (targetName != null) - path = propertyNames[i]; - } - // If TargetName is found return, otherwise continue to next delegate - return targetName ?? next(targetObject); + targetName = ValueAsString(targetObject, propertyName: propertyNames[i], caseSensitive: caseSensitive); + if (targetName != null) + path = propertyNames[i]; } + // If TargetName is found return, otherwise continue to next delegate + return targetName ?? next(targetObject); + } - /// - /// Get the TargetName of the object by using any of the specified property names. - /// - /// One or more property names to use to bind TargetName. - /// Determines if binding properties are case-sensitive. - /// A PSObject to bind. - /// The next delegate function to check if all of the property names can not be found. - /// The object path that was used for binding. - /// The TargetName of the object. - private static string NestedTargetPropertyBinding(string[] propertyNames, bool caseSensitive, object targetObject, BindTargetName next, out string path) + /// + /// Get the TargetName of the object by using any of the specified property names. + /// + /// One or more property names to use to bind TargetName. + /// Determines if binding properties are case-sensitive. + /// A PSObject to bind. + /// The next delegate function to check if all of the property names can not be found. + /// The object path that was used for binding. + /// The TargetName of the object. + private static string NestedTargetPropertyBinding(string[] propertyNames, bool caseSensitive, object targetObject, BindTargetName next, out string path) + { + path = null; + string targetName = null; + var score = int.MaxValue; + for (var i = 0; i < propertyNames.Length && score > propertyNames.Length; i++) { - path = null; - string targetName = null; - var score = int.MaxValue; - for (var i = 0; i < propertyNames.Length && score > propertyNames.Length; i++) + if (ObjectHelper.GetPath( + bindingContext: PipelineContext.CurrentThread, + targetObject: targetObject, + path: propertyNames[i], + caseSensitive: caseSensitive, + value: out object value)) { - if (ObjectHelper.GetPath( - bindingContext: PipelineContext.CurrentThread, - targetObject: targetObject, - path: propertyNames[i], - caseSensitive: caseSensitive, - value: out object value)) - { - path = propertyNames[i]; - targetName = value.ToString(); - score = i; - } + path = propertyNames[i]; + targetName = value.ToString(); + score = i; } - // If TargetName is found return, otherwise continue to next delegate - return targetName ?? next(targetObject); } + // If TargetName is found return, otherwise continue to next delegate + return targetName ?? next(targetObject); + } - /// - /// Calculate a hash for an object to use as TargetName. - /// - /// A PSObject to hash. - /// The TargetName of the object. - private static string GetUnboundObjectTargetName(object targetObject) + /// + /// Calculate a hash for an object to use as TargetName. + /// + /// A PSObject to hash. + /// The TargetName of the object. + private static string GetUnboundObjectTargetName(object targetObject) + { + var settings = new JsonSerializerSettings { - var settings = new JsonSerializerSettings - { - Formatting = Formatting.None, - TypeNameHandling = TypeNameHandling.None, - MaxDepth = 1024, - Culture = CultureInfo.InvariantCulture - }; - - settings.Converters.Insert(0, new PSObjectJsonConverter()); - var json = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(targetObject, settings)); - var name = PipelineContext.CurrentThread.ObjectHashAlgorithm.GetDigest(json); - return name.Substring(0, name.Length > 50 ? 50 : name.Length); - } + Formatting = Formatting.None, + TypeNameHandling = TypeNameHandling.None, + MaxDepth = 1024, + Culture = CultureInfo.InvariantCulture + }; + + settings.Converters.Insert(0, new PSObjectJsonConverter()); + var json = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(targetObject, settings)); + var name = PipelineContext.CurrentThread.ObjectHashAlgorithm.GetDigest(json); + return name.Substring(0, name.Length > 50 ? 50 : name.Length); + } - /// - /// Try to get TargetName from specified property. - /// - private static bool TryGetTargetName(object targetObject, string propertyName, out string targetName) - { - targetName = ValueAsString(targetObject, propertyName, false); - return targetName != null; - } + /// + /// Try to get TargetName from specified property. + /// + private static bool TryGetTargetName(object targetObject, string propertyName, out string targetName) + { + targetName = ValueAsString(targetObject, propertyName, false); + return targetName != null; + } - /// - /// Get the TargetType by reading TypeNames of the PSObject. - /// - /// A PSObject to bind. - /// The TargetObject of the object. - private static string DefaultTargetTypeBinding(object targetObject) - { - return TryGetInfoTargetType(targetObject, out var targetType) ? targetType : GetTypeNames(targetObject); - } + /// + /// Get the TargetType by reading TypeNames of the PSObject. + /// + /// A PSObject to bind. + /// The TargetObject of the object. + private static string DefaultTargetTypeBinding(object targetObject) + { + return TryGetInfoTargetType(targetObject, out var targetType) ? targetType : GetTypeNames(targetObject); + } - [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0046:Convert to conditional expression", Justification = "Avoid nested conditional expressions that increase complexity.")] - private static string GetTypeNames(object targetObject) - { - if (targetObject == null) - return null; + [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0046:Convert to conditional expression", Justification = "Avoid nested conditional expressions that increase complexity.")] + private static string GetTypeNames(object targetObject) + { + if (targetObject == null) + return null; - return targetObject is PSObject pso ? pso.TypeNames[0] : targetObject.GetType().FullName; - } + return targetObject is PSObject pso ? pso.TypeNames[0] : targetObject.GetType().FullName; + } - private static string DefaultFieldBinding(object targetObject) - { - return null; - } + private static string DefaultFieldBinding(object targetObject) + { + return null; + } - private static bool TryGetInfoTargetName(object targetObject, out string targetName) - { - targetName = null; - var baseObject = ExpressionHelpers.GetBaseObject(targetObject); - if (targetObject is PSObject pso && pso.TryTargetInfo(out var targetInfoMember) && targetInfoMember.TargetName != null) - targetName = targetInfoMember.TargetName; + private static bool TryGetInfoTargetName(object targetObject, out string targetName) + { + targetName = null; + var baseObject = ExpressionHelpers.GetBaseObject(targetObject); + if (targetObject is PSObject pso && pso.TryTargetInfo(out var targetInfoMember) && targetInfoMember.TargetName != null) + targetName = targetInfoMember.TargetName; - if (baseObject is ITargetInfo info) - targetName = info.TargetName; + if (baseObject is ITargetInfo info) + targetName = info.TargetName; - return targetName != null; - } + return targetName != null; + } - private static bool TryGetInfoTargetType(object targetObject, out string targetType) - { - targetType = null; - var baseObject = ExpressionHelpers.GetBaseObject(targetObject); - if (targetObject is PSObject pso && pso.TryTargetInfo(out var targetInfoMember) && targetInfoMember.TargetType != null) - targetType = targetInfoMember.TargetType; + private static bool TryGetInfoTargetType(object targetObject, out string targetType) + { + targetType = null; + var baseObject = ExpressionHelpers.GetBaseObject(targetObject); + if (targetObject is PSObject pso && pso.TryTargetInfo(out var targetInfoMember) && targetInfoMember.TargetType != null) + targetType = targetInfoMember.TargetType; - if (baseObject is ITargetInfo info) - targetType = info.TargetType; + if (baseObject is ITargetInfo info) + targetType = info.TargetType; - return targetType != null; - } + return targetType != null; + } - private static string ValueAsString(object o, string propertyName, bool caseSensitive) - { - return ObjectHelper.GetPath(bindingContext: null, targetObject: o, path: propertyName, caseSensitive: caseSensitive, value: out object value) && value != null ? value.ToString() : null; - } + private static string ValueAsString(object o, string propertyName, bool caseSensitive) + { + return ObjectHelper.GetPath(bindingContext: null, targetObject: o, path: propertyName, caseSensitive: caseSensitive, value: out object value) && value != null ? value.ToString() : null; } } diff --git a/src/PSRule/Pipeline/PipelineLogger.cs b/src/PSRule/Pipeline/PipelineLogger.cs index a5562a0613..a4a7e20fc2 100644 --- a/src/PSRule/Pipeline/PipelineLogger.cs +++ b/src/PSRule/Pipeline/PipelineLogger.cs @@ -4,302 +4,301 @@ using System.Management.Automation; using PSRule.Configuration; -namespace PSRule.Pipeline +namespace PSRule.Pipeline; + +internal abstract class PipelineLoggerBase : IPipelineWriter { - internal abstract class PipelineLoggerBase : IPipelineWriter + private const string Source = "PSRule"; + private const string HostTag = "PSHOST"; + + protected string ScopeName { get; private set; } + + public bool HadErrors { get; private set; } + + public bool HadFailures { get; private set; } + + #region Logging + + public void WriteError(ErrorRecord errorRecord) { - private const string Source = "PSRule"; - private const string HostTag = "PSHOST"; + HadErrors = true; + if (!ShouldWriteError() || errorRecord == null) + return; - protected string ScopeName { get; private set; } + DoWriteError(errorRecord); + } - public bool HadErrors { get; private set; } + public void WriteVerbose(string message) + { + if (!ShouldWriteVerbose() || string.IsNullOrEmpty(message)) + return; - public bool HadFailures { get; private set; } + DoWriteVerbose(message); + } - #region Logging + public void WriteDebug(DebugRecord debugRecord) + { + if (!ShouldWriteDebug()) + return; - public void WriteError(ErrorRecord errorRecord) - { - HadErrors = true; - if (!ShouldWriteError() || errorRecord == null) - return; + DoWriteDebug(debugRecord); + } - DoWriteError(errorRecord); - } + public void WriteDebug(string text, params object[] args) + { + if (string.IsNullOrEmpty(text) || !ShouldWriteDebug()) + return; - public void WriteVerbose(string message) - { - if (!ShouldWriteVerbose() || string.IsNullOrEmpty(message)) - return; + text = args == null || args.Length == 0 ? text : string.Format(Thread.CurrentThread.CurrentCulture, text, args); + DoWriteDebug(new DebugRecord(text)); + } - DoWriteVerbose(message); - } + public void WriteInformation(InformationRecord informationRecord) + { + if (!ShouldWriteInformation()) + return; - public void WriteDebug(DebugRecord debugRecord) - { - if (!ShouldWriteDebug()) - return; + DoWriteInformation(informationRecord); + } - DoWriteDebug(debugRecord); - } + public void WriteHost(HostInformationMessage info) + { + var record = new InformationRecord(info, Source); + record.Tags.Add(HostTag); + DoWriteInformation(record); + } - public void WriteDebug(string text, params object[] args) - { - if (string.IsNullOrEmpty(text) || !ShouldWriteDebug()) - return; + public void WriteWarning(string message) + { + if (!ShouldWriteWarning()) + return; - text = args == null || args.Length == 0 ? text : string.Format(Thread.CurrentThread.CurrentCulture, text, args); - DoWriteDebug(new DebugRecord(text)); - } + DoWriteWarning(message); + } - public void WriteInformation(InformationRecord informationRecord) - { - if (!ShouldWriteInformation()) - return; + public void WriteObject(object sendToPipeline, bool enumerateCollection) + { + DoWriteObject(sendToPipeline, enumerateCollection); + } - DoWriteInformation(informationRecord); - } + public void EnterScope(string scopeName) + { + ScopeName = scopeName; + } - public void WriteHost(HostInformationMessage info) - { - var record = new InformationRecord(info, Source); - record.Tags.Add(HostTag); - DoWriteInformation(record); - } + public void ExitScope() + { + ScopeName = null; + } - public void WriteWarning(string message) - { - if (!ShouldWriteWarning()) - return; + #endregion Logging - DoWriteWarning(message); - } + public virtual bool ShouldWriteError() + { + return true; + } - public void WriteObject(object sendToPipeline, bool enumerateCollection) - { - DoWriteObject(sendToPipeline, enumerateCollection); - } + public virtual bool ShouldWriteWarning() + { + return true; + } - public void EnterScope(string scopeName) - { - ScopeName = scopeName; - } + public virtual bool ShouldWriteVerbose() + { + return true; + } + + public virtual bool ShouldWriteInformation() + { + return true; + } + + public virtual bool ShouldWriteDebug() + { + return true; + } + + public virtual void Begin() + { - public void ExitScope() - { - ScopeName = null; - } + } + + public virtual void End() + { + + } + + #region IDisposable + + protected virtual void Dispose(bool disposing) + { + // Do nothing, but allow override. + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } - #endregion Logging + #endregion IDisposable - public virtual bool ShouldWriteError() - { - return true; - } + protected abstract void DoWriteError(ErrorRecord errorRecord); - public virtual bool ShouldWriteWarning() - { - return true; - } + protected abstract void DoWriteVerbose(string message); - public virtual bool ShouldWriteVerbose() - { - return true; - } + protected abstract void DoWriteWarning(string message); - public virtual bool ShouldWriteInformation() - { - return true; - } + protected abstract void DoWriteInformation(InformationRecord informationRecord); - public virtual bool ShouldWriteDebug() - { - return true; - } - - public virtual void Begin() - { - - } - - public virtual void End() - { - - } - - #region IDisposable - - protected virtual void Dispose(bool disposing) - { - // Do nothing, but allow override. - } - - public void Dispose() - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(disposing: true); - GC.SuppressFinalize(this); - } - - #endregion IDisposable - - protected abstract void DoWriteError(ErrorRecord errorRecord); - - protected abstract void DoWriteVerbose(string message); - - protected abstract void DoWriteWarning(string message); - - protected abstract void DoWriteInformation(InformationRecord informationRecord); - - protected abstract void DoWriteDebug(DebugRecord debugRecord); - - protected abstract void DoWriteObject(object sendToPipeline, bool enumerateCollection); - } - - internal sealed class PipelineLogger : PipelineLoggerBase - { - private const string ErrorPreference = "ErrorActionPreference"; - private const string WarningPreference = "WarningPreference"; - private const string VerbosePreference = "VerbosePreference"; - private const string InformationPreference = "InformationPreference"; - private const string DebugPreference = "DebugPreference"; - - private HashSet _VerboseFilter; - private HashSet _DebugFilter; - - private Action _OnWriteWarning; - private Action _OnWriteVerbose; - private Action _OnWriteError; - private Action _OnWriteInformation; - private Action _OnWriteDebug; - internal Action OnWriteObject; - - private bool _LogError; - private bool _LogWarning; - private bool _LogVerbose; - private bool _LogInformation; - private bool _LogDebug; - - internal void UseCommandRuntime(PSCmdlet commandRuntime) - { - _OnWriteVerbose = commandRuntime.WriteVerbose; - _OnWriteWarning = commandRuntime.WriteWarning; - _OnWriteError = commandRuntime.WriteError; - _OnWriteInformation = commandRuntime.WriteInformation; - _OnWriteDebug = commandRuntime.WriteDebug; - OnWriteObject = commandRuntime.WriteObject; - } - - internal void UseExecutionContext(EngineIntrinsics executionContext) - { - _LogError = GetPreferenceVariable(executionContext, ErrorPreference); - _LogWarning = GetPreferenceVariable(executionContext, WarningPreference); - _LogVerbose = GetPreferenceVariable(executionContext, VerbosePreference); - _LogInformation = GetPreferenceVariable(executionContext, InformationPreference); - _LogDebug = GetPreferenceVariable(executionContext, DebugPreference); - } - - internal void Configure(PSRuleOption option) - { - if (option.Logging.LimitVerbose != null && option.Logging.LimitVerbose.Length > 0) - _VerboseFilter = new HashSet(option.Logging.LimitVerbose); - - if (option.Logging.LimitDebug != null && option.Logging.LimitDebug.Length > 0) - _DebugFilter = new HashSet(option.Logging.LimitDebug); - } - - private static bool GetPreferenceVariable(EngineIntrinsics executionContext, string variableName) - { - var preference = (ActionPreference)executionContext.SessionState.PSVariable.GetValue(variableName); - return preference != ActionPreference.Ignore && - !(preference == ActionPreference.SilentlyContinue && - (variableName == VerbosePreference || variableName == DebugPreference)); - } - - #region Internal logging methods - - /// - /// Core methods to hand off to logger. - /// - /// A valid PowerShell error record. - protected override void DoWriteError(ErrorRecord errorRecord) - { - if (_OnWriteError == null) - return; - - _OnWriteError(errorRecord); - } - - /// - /// Core method to hand off verbose messages to logger. - /// - /// A message to log. - protected override void DoWriteVerbose(string message) - { - if (_OnWriteVerbose == null) - return; - - _OnWriteVerbose(message); - } - - /// - /// Core method to hand off warning messages to logger. - /// - /// A message to log - protected override void DoWriteWarning(string message) - { - if (_OnWriteWarning == null) - return; - - _OnWriteWarning(message); - } - - /// - /// Core method to hand off information messages to logger. - /// - protected override void DoWriteInformation(InformationRecord informationRecord) - { - if (_OnWriteInformation == null) - return; - - _OnWriteInformation(informationRecord); - } - - /// - /// Core method to hand off debug messages to logger. - /// - protected override void DoWriteDebug(DebugRecord debugRecord) - { - if (_OnWriteDebug == null) - return; - - _OnWriteDebug(debugRecord.Message); - } - - protected override void DoWriteObject(object sendToPipeline, bool enumerateCollection) - { - if (OnWriteObject == null) - return; - - OnWriteObject(sendToPipeline, enumerateCollection); - } - - #endregion Internal logging methods - - public override bool ShouldWriteVerbose() - { - return _LogVerbose && (_VerboseFilter == null || ScopeName == null || _VerboseFilter.Contains(ScopeName)); - } - - public override bool ShouldWriteInformation() - { - return true; - } - - public override bool ShouldWriteDebug() - { - return _LogDebug && (_DebugFilter == null || ScopeName == null || _DebugFilter.Contains(ScopeName)); - } + protected abstract void DoWriteDebug(DebugRecord debugRecord); + + protected abstract void DoWriteObject(object sendToPipeline, bool enumerateCollection); +} + +internal sealed class PipelineLogger : PipelineLoggerBase +{ + private const string ErrorPreference = "ErrorActionPreference"; + private const string WarningPreference = "WarningPreference"; + private const string VerbosePreference = "VerbosePreference"; + private const string InformationPreference = "InformationPreference"; + private const string DebugPreference = "DebugPreference"; + + private HashSet _VerboseFilter; + private HashSet _DebugFilter; + + private Action _OnWriteWarning; + private Action _OnWriteVerbose; + private Action _OnWriteError; + private Action _OnWriteInformation; + private Action _OnWriteDebug; + internal Action OnWriteObject; + + private bool _LogError; + private bool _LogWarning; + private bool _LogVerbose; + private bool _LogInformation; + private bool _LogDebug; + + internal void UseCommandRuntime(PSCmdlet commandRuntime) + { + _OnWriteVerbose = commandRuntime.WriteVerbose; + _OnWriteWarning = commandRuntime.WriteWarning; + _OnWriteError = commandRuntime.WriteError; + _OnWriteInformation = commandRuntime.WriteInformation; + _OnWriteDebug = commandRuntime.WriteDebug; + OnWriteObject = commandRuntime.WriteObject; + } + + internal void UseExecutionContext(EngineIntrinsics executionContext) + { + _LogError = GetPreferenceVariable(executionContext, ErrorPreference); + _LogWarning = GetPreferenceVariable(executionContext, WarningPreference); + _LogVerbose = GetPreferenceVariable(executionContext, VerbosePreference); + _LogInformation = GetPreferenceVariable(executionContext, InformationPreference); + _LogDebug = GetPreferenceVariable(executionContext, DebugPreference); + } + + internal void Configure(PSRuleOption option) + { + if (option.Logging.LimitVerbose != null && option.Logging.LimitVerbose.Length > 0) + _VerboseFilter = new HashSet(option.Logging.LimitVerbose); + + if (option.Logging.LimitDebug != null && option.Logging.LimitDebug.Length > 0) + _DebugFilter = new HashSet(option.Logging.LimitDebug); + } + + private static bool GetPreferenceVariable(EngineIntrinsics executionContext, string variableName) + { + var preference = (ActionPreference)executionContext.SessionState.PSVariable.GetValue(variableName); + return preference != ActionPreference.Ignore && + !(preference == ActionPreference.SilentlyContinue && + (variableName == VerbosePreference || variableName == DebugPreference)); + } + + #region Internal logging methods + + /// + /// Core methods to hand off to logger. + /// + /// A valid PowerShell error record. + protected override void DoWriteError(ErrorRecord errorRecord) + { + if (_OnWriteError == null) + return; + + _OnWriteError(errorRecord); + } + + /// + /// Core method to hand off verbose messages to logger. + /// + /// A message to log. + protected override void DoWriteVerbose(string message) + { + if (_OnWriteVerbose == null) + return; + + _OnWriteVerbose(message); + } + + /// + /// Core method to hand off warning messages to logger. + /// + /// A message to log + protected override void DoWriteWarning(string message) + { + if (_OnWriteWarning == null) + return; + + _OnWriteWarning(message); + } + + /// + /// Core method to hand off information messages to logger. + /// + protected override void DoWriteInformation(InformationRecord informationRecord) + { + if (_OnWriteInformation == null) + return; + + _OnWriteInformation(informationRecord); + } + + /// + /// Core method to hand off debug messages to logger. + /// + protected override void DoWriteDebug(DebugRecord debugRecord) + { + if (_OnWriteDebug == null) + return; + + _OnWriteDebug(debugRecord.Message); + } + + protected override void DoWriteObject(object sendToPipeline, bool enumerateCollection) + { + if (OnWriteObject == null) + return; + + OnWriteObject(sendToPipeline, enumerateCollection); + } + + #endregion Internal logging methods + + public override bool ShouldWriteVerbose() + { + return _LogVerbose && (_VerboseFilter == null || ScopeName == null || _VerboseFilter.Contains(ScopeName)); + } + + public override bool ShouldWriteInformation() + { + return true; + } + + public override bool ShouldWriteDebug() + { + return _LogDebug && (_DebugFilter == null || ScopeName == null || _DebugFilter.Contains(ScopeName)); } } diff --git a/src/PSRule/Pipeline/PipelineReader.cs b/src/PSRule/Pipeline/PipelineReader.cs index 3e6c9b4db1..0c66466aa3 100644 --- a/src/PSRule/Pipeline/PipelineReader.cs +++ b/src/PSRule/Pipeline/PipelineReader.cs @@ -4,97 +4,96 @@ using System.Collections.Concurrent; using System.Management.Automation; -namespace PSRule.Pipeline +namespace PSRule.Pipeline; + +internal sealed class PipelineReader { - internal sealed class PipelineReader + private readonly VisitTargetObject _Input; + private readonly InputPathBuilder _InputPath; + private readonly PathFilter _InputFilter; + private readonly ConcurrentQueue _Queue; + + public PipelineReader(VisitTargetObject input, InputPathBuilder inputPath, PathFilter inputFilter) { - private readonly VisitTargetObject _Input; - private readonly InputPathBuilder _InputPath; - private readonly PathFilter _InputFilter; - private readonly ConcurrentQueue _Queue; + _Input = input; + _InputPath = inputPath; + _InputFilter = inputFilter; + _Queue = new ConcurrentQueue(); + } - public PipelineReader(VisitTargetObject input, InputPathBuilder inputPath, PathFilter inputFilter) - { - _Input = input; - _InputPath = inputPath; - _InputFilter = inputFilter; - _Queue = new ConcurrentQueue(); - } + public int Count => _Queue.Count; - public int Count => _Queue.Count; + public bool IsEmpty => _Queue.IsEmpty; - public bool IsEmpty => _Queue.IsEmpty; + public void Enqueue(PSObject sourceObject, string targetType = null, bool skipExpansion = false) + { + if (sourceObject == null) + return; - public void Enqueue(PSObject sourceObject, string targetType = null, bool skipExpansion = false) + var targetObject = new TargetObject(sourceObject, targetType: targetType); + if (_Input == null || skipExpansion) { - if (sourceObject == null) - return; + EnqueueInternal(targetObject); + return; + } - var targetObject = new TargetObject(sourceObject, targetType: targetType); - if (_Input == null || skipExpansion) - { - EnqueueInternal(targetObject); - return; - } + // Visit the object, which may change or expand the object + var input = _Input(targetObject); + if (input == null) + return; - // Visit the object, which may change or expand the object - var input = _Input(targetObject); - if (input == null) - return; + foreach (var item in input) + EnqueueInternal(item); + } - foreach (var item in input) - EnqueueInternal(item); - } + public bool TryDequeue(out TargetObject sourceObject) + { + return _Queue.TryDequeue(out sourceObject); + } - public bool TryDequeue(out TargetObject sourceObject) - { - return _Queue.TryDequeue(out sourceObject); - } + public void Open() + { + if (_InputPath == null || _InputPath.Count == 0) + return; - public void Open() + // Read each file + var files = _InputPath.Build(); + for (var i = 0; i < files.Length; i++) { - if (_InputPath == null || _InputPath.Count == 0) - return; - - // Read each file - var files = _InputPath.Build(); - for (var i = 0; i < files.Length; i++) + if (files[i].IsUrl) { - if (files[i].IsUrl) - { - Enqueue(PSObject.AsPSObject(new Uri(files[i].FullName))); - } - else - { - Enqueue(PSObject.AsPSObject(files[i])); - } + Enqueue(PSObject.AsPSObject(new Uri(files[i].FullName))); } - } - - private void EnqueueInternal(TargetObject targetObject) - { - if (ShouldQueue(targetObject)) - _Queue.Enqueue(targetObject); - } - - private bool ShouldQueue(TargetObject targetObject) - { - if (_InputFilter != null && targetObject.Source != null && targetObject.Source.Count > 0) + else { - foreach (var source in targetObject.Source.GetSourceInfo()) - if (!_InputFilter.Match(source.File)) - return false; + Enqueue(PSObject.AsPSObject(files[i])); } - return true; } + } - /// - /// Add a path to the list of inputs. - /// - /// The path of files to add. - internal void Add(string path) + private void EnqueueInternal(TargetObject targetObject) + { + if (ShouldQueue(targetObject)) + _Queue.Enqueue(targetObject); + } + + private bool ShouldQueue(TargetObject targetObject) + { + if (_InputFilter != null && targetObject.Source != null && targetObject.Source.Count > 0) { - _InputPath.Add(path); + foreach (var source in targetObject.Source.GetSourceInfo()) + if (!_InputFilter.Match(source.File)) + return false; } + return true; + } + + /// + /// Add a path to the list of inputs. + /// + /// The path of files to add. + internal void Add(string path) + { + _InputPath.Add(path); } } diff --git a/src/PSRule/Pipeline/PipelineReciever.cs b/src/PSRule/Pipeline/PipelineReciever.cs index f163be024e..77fc4356dc 100644 --- a/src/PSRule/Pipeline/PipelineReciever.cs +++ b/src/PSRule/Pipeline/PipelineReciever.cs @@ -13,336 +13,335 @@ using YamlDotNet.Serialization; using YamlDotNet.Serialization.NodeDeserializers; -namespace PSRule.Pipeline +namespace PSRule.Pipeline; + +internal delegate IEnumerable VisitTargetObject(TargetObject sourceObject); +internal delegate IEnumerable VisitTargetObjectAction(TargetObject sourceObject, VisitTargetObject next); + +internal static class PipelineReceiverActions { - internal delegate IEnumerable VisitTargetObject(TargetObject sourceObject); - internal delegate IEnumerable VisitTargetObjectAction(TargetObject sourceObject, VisitTargetObject next); + private const string InputFileInfo_GitHead = ".git/HEAD"; + private const string JSON = ".json"; + private const string JSONC = ".jsonc"; + private const string YAML = ".yaml"; + private const string YML = ".yml"; + private const string MD = ".md"; + private const string MARKDOWN = ".markdown"; + private const string PSD1 = ".psd1"; + + private static readonly TargetObject[] EmptyArray = Array.Empty(); + + public static IEnumerable PassThru(TargetObject targetObject) + { + yield return targetObject; + } - internal static class PipelineReceiverActions + public static IEnumerable DetectInputFormat(TargetObject targetObject, VisitTargetObject next) { - private const string InputFileInfo_GitHead = ".git/HEAD"; - private const string JSON = ".json"; - private const string JSONC = ".jsonc"; - private const string YAML = ".yaml"; - private const string YML = ".yml"; - private const string MD = ".md"; - private const string MARKDOWN = ".markdown"; - private const string PSD1 = ".psd1"; - - private static readonly TargetObject[] EmptyArray = Array.Empty(); - - public static IEnumerable PassThru(TargetObject targetObject) + var pathExtension = GetPathExtension(targetObject); + + // Handle JSON + if (pathExtension == JSON || pathExtension == JSONC) { - yield return targetObject; + return ConvertFromJson(targetObject, next); } - - public static IEnumerable DetectInputFormat(TargetObject targetObject, VisitTargetObject next) + // Handle YAML + else if (pathExtension == YAML || pathExtension == YML) + { + return ConvertFromYaml(targetObject, next); + } + // Handle Markdown + else if (pathExtension == MD || pathExtension == MARKDOWN) + { + return ConvertFromMarkdown(targetObject, next); + } + // Handle PowerShell Data + else if (pathExtension == PSD1) { - var pathExtension = GetPathExtension(targetObject); + return ConvertFromPowerShellData(targetObject, next); + } + return new TargetObject[] { targetObject }; + } - // Handle JSON - if (pathExtension == JSON || pathExtension == JSONC) - { - return ConvertFromJson(targetObject, next); - } - // Handle YAML - else if (pathExtension == YAML || pathExtension == YML) - { - return ConvertFromYaml(targetObject, next); - } - // Handle Markdown - else if (pathExtension == MD || pathExtension == MARKDOWN) - { - return ConvertFromMarkdown(targetObject, next); - } - // Handle PowerShell Data - else if (pathExtension == PSD1) - { - return ConvertFromPowerShellData(targetObject, next); - } + public static IEnumerable ConvertFromJson(TargetObject targetObject, VisitTargetObject next) + { + // Only attempt to deserialize if the input is a string, file or URI + if (!IsAcceptedType(targetObject)) return new TargetObject[] { targetObject }; - } - public static IEnumerable ConvertFromJson(TargetObject targetObject, VisitTargetObject next) + var reader = ReadAsReader(targetObject, out var sourceInfo); + try { - // Only attempt to deserialize if the input is a string, file or URI - if (!IsAcceptedType(targetObject)) - return new TargetObject[] { targetObject }; - - var reader = ReadAsReader(targetObject, out var sourceInfo); - try - { - var d = new JsonSerializer(); // Think about caching this. - d.Converters.Add(new PSObjectArrayJsonConverter(sourceInfo)); - var value = d.Deserialize(AsJsonTextReader(reader)); - return VisitItems(value, sourceInfo, next); - } - catch (Exception ex) - { - if (sourceInfo != null && !string.IsNullOrEmpty(sourceInfo.File)) - { - RunspaceContext.CurrentThread.Writer.ErrorReadFileFailed(sourceInfo.File, ex); - return EmptyArray; - } - throw; - } - finally + var d = new JsonSerializer(); // Think about caching this. + d.Converters.Add(new PSObjectArrayJsonConverter(sourceInfo)); + var value = d.Deserialize(AsJsonTextReader(reader)); + return VisitItems(value, sourceInfo, next); + } + catch (Exception ex) + { + if (sourceInfo != null && !string.IsNullOrEmpty(sourceInfo.File)) { - reader.Dispose(); + RunspaceContext.CurrentThread.Writer.ErrorReadFileFailed(sourceInfo.File, ex); + return EmptyArray; } + throw; + } + finally + { + reader.Dispose(); } + } + + public static IEnumerable ConvertFromYaml(TargetObject targetObject, VisitTargetObject next) + { + // Only attempt to deserialize if the input is a string, file or URI + if (!IsAcceptedType(targetObject)) + return new TargetObject[] { targetObject }; - public static IEnumerable ConvertFromYaml(TargetObject targetObject, VisitTargetObject next) + var reader = ReadAsReader(targetObject, out var sourceInfo); + var d = new DeserializerBuilder() + .IgnoreUnmatchedProperties() + .WithTypeConverter(new PSObjectYamlTypeConverter()) + .WithNodeDeserializer( + inner => new PSObjectYamlDeserializer(inner, sourceInfo), + s => s.InsteadOf()) + .Build(); + + try { - // Only attempt to deserialize if the input is a string, file or URI - if (!IsAcceptedType(targetObject)) - return new TargetObject[] { targetObject }; - - var reader = ReadAsReader(targetObject, out var sourceInfo); - var d = new DeserializerBuilder() - .IgnoreUnmatchedProperties() - .WithTypeConverter(new PSObjectYamlTypeConverter()) - .WithNodeDeserializer( - inner => new PSObjectYamlDeserializer(inner, sourceInfo), - s => s.InsteadOf()) - .Build(); - - try - { - var parser = new YamlDotNet.Core.Parser(reader); - var result = new List(); - parser.TryConsume(out _); - while (parser.Current is DocumentStart) - { - var item = d.Deserialize(parser); - if (item == null) - continue; - - var items = VisitItems(item, sourceInfo, next); - if (items == null) - continue; - - result.AddRange(items); - } - - return result.Count == 0 ? EmptyArray : result.ToArray(); - } - catch (Exception ex) + var parser = new YamlDotNet.Core.Parser(reader); + var result = new List(); + parser.TryConsume(out _); + while (parser.Current is DocumentStart) { - if (sourceInfo != null && !string.IsNullOrEmpty(sourceInfo.File)) - { - RunspaceContext.CurrentThread.Writer.ErrorReadFileFailed(sourceInfo.File, ex); - return EmptyArray; - } - throw; + var item = d.Deserialize(parser); + if (item == null) + continue; + + var items = VisitItems(item, sourceInfo, next); + if (items == null) + continue; + + result.AddRange(items); } - finally + + return result.Count == 0 ? EmptyArray : result.ToArray(); + } + catch (Exception ex) + { + if (sourceInfo != null && !string.IsNullOrEmpty(sourceInfo.File)) { - reader.Dispose(); + RunspaceContext.CurrentThread.Writer.ErrorReadFileFailed(sourceInfo.File, ex); + return EmptyArray; } + throw; } - - public static IEnumerable ConvertFromMarkdown(TargetObject targetObject, VisitTargetObject next) + finally { - // Only attempt to deserialize if the input is a string or a file - if (!IsAcceptedType(targetObject)) - return new TargetObject[] { targetObject }; - - var markdown = ReadAsString(targetObject, out var sourceInfo); - var value = MarkdownConvert.DeserializeObject(markdown); - return VisitItems(value, sourceInfo, next); + reader.Dispose(); } + } - public static IEnumerable ConvertFromPowerShellData(TargetObject targetObject, VisitTargetObject next) - { - // Only attempt to deserialize if the input is a string or a file - if (!IsAcceptedType(targetObject)) - return new TargetObject[] { targetObject }; - - var data = ReadAsString(targetObject, out var sourceInfo); - var ast = System.Management.Automation.Language.Parser.ParseInput(data, out _, out _); - var hashtables = ast.FindAll(item => item is System.Management.Automation.Language.HashtableAst, false); - if (hashtables == null) - return EmptyArray; + public static IEnumerable ConvertFromMarkdown(TargetObject targetObject, VisitTargetObject next) + { + // Only attempt to deserialize if the input is a string or a file + if (!IsAcceptedType(targetObject)) + return new TargetObject[] { targetObject }; - var result = new List(); - foreach (var hashtable in hashtables) - { - if (hashtable?.Parent?.Parent?.Parent?.Parent == ast) - result.Add(PSObject.AsPSObject(hashtable.SafeGetValue())); - } - var value = result.ToArray(); - return VisitItems(value, sourceInfo, next); + var markdown = ReadAsString(targetObject, out var sourceInfo); + var value = MarkdownConvert.DeserializeObject(markdown); + return VisitItems(value, sourceInfo, next); + } + + public static IEnumerable ConvertFromPowerShellData(TargetObject targetObject, VisitTargetObject next) + { + // Only attempt to deserialize if the input is a string or a file + if (!IsAcceptedType(targetObject)) + return new TargetObject[] { targetObject }; + + var data = ReadAsString(targetObject, out var sourceInfo); + var ast = System.Management.Automation.Language.Parser.ParseInput(data, out _, out _); + var hashtables = ast.FindAll(item => item is System.Management.Automation.Language.HashtableAst, false); + if (hashtables == null) + return EmptyArray; + + var result = new List(); + foreach (var hashtable in hashtables) + { + if (hashtable?.Parent?.Parent?.Parent?.Parent == ast) + result.Add(PSObject.AsPSObject(hashtable.SafeGetValue())); } + var value = result.ToArray(); + return VisitItems(value, sourceInfo, next); + } - public static IEnumerable ConvertFromGitHead(TargetObject targetObject, VisitTargetObject next) + public static IEnumerable ConvertFromGitHead(TargetObject targetObject, VisitTargetObject next) + { + // Only attempt to convert if Git HEAD file + if (!IsGitHead(targetObject)) + return new TargetObject[] { targetObject }; + + var value = PSObject.AsPSObject(GetRepositoryInfo(targetObject, out var sourceInfo)); + return VisitItems(new PSObject[] { value }, sourceInfo, next); + } + + public static IEnumerable ReadObjectPath(TargetObject targetObject, VisitTargetObject source, string objectPath, bool caseSensitive) + { + if (!ObjectHelper.GetPath( + bindingContext: null, + targetObject: targetObject.Value, + path: objectPath, + caseSensitive: caseSensitive, + value: out object nestedObject)) + return EmptyArray; + + var nestedType = nestedObject.GetType(); + if (typeof(IEnumerable).IsAssignableFrom(nestedType)) { - // Only attempt to convert if Git HEAD file - if (!IsGitHead(targetObject)) - return new TargetObject[] { targetObject }; + var result = new List(); + foreach (var item in (nestedObject as IEnumerable)) + result.Add(new TargetObject(PSObject.AsPSObject(item))); - var value = PSObject.AsPSObject(GetRepositoryInfo(targetObject, out var sourceInfo)); - return VisitItems(new PSObject[] { value }, sourceInfo, next); + return result.ToArray(); } - - public static IEnumerable ReadObjectPath(TargetObject targetObject, VisitTargetObject source, string objectPath, bool caseSensitive) + else { - if (!ObjectHelper.GetPath( - bindingContext: null, - targetObject: targetObject.Value, - path: objectPath, - caseSensitive: caseSensitive, - value: out object nestedObject)) - return EmptyArray; + return new TargetObject[] { new(PSObject.AsPSObject(nestedObject), targetObject.Source) }; + } + } - var nestedType = nestedObject.GetType(); - if (typeof(IEnumerable).IsAssignableFrom(nestedType)) - { - var result = new List(); - foreach (var item in (nestedObject as IEnumerable)) - result.Add(new TargetObject(PSObject.AsPSObject(item))); + private static string GetPathExtension(TargetObject targetObject) + { + if (targetObject.Value.BaseObject is InputFileInfo inputFileInfo) + return inputFileInfo.Extension; - return result.ToArray(); - } - else - { - return new TargetObject[] { new TargetObject(PSObject.AsPSObject(nestedObject), targetObject.Source) }; - } - } + if (targetObject.Value.BaseObject is FileInfo fileInfo) + return fileInfo.Extension; - private static string GetPathExtension(TargetObject targetObject) - { - if (targetObject.Value.BaseObject is InputFileInfo inputFileInfo) - return inputFileInfo.Extension; + if (targetObject.Value.BaseObject is Uri uri) + return Path.GetExtension(uri.OriginalString); - if (targetObject.Value.BaseObject is FileInfo fileInfo) - return fileInfo.Extension; + return null; + } - if (targetObject.Value.BaseObject is Uri uri) - return Path.GetExtension(uri.OriginalString); + private static bool IsAcceptedType(TargetObject targetObject) + { + return targetObject.Value.BaseObject is string || + targetObject.Value.BaseObject is InputFileInfo || + targetObject.Value.BaseObject is FileInfo || + targetObject.Value.BaseObject is Uri; + } + private static bool IsGitHead(TargetObject targetObject) + { + return targetObject.Value.BaseObject is InputFileInfo info && info.DisplayName == InputFileInfo_GitHead; + } + + private static RepositoryInfo GetRepositoryInfo(TargetObject targetObject, out TargetSourceInfo sourceInfo) + { + sourceInfo = null; + if (targetObject.Value.BaseObject is not InputFileInfo inputFileInfo) return null; - } - private static bool IsAcceptedType(TargetObject targetObject) + sourceInfo = new TargetSourceInfo(inputFileInfo); + GitHelper.TryHeadRef(out var headRef, inputFileInfo.DirectoryName); + return new RepositoryInfo(inputFileInfo.BasePath, headRef); + } + + private static string ReadAsString(TargetObject targetObject, out TargetSourceInfo sourceInfo) + { + sourceInfo = null; + if (targetObject.Value.BaseObject is string) { - return targetObject.Value.BaseObject is string || - targetObject.Value.BaseObject is InputFileInfo || - targetObject.Value.BaseObject is FileInfo || - targetObject.Value.BaseObject is Uri; + return targetObject.Value.BaseObject.ToString(); } - - private static bool IsGitHead(TargetObject targetObject) + else if (targetObject.Value.BaseObject is InputFileInfo inputFileInfo) { - return targetObject.Value.BaseObject is InputFileInfo info && info.DisplayName == InputFileInfo_GitHead; + sourceInfo = new TargetSourceInfo(inputFileInfo); + using var reader = new StreamReader(inputFileInfo.FullName); + return reader.ReadToEnd(); } - - private static RepositoryInfo GetRepositoryInfo(TargetObject targetObject, out TargetSourceInfo sourceInfo) + else if (targetObject.Value.BaseObject is FileInfo fileInfo) + { + sourceInfo = new TargetSourceInfo(fileInfo); + using var reader = new StreamReader(fileInfo.FullName); + return reader.ReadToEnd(); + } + else { - sourceInfo = null; - if (targetObject.Value.BaseObject is not InputFileInfo inputFileInfo) - return null; + var uri = targetObject.Value.BaseObject as Uri; + sourceInfo = new TargetSourceInfo(uri); + using var webClient = new WebClient(); + return webClient.DownloadString(uri); + } + } + private static TextReader ReadAsReader(TargetObject targetObject, out TargetSourceInfo sourceInfo) + { + sourceInfo = null; + if (targetObject.Value.BaseObject is string) + { + return new StringReader(targetObject.Value.BaseObject.ToString()); + } + else if (targetObject.Value.BaseObject is InputFileInfo inputFileInfo) + { sourceInfo = new TargetSourceInfo(inputFileInfo); - GitHelper.TryHeadRef(out var headRef, inputFileInfo.DirectoryName); - return new RepositoryInfo(inputFileInfo.BasePath, headRef); + return new StreamReader(inputFileInfo.FullName); } - - private static string ReadAsString(TargetObject targetObject, out TargetSourceInfo sourceInfo) + else if (targetObject.Value.BaseObject is FileInfo fileInfo) { - sourceInfo = null; - if (targetObject.Value.BaseObject is string) - { - return targetObject.Value.BaseObject.ToString(); - } - else if (targetObject.Value.BaseObject is InputFileInfo inputFileInfo) - { - sourceInfo = new TargetSourceInfo(inputFileInfo); - using var reader = new StreamReader(inputFileInfo.FullName); - return reader.ReadToEnd(); - } - else if (targetObject.Value.BaseObject is FileInfo fileInfo) - { - sourceInfo = new TargetSourceInfo(fileInfo); - using var reader = new StreamReader(fileInfo.FullName); - return reader.ReadToEnd(); - } - else - { - var uri = targetObject.Value.BaseObject as Uri; - sourceInfo = new TargetSourceInfo(uri); - using var webClient = new WebClient(); - return webClient.DownloadString(uri); - } + sourceInfo = new TargetSourceInfo(fileInfo); + return new StreamReader(fileInfo.FullName); } - - private static TextReader ReadAsReader(TargetObject targetObject, out TargetSourceInfo sourceInfo) + else { - sourceInfo = null; - if (targetObject.Value.BaseObject is string) - { - return new StringReader(targetObject.Value.BaseObject.ToString()); - } - else if (targetObject.Value.BaseObject is InputFileInfo inputFileInfo) - { - sourceInfo = new TargetSourceInfo(inputFileInfo); - return new StreamReader(inputFileInfo.FullName); - } - else if (targetObject.Value.BaseObject is FileInfo fileInfo) - { - sourceInfo = new TargetSourceInfo(fileInfo); - return new StreamReader(fileInfo.FullName); - } - else - { - var uri = targetObject.Value.BaseObject as Uri; - sourceInfo = new TargetSourceInfo(uri); - using var webClient = new WebClient(); - return new StringReader(webClient.DownloadString(uri)); - } + var uri = targetObject.Value.BaseObject as Uri; + sourceInfo = new TargetSourceInfo(uri); + using var webClient = new WebClient(); + return new StringReader(webClient.DownloadString(uri)); } + } - private static IEnumerable VisitItem(PSObject value, TargetSourceInfo sourceInfo, VisitTargetObject next) - { - if (value == null) - return EmptyArray; + private static IEnumerable VisitItem(PSObject value, TargetSourceInfo sourceInfo, VisitTargetObject next) + { + if (value == null) + return EmptyArray; - var items = next(new TargetObject(value)); - if (items == null) - return EmptyArray; + var items = next(new TargetObject(value)); + if (items == null) + return EmptyArray; - foreach (var i in items) - NoteSource(i, sourceInfo); + foreach (var i in items) + NoteSource(i, sourceInfo); - return items; - } + return items; + } - private static IEnumerable VisitItems(IEnumerable value, TargetSourceInfo sourceInfo, VisitTargetObject next) - { - if (value == null) - return EmptyArray; + private static IEnumerable VisitItems(IEnumerable value, TargetSourceInfo sourceInfo, VisitTargetObject next) + { + if (value == null) + return EmptyArray; - var result = new List(); - foreach (var item in value) - result.AddRange(VisitItem(item, sourceInfo, next)); + var result = new List(); + foreach (var item in value) + result.AddRange(VisitItem(item, sourceInfo, next)); - return result.Count == 0 ? EmptyArray : result.ToArray(); - } + return result.Count == 0 ? EmptyArray : result.ToArray(); + } - private static void NoteSource(TargetObject value, TargetSourceInfo source) - { - if (value == null || source == null) - return; + private static void NoteSource(TargetObject value, TargetSourceInfo source) + { + if (value == null || source == null) + return; - value.Value.UseTargetInfo(out var targetInfo); - targetInfo.UpdateSource(source); - value.Source.AddRange(targetInfo.Source.ToArray()); - value.Issue.AddRange(targetInfo.Issue.ToArray()); - } + value.Value.UseTargetInfo(out var targetInfo); + targetInfo.UpdateSource(source); + value.Source.AddRange(targetInfo.Source.ToArray()); + value.Issue.AddRange(targetInfo.Issue.ToArray()); + } - private static JsonTextReader AsJsonTextReader(TextReader reader) - { - return new JsonTextReader(reader); - } + private static JsonTextReader AsJsonTextReader(TextReader reader) + { + return new JsonTextReader(reader); } } diff --git a/src/PSRule/Pipeline/PipelineWriter.cs b/src/PSRule/Pipeline/PipelineWriter.cs index 9ab64fea95..708ee0be59 100644 --- a/src/PSRule/Pipeline/PipelineWriter.cs +++ b/src/PSRule/Pipeline/PipelineWriter.cs @@ -6,456 +6,455 @@ using PSRule.Resources; using PSRule.Rules; -namespace PSRule.Pipeline +namespace PSRule.Pipeline; + +/// +/// An writer which recieves output from PSRule. +/// +public interface IPipelineWriter : IDisposable { /// - /// An writer which recieves output from PSRule. + /// Determines if any errors were reported. /// - public interface IPipelineWriter : IDisposable - { - /// - /// Determines if any errors were reported. - /// - bool HadErrors { get; } - - /// - /// Determines if an failures were reported. - /// - bool HadFailures { get; } - - /// - /// Write a verbose message. - /// - void WriteVerbose(string message); - - /// - /// Determines if a verbose message should be written to output. - /// - bool ShouldWriteVerbose(); - - /// - /// Write a warning message. - /// - void WriteWarning(string message); - - /// - /// Determines if a warning message should be written to output. - /// - bool ShouldWriteWarning(); - - /// - /// Write an error message. - /// - void WriteError(ErrorRecord errorRecord); - - /// - /// Determines if an error message should be written to output. - /// - bool ShouldWriteError(); - - /// - /// Write an informational message. - /// - void WriteInformation(InformationRecord informationRecord); - - /// - /// Write a message to the host process. - /// - void WriteHost(HostInformationMessage info); - - /// - /// Determines if an informational message should be written to output. - /// - bool ShouldWriteInformation(); - - /// - /// Write a debug message. - /// - void WriteDebug(string text, params object[] args); - - /// - /// Determines if a debug message should be written to output. - /// - bool ShouldWriteDebug(); - - /// - /// Write an object to output. - /// - /// The object to write to the pipeline. - /// Determines when the object is enumerable if it should be enumerated as more then one object. - void WriteObject(object sendToPipeline, bool enumerateCollection); - - /// - /// Enter a logging scope. - /// - void EnterScope(string scopeName); - - /// - /// Exit a logging scope. - /// - void ExitScope(); - - /// - /// Start and initialize the writer. - /// - void Begin(); - - /// - /// Stop and finalized the writer. - /// - void End(); - } + bool HadErrors { get; } /// - /// A base class for writers. + /// Determines if an failures were reported. /// - internal abstract class PipelineWriter : IPipelineWriter - { - protected const string ErrorPreference = "ErrorActionPreference"; - protected const string WarningPreference = "WarningPreference"; - protected const string VerbosePreference = "VerbosePreference"; - protected const string InformationPreference = "InformationPreference"; - protected const string DebugPreference = "DebugPreference"; + bool HadFailures { get; } - private readonly IPipelineWriter _Writer; - private readonly ShouldProcess _ShouldProcess; + /// + /// Write a verbose message. + /// + void WriteVerbose(string message); - protected readonly PSRuleOption Option; + /// + /// Determines if a verbose message should be written to output. + /// + bool ShouldWriteVerbose(); - private bool _IsDisposed; - private bool _HadErrors; - private bool _HadFailures; + /// + /// Write a warning message. + /// + void WriteWarning(string message); - protected PipelineWriter(IPipelineWriter inner, PSRuleOption option, ShouldProcess shouldProcess) - { - _Writer = inner; - _ShouldProcess = shouldProcess; - Option = option; - } + /// + /// Determines if a warning message should be written to output. + /// + bool ShouldWriteWarning(); - bool IPipelineWriter.HadErrors => HadErrors; + /// + /// Write an error message. + /// + void WriteError(ErrorRecord errorRecord); - bool IPipelineWriter.HadFailures => HadFailures; + /// + /// Determines if an error message should be written to output. + /// + bool ShouldWriteError(); - /// - public virtual bool HadErrors - { - get - { - return _HadErrors || (_Writer != null && _Writer.HadErrors); - } - set - { - _HadErrors = value; - } - } + /// + /// Write an informational message. + /// + void WriteInformation(InformationRecord informationRecord); - /// - public virtual bool HadFailures - { - get - { - return _HadFailures || (_Writer != null && _Writer.HadFailures); - } - set - { - _HadFailures = value; - } - } + /// + /// Write a message to the host process. + /// + void WriteHost(HostInformationMessage info); - /// - public virtual void Begin() - { - if (_Writer == null) - return; + /// + /// Determines if an informational message should be written to output. + /// + bool ShouldWriteInformation(); - _Writer.Begin(); - } + /// + /// Write a debug message. + /// + void WriteDebug(string text, params object[] args); - /// - public virtual void WriteObject(object sendToPipeline, bool enumerateCollection) - { - if (_Writer == null || sendToPipeline == null) - return; + /// + /// Determines if a debug message should be written to output. + /// + bool ShouldWriteDebug(); - _Writer.WriteObject(sendToPipeline, enumerateCollection); - } + /// + /// Write an object to output. + /// + /// The object to write to the pipeline. + /// Determines when the object is enumerable if it should be enumerated as more then one object. + void WriteObject(object sendToPipeline, bool enumerateCollection); - /// - public virtual void End() - { - if (_Writer == null) - return; + /// + /// Enter a logging scope. + /// + void EnterScope(string scopeName); - _Writer.End(); - } + /// + /// Exit a logging scope. + /// + void ExitScope(); - /// - public virtual void WriteVerbose(string message) - { - if (_Writer == null || string.IsNullOrEmpty(message)) - return; + /// + /// Start and initialize the writer. + /// + void Begin(); - _Writer.WriteVerbose(message); - } + /// + /// Stop and finalized the writer. + /// + void End(); +} - /// - public virtual bool ShouldWriteVerbose() - { - return _Writer != null && _Writer.ShouldWriteVerbose(); - } +/// +/// A base class for writers. +/// +internal abstract class PipelineWriter : IPipelineWriter +{ + protected const string ErrorPreference = "ErrorActionPreference"; + protected const string WarningPreference = "WarningPreference"; + protected const string VerbosePreference = "VerbosePreference"; + protected const string InformationPreference = "InformationPreference"; + protected const string DebugPreference = "DebugPreference"; - /// - public virtual void WriteWarning(string message) - { - if (_Writer == null || string.IsNullOrEmpty(message)) - return; + private readonly IPipelineWriter _Writer; + private readonly ShouldProcess _ShouldProcess; - _Writer.WriteWarning(message); - } + protected readonly PSRuleOption Option; - /// - public virtual bool ShouldWriteWarning() - { - return _Writer != null && _Writer.ShouldWriteWarning(); - } + private bool _IsDisposed; + private bool _HadErrors; + private bool _HadFailures; - /// - public virtual void WriteError(ErrorRecord errorRecord) - { - if (_Writer == null || errorRecord == null) - return; + protected PipelineWriter(IPipelineWriter inner, PSRuleOption option, ShouldProcess shouldProcess) + { + _Writer = inner; + _ShouldProcess = shouldProcess; + Option = option; + } - _Writer.WriteError(errorRecord); - } + bool IPipelineWriter.HadErrors => HadErrors; - /// - public virtual bool ShouldWriteError() + bool IPipelineWriter.HadFailures => HadFailures; + + /// + public virtual bool HadErrors + { + get { - return _Writer != null && _Writer.ShouldWriteError(); + return _HadErrors || (_Writer != null && _Writer.HadErrors); } - - /// - public virtual void WriteInformation(InformationRecord informationRecord) + set { - if (_Writer == null || informationRecord == null) - return; - - _Writer.WriteInformation(informationRecord); + _HadErrors = value; } + } - /// - public virtual void WriteHost(HostInformationMessage info) + /// + public virtual bool HadFailures + { + get { - if (_Writer == null) - return; - - _Writer.WriteHost(info); + return _HadFailures || (_Writer != null && _Writer.HadFailures); } - - /// - public virtual bool ShouldWriteInformation() + set { - return _Writer != null && _Writer.ShouldWriteInformation(); + _HadFailures = value; } + } - /// - public virtual void WriteDebug(string text, params object[] args) - { - if (_Writer == null || string.IsNullOrEmpty(text) || !ShouldWriteDebug()) - return; + /// + public virtual void Begin() + { + if (_Writer == null) + return; - text = args == null || args.Length == 0 ? text : string.Format(Thread.CurrentThread.CurrentCulture, text, args); - _Writer.WriteDebug(text); - } + _Writer.Begin(); + } - /// - public virtual bool ShouldWriteDebug() - { - return _Writer != null && _Writer.ShouldWriteDebug(); - } + /// + public virtual void WriteObject(object sendToPipeline, bool enumerateCollection) + { + if (_Writer == null || sendToPipeline == null) + return; - /// - public virtual void EnterScope(string scopeName) - { - if (_Writer == null) - return; + _Writer.WriteObject(sendToPipeline, enumerateCollection); + } - _Writer.EnterScope(scopeName); - } + /// + public virtual void End() + { + if (_Writer == null) + return; - /// - public virtual void ExitScope() - { - if (_Writer == null) - return; + _Writer.End(); + } - _Writer.ExitScope(); - } + /// + public virtual void WriteVerbose(string message) + { + if (_Writer == null || string.IsNullOrEmpty(message)) + return; - #region IDisposable + _Writer.WriteVerbose(message); + } - protected virtual void Dispose(bool disposing) - { - if (!_IsDisposed) - { - if (disposing && _Writer != null) - _Writer.Dispose(); + /// + public virtual bool ShouldWriteVerbose() + { + return _Writer != null && _Writer.ShouldWriteVerbose(); + } - _IsDisposed = true; - } - } + /// + public virtual void WriteWarning(string message) + { + if (_Writer == null || string.IsNullOrEmpty(message)) + return; - public void Dispose() - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(disposing: true); - GC.SuppressFinalize(this); - } + _Writer.WriteWarning(message); + } - #endregion IDisposable + /// + public virtual bool ShouldWriteWarning() + { + return _Writer != null && _Writer.ShouldWriteWarning(); + } - protected void WriteErrorInfo(RuleRecord record) - { - if (record == null || record.Error == null) - return; - - var errorRecord = new ErrorRecord( - record.Error.Exception, - record.Error.ErrorId, - record.Error.Category, - record.TargetName - ); - errorRecord.CategoryInfo.TargetType = record.TargetType; - errorRecord.ErrorDetails = new ErrorDetails(string.Format( - Thread.CurrentThread.CurrentCulture, - PSRuleResources.ErrorDetailMessage, - record.RuleId, - record.Error.Message, - record.Error.ScriptExtent.File, - record.Error.ScriptExtent.StartLineNumber, - record.Error.ScriptExtent.StartColumnNumber - )); - WriteError(errorRecord); - } + /// + public virtual void WriteError(ErrorRecord errorRecord) + { + if (_Writer == null || errorRecord == null) + return; - private bool ShouldProcess(string target, string action) - { - return _ShouldProcess == null || _ShouldProcess(target, action); - } + _Writer.WriteError(errorRecord); + } - private bool CreatePath(string path) - { - var parentPath = Directory.GetParent(path); - if (!parentPath.Exists && ShouldProcess(target: parentPath.FullName, action: PSRuleResources.ShouldCreatePath)) - { - Directory.CreateDirectory(path: parentPath.FullName); - return true; - } - return parentPath.Exists; - } + /// + public virtual bool ShouldWriteError() + { + return _Writer != null && _Writer.ShouldWriteError(); + } - protected bool CreateFile(string path) - { - return CreatePath(path) && ShouldProcess(target: path, action: PSRuleResources.ShouldWriteFile); - } + /// + public virtual void WriteInformation(InformationRecord informationRecord) + { + if (_Writer == null || informationRecord == null) + return; + + _Writer.WriteInformation(informationRecord); + } + + /// + public virtual void WriteHost(HostInformationMessage info) + { + if (_Writer == null) + return; + + _Writer.WriteHost(info); + } + + /// + public virtual bool ShouldWriteInformation() + { + return _Writer != null && _Writer.ShouldWriteInformation(); + } + + /// + public virtual void WriteDebug(string text, params object[] args) + { + if (_Writer == null || string.IsNullOrEmpty(text) || !ShouldWriteDebug()) + return; - /// - /// Get the value of a preference variable. - /// - protected static ActionPreference GetPreferenceVariable(System.Management.Automation.SessionState sessionState, string variableName) + text = args == null || args.Length == 0 ? text : string.Format(Thread.CurrentThread.CurrentCulture, text, args); + _Writer.WriteDebug(text); + } + + /// + public virtual bool ShouldWriteDebug() + { + return _Writer != null && _Writer.ShouldWriteDebug(); + } + + /// + public virtual void EnterScope(string scopeName) + { + if (_Writer == null) + return; + + _Writer.EnterScope(scopeName); + } + + /// + public virtual void ExitScope() + { + if (_Writer == null) + return; + + _Writer.ExitScope(); + } + + #region IDisposable + + protected virtual void Dispose(bool disposing) + { + if (!_IsDisposed) { - return (ActionPreference)sessionState.PSVariable.GetValue(variableName); + if (disposing && _Writer != null) + _Writer.Dispose(); + + _IsDisposed = true; } } - internal abstract class ResultOutputWriter : PipelineWriter + public void Dispose() { - private readonly List _Result; + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } - protected ResultOutputWriter(IPipelineWriter inner, PSRuleOption option, ShouldProcess shouldProcess) - : base(inner, option, shouldProcess) + #endregion IDisposable + + protected void WriteErrorInfo(RuleRecord record) + { + if (record == null || record.Error == null) + return; + + var errorRecord = new ErrorRecord( + record.Error.Exception, + record.Error.ErrorId, + record.Error.Category, + record.TargetName + ); + errorRecord.CategoryInfo.TargetType = record.TargetType; + errorRecord.ErrorDetails = new ErrorDetails(string.Format( + Thread.CurrentThread.CurrentCulture, + PSRuleResources.ErrorDetailMessage, + record.RuleId, + record.Error.Message, + record.Error.ScriptExtent.File, + record.Error.ScriptExtent.StartLineNumber, + record.Error.ScriptExtent.StartColumnNumber + )); + WriteError(errorRecord); + } + + private bool ShouldProcess(string target, string action) + { + return _ShouldProcess == null || _ShouldProcess(target, action); + } + + private bool CreatePath(string path) + { + var parentPath = Directory.GetParent(path); + if (!parentPath.Exists && ShouldProcess(target: parentPath.FullName, action: PSRuleResources.ShouldCreatePath)) { - _Result = new List(); + Directory.CreateDirectory(path: parentPath.FullName); + return true; } + return parentPath.Exists; + } + + protected bool CreateFile(string path) + { + return CreatePath(path) && ShouldProcess(target: path, action: PSRuleResources.ShouldWriteFile); + } + + /// + /// Get the value of a preference variable. + /// + protected static ActionPreference GetPreferenceVariable(System.Management.Automation.SessionState sessionState, string variableName) + { + return (ActionPreference)sessionState.PSVariable.GetValue(variableName); + } +} + +internal abstract class ResultOutputWriter : PipelineWriter +{ + private readonly List _Result; + + protected ResultOutputWriter(IPipelineWriter inner, PSRuleOption option, ShouldProcess shouldProcess) + : base(inner, option, shouldProcess) + { + _Result = new List(); + } - public override void WriteObject(object sendToPipeline, bool enumerateCollection) + public override void WriteObject(object sendToPipeline, bool enumerateCollection) + { + if (sendToPipeline is InvokeResult && Option.Output.As == ResultFormat.Summary) { - if (sendToPipeline is InvokeResult && Option.Output.As == ResultFormat.Summary) - { - base.WriteObject(sendToPipeline, enumerateCollection); - return; - } - - if (sendToPipeline is InvokeResult result) - { - Add(typeof(T) == typeof(RuleRecord) ? result.AsRecord() : result); - } - else - { - Add(sendToPipeline); - } base.WriteObject(sendToPipeline, enumerateCollection); + return; } - protected void Add(object o) + if (sendToPipeline is InvokeResult result) { - if (o is T[] collection) - _Result.AddRange(collection); - else if (o is T item) - _Result.Add(item); + Add(typeof(T) == typeof(RuleRecord) ? result.AsRecord() : result); } - - /// - /// Clear any buffers from the writer. - /// - protected virtual void Flush() { } - - protected T[] GetResults() + else { - return _Result.ToArray(); + Add(sendToPipeline); } + base.WriteObject(sendToPipeline, enumerateCollection); + } + + protected void Add(object o) + { + if (o is T[] collection) + _Result.AddRange(collection); + else if (o is T item) + _Result.Add(item); + } + + /// + /// Clear any buffers from the writer. + /// + protected virtual void Flush() { } + + protected T[] GetResults() + { + return _Result.ToArray(); } +} - internal abstract class SerializationOutputWriter : ResultOutputWriter +internal abstract class SerializationOutputWriter : ResultOutputWriter +{ + protected SerializationOutputWriter(IPipelineWriter inner, PSRuleOption option, ShouldProcess shouldProcess) + : base(inner, option, shouldProcess) { } + + public sealed override void End() { - protected SerializationOutputWriter(IPipelineWriter inner, PSRuleOption option, ShouldProcess shouldProcess) - : base(inner, option, shouldProcess) { } + var results = GetResults(); + base.WriteObject(Serialize(results), false); + ProcessError(results); + Flush(); + base.End(); + } - public sealed override void End() + public override void WriteObject(object sendToPipeline, bool enumerateCollection) + { + if (sendToPipeline is InvokeResult && Option.Output.As == ResultFormat.Summary) { - var results = GetResults(); - base.WriteObject(Serialize(results), false); - ProcessError(results); - Flush(); - base.End(); + base.WriteObject(sendToPipeline, enumerateCollection); + return; } - public override void WriteObject(object sendToPipeline, bool enumerateCollection) + if (sendToPipeline is InvokeResult result) { - if (sendToPipeline is InvokeResult && Option.Output.As == ResultFormat.Summary) - { - base.WriteObject(sendToPipeline, enumerateCollection); - return; - } - - if (sendToPipeline is InvokeResult result) - { - Add(result.AsRecord()); - return; - } - Add(sendToPipeline); + Add(result.AsRecord()); + return; } + Add(sendToPipeline); + } - protected abstract string Serialize(T[] o); + protected abstract string Serialize(T[] o); - private void ProcessError(T[] results) + private void ProcessError(T[] results) + { + for (var i = 0; i < results.Length; i++) { - for (var i = 0; i < results.Length; i++) - { - if (results[i] is RuleRecord record) - WriteErrorInfo(record); - } + if (results[i] is RuleRecord record) + WriteErrorInfo(record); } } } diff --git a/src/PSRule/Pipeline/PipelineWriterExtensions.cs b/src/PSRule/Pipeline/PipelineWriterExtensions.cs index 631421dec0..0790d862ac 100644 --- a/src/PSRule/Pipeline/PipelineWriterExtensions.cs +++ b/src/PSRule/Pipeline/PipelineWriterExtensions.cs @@ -4,114 +4,113 @@ using System.Management.Automation; using PSRule.Resources; -namespace PSRule.Pipeline +namespace PSRule.Pipeline; + +/// +/// Extensions for the . +/// +public static class PipelineWriterExtensions { /// - /// Extensions for the . + /// Write a debug message. /// - public static class PipelineWriterExtensions + public static void WriteDebug(this IPipelineWriter writer, DebugRecord debugRecord) + { + if (debugRecord == null) + return; + + writer.WriteDebug(debugRecord.Message); + } + + internal static void DebugMessage(this IPipelineWriter logger, string message) + { + if (logger == null || !logger.ShouldWriteDebug()) + return; + + logger.WriteDebug(new DebugRecord(message)); + } + + internal static void WarnRulePathNotFound(this IPipelineWriter writer) + { + if (writer == null || !writer.ShouldWriteWarning()) + return; + + writer.WriteWarning(PSRuleResources.RulePathNotFound); + } + + internal static void WriteWarning(this IPipelineWriter writer, string message, params object[] args) + { + if (writer == null || !writer.ShouldWriteWarning() || string.IsNullOrEmpty(message)) + return; + + writer.WriteWarning(Format(message, args)); + } + + internal static void ErrorRequiredVersionMismatch(this IPipelineWriter writer, string moduleName, string moduleVersion, string requiredVersion) + { + if (writer == null || !writer.ShouldWriteError()) + return; + + writer.WriteError( + new PipelineBuilderException(string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.RequiredVersionMismatch, moduleName, moduleVersion, requiredVersion)), + "PSRule.RequiredVersionMismatch", + ErrorCategory.InvalidOperation + ); + } + + internal static void ErrorReadFileFailed(this IPipelineWriter writer, string path, Exception innerException) + { + if (writer == null || !writer.ShouldWriteError()) + return; + + writer.WriteError( + new PipelineSerializationException(string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.ReadFileFailed, path, innerException.Message), path, innerException), + "PSRule.ReadFileFailed", + ErrorCategory.InvalidData + ); + } + + internal static void ErrorReadInputFailed(this IPipelineWriter writer, string path, Exception innerException) + { + if (writer == null || !writer.ShouldWriteError()) + return; + + writer.WriteError( + new PipelineSerializationException(string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.ReadInputFailed, path, innerException.Message), path, innerException), + "PSRule.ReadInputFailed", + ErrorCategory.ReadError + ); + } + + internal static void VerboseInputAdded(this IPipelineWriter writer, string path) + { + if (writer == null || !writer.ShouldWriteVerbose() || string.IsNullOrEmpty(path)) + return; + + writer.WriteVerbose(string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.InputAdded, path)); + } + + internal static void WriteError(this IPipelineWriter writer, PipelineException exception, string errorId, ErrorCategory errorCategory) + { + if (writer == null) + return; + + writer.WriteError(new ErrorRecord(exception, errorId, errorCategory, null)); + } + + internal static void WriteDebug(this IPipelineWriter writer, string message, params object[] args) + { + if (writer == null || !writer.ShouldWriteDebug() || string.IsNullOrEmpty(message)) + return; + + writer.WriteDebug(new DebugRecord + ( + message: Format(message, args) + )); + } + + private static string Format(string message, params object[] args) { - /// - /// Write a debug message. - /// - public static void WriteDebug(this IPipelineWriter writer, DebugRecord debugRecord) - { - if (debugRecord == null) - return; - - writer.WriteDebug(debugRecord.Message); - } - - internal static void DebugMessage(this IPipelineWriter logger, string message) - { - if (logger == null || !logger.ShouldWriteDebug()) - return; - - logger.WriteDebug(new DebugRecord(message)); - } - - internal static void WarnRulePathNotFound(this IPipelineWriter writer) - { - if (writer == null || !writer.ShouldWriteWarning()) - return; - - writer.WriteWarning(PSRuleResources.RulePathNotFound); - } - - internal static void WriteWarning(this IPipelineWriter writer, string message, params object[] args) - { - if (writer == null || !writer.ShouldWriteWarning() || string.IsNullOrEmpty(message)) - return; - - writer.WriteWarning(Format(message, args)); - } - - internal static void ErrorRequiredVersionMismatch(this IPipelineWriter writer, string moduleName, string moduleVersion, string requiredVersion) - { - if (writer == null || !writer.ShouldWriteError()) - return; - - writer.WriteError( - new PipelineBuilderException(string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.RequiredVersionMismatch, moduleName, moduleVersion, requiredVersion)), - "PSRule.RequiredVersionMismatch", - ErrorCategory.InvalidOperation - ); - } - - internal static void ErrorReadFileFailed(this IPipelineWriter writer, string path, Exception innerException) - { - if (writer == null || !writer.ShouldWriteError()) - return; - - writer.WriteError( - new PipelineSerializationException(string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.ReadFileFailed, path, innerException.Message), path, innerException), - "PSRule.ReadFileFailed", - ErrorCategory.InvalidData - ); - } - - internal static void ErrorReadInputFailed(this IPipelineWriter writer, string path, Exception innerException) - { - if (writer == null || !writer.ShouldWriteError()) - return; - - writer.WriteError( - new PipelineSerializationException(string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.ReadInputFailed, path, innerException.Message), path, innerException), - "PSRule.ReadInputFailed", - ErrorCategory.ReadError - ); - } - - internal static void VerboseInputAdded(this IPipelineWriter writer, string path) - { - if (writer == null || !writer.ShouldWriteVerbose() || string.IsNullOrEmpty(path)) - return; - - writer.WriteVerbose(string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.InputAdded, path)); - } - - internal static void WriteError(this IPipelineWriter writer, PipelineException exception, string errorId, ErrorCategory errorCategory) - { - if (writer == null) - return; - - writer.WriteError(new ErrorRecord(exception, errorId, errorCategory, null)); - } - - internal static void WriteDebug(this IPipelineWriter writer, string message, params object[] args) - { - if (writer == null || !writer.ShouldWriteDebug() || string.IsNullOrEmpty(message)) - return; - - writer.WriteDebug(new DebugRecord - ( - message: Format(message, args) - )); - } - - private static string Format(string message, params object[] args) - { - return args == null || args.Length == 0 ? message : string.Format(Thread.CurrentThread.CurrentCulture, message, args); - } + return args == null || args.Length == 0 ? message : string.Format(Thread.CurrentThread.CurrentCulture, message, args); } } diff --git a/src/PSRule/Pipeline/RulePipeline.cs b/src/PSRule/Pipeline/RulePipeline.cs index e62a9e01a3..207ff27ed0 100644 --- a/src/PSRule/Pipeline/RulePipeline.cs +++ b/src/PSRule/Pipeline/RulePipeline.cs @@ -4,84 +4,83 @@ using System.Management.Automation; using PSRule.Runtime; -namespace PSRule.Pipeline +namespace PSRule.Pipeline; + +internal abstract class RulePipeline : IPipeline { - internal abstract class RulePipeline : IPipeline - { - protected readonly PipelineContext Pipeline; - protected readonly RunspaceContext Context; - protected readonly Source[] Source; - protected readonly PipelineReader Reader; - protected readonly IPipelineWriter Writer; + protected readonly PipelineContext Pipeline; + protected readonly RunspaceContext Context; + protected readonly Source[] Source; + protected readonly PipelineReader Reader; + protected readonly IPipelineWriter Writer; - // Track whether Dispose has been called. - private bool _Disposed; + // Track whether Dispose has been called. + private bool _Disposed; - protected RulePipeline(PipelineContext context, Source[] source, PipelineReader reader, IPipelineWriter writer) - { - Result = new DefaultPipelineResult(writer); - Pipeline = context; - Context = new RunspaceContext(Pipeline, writer); - Source = source; - Reader = reader; - Writer = writer; - - // Initialize contexts - Context.Init(source); - } + protected RulePipeline(PipelineContext context, Source[] source, PipelineReader reader, IPipelineWriter writer) + { + Result = new DefaultPipelineResult(writer); + Pipeline = context; + Context = new RunspaceContext(Pipeline, writer); + Source = source; + Reader = reader; + Writer = writer; + + // Initialize contexts + Context.Init(source); + } - #region IPipeline + #region IPipeline - /// - IPipelineResult IPipeline.Result => Result; + /// + IPipelineResult IPipeline.Result => Result; - /// - public DefaultPipelineResult Result { get; } + /// + public DefaultPipelineResult Result { get; } - /// - public virtual void Begin() - { - Writer.Begin(); - Context.Begin(); - Reader.Open(); - } + /// + public virtual void Begin() + { + Writer.Begin(); + Context.Begin(); + Reader.Open(); + } - /// - public virtual void Process(PSObject sourceObject) - { - // Do nothing - } + /// + public virtual void Process(PSObject sourceObject) + { + // Do nothing + } - /// - public virtual void End() - { - Writer.End(); - } + /// + public virtual void End() + { + Writer.End(); + } - #endregion IPipeline + #endregion IPipeline - #region IDisposable + #region IDisposable - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } - protected virtual void Dispose(bool disposing) + protected virtual void Dispose(bool disposing) + { + if (!_Disposed) { - if (!_Disposed) + if (disposing) { - if (disposing) - { - Writer.Dispose(); - Context.Dispose(); - Pipeline.Dispose(); - } - _Disposed = true; + Writer.Dispose(); + Context.Dispose(); + Pipeline.Dispose(); } + _Disposed = true; } - - #endregion IDisposable } + + #endregion IDisposable } diff --git a/src/PSRule/Pipeline/Source.cs b/src/PSRule/Pipeline/Source.cs index 7ce2bd34cf..b115b09670 100644 --- a/src/PSRule/Pipeline/Source.cs +++ b/src/PSRule/Pipeline/Source.cs @@ -8,192 +8,191 @@ using PSRule.Runtime; using YamlDotNet.Serialization; -namespace PSRule.Pipeline +namespace PSRule.Pipeline; + +/// +/// The type of source file. +/// +public enum SourceType { /// - /// The type of source file. + /// PowerShell script file. /// - public enum SourceType - { - /// - /// PowerShell script file. - /// - Script = 1, - - /// - /// YAML file. - /// - Yaml = 2, - - /// - /// JSON or JSON with comments file. - /// - Json = 3 - } + Script = 1, /// - /// A source file containing resources that will be loaded and interpreted by PSRule. + /// YAML file. /// - [DebuggerDisplay("{Type}: {Path}")] - public sealed class SourceFile - { - private bool? _Exists; - - internal Source Source; - - /// - /// Create an instance of a PSRule source. - /// - /// The file path to the source. - /// The name of the module if the source was loaded from a module. - /// The type of source file. - /// The base path to use for loading help content. - public SourceFile(string path, string module, SourceType type, string helpPath) - { - Path = path; - Module = module; - Type = type; - HelpPath = helpPath; - } + Yaml = 2, - /// - /// The file path to the source. - /// - [JsonProperty(PropertyName = "path")] - public string Path { get; } - - /// - /// The name of the module if the source was loaded from a module. - /// - [JsonProperty(PropertyName = "moduleName")] - public string Module { get; } - - /// - /// The type of source file. - /// - [YamlIgnore] - [JsonIgnore] - public SourceType Type { get; } - - /// - /// The base path to use for loading help content. - /// - [YamlIgnore] - [JsonIgnore] - public string HelpPath { get; } - - internal bool Exists() - { - if (!_Exists.HasValue) - _Exists = File.Exists(Path); + /// + /// JSON or JSON with comments file. + /// + Json = 3 +} - return _Exists.Value; - } +/// +/// A source file containing resources that will be loaded and interpreted by PSRule. +/// +[DebuggerDisplay("{Type}: {Path}")] +public sealed class SourceFile +{ + private bool? _Exists; - internal bool IsDependency() - { - return Source.Dependency; - } + internal Source Source; + + /// + /// Create an instance of a PSRule source. + /// + /// The file path to the source. + /// The name of the module if the source was loaded from a module. + /// The type of source file. + /// The base path to use for loading help content. + public SourceFile(string path, string module, SourceType type, string helpPath) + { + Path = path; + Module = module; + Type = type; + HelpPath = helpPath; } /// - /// A PSRule source containing one or more source files. + /// The file path to the source. + /// + [JsonProperty(PropertyName = "path")] + public string Path { get; } + + /// + /// The name of the module if the source was loaded from a module. + /// + [JsonProperty(PropertyName = "moduleName")] + public string Module { get; } + + /// + /// The type of source file. + /// + [YamlIgnore] + [JsonIgnore] + public SourceType Type { get; } + + /// + /// The base path to use for loading help content. /// - public sealed class Source + [YamlIgnore] + [JsonIgnore] + public string HelpPath { get; } + + internal bool Exists() + { + if (!_Exists.HasValue) + _Exists = File.Exists(Path); + + return _Exists.Value; + } + + internal bool IsDependency() { - internal bool Dependency; + return Source.Dependency; + } +} - internal readonly ModuleInfo Module; +/// +/// A PSRule source containing one or more source files. +/// +public sealed class Source +{ + internal bool Dependency; + + internal readonly ModuleInfo Module; + + internal Source(string path, SourceFile[] file) + { + Path = path; + Module = null; + File = file; + Dependency = false; + SetSource(); + } - internal Source(string path, SourceFile[] file) + internal Source(ModuleInfo module, SourceFile[] file, bool dependency) + { + Path = module.Path; + Module = module; + File = file; + Dependency = dependency; + SetSource(); + } + + internal sealed class ModuleInfo + { + private const string FIELD_PRERELEASE = "Prerelease"; + private const string FIELD_PSDATA = "PSData"; + private const string PRERELEASE_SEPARATOR = "-"; + + public readonly string Path; + public readonly string Name; + public readonly string Version; + public readonly string ProjectUri; + public readonly string Guid; + public readonly string CompanyName; + + public ModuleInfo(PSModuleInfo info) { - Path = path; - Module = null; - File = file; - Dependency = false; - SetSource(); + Path = info.ModuleBase; + Name = info.Name; + Version = info.Version?.ToString(); + ProjectUri = info.ProjectUri?.ToString(); + Guid = info.Guid.ToString(); + CompanyName = info.CompanyName; + if (TryPrivateData(info, FIELD_PSDATA, out var psData) && psData.ContainsKey(FIELD_PRERELEASE)) + Version = string.Concat(Version, PRERELEASE_SEPARATOR, psData[FIELD_PRERELEASE].ToString()); } - internal Source(ModuleInfo module, SourceFile[] file, bool dependency) + public ModuleInfo(string path, string name, string version, string projectUri, string guid, string companyName, string prerelease) { - Path = module.Path; - Module = module; - File = file; - Dependency = dependency; - SetSource(); + Path = path; + Name = name; + Version = version; + ProjectUri = projectUri; + Guid = guid; + CompanyName = companyName; + if (!string.IsNullOrEmpty(prerelease)) + Version = string.Concat(version, PRERELEASE_SEPARATOR, prerelease); } - internal sealed class ModuleInfo + private static bool TryPrivateData(PSModuleInfo info, string propertyName, out Hashtable value) { - private const string FIELD_PRERELEASE = "Prerelease"; - private const string FIELD_PSDATA = "PSData"; - private const string PRERELEASE_SEPARATOR = "-"; - - public readonly string Path; - public readonly string Name; - public readonly string Version; - public readonly string ProjectUri; - public readonly string Guid; - public readonly string CompanyName; - - public ModuleInfo(PSModuleInfo info) + value = null; + if (info.PrivateData is Hashtable privateData && privateData.ContainsKey(propertyName) && privateData[propertyName] is Hashtable data) { - Path = info.ModuleBase; - Name = info.Name; - Version = info.Version?.ToString(); - ProjectUri = info.ProjectUri?.ToString(); - Guid = info.Guid.ToString(); - CompanyName = info.CompanyName; - if (TryPrivateData(info, FIELD_PSDATA, out var psData) && psData.ContainsKey(FIELD_PRERELEASE)) - Version = string.Concat(Version, PRERELEASE_SEPARATOR, psData[FIELD_PRERELEASE].ToString()); - } - - public ModuleInfo(string path, string name, string version, string projectUri, string guid, string companyName, string prerelease) - { - Path = path; - Name = name; - Version = version; - ProjectUri = projectUri; - Guid = guid; - CompanyName = companyName; - if (!string.IsNullOrEmpty(prerelease)) - Version = string.Concat(version, PRERELEASE_SEPARATOR, prerelease); - } - - private static bool TryPrivateData(PSModuleInfo info, string propertyName, out Hashtable value) - { - value = null; - if (info.PrivateData is Hashtable privateData && privateData.ContainsKey(propertyName) && privateData[propertyName] is Hashtable data) - { - value = data; - return true; - } - return false; + value = data; + return true; } + return false; } + } - /// - /// The base path of the source. - /// - public string Path { get; } + /// + /// The base path of the source. + /// + public string Path { get; } - /// - /// An array of source files. - /// - public SourceFile[] File { get; } + /// + /// An array of source files. + /// + public SourceFile[] File { get; } - internal string Scope + internal string Scope + { + get { - get - { - return LanguageScope.Normalize(Module?.Name); - } + return LanguageScope.Normalize(Module?.Name); } + } - private void SetSource() - { - for (var i = 0; File != null && i < File.Length; i++) - File[i].Source = this; - } + private void SetSource() + { + for (var i = 0; File != null && i < File.Length; i++) + File[i].Source = this; } } diff --git a/src/PSRule/Pipeline/SourcePipeline.cs b/src/PSRule/Pipeline/SourcePipeline.cs index 1c93d51712..0154366757 100644 --- a/src/PSRule/Pipeline/SourcePipeline.cs +++ b/src/PSRule/Pipeline/SourcePipeline.cs @@ -8,531 +8,530 @@ using PSRule.Pipeline.Output; using PSRule.Resources; -namespace PSRule.Pipeline +namespace PSRule.Pipeline; + +/// +/// A helper to build a list of sources for a PowerShell pipeline. +/// +public interface ISourcePipelineBuilder { /// - /// A helper to build a list of sources for a PowerShell pipeline. + /// Determines if PowerShell should automatically load the module. /// - public interface ISourcePipelineBuilder - { - /// - /// Determines if PowerShell should automatically load the module. - /// - bool ShouldLoadModule { get; } - - /// - /// Log a verbose message for scanning sources. - /// - void VerboseScanSource(string path); - - /// - /// Log a verbose message for source modules. - /// - void VerboseFoundModules(int count); - - /// - /// Log a verbose message for scanning for modules. - /// - void VerboseScanModule(string moduleName); - - /// - /// Add loose files as a source. - /// - /// An array of file or directory paths containing one or more rule files. - /// Determine if the default rule path is excluded. When set to true the default rule path is excluded. - void Directory(string[] path, bool excludeDefaultRulePath = false); - - /// - /// Add loose files as a source. - /// - /// A file or directory path containing one or more rule files. - /// Determine if the default rule path is excluded. When set to true the default rule path is excluded. - void Directory(string path, bool excludeDefaultRulePath = false); - - /// - /// Add a module source. - /// - /// The module info. - void Module(PSModuleInfo[] module); - - /// - /// Build a list of sources for executing within PSRule. - /// - /// A list of sources. - Source[] Build(); - } + bool ShouldLoadModule { get; } /// - /// A helper to build a list of sources for a command-line tool pipeline. + /// Log a verbose message for scanning sources. /// - public interface ISourceCommandlineBuilder - { - /// - /// Add loose files as a source. - /// - /// An array of file or directory paths containing one or more rule files. - /// Determine if the default rule path is excluded. When set to true the default rule path is excluded. - void Directory(string[] path, bool excludeDefaultRulePath = false); - - /// - /// Add loose files as a source. - /// - /// A file or directory path containing one or more rule files. - /// Determine if the default rule path is excluded. When set to true the default rule path is excluded. - void Directory(string path, bool excludeDefaultRulePath = false); - - /// - /// Add a module source. - /// - /// The name of the module. - /// A specific version of the module. - void ModuleByName(string name, string version = null); - - /// - /// Build a list of sources for executing within PSRule. - /// - /// A list of sources. - Source[] Build(); - } + void VerboseScanSource(string path); /// - /// A helper to build a list of rule sources for discovery. + /// Log a verbose message for source modules. /// - public sealed class SourcePipelineBuilder : ISourcePipelineBuilder, ISourceCommandlineBuilder - { - private const string SOURCE_FILE_EXTENSION_YAML = ".yaml"; - private const string SOURCE_FILE_EXTENSION_YML = ".yml"; - private const string SOURCE_FILE_EXTENSION_JSON = ".json"; - private const string SOURCE_FILE_EXTENSION_JSONC = ".jsonc"; - private const string SOURCE_FILE_EXTENSION_PS1 = ".ps1"; - private const string SOURCE_FILE_PATTERN = "*.Rule.*"; - private const string RULE_MODULE_TAG = "PSRule-rules"; - private const string DEFAULT_RULE_PATH = ".ps-rule/"; - - private readonly Dictionary _Source; - private readonly IHostContext _HostContext; - private readonly HostPipelineWriter _Writer; - private readonly bool _UseDefaultPath; - private readonly string _LocalPath; - - internal SourcePipelineBuilder(IHostContext hostContext, PSRuleOption option, string localPath = null) - { - _Source = new Dictionary(StringComparer.OrdinalIgnoreCase); - _HostContext = hostContext; - _Writer = new HostPipelineWriter(hostContext, option, ShouldProcess); - _Writer.EnterScope("[Discovery.Source]"); - _UseDefaultPath = option == null || option.Include == null || option.Include.Path == null; - _LocalPath = localPath; - - // Include paths from options - if (!_UseDefaultPath) - Directory(option.Include.Path); - } + void VerboseFoundModules(int count); - /// - public bool ShouldLoadModule => _HostContext.GetAutoLoadingPreference() == PSModuleAutoLoadingPreference.All; + /// + /// Log a verbose message for scanning for modules. + /// + void VerboseScanModule(string moduleName); - #region Logging + /// + /// Add loose files as a source. + /// + /// An array of file or directory paths containing one or more rule files. + /// Determine if the default rule path is excluded. When set to true the default rule path is excluded. + void Directory(string[] path, bool excludeDefaultRulePath = false); - /// - public void VerboseScanSource(string path) - { - Log(PSRuleResources.ScanSource, path); - } + /// + /// Add loose files as a source. + /// + /// A file or directory path containing one or more rule files. + /// Determine if the default rule path is excluded. When set to true the default rule path is excluded. + void Directory(string path, bool excludeDefaultRulePath = false); - /// - public void VerboseFoundModules(int count) - { - Log(PSRuleResources.FoundModules, count); - } + /// + /// Add a module source. + /// + /// The module info. + void Module(PSModuleInfo[] module); - /// - public void VerboseScanModule(string moduleName) - { - Log(PSRuleResources.ScanModule, moduleName); - } + /// + /// Build a list of sources for executing within PSRule. + /// + /// A list of sources. + Source[] Build(); +} - private void Log(string message, params object[] args) - { - if (!_Writer.ShouldWriteVerbose()) - return; +/// +/// A helper to build a list of sources for a command-line tool pipeline. +/// +public interface ISourceCommandlineBuilder +{ + /// + /// Add loose files as a source. + /// + /// An array of file or directory paths containing one or more rule files. + /// Determine if the default rule path is excluded. When set to true the default rule path is excluded. + void Directory(string[] path, bool excludeDefaultRulePath = false); - _Writer.WriteVerbose(string.Format(Thread.CurrentThread.CurrentCulture, message, args)); - } + /// + /// Add loose files as a source. + /// + /// A file or directory path containing one or more rule files. + /// Determine if the default rule path is excluded. When set to true the default rule path is excluded. + void Directory(string path, bool excludeDefaultRulePath = false); - private void Debug(string message, params object[] args) - { - if (!_Writer.ShouldWriteDebug()) - return; + /// + /// Add a module source. + /// + /// The name of the module. + /// A specific version of the module. + void ModuleByName(string name, string version = null); - _Writer.WriteDebug(string.Format(Thread.CurrentThread.CurrentCulture, message, args)); - } + /// + /// Build a list of sources for executing within PSRule. + /// + /// A list of sources. + Source[] Build(); +} - #endregion Logging +/// +/// A helper to build a list of rule sources for discovery. +/// +public sealed class SourcePipelineBuilder : ISourcePipelineBuilder, ISourceCommandlineBuilder +{ + private const string SOURCE_FILE_EXTENSION_YAML = ".yaml"; + private const string SOURCE_FILE_EXTENSION_YML = ".yml"; + private const string SOURCE_FILE_EXTENSION_JSON = ".json"; + private const string SOURCE_FILE_EXTENSION_JSONC = ".jsonc"; + private const string SOURCE_FILE_EXTENSION_PS1 = ".ps1"; + private const string SOURCE_FILE_PATTERN = "*.Rule.*"; + private const string RULE_MODULE_TAG = "PSRule-rules"; + private const string DEFAULT_RULE_PATH = ".ps-rule/"; + + private readonly Dictionary _Source; + private readonly IHostContext _HostContext; + private readonly HostPipelineWriter _Writer; + private readonly bool _UseDefaultPath; + private readonly string _LocalPath; + + internal SourcePipelineBuilder(IHostContext hostContext, PSRuleOption option, string localPath = null) + { + _Source = new Dictionary(StringComparer.OrdinalIgnoreCase); + _HostContext = hostContext; + _Writer = new HostPipelineWriter(hostContext, option, ShouldProcess); + _Writer.EnterScope("[Discovery.Source]"); + _UseDefaultPath = option == null || option.Include == null || option.Include.Path == null; + _LocalPath = localPath; + + // Include paths from options + if (!_UseDefaultPath) + Directory(option.Include.Path); + } - /// - public void Directory(string[] path, bool excludeDefaultRulePath = false) - { - if (path == null || path.Length == 0) - return; + /// + public bool ShouldLoadModule => _HostContext.GetAutoLoadingPreference() == PSModuleAutoLoadingPreference.All; - for (var i = 0; i < path.Length; i++) - Directory(path[i], excludeDefaultRulePath); - } + #region Logging - /// - public void Directory(string path, bool excludeDefaultRulePath = false) - { - if (string.IsNullOrEmpty(path)) - return; + /// + public void VerboseScanSource(string path) + { + Log(PSRuleResources.ScanSource, path); + } - VerboseScanSource(path); - path = Environment.GetRootedPath(path); - var files = GetFiles(path, null, excludeDefaultRulePath); - if (files == null || files.Length == 0) - return; + /// + public void VerboseFoundModules(int count) + { + Log(PSRuleResources.FoundModules, count); + } - Source(new Source(path, files)); - } + /// + public void VerboseScanModule(string moduleName) + { + Log(PSRuleResources.ScanModule, moduleName); + } - /// - public void Module(PSModuleInfo[] module) - { - if (module == null || module.Length == 0) - return; + private void Log(string message, params object[] args) + { + if (!_Writer.ShouldWriteVerbose()) + return; - for (var i = 0; i < module.Length; i++) - Module(module[i], dependency: false); - } + _Writer.WriteVerbose(string.Format(Thread.CurrentThread.CurrentCulture, message, args)); + } - /// - public void ModuleByName(string name, string version = null) - { - var basePath = FindModule(name, version); - if (basePath == null) - throw new PipelineBuilderException(PSRuleResources.ModuleNotFound); + private void Debug(string message, params object[] args) + { + if (!_Writer.ShouldWriteDebug()) + return; - var info = LoadManifest(basePath); - if (info == null) - throw new PipelineBuilderException(PSRuleResources.ModuleNotFound); + _Writer.WriteDebug(string.Format(Thread.CurrentThread.CurrentCulture, message, args)); + } - VerboseScanModule(info.Name); - var files = GetFiles(basePath, basePath, excludeDefaultRulePath: false, info.Name); - if (files == null || files.Length == 0) - return; + #endregion Logging - Source(new Source(info, files, dependency: false)); + /// + public void Directory(string[] path, bool excludeDefaultRulePath = false) + { + if (path == null || path.Length == 0) + return; - // Import dependencies - //for (var i = 0; module.RequiredModules != null && i < module.RequiredModules.Count; i++) - // Module(module.RequiredModules[i], dependency: true); - } + for (var i = 0; i < path.Length; i++) + Directory(path[i], excludeDefaultRulePath); + } - private string FindModule(string name, string version) - { - return TryPackagedModule(name, out var path) || - TryInstalledModule(name, version, out path) ? path : null; - } + /// + public void Directory(string path, bool excludeDefaultRulePath = false) + { + if (string.IsNullOrEmpty(path)) + return; - /// - /// Try to find a packaged module found relative to the tool. - /// - private bool TryPackagedModule(string name, out string path) - { - path = null; - if (_LocalPath == null) - return false; + VerboseScanSource(path); + path = Environment.GetRootedPath(path); + var files = GetFiles(path, null, excludeDefaultRulePath); + if (files == null || files.Length == 0) + return; - Log($"Looking for modules in: {_LocalPath}"); - path = Environment.GetRootedBasePath(Path.Combine(_LocalPath, "Modules", name)); - return System.IO.Directory.Exists(path); - } + Source(new Source(path, files)); + } - /// - /// Try to find a module installed into PowerShell. - /// - private bool TryInstalledModule(string name, string version, out string path) + /// + public void Module(PSModuleInfo[] module) + { + if (module == null || module.Length == 0) + return; + + for (var i = 0; i < module.Length; i++) + Module(module[i], dependency: false); + } + + /// + public void ModuleByName(string name, string version = null) + { + var basePath = FindModule(name, version); + if (basePath == null) + throw new PipelineBuilderException(PSRuleResources.ModuleNotFound); + + var info = LoadManifest(basePath); + if (info == null) + throw new PipelineBuilderException(PSRuleResources.ModuleNotFound); + + VerboseScanModule(info.Name); + var files = GetFiles(basePath, basePath, excludeDefaultRulePath: false, info.Name); + if (files == null || files.Length == 0) + return; + + Source(new Source(info, files, dependency: false)); + + // Import dependencies + //for (var i = 0; module.RequiredModules != null && i < module.RequiredModules.Count; i++) + // Module(module.RequiredModules[i], dependency: true); + } + + private string FindModule(string name, string version) + { + return TryPackagedModule(name, out var path) || + TryInstalledModule(name, version, out path) ? path : null; + } + + /// + /// Try to find a packaged module found relative to the tool. + /// + private bool TryPackagedModule(string name, out string path) + { + path = null; + if (_LocalPath == null) + return false; + + Log($"Looking for modules in: {_LocalPath}"); + path = Environment.GetRootedBasePath(Path.Combine(_LocalPath, "Modules", name)); + return System.IO.Directory.Exists(path); + } + + /// + /// Try to find a module installed into PowerShell. + /// + private bool TryInstalledModule(string name, string version, out string path) + { + path = null; + if (!Environment.TryPathEnvironmentVariable("PSModulePath", out var searchPaths)) + return false; + + var unsorted = new List(); + Log("Looking for modules installed to PowerShell."); + for (var i = 0; i < searchPaths.Length; i++) { - path = null; - if (!Environment.TryPathEnvironmentVariable("PSModulePath", out var searchPaths)) - return false; + Debug($"Looking for modules search paths: {searchPaths[i]}"); + var searchPath = Environment.GetRootedBasePath(Path.Combine(searchPaths[i], name)); - var unsorted = new List(); - Log("Looking for modules installed to PowerShell."); - for (var i = 0; i < searchPaths.Length; i++) + // Try a specific version. + if (!string.IsNullOrEmpty(version)) { - Debug($"Looking for modules search paths: {searchPaths[i]}"); - var searchPath = Environment.GetRootedBasePath(Path.Combine(searchPaths[i], name)); + var versionPath = Path.Combine(searchPath, version); + var manifestPath = Path.Combine(versionPath, GetManifestName(name)); + if (File.Exists(manifestPath)) + { + Debug($"Found module manifest: {manifestPath}"); + unsorted.Add(versionPath); + } + continue; + } - // Try a specific version. - if (!string.IsNullOrEmpty(version)) + // Get other versions. + if (System.IO.Directory.Exists(searchPath)) + { + foreach (var versionPath in System.IO.Directory.EnumerateDirectories(searchPath)) { - var versionPath = Path.Combine(searchPath, version); var manifestPath = Path.Combine(versionPath, GetManifestName(name)); if (File.Exists(manifestPath)) { Debug($"Found module manifest: {manifestPath}"); unsorted.Add(versionPath); } - continue; - } - - // Get other versions. - if (System.IO.Directory.Exists(searchPath)) - { - foreach (var versionPath in System.IO.Directory.EnumerateDirectories(searchPath)) - { - var manifestPath = Path.Combine(versionPath, GetManifestName(name)); - if (File.Exists(manifestPath)) - { - Debug($"Found module manifest: {manifestPath}"); - unsorted.Add(versionPath); - } - } } } - if (unsorted.Count == 0) - return false; + } + if (unsorted.Count == 0) + return false; - var sorted = SortModulePath(unsorted); - if (sorted.Length > 0) - path = sorted[0]; + var sorted = SortModulePath(unsorted); + if (sorted.Length > 0) + path = sorted[0]; - return sorted.Length > 0; - } + return sorted.Length > 0; + } - private static string[] SortModulePath(IEnumerable values) - { - var results = values.ToArray(); - Array.Sort(results, new ModulePathComparer()); - return results; - } + private static string[] SortModulePath(IEnumerable values) + { + var results = values.ToArray(); + Array.Sort(results, new ModulePathComparer()); + return results; + } - private Source.ModuleInfo LoadManifest(string basePath) + private Source.ModuleInfo LoadManifest(string basePath) + { + var name = Path.GetFileName(Path.GetDirectoryName(basePath)); + var path = Path.Combine(basePath, GetManifestName(name)); + if (!File.Exists(path)) + return null; + + Log("Loading manifest for: {0}", basePath); + using var reader = new StreamReader(path); + var data = reader.ReadToEnd(); + var ast = System.Management.Automation.Language.Parser.ParseInput(data, out _, out _); + var hashtable = ast.FindAll(item => item is System.Management.Automation.Language.HashtableAst, false).FirstOrDefault(); + if (hashtable == null || hashtable.SafeGetValue() is not Hashtable manifest) + return null; + + var version = manifest["ModuleVersion"] as string; + var guid = manifest["GUID"] as string; + var companyName = manifest["CompanyName"] as string; + var privateData = manifest["PrivateData"] as Hashtable; + var psData = privateData["PSData"] as Hashtable; + var projectUri = psData["ProjectUri"] as string; + var prerelease = psData["Prerelease"] as string; + + if (TryRequiredAssemblies(manifest["RequiredAssemblies"], out var requiredAssemblies)) { - var name = Path.GetFileName(Path.GetDirectoryName(basePath)); - var path = Path.Combine(basePath, GetManifestName(name)); - if (!File.Exists(path)) - return null; - - Log("Loading manifest for: {0}", basePath); - using var reader = new StreamReader(path); - var data = reader.ReadToEnd(); - var ast = System.Management.Automation.Language.Parser.ParseInput(data, out _, out _); - var hashtable = ast.FindAll(item => item is System.Management.Automation.Language.HashtableAst, false).FirstOrDefault(); - if (hashtable == null || hashtable.SafeGetValue() is not Hashtable manifest) - return null; - - var version = manifest["ModuleVersion"] as string; - var guid = manifest["GUID"] as string; - var companyName = manifest["CompanyName"] as string; - var privateData = manifest["PrivateData"] as Hashtable; - var psData = privateData["PSData"] as Hashtable; - var projectUri = psData["ProjectUri"] as string; - var prerelease = psData["Prerelease"] as string; - - if (TryRequiredAssemblies(manifest["RequiredAssemblies"], out var requiredAssemblies)) + foreach (var a in requiredAssemblies) { - foreach (var a in requiredAssemblies) - { - var assemblyPath = Path.Combine(basePath, a); - Log("Loading assembly: {0}", assemblyPath); - Assembly.LoadFile(assemblyPath); - } + var assemblyPath = Path.Combine(basePath, a); + Log("Loading assembly: {0}", assemblyPath); + Assembly.LoadFile(assemblyPath); } - return new Source.ModuleInfo(basePath, name, version, projectUri, guid, companyName, prerelease); } + return new Source.ModuleInfo(basePath, name, version, projectUri, guid, companyName, prerelease); + } - private static bool TryRequiredAssemblies(object value, out IEnumerable requiredAssemblies) - { - requiredAssemblies = null; - if (value == null) return false; + private static bool TryRequiredAssemblies(object value, out IEnumerable requiredAssemblies) + { + requiredAssemblies = null; + if (value == null) return false; - if (value is string s) - requiredAssemblies = new string[] { s }; + if (value is string s) + requiredAssemblies = new string[] { s }; - if (value is Array array) - requiredAssemblies = array.OfType().ToArray(); + if (value is Array array) + requiredAssemblies = array.OfType().ToArray(); - return requiredAssemblies != null; - } + return requiredAssemblies != null; + } - private static string GetManifestName(string name) - { - return string.Concat(name, ".psd1"); - } + private static string GetManifestName(string name) + { + return string.Concat(name, ".psd1"); + } - /// - /// Add a module source - /// - /// The module info. - /// Flags the source as only a dependency. - private void Module(PSModuleInfo module, bool dependency) - { - if (module == null || !IsRuleModule(module)) - return; + /// + /// Add a module source + /// + /// The module info. + /// Flags the source as only a dependency. + private void Module(PSModuleInfo module, bool dependency) + { + if (module == null || !IsRuleModule(module)) + return; - VerboseScanModule(module.Name); - var files = GetFiles(module.ModuleBase, module.ModuleBase, excludeDefaultRulePath: false, module.Name); - if (files == null || files.Length == 0) - return; + VerboseScanModule(module.Name); + var files = GetFiles(module.ModuleBase, module.ModuleBase, excludeDefaultRulePath: false, module.Name); + if (files == null || files.Length == 0) + return; - Source(new Source(new Source.ModuleInfo(module), files, dependency)); + Source(new Source(new Source.ModuleInfo(module), files, dependency)); - // Import dependencies - for (var i = 0; module.RequiredModules != null && i < module.RequiredModules.Count; i++) - Module(module.RequiredModules[i], dependency: true); - } + // Import dependencies + for (var i = 0; module.RequiredModules != null && i < module.RequiredModules.Count; i++) + Module(module.RequiredModules[i], dependency: true); + } - private static bool IsRuleModule(PSModuleInfo module) - { - if (module.Tags == null) - return false; + private static bool IsRuleModule(PSModuleInfo module) + { + if (module.Tags == null) + return false; - foreach (var tag in module.Tags) - if (StringComparer.OrdinalIgnoreCase.Equals(RULE_MODULE_TAG, tag)) - return true; + foreach (var tag in module.Tags) + if (StringComparer.OrdinalIgnoreCase.Equals(RULE_MODULE_TAG, tag)) + return true; - return false; - } + return false; + } - /// - public Source[] Build() - { - Default(); - return _Source.Values.ToArray(); - } + /// + public Source[] Build() + { + Default(); + return _Source.Values.ToArray(); + } - private void Default() - { - if (_UseDefaultPath) - Directory(DEFAULT_RULE_PATH); - } + private void Default() + { + if (_UseDefaultPath) + Directory(DEFAULT_RULE_PATH); + } - private void Source(Source source) - { - if (source == null) - return; + private void Source(Source source) + { + if (source == null) + return; - // Prefer non-dependencies - var key = string.Concat(source.Module?.Name, ": ", source.Path); - if (_Source.ContainsKey(key) && source.Dependency) - return; + // Prefer non-dependencies + var key = string.Concat(source.Module?.Name, ": ", source.Path); + if (_Source.ContainsKey(key) && source.Dependency) + return; - _Source[key] = source; - } + _Source[key] = source; + } - private static SourceFile[] GetFiles(string path, string helpPath, bool excludeDefaultRulePath, string moduleName = null) + private static SourceFile[] GetFiles(string path, string helpPath, bool excludeDefaultRulePath, string moduleName = null) + { + var rootedPath = Environment.GetRootedPath(path); + var extension = Path.GetExtension(rootedPath); + if (IsSourceFile(extension)) { - var rootedPath = Environment.GetRootedPath(path); - var extension = Path.GetExtension(rootedPath); - if (IsSourceFile(extension)) - { - return IncludeFile(rootedPath, helpPath); - } - else if (System.IO.Directory.Exists(rootedPath)) - { - return IncludePath(rootedPath, helpPath, moduleName, excludeDefaultRulePath); - } - return null; + return IncludeFile(rootedPath, helpPath); } - - private static bool ShouldInclude(string path) + else if (System.IO.Directory.Exists(rootedPath)) { - return path.EndsWith(".rule.ps1", StringComparison.OrdinalIgnoreCase) || - path.EndsWith(".rule.yaml", StringComparison.OrdinalIgnoreCase) || - path.EndsWith(".rule.yml", StringComparison.OrdinalIgnoreCase) || - path.EndsWith(".rule.json", StringComparison.OrdinalIgnoreCase) || - path.EndsWith(".rule.jsonc", StringComparison.OrdinalIgnoreCase); + return IncludePath(rootedPath, helpPath, moduleName, excludeDefaultRulePath); } + return null; + } - private static SourceFile[] IncludeFile(string path, string helpPath) - { - if (!File.Exists(path)) - throw new FileNotFoundException(PSRuleResources.SourceNotFound, path); + private static bool ShouldInclude(string path) + { + return path.EndsWith(".rule.ps1", StringComparison.OrdinalIgnoreCase) || + path.EndsWith(".rule.yaml", StringComparison.OrdinalIgnoreCase) || + path.EndsWith(".rule.yml", StringComparison.OrdinalIgnoreCase) || + path.EndsWith(".rule.json", StringComparison.OrdinalIgnoreCase) || + path.EndsWith(".rule.jsonc", StringComparison.OrdinalIgnoreCase); + } - helpPath ??= Path.GetDirectoryName(path); - return new SourceFile[] { new SourceFile(path, null, GetSourceType(path), helpPath) }; - } + private static SourceFile[] IncludeFile(string path, string helpPath) + { + if (!File.Exists(path)) + throw new FileNotFoundException(PSRuleResources.SourceNotFound, path); - private static SourceFile[] IncludePath(string path, string helpPath, string moduleName, bool excludeDefaultRulePath) - { - if (!excludeDefaultRulePath) - { - var allFiles = System.IO.Directory.EnumerateFiles(path, SOURCE_FILE_PATTERN, SearchOption.AllDirectories); - return GetSourceFiles(allFiles, helpPath, moduleName); - } - var filteredFiles = FilterFiles(path, SOURCE_FILE_PATTERN, dir => !PathContainsDefaultRulePath(dir)); - return GetSourceFiles(filteredFiles, helpPath, moduleName); - } + helpPath ??= Path.GetDirectoryName(path); + return new SourceFile[] { new(path, null, GetSourceType(path), helpPath) }; + } - private static bool PathContainsDefaultRulePath(string path) + private static SourceFile[] IncludePath(string path, string helpPath, string moduleName, bool excludeDefaultRulePath) + { + if (!excludeDefaultRulePath) { - return path.Contains(DEFAULT_RULE_PATH.TrimEnd(Path.AltDirectorySeparatorChar), StringComparison.OrdinalIgnoreCase); + var allFiles = System.IO.Directory.EnumerateFiles(path, SOURCE_FILE_PATTERN, SearchOption.AllDirectories); + return GetSourceFiles(allFiles, helpPath, moduleName); } + var filteredFiles = FilterFiles(path, SOURCE_FILE_PATTERN, dir => !PathContainsDefaultRulePath(dir)); + return GetSourceFiles(filteredFiles, helpPath, moduleName); + } - private static SourceFile[] GetSourceFiles(IEnumerable files, string helpPath, string moduleName) + private static bool PathContainsDefaultRulePath(string path) + { + return path.Contains(DEFAULT_RULE_PATH.TrimEnd(Path.AltDirectorySeparatorChar), StringComparison.OrdinalIgnoreCase); + } + + private static SourceFile[] GetSourceFiles(IEnumerable files, string helpPath, string moduleName) + { + var result = new List(); + foreach (var file in files) { - var result = new List(); - foreach (var file in files) + if (ShouldInclude(file)) { - if (ShouldInclude(file)) - { - helpPath ??= Path.GetDirectoryName(file); - result.Add(new SourceFile(file, moduleName, GetSourceType(file), helpPath)); - } + helpPath ??= Path.GetDirectoryName(file); + result.Add(new SourceFile(file, moduleName, GetSourceType(file), helpPath)); } - return result.ToArray(); } + return result.ToArray(); + } - private static IEnumerable FilterFiles(string path, string filePattern, Func directoryFilter) - { - foreach (var file in System.IO.Directory.GetFiles(path, filePattern, SearchOption.TopDirectoryOnly)) - yield return file; + private static IEnumerable FilterFiles(string path, string filePattern, Func directoryFilter) + { + foreach (var file in System.IO.Directory.GetFiles(path, filePattern, SearchOption.TopDirectoryOnly)) + yield return file; - var filteredDirectories = System.IO.Directory - .GetDirectories(path, "*.*", SearchOption.TopDirectoryOnly) - .Where(directoryFilter); + var filteredDirectories = System.IO.Directory + .GetDirectories(path, "*.*", SearchOption.TopDirectoryOnly) + .Where(directoryFilter); - foreach (var directory in filteredDirectories) - { - foreach (var file in FilterFiles(directory, filePattern, directoryFilter)) - yield return file; - } - } - - private static SourceType GetSourceType(string path) + foreach (var directory in filteredDirectories) { - var extension = Path.GetExtension(path); - if (IsYamlFile(extension)) - { - return SourceType.Yaml; - } - else if (IsJsonFile(extension)) - { - return SourceType.Json; - } - return SourceType.Script; + foreach (var file in FilterFiles(directory, filePattern, directoryFilter)) + yield return file; } + } - private static bool IsSourceFile(string extension) + private static SourceType GetSourceType(string path) + { + var extension = Path.GetExtension(path); + if (IsYamlFile(extension)) { - return extension == SOURCE_FILE_EXTENSION_PS1 || IsYamlFile(extension) || IsJsonFile(extension); + return SourceType.Yaml; } - - private static bool IsYamlFile(string extension) + else if (IsJsonFile(extension)) { - return extension == SOURCE_FILE_EXTENSION_YAML || extension == SOURCE_FILE_EXTENSION_YML; + return SourceType.Json; } + return SourceType.Script; + } - private static bool IsJsonFile(string extension) - { - return extension == SOURCE_FILE_EXTENSION_JSON || extension == SOURCE_FILE_EXTENSION_JSONC; - } + private static bool IsSourceFile(string extension) + { + return extension == SOURCE_FILE_EXTENSION_PS1 || IsYamlFile(extension) || IsJsonFile(extension); + } - private bool ShouldProcess(string target, string action) - { - return _HostContext == null || _HostContext.ShouldProcess(target, action); - } + private static bool IsYamlFile(string extension) + { + return extension == SOURCE_FILE_EXTENSION_YAML || extension == SOURCE_FILE_EXTENSION_YML; + } + + private static bool IsJsonFile(string extension) + { + return extension == SOURCE_FILE_EXTENSION_JSON || extension == SOURCE_FILE_EXTENSION_JSONC; + } + + private bool ShouldProcess(string target, string action) + { + return _HostContext == null || _HostContext.ShouldProcess(target, action); } } diff --git a/src/PSRule/Pipeline/TargetBinder.cs b/src/PSRule/Pipeline/TargetBinder.cs index 18f5049a7e..d26ed9bf04 100644 --- a/src/PSRule/Pipeline/TargetBinder.cs +++ b/src/PSRule/Pipeline/TargetBinder.cs @@ -6,323 +6,322 @@ using PSRule.Runtime; using static PSRule.Pipeline.TargetBinder; -namespace PSRule.Pipeline +namespace PSRule.Pipeline; + +/// +/// Responsible for handling binding for a given target object. +/// +internal interface ITargetBinder { - /// - /// Responsible for handling binding for a given target object. - /// - internal interface ITargetBinder - { - void Bind(TargetObject targetObject); + void Bind(TargetObject targetObject); - ITargetBindingContext Using(string languageScope); + ITargetBindingContext Using(string languageScope); - ITargetBindingResult Result(string languageScope); - } + ITargetBindingResult Result(string languageScope); +} + +/// +/// A binding context specific to a language scope. +/// +internal interface ITargetBindingContext +{ + string LanguageScope { get; } + ITargetBindingResult Bind(object o); + + ITargetBindingResult Bind(TargetObject o); +} + +internal interface ITargetBindingResult +{ /// - /// A binding context specific to a language scope. + /// The bound TargetName of the target object. /// - internal interface ITargetBindingContext - { - string LanguageScope { get; } + string TargetName { get; } - ITargetBindingResult Bind(object o); + string TargetNamePath { get; } - ITargetBindingResult Bind(TargetObject o); - } + /// + /// The bound TargetType of the target object. + /// + string TargetType { get; } - internal interface ITargetBindingResult - { - /// - /// The bound TargetName of the target object. - /// - string TargetName { get; } + string TargetTypePath { get; } - string TargetNamePath { get; } + /// + /// Additional bound fields of the target object. + /// + Hashtable Field { get; } - /// - /// The bound TargetType of the target object. - /// - string TargetType { get; } + bool ShouldFilter { get; } +} - string TargetTypePath { get; } +/// +/// Builds a TargetBinder. +/// +internal sealed class TargetBinderBuilder +{ + private readonly List _BindingContext; + private readonly HashSet _TypeFilter; + private readonly BindTargetMethod _BindTargetName; + private readonly BindTargetMethod _BindTargetType; + private readonly BindTargetMethod _BindField; - /// - /// Additional bound fields of the target object. - /// - Hashtable Field { get; } + public TargetBinderBuilder(BindTargetMethod bindTargetName, BindTargetMethod bindTargetType, BindTargetMethod bindField, string[] typeFilter) + { + _BindTargetName = bindTargetName; + _BindTargetType = bindTargetType; + _BindField = bindField; + _BindingContext = new List(); + if (typeFilter != null && typeFilter.Length > 0) + _TypeFilter = new HashSet(typeFilter, StringComparer.OrdinalIgnoreCase); + } - bool ShouldFilter { get; } + /// + /// Build a TargetBinder. + /// + public ITargetBinder Build() + { + return new TargetBinder(_BindingContext.ToArray()); } /// - /// Builds a TargetBinder. + /// Add a target binding context. /// - internal sealed class TargetBinderBuilder + public void With(ITargetBindingContext bindingContext) { - private readonly List _BindingContext; - private readonly HashSet _TypeFilter; - private readonly BindTargetMethod _BindTargetName; - private readonly BindTargetMethod _BindTargetType; - private readonly BindTargetMethod _BindField; + _BindingContext.Add(bindingContext); + } - public TargetBinderBuilder(BindTargetMethod bindTargetName, BindTargetMethod bindTargetType, BindTargetMethod bindField, string[] typeFilter) - { - _BindTargetName = bindTargetName; - _BindTargetType = bindTargetType; - _BindField = bindField; - _BindingContext = new List(); - if (typeFilter != null && typeFilter.Length > 0) - _TypeFilter = new HashSet(typeFilter, StringComparer.OrdinalIgnoreCase); - } + /// + /// Add a target binding context from language scope. Current this only use for tests. + /// + internal void With(ILanguageScope languageScope) + { + With(new TargetBindingContext(languageScope.Name, languageScope.Binding, _BindTargetName, _BindTargetType, _BindField, _TypeFilter)); + } +} - /// - /// Build a TargetBinder. - /// - public ITargetBinder Build() - { - return new TargetBinder(_BindingContext.ToArray()); - } +/// +/// Responsible for handling binding for a given target object. +/// +internal sealed class TargetBinder : ITargetBinder +{ + private const string STANDALONE_SCOPE = "."; - /// - /// Add a target binding context. - /// - public void With(ITargetBindingContext bindingContext) - { - _BindingContext.Add(bindingContext); - } + private readonly Dictionary _BindingContext; + private readonly Dictionary _BindingResult; - /// - /// Add a target binding context from language scope. Current this only use for tests. - /// - internal void With(ILanguageScope languageScope) - { - With(new TargetBindingContext(languageScope.Name, languageScope.Binding, _BindTargetName, _BindTargetType, _BindField, _TypeFilter)); - } + internal TargetBinder(ITargetBindingContext[] bindingContext) + { + _BindingContext = new Dictionary(); + _BindingResult = new Dictionary(); + for (var i = 0; bindingContext != null && i < bindingContext.Length; i++) + _BindingContext.Add(bindingContext[i].LanguageScope ?? STANDALONE_SCOPE, bindingContext[i]); } - /// - /// Responsible for handling binding for a given target object. - /// - internal sealed class TargetBinder : ITargetBinder + private sealed class ImmutableHashtable : Hashtable { - private const string STANDALONE_SCOPE = "."; + private bool _ReadOnly; - private readonly Dictionary _BindingContext; - private readonly Dictionary _BindingResult; + internal ImmutableHashtable() + : base(StringComparer.OrdinalIgnoreCase) { } - internal TargetBinder(ITargetBindingContext[] bindingContext) - { - _BindingContext = new Dictionary(); - _BindingResult = new Dictionary(); - for (var i = 0; bindingContext != null && i < bindingContext.Length; i++) - _BindingContext.Add(bindingContext[i].LanguageScope ?? STANDALONE_SCOPE, bindingContext[i]); - } + public override bool IsReadOnly => _ReadOnly; - private sealed class ImmutableHashtable : Hashtable + public override void Add(object key, object value) { - private bool _ReadOnly; + if (_ReadOnly) + throw new InvalidOperationException(); - internal ImmutableHashtable() - : base(StringComparer.OrdinalIgnoreCase) { } - - public override bool IsReadOnly => _ReadOnly; + base.Add(key, value); + } - public override void Add(object key, object value) - { - if (_ReadOnly) - throw new InvalidOperationException(); + public override void Clear() + { + if (_ReadOnly) + throw new InvalidOperationException(); - base.Add(key, value); - } + base.Clear(); + } - public override void Clear() - { - if (_ReadOnly) - throw new InvalidOperationException(); + public override void Remove(object key) + { + if (_ReadOnly) + throw new InvalidOperationException(); - base.Clear(); - } + base.Remove(key); + } - public override void Remove(object key) + public override object this[object key] + { + get => base[key]; + set { if (_ReadOnly) throw new InvalidOperationException(); - base.Remove(key); - } - - public override object this[object key] - { - get => base[key]; - set - { - if (_ReadOnly) - throw new InvalidOperationException(); - - base[key] = value; - } - } - - internal void Protect() - { - _ReadOnly = true; + base[key] = value; } } - internal sealed class TargetBindingResult : ITargetBindingResult + internal void Protect() { - public TargetBindingResult(string targetName, string targetNamePath, string targetType, string targetTypePath, bool shouldFilter, Hashtable field) - { - TargetName = targetName; - TargetNamePath = targetNamePath; - TargetType = targetType; - TargetTypePath = targetTypePath; - ShouldFilter = shouldFilter; - Field = field; - } - - /// - public string TargetName { get; } - - /// - public string TargetNamePath { get; } + _ReadOnly = true; + } + } - /// - public string TargetType { get; } + internal sealed class TargetBindingResult : ITargetBindingResult + { + public TargetBindingResult(string targetName, string targetNamePath, string targetType, string targetTypePath, bool shouldFilter, Hashtable field) + { + TargetName = targetName; + TargetNamePath = targetNamePath; + TargetType = targetType; + TargetTypePath = targetTypePath; + ShouldFilter = shouldFilter; + Field = field; + } - /// - public string TargetTypePath { get; } + /// + public string TargetName { get; } - /// - public bool ShouldFilter { get; } + /// + public string TargetNamePath { get; } - /// - public Hashtable Field { get; } - } + /// + public string TargetType { get; } - internal sealed class TargetBindingContext : ITargetBindingContext - { - 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, BindingOption bindingOption, BindTargetMethod bindTargetName, BindTargetMethod bindTargetType, BindTargetMethod bindField, HashSet typeFilter) - { - LanguageScope = languageScope; - _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; - _TypeFilter = typeFilter; - } - - public string LanguageScope { get; } + /// + public string TargetTypePath { get; } - public ITargetBindingResult Bind(TargetObject o) - { - var targetNamePath = "."; - var targetName = _PreferTargetInfo && o.TargetName != null ? o.TargetName : _BindTargetName(_TargetName, !_IgnoreCase, _PreferTargetInfo, o.Value, out targetNamePath); - var 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, new[] { _Field }, !_IgnoreCase, o.Value); - return Bind(targetName, targetNamePath, targetType, targetTypePath, field); - } + /// + public bool ShouldFilter { get; } - public ITargetBindingResult Bind(object o) - { - 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)); + /// + public Hashtable Field { get; } + } - // Bind custom fields - var field = BindField(_BindField, new[] { _Field }, !_IgnoreCase, o); - return Bind(targetName, targetNamePath, targetType, targetTypePath, field); - } + internal sealed class TargetBindingContext : ITargetBindingContext + { + 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; - private ITargetBindingResult Bind(string targetName, string targetNamePath, string targetType, string targetTypePath, Hashtable field) - { - var shouldFilter = !(_TypeFilter == null || _TypeFilter.Contains(targetType)); - - // Use qualified name - if (_UseQualifiedName) - targetName = string.Concat(targetType, _NameSeparator, targetName); - - return new TargetBindingResult - ( - targetName: targetName, - targetNamePath: targetNamePath, - targetType: targetType, - targetTypePath: targetTypePath, - shouldFilter: shouldFilter, - field: field - ); - } + public TargetBindingContext(string languageScope, BindingOption bindingOption, BindTargetMethod bindTargetName, BindTargetMethod bindTargetType, BindTargetMethod bindField, HashSet typeFilter) + { + LanguageScope = languageScope; + _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; + _TypeFilter = typeFilter; } - public bool ShouldFilter { get; private set; } + public string LanguageScope { get; } - /// - /// Bind target object based on the supplied baseline. - /// - public void Bind(TargetObject targetObject) + public ITargetBindingResult Bind(TargetObject o) { - foreach (var bindingContext in _BindingContext.Values) - _BindingResult[bindingContext.LanguageScope] = bindingContext.Bind(targetObject); + var targetNamePath = "."; + var targetName = _PreferTargetInfo && o.TargetName != null ? o.TargetName : _BindTargetName(_TargetName, !_IgnoreCase, _PreferTargetInfo, o.Value, out targetNamePath); + var 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, new[] { _Field }, !_IgnoreCase, o.Value); + return Bind(targetName, targetNamePath, targetType, targetTypePath, field); } - public ITargetBindingContext Using(string languageScope) + public ITargetBindingResult Bind(object o) { - return _BindingContext.TryGetValue(languageScope ?? STANDALONE_SCOPE, out var result) ? result : null; + 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, new[] { _Field }, !_IgnoreCase, o); + return Bind(targetName, targetNamePath, targetType, targetTypePath, field); } - public ITargetBindingResult Result(string languageScope) + private ITargetBindingResult Bind(string targetName, string targetNamePath, string targetType, string targetTypePath, Hashtable field) { - return _BindingResult.TryGetValue(languageScope ?? STANDALONE_SCOPE, out var result) ? result : null; + var shouldFilter = !(_TypeFilter == null || _TypeFilter.Contains(targetType)); + + // Use qualified name + if (_UseQualifiedName) + targetName = string.Concat(targetType, _NameSeparator, targetName); + + return new TargetBindingResult + ( + targetName: targetName, + targetNamePath: targetNamePath, + targetType: targetType, + targetTypePath: targetTypePath, + shouldFilter: shouldFilter, + field: field + ); } + } + + public bool ShouldFilter { get; private set; } + + /// + /// Bind target object based on the supplied baseline. + /// + public void Bind(TargetObject targetObject) + { + foreach (var bindingContext in _BindingContext.Values) + _BindingResult[bindingContext.LanguageScope] = bindingContext.Bind(targetObject); + } - /// - /// Bind additional fields. - /// - private static Hashtable BindField(BindTargetMethod bindField, FieldMap[] map, bool caseSensitive, object o) + public ITargetBindingContext Using(string languageScope) + { + return _BindingContext.TryGetValue(languageScope ?? STANDALONE_SCOPE, out var result) ? result : null; + } + + public ITargetBindingResult Result(string languageScope) + { + return _BindingResult.TryGetValue(languageScope ?? STANDALONE_SCOPE, out var result) ? result : null; + } + + /// + /// Bind additional fields. + /// + private static Hashtable BindField(BindTargetMethod bindField, FieldMap[] map, bool caseSensitive, object o) + { + if (map == null || map.Length == 0) + return null; + + var hashtable = new ImmutableHashtable(); + for (var i = 0; i < map.Length; i++) { - if (map == null || map.Length == 0) - return null; + if (map[i] == null || map[i].Count == 0) + continue; - var hashtable = new ImmutableHashtable(); - for (var i = 0; i < map.Length; i++) + foreach (var field in map[i]) { - if (map[i] == null || map[i].Count == 0) + if (hashtable.ContainsKey(field.Key)) continue; - foreach (var field in map[i]) - { - if (hashtable.ContainsKey(field.Key)) - continue; - - hashtable.Add(field.Key, bindField(field.Value, caseSensitive, false, o, out _)); - } + hashtable.Add(field.Key, bindField(field.Value, caseSensitive, false, o, out _)); } - hashtable.Protect(); - return hashtable; } + hashtable.Protect(); + return hashtable; } } diff --git a/src/PSRule/Pipeline/TargetObject.cs b/src/PSRule/Pipeline/TargetObject.cs index 03cf396e19..1fcb0af980 100644 --- a/src/PSRule/Pipeline/TargetObject.cs +++ b/src/PSRule/Pipeline/TargetObject.cs @@ -8,132 +8,131 @@ using PSRule.Data; using PSRule.Definitions.Selectors; -namespace PSRule.Pipeline -{ - internal abstract class TargetObjectAnnotation - { +namespace PSRule.Pipeline; - } +internal abstract class TargetObjectAnnotation +{ - internal sealed class SelectorTargetAnnotation : TargetObjectAnnotation - { - private readonly Dictionary _Results; +} - public SelectorTargetAnnotation() - { - _Results = new Dictionary(); - } +internal sealed class SelectorTargetAnnotation : TargetObjectAnnotation +{ + private readonly Dictionary _Results; - public bool TryGetSelectorResult(SelectorVisitor selector, out bool result) - { - return _Results.TryGetValue(selector.InstanceId, out result); - } + public SelectorTargetAnnotation() + { + _Results = new Dictionary(); + } - public void SetSelectorResult(SelectorVisitor selector, bool result) - { - _Results[selector.InstanceId] = result; - } + public bool TryGetSelectorResult(SelectorVisitor selector, out bool result) + { + return _Results.TryGetValue(selector.InstanceId, out result); } - /// - /// An object processed by PSRule. - /// - public sealed class TargetObject : ITargetObject + public void SetSelectorResult(SelectorVisitor selector, bool result) { - private readonly Dictionary _Annotations; + _Results[selector.InstanceId] = result; + } +} - private Hashtable _Data; +/// +/// An object processed by PSRule. +/// +public sealed class TargetObject : ITargetObject +{ + private readonly Dictionary _Annotations; - internal TargetObject(PSObject o) - : this(o, null) { } + private Hashtable _Data; - internal TargetObject(PSObject o, TargetSourceCollection source) - { - o.ConvertTargetInfoProperty(); - o.ConvertTargetInfoType(); - Source = ReadSourceInfo(o, source); - Issue = ReadIssueInfo(o, null); - TargetName = o.GetTargetName(); - TargetType = o.GetTargetType(); - Scope = o.GetScope(); - Path = ReadPath(o); - Value = Convert(o); - _Annotations = new Dictionary(); - } + internal TargetObject(PSObject o) + : this(o, null) { } - internal TargetObject(PSObject o, string targetName = null, string targetType = null, string[] scope = null) - : this(o, null) - { - if (!string.IsNullOrEmpty(targetName)) - TargetName = targetName; + internal TargetObject(PSObject o, TargetSourceCollection source) + { + o.ConvertTargetInfoProperty(); + o.ConvertTargetInfoType(); + Source = ReadSourceInfo(o, source); + Issue = ReadIssueInfo(o, null); + TargetName = o.GetTargetName(); + TargetType = o.GetTargetType(); + Scope = o.GetScope(); + Path = ReadPath(o); + Value = Convert(o); + _Annotations = new Dictionary(); + } - if (!string.IsNullOrEmpty(targetType)) - TargetType = targetType; + internal TargetObject(PSObject o, string targetName = null, string targetType = null, string[] scope = null) + : this(o, null) + { + if (!string.IsNullOrEmpty(targetName)) + TargetName = targetName; - if (scope != null && scope.Length > 0) - Scope = scope; - } + if (!string.IsNullOrEmpty(targetType)) + TargetType = targetType; - internal PSObject Value { get; } + if (scope != null && scope.Length > 0) + Scope = scope; + } - internal TargetSourceCollection Source { get; private set; } + internal PSObject Value { get; } - internal TargetIssueCollection Issue { get; private set; } + internal TargetSourceCollection Source { get; private set; } - internal string TargetName { [DebuggerStepThrough] get; } + internal TargetIssueCollection Issue { get; private set; } - internal string TargetType { [DebuggerStepThrough] get; } + internal string TargetName { [DebuggerStepThrough] get; } - internal string[] Scope { [DebuggerStepThrough] get; } + internal string TargetType { [DebuggerStepThrough] get; } - internal string Path { [DebuggerStepThrough] get; } + internal string[] Scope { [DebuggerStepThrough] get; } - internal Hashtable GetData() - { - return _Data == null || _Data.Count == 0 ? null : _Data; - } + internal string Path { [DebuggerStepThrough] get; } - internal Hashtable RequireData() - { - _Data ??= new Hashtable(); - return _Data; - } + internal Hashtable GetData() + { + return _Data == null || _Data.Count == 0 ? null : _Data; + } - internal T GetAnnotation() where T : TargetObjectAnnotation, new() - { - if (!_Annotations.TryGetValue(typeof(T), out var value)) - { - value = new T(); - _Annotations.Add(typeof(T), value); - } - return (T)value; - } + internal Hashtable RequireData() + { + _Data ??= new Hashtable(); + return _Data; + } - private static string ReadPath(PSObject o) + internal T GetAnnotation() where T : TargetObjectAnnotation, new() + { + if (!_Annotations.TryGetValue(typeof(T), out var value)) { - return o.GetTargetPath(); + value = new T(); + _Annotations.Add(typeof(T), value); } + return (T)value; + } - private static TargetSourceCollection ReadSourceInfo(PSObject o, TargetSourceCollection source) - { - var result = source ?? new TargetSourceCollection(); - if (ExpressionHelpers.GetBaseObject(o) is ITargetInfo targetInfo) - result.Add(targetInfo.Source); + private static string ReadPath(PSObject o) + { + return o.GetTargetPath(); + } - result.AddRange(o.GetSourceInfo()); - return result; - } + private static TargetSourceCollection ReadSourceInfo(PSObject o, TargetSourceCollection source) + { + var result = source ?? new TargetSourceCollection(); + if (ExpressionHelpers.GetBaseObject(o) is ITargetInfo targetInfo) + result.Add(targetInfo.Source); - private static TargetIssueCollection ReadIssueInfo(PSObject o, TargetIssueCollection issue) - { - var result = issue ?? new TargetIssueCollection(); - result.AddRange(o.GetIssueInfo()); - return result; - } + result.AddRange(o.GetSourceInfo()); + return result; + } - private static PSObject Convert(PSObject o) - { - return o.BaseObject is JToken token ? JsonHelper.ToPSObject(token) : o; - } + private static TargetIssueCollection ReadIssueInfo(PSObject o, TargetIssueCollection issue) + { + var result = issue ?? new TargetIssueCollection(); + result.AddRange(o.GetIssueInfo()); + return result; + } + + private static PSObject Convert(PSObject o) + { + return o.BaseObject is JToken token ? JsonHelper.ToPSObject(token) : o; } } diff --git a/src/PSRule/Pipeline/TestPipeline.cs b/src/PSRule/Pipeline/TestPipeline.cs index 41b9d7e06f..1defda0dba 100644 --- a/src/PSRule/Pipeline/TestPipeline.cs +++ b/src/PSRule/Pipeline/TestPipeline.cs @@ -3,47 +3,46 @@ using PSRule.Rules; -namespace PSRule.Pipeline +namespace PSRule.Pipeline; + +internal sealed class TestPipelineBuilder : InvokePipelineBuilderBase { - internal sealed class TestPipelineBuilder : InvokePipelineBuilderBase + internal TestPipelineBuilder(Source[] source, IHostContext hostContext) + : base(source, hostContext) { } + + private sealed class BooleanWriter : PipelineWriter { - internal TestPipelineBuilder(Source[] source, IHostContext hostContext) - : base(source, hostContext) { } + private readonly RuleOutcome _Outcome; - private sealed class BooleanWriter : PipelineWriter + internal BooleanWriter(PipelineWriter output, RuleOutcome outcome, ShouldProcess shouldProcess) + : base(output, null, shouldProcess) { - private readonly RuleOutcome _Outcome; - - internal BooleanWriter(PipelineWriter output, RuleOutcome outcome, ShouldProcess shouldProcess) - : base(output, null, shouldProcess) - { - _Outcome = outcome; - } - - public override void WriteObject(object sendToPipeline, bool enumerateCollection) - { - if (sendToPipeline is not InvokeResult result || !ShouldOutput(result.Outcome)) - return; - - base.WriteObject(result.IsSuccess(), false); - } - - private bool ShouldOutput(RuleOutcome outcome) - { - return _Outcome == RuleOutcome.All || - (_Outcome == RuleOutcome.None && outcome == RuleOutcome.None) || - (_Outcome & outcome) > 0; - } + _Outcome = outcome; } - protected override PipelineWriter PrepareWriter() + public override void WriteObject(object sendToPipeline, bool enumerateCollection) { - return new BooleanWriter(GetOutput(), Option.Output.Outcome.Value, ShouldProcess); + if (sendToPipeline is not InvokeResult result || !ShouldOutput(result.Outcome)) + return; + + base.WriteObject(result.IsSuccess(), false); } - private static new bool ShouldProcess(string target, string action) + private bool ShouldOutput(RuleOutcome outcome) { - return true; + return _Outcome == RuleOutcome.All || + (_Outcome == RuleOutcome.None && outcome == RuleOutcome.None) || + (_Outcome & outcome) > 0; } } + + protected override PipelineWriter PrepareWriter() + { + return new BooleanWriter(GetOutput(), Option.Output.Outcome.Value, ShouldProcess); + } + + private static new bool ShouldProcess(string target, string action) + { + return true; + } } diff --git a/src/PSRule/Rules/ErrorInfo.cs b/src/PSRule/Rules/ErrorInfo.cs index 8dffd4e404..44c456ac5a 100644 --- a/src/PSRule/Rules/ErrorInfo.cs +++ b/src/PSRule/Rules/ErrorInfo.cs @@ -6,72 +6,71 @@ using Newtonsoft.Json; using YamlDotNet.Serialization; -namespace PSRule.Rules +namespace PSRule.Rules; + +/// +/// Information about an error that occurred within PSRule. +/// +public sealed class ErrorInfo { - /// - /// Information about an error that occurred within PSRule. - /// - public sealed class ErrorInfo + internal ErrorInfo(string message, string scriptStackTrace, string errorId, Exception exception, ErrorCategory category, string positionMessage, IScriptExtent scriptExtent) { - internal ErrorInfo(string message, string scriptStackTrace, string errorId, Exception exception, ErrorCategory category, string positionMessage, IScriptExtent scriptExtent) - { - Message = message; - ScriptStackTrace = scriptStackTrace; - ErrorId = errorId; - Exception = exception; - Category = category; - ExceptionType = Exception?.GetType()?.FullName; - PositionMessage = positionMessage; - ScriptExtent = scriptExtent; - } + Message = message; + ScriptStackTrace = scriptStackTrace; + ErrorId = errorId; + Exception = exception; + Category = category; + ExceptionType = Exception?.GetType()?.FullName; + PositionMessage = positionMessage; + ScriptExtent = scriptExtent; + } - /// - /// An error message describing the issue. - /// - [JsonProperty(PropertyName = "message")] - public string Message { get; } + /// + /// An error message describing the issue. + /// + [JsonProperty(PropertyName = "message")] + public string Message { get; } - /// - /// A PSRule script stack trace. - /// - [JsonProperty(PropertyName = "scriptStackTrace")] - public string ScriptStackTrace { get; } + /// + /// A PSRule script stack trace. + /// + [JsonProperty(PropertyName = "scriptStackTrace")] + public string ScriptStackTrace { get; } - /// - /// A fully qualified identifier of the error. - /// - [JsonProperty(PropertyName = "errorId")] - public string ErrorId { get; } + /// + /// A fully qualified identifier of the error. + /// + [JsonProperty(PropertyName = "errorId")] + public string ErrorId { get; } - /// - /// The related error exception. - /// - [JsonIgnore] - [YamlIgnore] - public Exception Exception { get; } + /// + /// The related error exception. + /// + [JsonIgnore] + [YamlIgnore] + public Exception Exception { get; } - /// - /// The exception type. - /// - [JsonProperty(PropertyName = "exceptionType")] - public string ExceptionType { get; } + /// + /// The exception type. + /// + [JsonProperty(PropertyName = "exceptionType")] + public string ExceptionType { get; } - /// - /// The error category. - /// - [JsonProperty(PropertyName = "category")] - public ErrorCategory Category { get; } + /// + /// The error category. + /// + [JsonProperty(PropertyName = "category")] + public ErrorCategory Category { get; } - /// - /// A positional message for the error. - /// - [JsonIgnore] - public string PositionMessage { get; } + /// + /// A positional message for the error. + /// + [JsonIgnore] + public string PositionMessage { get; } - /// - /// The extent for the error. - /// - [JsonIgnore] - public IScriptExtent ScriptExtent { get; } - } + /// + /// The extent for the error. + /// + [JsonIgnore] + public IScriptExtent ScriptExtent { get; } } diff --git a/src/PSRule/Rules/PowerShellCondition.cs b/src/PSRule/Rules/PowerShellCondition.cs index 224f98742b..2752e2ccdb 100644 --- a/src/PSRule/Rules/PowerShellCondition.cs +++ b/src/PSRule/Rules/PowerShellCondition.cs @@ -6,60 +6,59 @@ using PSRule.Definitions; using PSRule.Pipeline; -namespace PSRule.Rules +namespace PSRule.Rules; + +internal sealed class PowerShellCondition : ICondition { - internal sealed class PowerShellCondition : ICondition - { - private const string ERROR_ACTION_PREFERENCE = "ErrorActionPreference"; + private const string ERROR_ACTION_PREFERENCE = "ErrorActionPreference"; - private readonly PowerShell _Condition; + private readonly PowerShell _Condition; - private bool _Disposed; + private bool _Disposed; - internal PowerShellCondition(ResourceId id, SourceFile source, PowerShell condition, ActionPreference errorAction) - { - _Condition = condition; - Id = id; - Source = source; - ErrorAction = errorAction; - } + internal PowerShellCondition(ResourceId id, SourceFile source, PowerShell condition, ActionPreference errorAction) + { + _Condition = condition; + Id = id; + Source = source; + ErrorAction = errorAction; + } - public ResourceId Id { get; } + public ResourceId Id { get; } - public SourceFile Source { get; } + public SourceFile Source { get; } - public ActionPreference ErrorAction { get; } + public ActionPreference ErrorAction { get; } - private void Dispose(bool disposing) + private void Dispose(bool disposing) + { + if (!_Disposed) { - if (!_Disposed) + if (disposing) { - if (disposing) - { - _Condition.Runspace = null; - _Condition.Dispose(); - } - _Disposed = true; + _Condition.Runspace = null; + _Condition.Dispose(); } + _Disposed = true; } + } - public void Dispose() - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(disposing: true); - System.GC.SuppressFinalize(this); - } + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + System.GC.SuppressFinalize(this); + } - public IConditionResult If() - { - _Condition.Streams.ClearStreams(); - _Condition.Runspace.SessionStateProxy.SetVariable(ERROR_ACTION_PREFERENCE, ErrorAction); - return GetResult(_Condition.Invoke()); - } + public IConditionResult If() + { + _Condition.Streams.ClearStreams(); + _Condition.Runspace.SessionStateProxy.SetVariable(ERROR_ACTION_PREFERENCE, ErrorAction); + return GetResult(_Condition.Invoke()); + } - private static Runtime.RuleConditionResult GetResult(Collection value) - { - return value == null || value.Count == 0 ? null : value[0]; - } + private static Runtime.RuleConditionResult GetResult(Collection value) + { + return value == null || value.Count == 0 ? null : value[0]; } } diff --git a/src/PSRule/Rules/Rule.cs b/src/PSRule/Rules/Rule.cs index eba447b535..6007b33f12 100644 --- a/src/PSRule/Rules/Rule.cs +++ b/src/PSRule/Rules/Rule.cs @@ -9,99 +9,98 @@ using PSRule.Pipeline; using YamlDotNet.Serialization; -namespace PSRule.Rules +namespace PSRule.Rules; + +/// +/// Define a single rule. +/// +[JsonObject] +public sealed class Rule : IDependencyTarget, ITargetInfo, IResource, IRuleV1 { /// - /// Define a single rule. + /// A unique identifier for the rule. /// - [JsonObject] - public sealed class Rule : IDependencyTarget, ITargetInfo, IResource, IRuleV1 - { - /// - /// A unique identifier for the rule. - /// - [JsonProperty(PropertyName = "id", Required = Required.Always)] - public ResourceId Id { get; set; } + [JsonProperty(PropertyName = "id", Required = Required.Always)] + public ResourceId Id { get; set; } - /// - /// The name of the rule. - /// - [JsonProperty(PropertyName = "name", Required = Required.Always)] - public string Name => Id.Name; + /// + /// The name of the rule. + /// + [JsonProperty(PropertyName = "name", Required = Required.Always)] + public string Name => Id.Name; - /// - /// If the rule fails, how serious is the result. - /// - [JsonProperty(PropertyName = "level")] - public SeverityLevel Level { get; set; } + /// + /// If the rule fails, how serious is the result. + /// + [JsonProperty(PropertyName = "level")] + public SeverityLevel Level { get; set; } - /// - /// A human readable block of text, used to identify the purpose of the rule. - /// - [JsonIgnore, YamlIgnore] - public string Synopsis => Info.Synopsis; + /// + /// A human readable block of text, used to identify the purpose of the rule. + /// + [JsonIgnore, YamlIgnore] + public string Synopsis => Info.Synopsis; - /// - /// One or more tags assigned to the rule. Tags are additional metadata used to select rules to execute and identify results. - /// - [JsonProperty(PropertyName = "tag")] - [DefaultValue(null)] - public ResourceTags Tag { get; set; } + /// + /// One or more tags assigned to the rule. Tags are additional metadata used to select rules to execute and identify results. + /// + [JsonProperty(PropertyName = "tag")] + [DefaultValue(null)] + public ResourceTags Tag { get; set; } - /// - [JsonProperty(PropertyName = "info")] - [DefaultValue(null)] - public RuleHelpInfo Info { get; set; } + /// + [JsonProperty(PropertyName = "info")] + [DefaultValue(null)] + public RuleHelpInfo Info { get; set; } - /// - [JsonProperty(PropertyName = "source")] - [DefaultValue(null)] - public SourceFile Source { get; set; } + /// + [JsonProperty(PropertyName = "source")] + [DefaultValue(null)] + public SourceFile Source { get; set; } - /// - /// Other rules that must completed successfully before calling this rule. - /// - [JsonProperty(PropertyName = "dependsOn")] - public ResourceId[] DependsOn { get; set; } + /// + /// Other rules that must completed successfully before calling this rule. + /// + [JsonProperty(PropertyName = "dependsOn")] + public ResourceId[] DependsOn { get; set; } - /// - [JsonIgnore, YamlIgnore] - public ResourceFlags Flags { get; set; } + /// + [JsonIgnore, YamlIgnore] + public ResourceFlags Flags { get; set; } - /// - [JsonIgnore, YamlIgnore] - public ISourceExtent Extent { get; set; } + /// + [JsonIgnore, YamlIgnore] + public ISourceExtent Extent { get; set; } - /// - [JsonIgnore, YamlIgnore] - public ResourceLabels Labels { get; set; } + /// + [JsonIgnore, YamlIgnore] + public ResourceLabels Labels { get; set; } - string ITargetInfo.TargetName => Name; + string ITargetInfo.TargetName => Name; - string ITargetInfo.TargetType => typeof(Rule).FullName; + string ITargetInfo.TargetType => typeof(Rule).FullName; - TargetSourceInfo ITargetInfo.Source => new() { File = Source.Path }; + TargetSourceInfo ITargetInfo.Source => new() { File = Source.Path }; - bool IDependencyTarget.Dependency => Source.IsDependency(); + bool IDependencyTarget.Dependency => Source.IsDependency(); - ResourceKind IResource.Kind => ResourceKind.Rule; + ResourceKind IResource.Kind => ResourceKind.Rule; - string IResource.ApiVersion => Specs.V1; + string IResource.ApiVersion => Specs.V1; - string IResource.Name => Name; + string IResource.Name => Name; - IResourceHelpInfo IResource.Info => Info; + IResourceHelpInfo IResource.Info => Info; - ResourceTags IResource.Tags => Tag; + ResourceTags IResource.Tags => Tag; - InfoString IRuleV1.Recommendation => ((IRuleHelpInfoV2)Info)?.Recommendation; + InfoString IRuleV1.Recommendation => ((IRuleHelpInfoV2)Info)?.Recommendation; - /// - [JsonIgnore, YamlIgnore] - public ResourceId? Ref { get; set; } + /// + [JsonIgnore, YamlIgnore] + public ResourceId? Ref { get; set; } - /// - [JsonIgnore, YamlIgnore] - public ResourceId[] Alias { get; set; } - } + /// + [JsonIgnore, YamlIgnore] + public ResourceId[] Alias { get; set; } } diff --git a/src/PSRule/Rules/RuleBlock.cs b/src/PSRule/Rules/RuleBlock.cs index 022a7d784a..45e4bb49ef 100644 --- a/src/PSRule/Rules/RuleBlock.cs +++ b/src/PSRule/Rules/RuleBlock.cs @@ -10,128 +10,127 @@ using PSRule.Runtime; using YamlDotNet.Serialization; -namespace PSRule.Rules -{ - internal delegate bool RulePrecondition(); +namespace PSRule.Rules; - internal delegate RuleConditionResult RuleCondition(); +internal delegate bool RulePrecondition(); - /// - /// Define an instance of a rule block. Each rule block has a unique id. - /// - [DebuggerDisplay("{Id} @{Source.Path}")] - internal sealed class RuleBlock : ILanguageBlock, IDependencyTarget, IDisposable, IResource, IRuleV1 - { - internal RuleBlock(SourceFile source, ResourceId id, ResourceId? @ref, SeverityLevel level, RuleHelpInfo info, ICondition condition, ResourceTags tag, ResourceId[] alias, ResourceId[] dependsOn, Hashtable configuration, ISourceExtent extent, ResourceFlags flags, ResourceLabels labels) - { - Source = source; - Name = id.Name; +internal delegate RuleConditionResult RuleCondition(); - // Get fully qualified Id, either RuleName or Module\RuleName - Id = id; - Ref = @ref; - Alias = alias; - - Level = level; - Info = info; - Condition = condition; - Tag = tag; - DependsOn = dependsOn; - Configuration = configuration; - Extent = extent; - Flags = flags; - Labels = labels; - } - - /// - /// A unique identifier for the rule. - /// - public ResourceId Id { get; } +/// +/// Define an instance of a rule block. Each rule block has a unique id. +/// +[DebuggerDisplay("{Id} @{Source.Path}")] +internal sealed class RuleBlock : ILanguageBlock, IDependencyTarget, IDisposable, IResource, IRuleV1 +{ + internal RuleBlock(SourceFile source, ResourceId id, ResourceId? @ref, SeverityLevel level, RuleHelpInfo info, ICondition condition, ResourceTags tag, ResourceId[] alias, ResourceId[] dependsOn, Hashtable configuration, ISourceExtent extent, ResourceFlags flags, ResourceLabels labels) + { + Source = source; + Name = id.Name; + + // Get fully qualified Id, either RuleName or Module\RuleName + Id = id; + Ref = @ref; + Alias = alias; + + Level = level; + Info = info; + Condition = condition; + Tag = tag; + DependsOn = dependsOn; + Configuration = configuration; + Extent = extent; + Flags = flags; + Labels = labels; + } - /// - public ResourceId? Ref { get; } + /// + /// A unique identifier for the rule. + /// + public ResourceId Id { get; } - /// - public ResourceId[] Alias { get; } + /// + public ResourceId? Ref { get; } - /// - /// If the rule fails, how serious is the result. - /// - public SeverityLevel Level { get; } + /// + public ResourceId[] Alias { get; } - /// - /// The name of the rule. - /// - public readonly string Name; + /// + /// If the rule fails, how serious is the result. + /// + public SeverityLevel Level { get; } - /// - /// The body of the rule definition where conditions are provided that either pass or fail the rule. - /// - [JsonIgnore] - [YamlIgnore] - public readonly ICondition Condition; + /// + /// The name of the rule. + /// + public readonly string Name; - /// - /// Other rules that must completed successfully before calling this rule. - /// - public readonly ResourceId[] DependsOn; + /// + /// The body of the rule definition where conditions are provided that either pass or fail the rule. + /// + [JsonIgnore] + [YamlIgnore] + public readonly ICondition Condition; - /// - /// Tags assigned to block. Tags are additional metadata used to select rules to execute and identify results. - /// - public readonly ResourceTags Tag; + /// + /// Other rules that must completed successfully before calling this rule. + /// + public readonly ResourceId[] DependsOn; - /// - public ResourceLabels Labels { get; } + /// + /// Tags assigned to block. Tags are additional metadata used to select rules to execute and identify results. + /// + public readonly ResourceTags Tag; - /// - /// Configuration defaults for the rule definition. - /// - /// - /// These defaults are used when the value does not exist in the baseline configuration. - /// - public readonly Hashtable Configuration; + /// + public ResourceLabels Labels { get; } - public readonly RuleHelpInfo Info; + /// + /// Configuration defaults for the rule definition. + /// + /// + /// These defaults are used when the value does not exist in the baseline configuration. + /// + public readonly Hashtable Configuration; - /// - public SourceFile Source { get; } + public readonly RuleHelpInfo Info; - /// - public ISourceExtent Extent { get; } + /// + public SourceFile Source { get; } - /// - [JsonIgnore] - [YamlIgnore] - public ResourceFlags Flags { get; } + /// + public ISourceExtent Extent { get; } - ResourceId[] IDependencyTarget.DependsOn => DependsOn; + /// + [JsonIgnore] + [YamlIgnore] + public ResourceFlags Flags { get; } - bool IDependencyTarget.Dependency => Source.IsDependency(); + ResourceId[] IDependencyTarget.DependsOn => DependsOn; - ResourceKind IResource.Kind => ResourceKind.Rule; + bool IDependencyTarget.Dependency => Source.IsDependency(); - string IResource.ApiVersion => Specs.V1; + ResourceKind IResource.Kind => ResourceKind.Rule; - string IResource.Name => Name; + string IResource.ApiVersion => Specs.V1; - ResourceTags IResource.Tags => Tag; + string IResource.Name => Name; - IResourceHelpInfo IResource.Info => Info; + ResourceTags IResource.Tags => Tag; - ResourceTags IRuleV1.Tag => Tag; + IResourceHelpInfo IResource.Info => Info; - string IRuleV1.Synopsis => Info.Synopsis; + ResourceTags IRuleV1.Tag => Tag; - InfoString IRuleV1.Recommendation => ((IRuleHelpInfoV2)Info)?.Recommendation; + string IRuleV1.Synopsis => Info.Synopsis; - #region IDisposable + InfoString IRuleV1.Recommendation => ((IRuleHelpInfoV2)Info)?.Recommendation; - public void Dispose() - { - Condition.Dispose(); - } + #region IDisposable - #endregion IDisposable + public void Dispose() + { + Condition.Dispose(); } + + #endregion IDisposable } diff --git a/src/PSRule/Rules/RuleHelpInfo.cs b/src/PSRule/Rules/RuleHelpInfo.cs index 2e050edba3..67a2ba3997 100644 --- a/src/PSRule/Rules/RuleHelpInfo.cs +++ b/src/PSRule/Rules/RuleHelpInfo.cs @@ -7,218 +7,217 @@ using PSRule.Definitions; using YamlDotNet.Serialization; -namespace PSRule.Rules +namespace PSRule.Rules; + +/// +/// A rule help information structure. +/// +public interface IRuleHelpInfoV2 : IResourceHelpInfo { /// - /// A rule help information structure. + /// The rule recommendation. /// - public interface IRuleHelpInfoV2 : IResourceHelpInfo - { - /// - /// The rule recommendation. - /// - InfoString Recommendation { get; } - - /// - /// Additional annotations, which are string key/ value pairs. - /// - Hashtable Annotations { get; } - - /// - /// The name of the module where the rule was loaded from. - /// - string ModuleName { get; } - - /// - /// Additional online links to reference information for the rule. - /// - Link[] Links { get; } - } + InfoString Recommendation { get; } /// - /// Extension methods for rule help information. + /// Additional annotations, which are string key/ value pairs. /// - public static class RuleHelpInfoExtensions - { - private const string ONLINE_HELP_LINK_ANNOTATION = "online version"; + Hashtable Annotations { get; } - /// - /// Get the URI for the online version of the documentation. - /// - /// Returns the URI when a valid link is set, otherwise null is returned. - public static Uri GetOnlineHelpUri(this IRuleHelpInfoV2 info) - { - var link = GetOnlineHelpUrl(info); - return link == null || - !Uri.TryCreate(link, UriKind.Absolute, out var result) ? - null : result; - } + /// + /// The name of the module where the rule was loaded from. + /// + string ModuleName { get; } - /// - /// Get the URL for the online version of the documentation. - /// - /// Returns the URL when set, otherwise null is returned. - public static string GetOnlineHelpUrl(this IRuleHelpInfoV2 info) - { - return info == null || - info.Annotations == null || - !info.Annotations.ContainsKey(ONLINE_HELP_LINK_ANNOTATION) ? - null : info.Annotations[ONLINE_HELP_LINK_ANNOTATION].ToString(); - } + /// + /// Additional online links to reference information for the rule. + /// + Link[] Links { get; } +} - /// - /// Determines if the online help link is set. - /// - /// Returns true when the online help link is set. Otherwise this method returns false. - internal static bool HasOnlineHelp(this IRuleHelpInfoV2 info) - { - return info != null && - info.Annotations != null && - info.Annotations.ContainsKey(ONLINE_HELP_LINK_ANNOTATION); - } +/// +/// Extension methods for rule help information. +/// +public static class RuleHelpInfoExtensions +{ + private const string ONLINE_HELP_LINK_ANNOTATION = "online version"; - /// - /// Set the online help link from the parameter. - /// - /// The info object. - /// A URL to the online help location. - internal static void SetOnlineHelpUrl(this IRuleHelpInfoV2 info, string url) - { - if (info == null || info.Annotations == null || string.IsNullOrEmpty(url)) return; - info.Annotations[ONLINE_HELP_LINK_ANNOTATION] = url; - } + /// + /// Get the URI for the online version of the documentation. + /// + /// Returns the URI when a valid link is set, otherwise null is returned. + public static Uri GetOnlineHelpUri(this IRuleHelpInfoV2 info) + { + var link = GetOnlineHelpUrl(info); + return link == null || + !Uri.TryCreate(link, UriKind.Absolute, out var result) ? + null : result; } /// - /// An URL link to reference information. + /// Get the URL for the online version of the documentation. /// - public sealed class Link + /// Returns the URL when set, otherwise null is returned. + public static string GetOnlineHelpUrl(this IRuleHelpInfoV2 info) { - internal Link(string name, string uri) - { - Name = name; - Uri = uri; - } + return info == null || + info.Annotations == null || + !info.Annotations.ContainsKey(ONLINE_HELP_LINK_ANNOTATION) ? + null : info.Annotations[ONLINE_HELP_LINK_ANNOTATION].ToString(); + } + + /// + /// Determines if the online help link is set. + /// + /// Returns true when the online help link is set. Otherwise this method returns false. + internal static bool HasOnlineHelp(this IRuleHelpInfoV2 info) + { + return info != null && + info.Annotations != null && + info.Annotations.ContainsKey(ONLINE_HELP_LINK_ANNOTATION); + } - /// - /// The display name of the link. - /// - public string Name { get; } + /// + /// Set the online help link from the parameter. + /// + /// The info object. + /// A URL to the online help location. + internal static void SetOnlineHelpUrl(this IRuleHelpInfoV2 info, string url) + { + if (info == null || info.Annotations == null || string.IsNullOrEmpty(url)) return; + info.Annotations[ONLINE_HELP_LINK_ANNOTATION] = url; + } +} - /// - /// The URL to the information, or the target link. - /// - public string Uri { get; } +/// +/// An URL link to reference information. +/// +public sealed class Link +{ + internal Link(string name, string uri) + { + Name = name; + Uri = uri; } /// - /// Output view helper class for rule help. + /// The display name of the link. /// - public sealed class RuleHelpInfo : IRuleHelpInfoV2 + public string Name { get; } + + /// + /// The URL to the information, or the target link. + /// + public string Uri { get; } +} + +/// +/// Output view helper class for rule help. +/// +public sealed class RuleHelpInfo : IRuleHelpInfoV2 +{ + private readonly InfoString _Synopsis; + private readonly InfoString _Description; + private readonly InfoString _Recommendation; + + internal RuleHelpInfo(string name, string displayName, string moduleName, InfoString synopsis = null, InfoString description = null, InfoString recommendation = null) { - private readonly InfoString _Synopsis; - private readonly InfoString _Description; - private readonly InfoString _Recommendation; + Name = name; + DisplayName = displayName; + ModuleName = moduleName; + _Synopsis = synopsis ?? new InfoString(); + _Description = description ?? new InfoString(); + _Recommendation = recommendation ?? new InfoString(); + } - internal RuleHelpInfo(string name, string displayName, string moduleName, InfoString synopsis = null, InfoString description = null, InfoString recommendation = null) - { - Name = name; - DisplayName = displayName; - ModuleName = moduleName; - _Synopsis = synopsis ?? new InfoString(); - _Description = description ?? new InfoString(); - _Recommendation = recommendation ?? new InfoString(); - } + /// + /// The name of the rule. + /// + [JsonProperty(PropertyName = "name")] + public string Name { get; private set; } - /// - /// The name of the rule. - /// - [JsonProperty(PropertyName = "name")] - public string Name { get; private set; } - - /// - /// A localized display name for the rule. - /// - [JsonProperty(PropertyName = "displayName")] - public string DisplayName { get; private set; } - - /// - /// The name of the module. - /// - /// - /// This will be null if the rule is not contained within a module. - /// - [JsonProperty(PropertyName = "moduleName")] - public string ModuleName { get; private set; } - - /// - /// The synopsis of the rule. - /// - [JsonProperty(PropertyName = "synopsis")] - public string Synopsis => _Synopsis.Text; - - /// - /// An extended description of the rule. - /// - [JsonProperty(PropertyName = "description")] - public string Description => _Description.Text; - - /// - /// The recommendation for the rule. - /// - [JsonProperty(PropertyName = "recommendation")] - public string Recommendation => _Recommendation.Text; - - /// - /// Additional notes for the rule. - /// - [JsonIgnore, YamlIgnore] - public string Notes { get; internal set; } - - /// - /// Reference links for the rule. - /// - [JsonIgnore, YamlIgnore] - public Link[] Links { get; internal set; } - - /// - /// Metadata annotations for the rule. - /// - [JsonProperty(PropertyName = "annotations")] - public Hashtable Annotations { get; internal set; } - - /// - [JsonIgnore, YamlIgnore] - InfoString IRuleHelpInfoV2.Recommendation => _Recommendation; - - /// - [JsonIgnore, YamlIgnore] - InfoString IResourceHelpInfo.Synopsis => _Synopsis; - - /// - [JsonIgnore, YamlIgnore] - InfoString IResourceHelpInfo.Description => _Description; - - /// - /// Get a view link string for display in rule help. - /// - public string GetLinkString() - { - if (Links == null) - return null; + /// + /// A localized display name for the rule. + /// + [JsonProperty(PropertyName = "displayName")] + public string DisplayName { get; private set; } + + /// + /// The name of the module. + /// + /// + /// This will be null if the rule is not contained within a module. + /// + [JsonProperty(PropertyName = "moduleName")] + public string ModuleName { get; private set; } + + /// + /// The synopsis of the rule. + /// + [JsonProperty(PropertyName = "synopsis")] + public string Synopsis => _Synopsis.Text; + + /// + /// An extended description of the rule. + /// + [JsonProperty(PropertyName = "description")] + public string Description => _Description.Text; - var sb = new StringBuilder(); - for (var i = 0; i < Links.Length; i++) + /// + /// The recommendation for the rule. + /// + [JsonProperty(PropertyName = "recommendation")] + public string Recommendation => _Recommendation.Text; + + /// + /// Additional notes for the rule. + /// + [JsonIgnore, YamlIgnore] + public string Notes { get; internal set; } + + /// + /// Reference links for the rule. + /// + [JsonIgnore, YamlIgnore] + public Link[] Links { get; internal set; } + + /// + /// Metadata annotations for the rule. + /// + [JsonProperty(PropertyName = "annotations")] + public Hashtable Annotations { get; internal set; } + + /// + [JsonIgnore, YamlIgnore] + InfoString IRuleHelpInfoV2.Recommendation => _Recommendation; + + /// + [JsonIgnore, YamlIgnore] + InfoString IResourceHelpInfo.Synopsis => _Synopsis; + + /// + [JsonIgnore, YamlIgnore] + InfoString IResourceHelpInfo.Description => _Description; + + /// + /// Get a view link string for display in rule help. + /// + public string GetLinkString() + { + if (Links == null) + return null; + + var sb = new StringBuilder(); + for (var i = 0; i < Links.Length; i++) + { + sb.Append(Links[i].Name); + if (!string.IsNullOrEmpty(Links[i].Uri)) { - sb.Append(Links[i].Name); - if (!string.IsNullOrEmpty(Links[i].Uri)) - { - sb.Append(": "); - sb.Append(Links[i].Uri); - } - sb.Append(System.Environment.NewLine); + sb.Append(": "); + sb.Append(Links[i].Uri); } - return sb.ToString(); + sb.Append(System.Environment.NewLine); } + return sb.ToString(); } } diff --git a/src/PSRule/Rules/RuleOutcome.cs b/src/PSRule/Rules/RuleOutcome.cs index fbaf3da370..0ddd17a165 100644 --- a/src/PSRule/Rules/RuleOutcome.cs +++ b/src/PSRule/Rules/RuleOutcome.cs @@ -4,54 +4,53 @@ using Newtonsoft.Json; using Newtonsoft.Json.Converters; -namespace PSRule.Rules +namespace PSRule.Rules; + +/// +/// The outcome of a rule. +/// +[JsonConverter(typeof(StringEnumConverter))] +[Flags] +public enum RuleOutcome { /// - /// The outcome of a rule. + /// The rule was not evaluated. /// - [JsonConverter(typeof(StringEnumConverter))] - [Flags] - public enum RuleOutcome - { - /// - /// The rule was not evaluated. - /// - None = 0, - - /// - /// The rule evaluated as false. - /// - Fail = 1, - - /// - /// The rule evaluated as true. - /// - Pass = 2, - - /// - /// The rule returned an error. - /// - Error = 4, - - /// - /// Any outcome when the rule was processed. - /// - /// - /// This flag is used to filter outcomes with Invoke-PSRule. - /// - Processed = Pass | Fail | Error, - - /// - /// Any outcome that is considered a problem that should be addressed. - /// - Problem = Fail | Error, - - /// - /// Any outcome. - /// - /// - /// This flag is used to filter outcomes with Invoke-PSRule. - /// - All = 255 - } + None = 0, + + /// + /// The rule evaluated as false. + /// + Fail = 1, + + /// + /// The rule evaluated as true. + /// + Pass = 2, + + /// + /// The rule returned an error. + /// + Error = 4, + + /// + /// Any outcome when the rule was processed. + /// + /// + /// This flag is used to filter outcomes with Invoke-PSRule. + /// + Processed = Pass | Fail | Error, + + /// + /// Any outcome that is considered a problem that should be addressed. + /// + Problem = Fail | Error, + + /// + /// Any outcome. + /// + /// + /// This flag is used to filter outcomes with Invoke-PSRule. + /// + All = 255 } diff --git a/src/PSRule/Rules/RuleOutcomeReason.cs b/src/PSRule/Rules/RuleOutcomeReason.cs index 0b7a3387ea..e63de54770 100644 --- a/src/PSRule/Rules/RuleOutcomeReason.cs +++ b/src/PSRule/Rules/RuleOutcomeReason.cs @@ -4,57 +4,56 @@ using Newtonsoft.Json; using Newtonsoft.Json.Converters; -namespace PSRule.Rules +namespace PSRule.Rules; + +/// +/// The reason for the outcome of a rule. +/// +[JsonConverter(typeof(StringEnumConverter))] +public enum RuleOutcomeReason { /// - /// The reason for the outcome of a rule. + /// A reason was not specified. + /// + None = 0, + + /// + /// The rule was processed. + /// + /// + /// This reason is used with the Pass, Fail and Error outcomes. + /// + Processed = 1, + + /// + /// The rule was not processed because the precondition returned false. + /// + /// + /// This reason is used with the None outcome. + /// + PreconditionFail = 2, + + /// + /// The rule was not processed because a dependency already returned an error or failed. + /// + /// + /// This reason is used with the None outcome. + /// + DependencyFail = 3, + + /// + /// The rule was processed but didn't return pass or fail. + /// + /// + /// This reason is used with the Fail outcome. + /// + Inconclusive = 4, + + /// + /// The rule was not processed because the Target Name was suppressed. /// - [JsonConverter(typeof(StringEnumConverter))] - public enum RuleOutcomeReason - { - /// - /// A reason was not specified. - /// - None = 0, - - /// - /// The rule was processed. - /// - /// - /// This reason is used with the Pass, Fail and Error outcomes. - /// - Processed = 1, - - /// - /// The rule was not processed because the precondition returned false. - /// - /// - /// This reason is used with the None outcome. - /// - PreconditionFail = 2, - - /// - /// The rule was not processed because a dependency already returned an error or failed. - /// - /// - /// This reason is used with the None outcome. - /// - DependencyFail = 3, - - /// - /// The rule was processed but didn't return pass or fail. - /// - /// - /// This reason is used with the Fail outcome. - /// - Inconclusive = 4, - - /// - /// The rule was not processed because the Target Name was suppressed. - /// - /// - /// This reason is used with the None outcome. - /// - Suppressed = 5 - } + /// + /// This reason is used with the None outcome. + /// + Suppressed = 5 } diff --git a/src/PSRule/Rules/RuleRecord.cs b/src/PSRule/Rules/RuleRecord.cs index 60293eb1ed..a6ed9e00fd 100644 --- a/src/PSRule/Rules/RuleRecord.cs +++ b/src/PSRule/Rules/RuleRecord.cs @@ -12,213 +12,212 @@ using PSRule.Pipeline; using YamlDotNet.Serialization; -namespace PSRule.Rules +namespace PSRule.Rules; + +/// +/// A detailed format for rule results. +/// +[DebuggerDisplay("{RuleId}, Outcome = {Outcome}")] +[JsonObject] +public sealed class RuleRecord : IDetailedRuleResultV2 { + private readonly TargetObject _TargetObject; + + internal readonly ResultDetail _Detail; + + internal RuleRecord(string runId, ResourceId ruleId, string @ref, TargetObject targetObject, string targetName, string targetType, ResourceTags tag, RuleHelpInfo info, Hashtable field, SeverityLevel level, ISourceExtent extent, RuleOutcome outcome = RuleOutcome.None, RuleOutcomeReason reason = RuleOutcomeReason.None) + { + _TargetObject = targetObject; + RunId = runId; + RuleId = ruleId.Value; + RuleName = ruleId.Name; + Ref = @ref; + TargetObject = targetObject.Value; + TargetName = targetName; + TargetType = targetType; + Outcome = outcome; + OutcomeReason = reason; + Info = info; + Source = targetObject.Source.GetSourceInfo(); + Level = level; + Extent = extent; + _Detail = new ResultDetail(); + if (tag != null) + Tag = tag.ToHashtable(); + + if (field != null && field.Count > 0) + Field = field; + } + + /// + /// A unique identifier for the run. + /// + [JsonProperty(PropertyName = "runId")] + public string RunId { get; } + + /// + /// A unique identifier for the rule. + /// + /// + /// An additional opaque identifer may also be provided by by . + /// + [JsonIgnore] + [YamlIgnore] + public readonly string RuleId; + + /// + /// The name of the rule. + /// + [JsonProperty(PropertyName = "ruleName")] + public readonly string RuleName; + + /// + /// A stable opaque unique identifier for the rule in addition to . + /// + public string Ref { get; } + + /// + /// If the rule fails, how serious is the result. + /// + [JsonProperty(PropertyName = "level")] + public SeverityLevel Level { get; } + + /// + /// A source location for the rule that executed. + /// + [JsonIgnore] + [YamlIgnore] + public ISourceExtent Extent { get; } + + /// + /// The outcome after the rule processes an object. + /// + [JsonProperty(PropertyName = "outcome")] + public RuleOutcome Outcome { get; internal set; } + + /// + /// An additional reason code for the . + /// + [JsonProperty(PropertyName = "outcomeReason")] + public RuleOutcomeReason OutcomeReason { get; internal set; } + + /// + /// A localized recommendation for the rule. + /// + [JsonIgnore] + [YamlIgnore] + public string Recommendation => Info.Recommendation?.Text ?? Info.Synopsis?.Text; + + /// + /// The reason for the failed condition. + /// + [DefaultValue(null)] + [JsonProperty(PropertyName = "reason")] + public string[] Reason => _Detail.Count > 0 ? _Detail.GetReasonStrings() : null; + + /// + /// A name to identify the target object. + /// + [JsonProperty(PropertyName = "targetName")] + public string TargetName { get; } + + /// + /// The type of the target object. + /// + [JsonProperty(PropertyName = "targetType")] + public string TargetType { get; } + /// - /// A detailed format for rule results. + /// The current target object. /// - [DebuggerDisplay("{RuleId}, Outcome = {Outcome}")] - [JsonObject] - public sealed class RuleRecord : IDetailedRuleResultV2 + [JsonIgnore] + [YamlIgnore] + public PSObject TargetObject { get; } + + /// + /// Custom data set by the rule for this target object. + /// + [JsonProperty(PropertyName = "data")] + public Hashtable Data => _TargetObject.GetData(); + + /// + /// A set of custom fields bound for the target object. + /// + [JsonProperty(PropertyName = "field")] + public Hashtable Field { get; } + + /// + /// Tags set for the rule. + /// + [DefaultValue(null)] + [JsonProperty(PropertyName = "tag")] + public Hashtable Tag { get; } + + /// + /// Help info for the rule. + /// + [DefaultValue(null)] + [JsonProperty(PropertyName = "info")] + public IRuleHelpInfoV2 Info { get; } + + /// + /// The execution time of the rule in millisecond. + /// + [DefaultValue(0f)] + [JsonProperty(PropertyName = "time")] + public long Time { get; internal set; } + + /// + /// Additional information if the rule errored. If the rule passed or failed this value is null. + /// + [DefaultValue(null)] + [JsonProperty(PropertyName = "error")] + public ErrorInfo Error { get; internal set; } + + /// + /// Source of target object. + /// + [DefaultValue(null)] + [JsonProperty(PropertyName = "source")] + public TargetSourceInfo[] Source { get; } + + /// + /// Rule reason details. + /// + [DefaultValue(null)] + [JsonProperty(PropertyName = "detail")] + [YamlMember()] + public IResultDetailV2 Detail => _Detail; + + /// + /// Determine if the rule is successful or skipped. + /// + public bool IsSuccess() + { + return Outcome == RuleOutcome.Pass || Outcome == RuleOutcome.None; + } + + /// + /// Determine if the rule was executed and resulted in an outcome. + /// + public bool IsProcessed() + { + return Outcome == RuleOutcome.Pass || Outcome == RuleOutcome.Fail || Outcome == RuleOutcome.Error; + } + + /// + /// Gets a string for output views in PowerShell. + /// + /// + /// This method is called by PowerShell. + /// + public string GetReasonViewString() + { + return Reason == null || Reason.Length == 0 ? string.Empty : string.Join(System.Environment.NewLine, Reason); + } + + internal bool HasSource() { - private readonly TargetObject _TargetObject; - - internal readonly ResultDetail _Detail; - - internal RuleRecord(string runId, ResourceId ruleId, string @ref, TargetObject targetObject, string targetName, string targetType, ResourceTags tag, RuleHelpInfo info, Hashtable field, SeverityLevel level, ISourceExtent extent, RuleOutcome outcome = RuleOutcome.None, RuleOutcomeReason reason = RuleOutcomeReason.None) - { - _TargetObject = targetObject; - RunId = runId; - RuleId = ruleId.Value; - RuleName = ruleId.Name; - Ref = @ref; - TargetObject = targetObject.Value; - TargetName = targetName; - TargetType = targetType; - Outcome = outcome; - OutcomeReason = reason; - Info = info; - Source = targetObject.Source.GetSourceInfo(); - Level = level; - Extent = extent; - _Detail = new ResultDetail(); - if (tag != null) - Tag = tag.ToHashtable(); - - if (field != null && field.Count > 0) - Field = field; - } - - /// - /// A unique identifier for the run. - /// - [JsonProperty(PropertyName = "runId")] - public string RunId { get; } - - /// - /// A unique identifier for the rule. - /// - /// - /// An additional opaque identifer may also be provided by by . - /// - [JsonIgnore] - [YamlIgnore] - public readonly string RuleId; - - /// - /// The name of the rule. - /// - [JsonProperty(PropertyName = "ruleName")] - public readonly string RuleName; - - /// - /// A stable opaque unique identifier for the rule in addition to . - /// - public string Ref { get; } - - /// - /// If the rule fails, how serious is the result. - /// - [JsonProperty(PropertyName = "level")] - public SeverityLevel Level { get; } - - /// - /// A source location for the rule that executed. - /// - [JsonIgnore] - [YamlIgnore] - public ISourceExtent Extent { get; } - - /// - /// The outcome after the rule processes an object. - /// - [JsonProperty(PropertyName = "outcome")] - public RuleOutcome Outcome { get; internal set; } - - /// - /// An additional reason code for the . - /// - [JsonProperty(PropertyName = "outcomeReason")] - public RuleOutcomeReason OutcomeReason { get; internal set; } - - /// - /// A localized recommendation for the rule. - /// - [JsonIgnore] - [YamlIgnore] - public string Recommendation => Info.Recommendation?.Text ?? Info.Synopsis?.Text; - - /// - /// The reason for the failed condition. - /// - [DefaultValue(null)] - [JsonProperty(PropertyName = "reason")] - public string[] Reason => _Detail.Count > 0 ? _Detail.GetReasonStrings() : null; - - /// - /// A name to identify the target object. - /// - [JsonProperty(PropertyName = "targetName")] - public string TargetName { get; } - - /// - /// The type of the target object. - /// - [JsonProperty(PropertyName = "targetType")] - public string TargetType { get; } - - /// - /// The current target object. - /// - [JsonIgnore] - [YamlIgnore] - public PSObject TargetObject { get; } - - /// - /// Custom data set by the rule for this target object. - /// - [JsonProperty(PropertyName = "data")] - public Hashtable Data => _TargetObject.GetData(); - - /// - /// A set of custom fields bound for the target object. - /// - [JsonProperty(PropertyName = "field")] - public Hashtable Field { get; } - - /// - /// Tags set for the rule. - /// - [DefaultValue(null)] - [JsonProperty(PropertyName = "tag")] - public Hashtable Tag { get; } - - /// - /// Help info for the rule. - /// - [DefaultValue(null)] - [JsonProperty(PropertyName = "info")] - public IRuleHelpInfoV2 Info { get; } - - /// - /// The execution time of the rule in millisecond. - /// - [DefaultValue(0f)] - [JsonProperty(PropertyName = "time")] - public long Time { get; internal set; } - - /// - /// Additional information if the rule errored. If the rule passed or failed this value is null. - /// - [DefaultValue(null)] - [JsonProperty(PropertyName = "error")] - public ErrorInfo Error { get; internal set; } - - /// - /// Source of target object. - /// - [DefaultValue(null)] - [JsonProperty(PropertyName = "source")] - public TargetSourceInfo[] Source { get; } - - /// - /// Rule reason details. - /// - [DefaultValue(null)] - [JsonProperty(PropertyName = "detail")] - [YamlMember()] - public IResultDetailV2 Detail => _Detail; - - /// - /// Determine if the rule is successful or skipped. - /// - public bool IsSuccess() - { - return Outcome == RuleOutcome.Pass || Outcome == RuleOutcome.None; - } - - /// - /// Determine if the rule was executed and resulted in an outcome. - /// - public bool IsProcessed() - { - return Outcome == RuleOutcome.Pass || Outcome == RuleOutcome.Fail || Outcome == RuleOutcome.Error; - } - - /// - /// Gets a string for output views in PowerShell. - /// - /// - /// This method is called by PowerShell. - /// - public string GetReasonViewString() - { - return Reason == null || Reason.Length == 0 ? string.Empty : string.Join(System.Environment.NewLine, Reason); - } - - internal bool HasSource() - { - return Source != null && Source.Length > 0; - } + return Source != null && Source.Length > 0; } } diff --git a/src/PSRule/Rules/RuleSummaryRecord.cs b/src/PSRule/Rules/RuleSummaryRecord.cs index 7f26248b2f..2ef34a4363 100644 --- a/src/PSRule/Rules/RuleSummaryRecord.cs +++ b/src/PSRule/Rules/RuleSummaryRecord.cs @@ -7,101 +7,100 @@ using Newtonsoft.Json; using PSRule.Definitions; -namespace PSRule.Rules +namespace PSRule.Rules; + +/// +/// A summary format for rule results. +/// +[DebuggerDisplay("{RuleId}, Outcome = {Outcome}")] +public sealed class RuleSummaryRecord { - /// - /// A summary format for rule results. - /// - [DebuggerDisplay("{RuleId}, Outcome = {Outcome}")] - public sealed class RuleSummaryRecord + internal RuleSummaryRecord(string ruleId, string ruleName, ResourceTags tag, RuleHelpInfo info) { - internal RuleSummaryRecord(string ruleId, string ruleName, ResourceTags tag, RuleHelpInfo info) - { - RuleId = ruleId; - RuleName = ruleName; - Tag = tag?.ToHashtable(); - Info = info; - } + RuleId = ruleId; + RuleName = ruleName; + Tag = tag?.ToHashtable(); + Info = info; + } - /// - /// The unique identifier for the rule. - /// - [JsonRequired] - public readonly string RuleId; + /// + /// The unique identifier for the rule. + /// + [JsonRequired] + public readonly string RuleId; - /// - /// The name of the rule. - /// - public readonly string RuleName; + /// + /// The name of the rule. + /// + public readonly string RuleName; - /// - /// The number of rule passes. - /// - public int Pass { get; internal set; } + /// + /// The number of rule passes. + /// + public int Pass { get; internal set; } - /// - /// The number of rule failures. - /// - public int Fail { get; internal set; } + /// + /// The number of rule failures. + /// + public int Fail { get; internal set; } - /// - /// The number of rile errors. - /// - public int Error { get; internal set; } + /// + /// The number of rile errors. + /// + public int Error { get; internal set; } - /// - /// The aggregate outcome after the rule processes all objects. - /// - public RuleOutcome Outcome + /// + /// The aggregate outcome after the rule processes all objects. + /// + public RuleOutcome Outcome + { + get { - get + if (Error > 0) { - if (Error > 0) - { - return RuleOutcome.Error; - } - else if (Fail > 0) - { - return RuleOutcome.Fail; - } - else if (Pass > 0) - { - return RuleOutcome.Pass; - } - return RuleOutcome.None; + return RuleOutcome.Error; } + else if (Fail > 0) + { + return RuleOutcome.Fail; + } + else if (Pass > 0) + { + return RuleOutcome.Pass; + } + return RuleOutcome.None; } + } - /// - /// Tags associated with the rule. - /// - [DefaultValue(null)] - [JsonProperty(PropertyName = "tag")] - public Hashtable Tag { get; internal set; } + /// + /// Tags associated with the rule. + /// + [DefaultValue(null)] + [JsonProperty(PropertyName = "tag")] + public Hashtable Tag { get; internal set; } - /// - /// Additional information associated with the rule. - /// - [DefaultValue(null)] - [JsonProperty(PropertyName = "info")] - public RuleHelpInfo Info { get; internal set; } + /// + /// Additional information associated with the rule. + /// + [DefaultValue(null)] + [JsonProperty(PropertyName = "info")] + public RuleHelpInfo Info { get; internal set; } - /// - /// Determines if the overall outcome is successful or a failure. - /// - public bool IsSuccess() - { - return Outcome == RuleOutcome.Pass || Outcome == RuleOutcome.None; - } + /// + /// Determines if the overall outcome is successful or a failure. + /// + public bool IsSuccess() + { + return Outcome == RuleOutcome.Pass || Outcome == RuleOutcome.None; + } - internal void Add(RuleOutcome outcome) - { - if (outcome == RuleOutcome.Pass) - Pass++; - else if (outcome == RuleOutcome.Fail) - Fail++; - else if (outcome == RuleOutcome.Error) - Error++; - } + internal void Add(RuleOutcome outcome) + { + if (outcome == RuleOutcome.Pass) + Pass++; + else if (outcome == RuleOutcome.Fail) + Fail++; + else if (outcome == RuleOutcome.Error) + Error++; } } diff --git a/src/PSRule/Rules/SuppressionFilter.cs b/src/PSRule/Rules/SuppressionFilter.cs index a0deb33e20..31230de9f2 100644 --- a/src/PSRule/Rules/SuppressionFilter.cs +++ b/src/PSRule/Rules/SuppressionFilter.cs @@ -8,182 +8,181 @@ using PSRule.Pipeline; using PSRule.Runtime; -namespace PSRule.Rules +namespace PSRule.Rules; + +[DebuggerDisplay("{_Index.Count}")] +internal sealed class SuppressionFilter { - [DebuggerDisplay("{_Index.Count}")] - internal sealed class SuppressionFilter - { - private readonly HashSet _Index; - private readonly bool _IsEmpty; - private readonly ResourceIndex _ResourceIndex; - private readonly Dictionary> _RuleSuppressionGroupIndex; + private readonly HashSet _Index; + private readonly bool _IsEmpty; + private readonly ResourceIndex _ResourceIndex; + private readonly Dictionary> _RuleSuppressionGroupIndex; - public SuppressionFilter(RunspaceContext context, SuppressionOption option, ResourceIndex resourceIndex) + public SuppressionFilter(RunspaceContext context, SuppressionOption option, ResourceIndex resourceIndex) + { + if (option == null || option.Count == 0 || resourceIndex.IsEmpty()) { - if (option == null || option.Count == 0 || resourceIndex.IsEmpty()) - { - _IsEmpty = true; - } - else - { - _ResourceIndex = resourceIndex; - _Index = Index(context, option); - _IsEmpty = _Index.Count == 0; - } + _IsEmpty = true; } - - public SuppressionFilter(IEnumerable suppressionGroups, ResourceIndex resourceIndex) + else { _ResourceIndex = resourceIndex; - _RuleSuppressionGroupIndex = new Dictionary>(); - IndexSuppressionGroups(suppressionGroups); + _Index = Index(context, option); + _IsEmpty = _Index.Count == 0; } + } - [DebuggerDisplay("{HashCode}, RuleName = {RuleName}, TargetName = {TargetName}")] - private sealed class SuppressionKey - { - public readonly string RuleName; - public readonly string TargetName; + public SuppressionFilter(IEnumerable suppressionGroups, ResourceIndex resourceIndex) + { + _ResourceIndex = resourceIndex; + _RuleSuppressionGroupIndex = new Dictionary>(); + IndexSuppressionGroups(suppressionGroups); + } - private readonly int _HashCode; + [DebuggerDisplay("{HashCode}, RuleName = {RuleName}, TargetName = {TargetName}")] + private sealed class SuppressionKey + { + public readonly string RuleName; + public readonly string TargetName; - public SuppressionKey(string ruleName, string targetName) - { - if (string.IsNullOrEmpty(ruleName)) - throw new ArgumentNullException(nameof(ruleName)); + private readonly int _HashCode; - if (string.IsNullOrEmpty(targetName)) - throw new ArgumentNullException(nameof(targetName)); + public SuppressionKey(string ruleName, string targetName) + { + if (string.IsNullOrEmpty(ruleName)) + throw new ArgumentNullException(nameof(ruleName)); - RuleName = ruleName; - TargetName = targetName; - _HashCode = CombineHashCode(); - } + if (string.IsNullOrEmpty(targetName)) + throw new ArgumentNullException(nameof(targetName)); - /// - public override int GetHashCode() - { - return _HashCode; - } + RuleName = ruleName; + TargetName = targetName; + _HashCode = CombineHashCode(); + } - /// - public override bool Equals(object obj) - { - if (obj is not SuppressionKey) - return false; + /// + public override int GetHashCode() + { + return _HashCode; + } - var k2 = obj as SuppressionKey; - return _HashCode == k2._HashCode && - StringComparer.OrdinalIgnoreCase.Equals(TargetName, k2.TargetName) && - StringComparer.OrdinalIgnoreCase.Equals(RuleName, k2.RuleName); - } + /// + public override bool Equals(object obj) + { + if (obj is not SuppressionKey) + return false; - private int CombineHashCode() - { - var h1 = RuleName.ToUpper(Thread.CurrentThread.CurrentCulture).GetHashCode(); - var h2 = TargetName.ToUpper(Thread.CurrentThread.CurrentCulture).GetHashCode(); - unchecked - { - // Get combined hash code for key - var rol5 = ((uint)h1 << 5) | ((uint)h1 >> 27); - return ((int)rol5 + h1) ^ h2; - } - } + var k2 = obj as SuppressionKey; + return _HashCode == k2._HashCode && + StringComparer.OrdinalIgnoreCase.Equals(TargetName, k2.TargetName) && + StringComparer.OrdinalIgnoreCase.Equals(RuleName, k2.RuleName); } - public bool Match(ResourceId id, string targetName) + private int CombineHashCode() { - return !_IsEmpty && - !string.IsNullOrEmpty(targetName) && - _Index.Contains(new SuppressionKey(id.Value, targetName)); + var h1 = RuleName.ToUpper(Thread.CurrentThread.CurrentCulture).GetHashCode(); + var h2 = TargetName.ToUpper(Thread.CurrentThread.CurrentCulture).GetHashCode(); + unchecked + { + // Get combined hash code for key + var rol5 = ((uint)h1 << 5) | ((uint)h1 >> 27); + return ((int)rol5 + h1) ^ h2; + } } + } - private HashSet Index(RunspaceContext context, SuppressionOption option) - { - var index = new HashSet(); + public bool Match(ResourceId id, string targetName) + { + return !_IsEmpty && + !string.IsNullOrEmpty(targetName) && + _Index.Contains(new SuppressionKey(id.Value, targetName)); + } - // Read suppress rules into index combined key (RuleName + TargetName) - foreach (var rule in option) - { - // Only add suppresion entries for rules that are loaded - if (!_ResourceIndex.TryFind(rule.Key, out var blockId, out var kind)) - continue; + private HashSet Index(RunspaceContext context, SuppressionOption option) + { + var index = new HashSet(); - if (kind == ResourceIdKind.Alias) - context.WarnAliasSuppression(blockId.Value, rule.Key); + // Read suppress rules into index combined key (RuleName + TargetName) + foreach (var rule in option) + { + // Only add suppresion entries for rules that are loaded + if (!_ResourceIndex.TryFind(rule.Key, out var blockId, out var kind)) + continue; - foreach (var targetName in rule.Value.TargetName) - { - var key = new SuppressionKey(blockId.Value, targetName); - if (!index.Contains(key)) - index.Add(key); - } + if (kind == ResourceIdKind.Alias) + context.WarnAliasSuppression(blockId.Value, rule.Key); + + foreach (var targetName in rule.Value.TargetName) + { + var key = new SuppressionKey(blockId.Value, targetName); + if (!index.Contains(key)) + index.Add(key); } - return index; } + return index; + } - /// - /// Attempts to fetch suppression group from rule suppression group index. - /// - /// The key rule id which indexes suppression groups. - /// The we are evaluating. - /// Information about a matching suppression group. - /// Boolean indicating if suppression group has been found. - public bool TrySuppressionGroup(string ruleId, TargetObject targetObject, out ISuppressionInfo suppression) + /// + /// Attempts to fetch suppression group from rule suppression group index. + /// + /// The key rule id which indexes suppression groups. + /// The we are evaluating. + /// Information about a matching suppression group. + /// Boolean indicating if suppression group has been found. + public bool TrySuppressionGroup(string ruleId, TargetObject targetObject, out ISuppressionInfo suppression) + { + suppression = null; + if (_RuleSuppressionGroupIndex.TryGetValue(ruleId, out var suppressionGroupVisitors)) { - suppression = null; - if (_RuleSuppressionGroupIndex.TryGetValue(ruleId, out var suppressionGroupVisitors)) + foreach (var visitor in suppressionGroupVisitors) { - foreach (var visitor in suppressionGroupVisitors) - { - if (visitor.TryMatch(targetObject.Value, out suppression)) - return true; - } + if (visitor.TryMatch(targetObject.Value, out suppression)) + return true; } - return false; } + return false; + } - /// - /// Index suppression groups by rule - /// - /// The suppression group collection - private void IndexSuppressionGroups(IEnumerable suppressionGroups) + /// + /// Index suppression groups by rule + /// + /// The suppression group collection + private void IndexSuppressionGroups(IEnumerable suppressionGroups) + { + foreach (var group in suppressionGroups) { - foreach (var group in suppressionGroups) + var rules = group.Rule; + + // If no rules are set, Add visitors to every rule + if (rules == null || rules.Length == 0) { - var rules = group.Rule; + foreach (var resource in _ResourceIndex.GetItems()) + AddSuppressionGroup(ruleId: resource.Id.Value, visitor: group); + } - // If no rules are set, Add visitors to every rule - if (rules == null || rules.Length == 0) + // Otherwise only add rules specified + else + { + foreach (var rule in rules) { - foreach (var resource in _ResourceIndex.GetItems()) - AddSuppressionGroup(ruleId: resource.Id.Value, visitor: group); - } + // Only add suppresion entries for rules that are loaded + if (!_ResourceIndex.TryFind(rule, out var blockId, out _)) + continue; - // Otherwise only add rules specified - else - { - foreach (var rule in rules) - { - // Only add suppresion entries for rules that are loaded - if (!_ResourceIndex.TryFind(rule, out var blockId, out _)) - continue; - - AddSuppressionGroup(ruleId: blockId.Value, visitor: group); - } + AddSuppressionGroup(ruleId: blockId.Value, visitor: group); } } } + } - /// - /// Add suppression group visitor to index - /// - private void AddSuppressionGroup(string ruleId, SuppressionGroupVisitor visitor) - { - if (!_RuleSuppressionGroupIndex.ContainsKey(ruleId)) - _RuleSuppressionGroupIndex.Add(ruleId, new List()); + /// + /// Add suppression group visitor to index + /// + private void AddSuppressionGroup(string ruleId, SuppressionGroupVisitor visitor) + { + if (!_RuleSuppressionGroupIndex.ContainsKey(ruleId)) + _RuleSuppressionGroupIndex.Add(ruleId, new List()); - _RuleSuppressionGroupIndex[ruleId].Add(visitor); - } + _RuleSuppressionGroupIndex[ruleId].Add(visitor); } } diff --git a/src/PSRule/Runtime/Assert.cs b/src/PSRule/Runtime/Assert.cs index f1b11b5b01..c83bec51a8 100644 --- a/src/PSRule/Runtime/Assert.cs +++ b/src/PSRule/Runtime/Assert.cs @@ -11,1595 +11,1594 @@ using PSRule.Pipeline; using PSRule.Resources; -namespace PSRule.Runtime +namespace PSRule.Runtime; + +/// +/// A set of assertion helpers that are exposed at runtime through the $Assert variable. +/// +public sealed class Assert { + private const string COMMASEPARATOR = ", "; + private const string PROPERTY_SCHEMA = "$schema"; + private const string VARIABLE_NAME = "Assert"; + private const string TYPENAME_STRING = "[string]"; + private const string TYPENAME_NULL = "null"; + + #region Authoring + /// - /// A set of assertion helpers that are exposed at runtime through the $Assert variable. + /// Create a result based on a boolean . /// - public sealed class Assert + /// A boolean condition that passes when set to true, and fails when set to false. + /// A localized reason why the assertion failed. This parameter is ignored if the assertion passed. + /// An assertion result. + public AssertResult Create(bool condition, string reason = null) { - private const string COMMASEPARATOR = ", "; - private const string PROPERTY_SCHEMA = "$schema"; - private const string VARIABLE_NAME = "Assert"; - private const string TYPENAME_STRING = "[string]"; - private const string TYPENAME_NULL = "null"; + return Create(condition, reason, args: null); + } - #region Authoring + /// + /// Create a result based on a boolean . + /// + /// A boolean condition that passes when set to true, and fails when set to false. + /// An unformatted localized reason why the assertion failed. This parameter is ignored if the assertion passed. + /// A list of arguments that are inserted into the format string. + /// An assertion result. + public AssertResult Create(bool condition, string reason, params object[] args) + { + return Create(Operand.FromTarget(), condition, reason, args); + } - /// - /// Create a result based on a boolean . - /// - /// A boolean condition that passes when set to true, and fails when set to false. - /// A localized reason why the assertion failed. This parameter is ignored if the assertion passed. - /// An assertion result. - public AssertResult Create(bool condition, string reason = null) - { - return Create(condition, reason, args: null); - } + /// + /// Create a result based on a boolean . + /// + /// The object path that was reported by the assertion. + /// A boolean condition that passes when set to true, and fails when set to false. + /// An unformatted localized reason why the assertion failed. This parameter is ignored if the assertion passed. + /// A list of arguments that are inserted into the format string. + /// An assertion result. + public AssertResult Create(string path, bool condition, string reason, params object[] args) + { + return Create(string.IsNullOrEmpty(path) ? Operand.FromTarget() : Operand.FromPath(path), condition, reason, args); + } - /// - /// Create a result based on a boolean . - /// - /// A boolean condition that passes when set to true, and fails when set to false. - /// An unformatted localized reason why the assertion failed. This parameter is ignored if the assertion passed. - /// A list of arguments that are inserted into the format string. - /// An assertion result. - public AssertResult Create(bool condition, string reason, params object[] args) - { - return Create(Operand.FromTarget(), condition, reason, args); - } + internal AssertResult Create(IOperand operand, bool condition, string reason, params object[] args) + { + if (!(RunspaceContext.CurrentThread.IsScope(RunspaceScope.Rule) || RunspaceContext.CurrentThread.IsScope(RunspaceScope.Precondition))) + throw new RuleException(string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.VariableConditionScope, VARIABLE_NAME)); - /// - /// Create a result based on a boolean . - /// - /// The object path that was reported by the assertion. - /// A boolean condition that passes when set to true, and fails when set to false. - /// An unformatted localized reason why the assertion failed. This parameter is ignored if the assertion passed. - /// A list of arguments that are inserted into the format string. - /// An assertion result. - public AssertResult Create(string path, bool condition, string reason, params object[] args) - { - return Create(string.IsNullOrEmpty(path) ? Operand.FromTarget() : Operand.FromPath(path), condition, reason, args); - } + return new AssertResult(operand, condition, reason, args); + } - internal AssertResult Create(IOperand operand, bool condition, string reason, params object[] args) - { - if (!(RunspaceContext.CurrentThread.IsScope(RunspaceScope.Rule) || RunspaceContext.CurrentThread.IsScope(RunspaceScope.Precondition))) - throw new RuleException(string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.VariableConditionScope, VARIABLE_NAME)); + /// + /// Create a result based on issues reported downstream. + /// + /// An array of issues reported downstream. + /// An assertion result. + public AssertResult Create(TargetIssueInfo[] issue) + { + if (issue == null || issue.Length == 0) + return Pass(); - return new AssertResult(operand, condition, reason, args); - } + var result = Fail(); + for (var i = 0; i < issue.Length; i++) + result.AddReason(string.IsNullOrEmpty(issue[i].Path) ? Operand.FromTarget() : Operand.FromPath(issue[i].Path), issue[i].Message); - /// - /// Create a result based on issues reported downstream. - /// - /// An array of issues reported downstream. - /// An assertion result. - public AssertResult Create(TargetIssueInfo[] issue) - { - if (issue == null || issue.Length == 0) - return Pass(); + return result; + } + + /// + /// Create a passing assertion result. + /// + /// An assertion result. + public AssertResult Pass() + { + return Create(condition: true); + } - var result = Fail(); - for (var i = 0; i < issue.Length; i++) - result.AddReason(string.IsNullOrEmpty(issue[i].Path) ? Operand.FromTarget() : Operand.FromPath(issue[i].Path), issue[i].Message); + /// + /// Create a failed assertion result. + /// + /// An assertion result. + public AssertResult Fail() + { + return Create(condition: false, reason: null, args: null); + } - return result; - } + /// + /// Create a failed assertion result. + /// + /// An unformatted localized reason why the assertion failed. + /// A list of arguments that are inserted into the format string. + /// An assertion result. + public AssertResult Fail(string reason, params object[] args) + { + return Create(condition: false, reason: reason, args: args); + } - /// - /// Create a passing assertion result. - /// - /// An assertion result. - public AssertResult Pass() - { - return Create(condition: true); - } + /// + /// Create a failed assertion result. + /// + /// An operand that was reported by the assertion. + /// An unformatted localized reason why the assertion failed. + /// A list of arguments that are inserted into the format string. + /// An assertion result. + public AssertResult Fail(IOperand operand, string reason, params object[] args) + { + return Create(operand, condition: false, reason: reason, args: args); + } - /// - /// Create a failed assertion result. - /// - /// An assertion result. - public AssertResult Fail() - { - return Create(condition: false, reason: null, args: null); - } + #endregion Authoring - /// - /// Create a failed assertion result. - /// - /// An unformatted localized reason why the assertion failed. - /// A list of arguments that are inserted into the format string. - /// An assertion result. - public AssertResult Fail(string reason, params object[] args) - { - return Create(condition: false, reason: reason, args: args); - } + #region Operators + + /// + /// Aggregates one or more results. If any one results is a pass, then pass is returned. + /// + public AssertResult AnyOf(params AssertResult[] results) + { + if (results == null || results.Length == 0) + return Fail(ReasonStrings.ResultsNotProvided); - /// - /// Create a failed assertion result. - /// - /// An operand that was reported by the assertion. - /// An unformatted localized reason why the assertion failed. - /// A list of arguments that are inserted into the format string. - /// An assertion result. - public AssertResult Fail(IOperand operand, string reason, params object[] args) + var result = Fail(); + for (var i = 0; i < results.Length; i++) { - return Create(operand, condition: false, reason: reason, args: args); + if (results[i].Result) + return Pass(); + else + result.AddReason(result); } + return result; + } - #endregion Authoring - - #region Operators + /// + /// Aggregates one or more results. If all results are a pass, then pass is returned. + /// + public AssertResult AllOf(params AssertResult[] results) + { + if (results == null || results.Length == 0) + return Fail(ReasonStrings.ResultsNotProvided); - /// - /// Aggregates one or more results. If any one results is a pass, then pass is returned. - /// - public AssertResult AnyOf(params AssertResult[] results) + var result = Fail(); + var shouldPass = true; + for (var i = 0; i < results.Length; i++) { - if (results == null || results.Length == 0) - return Fail(ReasonStrings.ResultsNotProvided); - - var result = Fail(); - for (var i = 0; i < results.Length; i++) + if (!results[i].Result) { - if (results[i].Result) - return Pass(); - else - result.AddReason(result); + result.AddReason(results[i]); + shouldPass = false; } - return result; } + return shouldPass ? Pass() : result; + } - /// - /// Aggregates one or more results. If all results are a pass, then pass is returned. - /// - public AssertResult AllOf(params AssertResult[] results) - { - if (results == null || results.Length == 0) - return Fail(ReasonStrings.ResultsNotProvided); + #endregion Operators - var result = Fail(); - var shouldPass = true; - for (var i = 0; i < results.Length; i++) - { - if (!results[i].Result) - { - result.AddReason(results[i]); - shouldPass = false; - } - } - return shouldPass ? Pass() : result; - } + #region Conditions - #endregion Operators + /// + /// The object should match the defined schema. + /// + public AssertResult JsonSchema(PSObject inputObject, string uri) + { + // Guard parameters + if (GuardNullParam(inputObject, nameof(inputObject), out var result) || + GuardNullOrEmptyParam(uri, nameof(uri), out result)) + return result; - #region Conditions + // Get the schema + if (!TryReadJson(uri, out var schemaContent)) + return Fail(ReasonStrings.JsonSchemaNotFound, uri); - /// - /// The object should match the defined schema. - /// - public AssertResult JsonSchema(PSObject inputObject, string uri) + var s = new JsonSerializer(); + var schema = s.Deserialize(JsonValue.Parse(schemaContent)); + + // Get the TargetObject + var json = JsonValue.Parse(inputObject.ToJson()); + + // Validate + var schemaOptions = new JsonSchemaOptions { - // Guard parameters - if (GuardNullParam(inputObject, nameof(inputObject), out var result) || - GuardNullOrEmptyParam(uri, nameof(uri), out result)) - return result; + OutputFormat = SchemaValidationOutputFormat.Basic + }; + var schemaResults = schema.Validate(json, schemaOptions); - // Get the schema - if (!TryReadJson(uri, out var schemaContent)) - return Fail(ReasonStrings.JsonSchemaNotFound, uri); + // Schema is valid + if (schemaResults.IsValid) + return Pass(); - var s = new JsonSerializer(); - var schema = s.Deserialize(JsonValue.Parse(schemaContent)); + // Handle schema invalid + result = Fail(); - // Get the TargetObject - var json = JsonValue.Parse(inputObject.ToJson()); + if (!string.IsNullOrEmpty(schemaResults.ErrorMessage)) + result.AddReason(Operand.FromTarget(), ReasonStrings.JsonSchemaInvalid, schemaResults.InstanceLocation.ToString(), schemaResults.ErrorMessage); - // Validate - var schemaOptions = new JsonSchemaOptions - { - OutputFormat = SchemaValidationOutputFormat.Basic - }; - var schemaResults = schema.Validate(json, schemaOptions); + foreach (var r in schemaResults.NestedResults) + if (!string.IsNullOrEmpty(r.ErrorMessage)) + result.AddReason(Operand.FromTarget(), ReasonStrings.JsonSchemaInvalid, r.InstanceLocation.ToString(), r.ErrorMessage); - // Schema is valid - if (schemaResults.IsValid) - return Pass(); + return result; + } - // Handle schema invalid - result = Fail(); + /// + /// The object should have the $schema property defined with the URI. + /// + /// + /// The parameter 'inputObject' is null. + /// The field '$schema' does not exist. + /// The field value '$schema' is not a string. + /// The value of '$schema' is null or empty. + /// None of the specified schemas match '{0}'. + /// + public AssertResult HasJsonSchema(PSObject inputObject, string[] uri = null, bool ignoreScheme = false) + { + // Guard parameters + if (GuardNullParam(inputObject, nameof(inputObject), out var result) || + GuardField(inputObject, PROPERTY_SCHEMA, false, out var fieldValue, out result) || + GuardString(Operand.FromPath(PROPERTY_SCHEMA), fieldValue, out var actualSchema, out result)) + return result; - if (!string.IsNullOrEmpty(schemaResults.ErrorMessage)) - result.AddReason(Operand.FromTarget(), ReasonStrings.JsonSchemaInvalid, schemaResults.InstanceLocation.ToString(), schemaResults.ErrorMessage); + if (string.IsNullOrEmpty(actualSchema)) + return Fail(Operand.FromPath(PROPERTY_SCHEMA), ReasonStrings.NotHasFieldValue, PROPERTY_SCHEMA); - foreach (var r in schemaResults.NestedResults) - if (!string.IsNullOrEmpty(r.ErrorMessage)) - result.AddReason(Operand.FromTarget(), ReasonStrings.JsonSchemaInvalid, r.InstanceLocation.ToString(), r.ErrorMessage); + return uri == null || uri.Length == 0 || ExpressionHelpers.AnySchema(actualSchema, uri, ignoreScheme, false) + ? Pass() + : Fail(Operand.FromPath(PROPERTY_SCHEMA), ReasonStrings.Assert_NotSpecifiedSchema, actualSchema); + } + /// + /// The object must have any of the specified fields. + /// + /// + /// The parameter 'inputObject' is null. + /// The parameter 'field' is null or empty. + /// Does not exist. + /// + public AssertResult HasField(PSObject inputObject, string[] field, bool caseSensitive = false) + { + // Guard parameters + if (GuardNullParam(inputObject, nameof(inputObject), out var result) || + GuardNullOrEmptyParam(field, nameof(field), out result)) return result; - } - /// - /// The object should have the $schema property defined with the URI. - /// - /// - /// The parameter 'inputObject' is null. - /// The field '$schema' does not exist. - /// The field value '$schema' is not a string. - /// The value of '$schema' is null or empty. - /// None of the specified schemas match '{0}'. - /// - public AssertResult HasJsonSchema(PSObject inputObject, string[] uri = null, bool ignoreScheme = false) + result = Fail(); + for (var i = 0; field != null && i < field.Length; i++) { - // Guard parameters - if (GuardNullParam(inputObject, nameof(inputObject), out var result) || - GuardField(inputObject, PROPERTY_SCHEMA, false, out var fieldValue, out result) || - GuardString(Operand.FromPath(PROPERTY_SCHEMA), fieldValue, out var actualSchema, out result)) - return result; - - if (string.IsNullOrEmpty(actualSchema)) - return Fail(Operand.FromPath(PROPERTY_SCHEMA), ReasonStrings.NotHasFieldValue, PROPERTY_SCHEMA); - - return uri == null || uri.Length == 0 || ExpressionHelpers.AnySchema(actualSchema, uri, ignoreScheme, false) - ? Pass() - : Fail(Operand.FromPath(PROPERTY_SCHEMA), ReasonStrings.Assert_NotSpecifiedSchema, actualSchema); + if (ExpressionHelpers.Exists(PipelineContext.CurrentThread, inputObject, field[i], caseSensitive)) + return Pass(); + + result.AddReason(Operand.FromPath(field[i]), ReasonStrings.Assert_Exists); } + return result; + } - /// - /// The object must have any of the specified fields. - /// - /// - /// The parameter 'inputObject' is null. - /// The parameter 'field' is null or empty. - /// Does not exist. - /// - public AssertResult HasField(PSObject inputObject, string[] field, bool caseSensitive = false) - { - // Guard parameters - if (GuardNullParam(inputObject, nameof(inputObject), out var result) || - GuardNullOrEmptyParam(field, nameof(field), out result)) - return result; + /// + /// The object must not have any of the specified fields. + /// + public AssertResult NotHasField(PSObject inputObject, string[] field, bool caseSensitive = false) + { + // Guard parameters + if (GuardNullParam(inputObject, nameof(inputObject), out var result) || + GuardNullOrEmptyParam(field, nameof(field), out result)) + return result; - result = Fail(); - for (var i = 0; field != null && i < field.Length; i++) + result = Pass(); + for (var i = 0; field != null && i < field.Length; i++) + { + if (ObjectHelper.GetPath( + bindingContext: PipelineContext.CurrentThread, + targetObject: inputObject, + path: field[i], + caseSensitive: caseSensitive, + value: out object _)) { - if (ExpressionHelpers.Exists(PipelineContext.CurrentThread, inputObject, field[i], caseSensitive)) - return Pass(); + if (result.Result) + result = Fail(); - result.AddReason(Operand.FromPath(field[i]), ReasonStrings.Assert_Exists); + result.AddReason(Operand.FromPath(field[i]), ReasonStrings.Assert_NotExists); } - return result; } + return result; + } - /// - /// The object must not have any of the specified fields. - /// - public AssertResult NotHasField(PSObject inputObject, string[] field, bool caseSensitive = false) - { - // Guard parameters - if (GuardNullParam(inputObject, nameof(inputObject), out var result) || - GuardNullOrEmptyParam(field, nameof(field), out result)) - return result; - - result = Pass(); - for (var i = 0; field != null && i < field.Length; i++) - { - if (ObjectHelper.GetPath( - bindingContext: PipelineContext.CurrentThread, - targetObject: inputObject, - path: field[i], - caseSensitive: caseSensitive, - value: out object _)) - { - if (result.Result) - result = Fail(); - - result.AddReason(Operand.FromPath(field[i]), ReasonStrings.Assert_NotExists); - } - } + /// + /// The object must have all of the specified fields. + /// + public AssertResult HasFields(PSObject inputObject, string[] field, bool caseSensitive = false) + { + // Guard parameters + if (GuardNullParam(inputObject, nameof(inputObject), out var result) || + GuardNullOrEmptyParam(field, nameof(field), out result)) return result; - } - /// - /// The object must have all of the specified fields. - /// - public AssertResult HasFields(PSObject inputObject, string[] field, bool caseSensitive = false) + result = Fail(); + var missing = 0; + for (var i = 0; field != null && i < field.Length; i++) { - // Guard parameters - if (GuardNullParam(inputObject, nameof(inputObject), out var result) || - GuardNullOrEmptyParam(field, nameof(field), out result)) - return result; - - result = Fail(); - var missing = 0; - for (var i = 0; field != null && i < field.Length; i++) + if (!ObjectHelper.GetPath( + bindingContext: PipelineContext.CurrentThread, + targetObject: inputObject, + path: field[i], + caseSensitive: caseSensitive, + value: out object _)) { - if (!ObjectHelper.GetPath( - bindingContext: PipelineContext.CurrentThread, - targetObject: inputObject, - path: field[i], - caseSensitive: caseSensitive, - value: out object _)) - { - result.AddReason(Operand.FromPath(field[i]), ReasonStrings.Assert_Exists); - missing++; - } + result.AddReason(Operand.FromPath(field[i]), ReasonStrings.Assert_Exists); + missing++; } - return missing == 0 ? Pass() : result; } + return missing == 0 ? Pass() : result; + } - /// - /// The object should have a specific field with a value set. - /// - /// - /// Does not exist. - /// Is null or empty. - /// Is set to '{0}'. - /// - public AssertResult HasFieldValue(PSObject inputObject, string field, object expectedValue = null) - { - // Guard parameters - if (GuardNullParam(inputObject, nameof(inputObject), out var result) || - GuardNullOrEmptyParam(field, nameof(field), out result)) - return result; + /// + /// The object should have a specific field with a value set. + /// + /// + /// Does not exist. + /// Is null or empty. + /// Is set to '{0}'. + /// + public AssertResult HasFieldValue(PSObject inputObject, string field, object expectedValue = null) + { + // Guard parameters + if (GuardNullParam(inputObject, nameof(inputObject), out var result) || + GuardNullOrEmptyParam(field, nameof(field), out result)) + return result; - // Assert - if (!ObjectHelper.GetPath( - bindingContext: PipelineContext.CurrentThread, - targetObject: inputObject, - path: field, - caseSensitive: false, - value: out object fieldValue)) - return Fail(Operand.FromPath(field), ReasonStrings.Assert_Exists); - else if (ExpressionHelpers.NullOrEmpty(fieldValue)) - return Fail(Operand.FromPath(field), ReasonStrings.Assert_IsNullOrEmpty); - else if (expectedValue != null && !ExpressionHelpers.Equal(expectedValue, fieldValue, caseSensitive: false)) - return Fail(Operand.FromPath(field), ReasonStrings.Assert_IsSetTo, fieldValue); + // Assert + if (!ObjectHelper.GetPath( + bindingContext: PipelineContext.CurrentThread, + targetObject: inputObject, + path: field, + caseSensitive: false, + value: out object fieldValue)) + return Fail(Operand.FromPath(field), ReasonStrings.Assert_Exists); + else if (ExpressionHelpers.NullOrEmpty(fieldValue)) + return Fail(Operand.FromPath(field), ReasonStrings.Assert_IsNullOrEmpty); + else if (expectedValue != null && !ExpressionHelpers.Equal(expectedValue, fieldValue, caseSensitive: false)) + return Fail(Operand.FromPath(field), ReasonStrings.Assert_IsSetTo, fieldValue); + + return Pass(); + } - return Pass(); - } + /// + /// The object should not have the field or the field value is set to the default value. + /// + public AssertResult HasDefaultValue(PSObject inputObject, string field, object defaultValue) + { + // Guard parameters + if (GuardNullParam(inputObject, nameof(inputObject), out var result) || + GuardNullOrEmptyParam(field, nameof(field), out result)) + return result; - /// - /// The object should not have the field or the field value is set to the default value. - /// - public AssertResult HasDefaultValue(PSObject inputObject, string field, object defaultValue) - { - // Guard parameters - if (GuardNullParam(inputObject, nameof(inputObject), out var result) || - GuardNullOrEmptyParam(field, nameof(field), out result)) - return result; + // Assert + return !ObjectHelper.GetPath( + bindingContext: PipelineContext.CurrentThread, + targetObject: inputObject, + path: field, + caseSensitive: false, + value: out object fieldValue) + || ExpressionHelpers.Equal(defaultValue, fieldValue, caseSensitive: false) + ? Pass() + : Fail(ReasonStrings.HasExpectedFieldValue, field, fieldValue); + } - // Assert - return !ObjectHelper.GetPath( - bindingContext: PipelineContext.CurrentThread, - targetObject: inputObject, - path: field, - caseSensitive: false, - value: out object fieldValue) - || ExpressionHelpers.Equal(defaultValue, fieldValue, caseSensitive: false) - ? Pass() - : Fail(ReasonStrings.HasExpectedFieldValue, field, fieldValue); - } + /// + /// The object field value must be null. + /// + public AssertResult Null(PSObject inputObject, string field) + { + // Guard parameters + if (GuardNullParam(inputObject, nameof(inputObject), out var result) || + GuardNullOrEmptyParam(field, nameof(field), out result)) + return result; - /// - /// The object field value must be null. - /// - public AssertResult Null(PSObject inputObject, string field) - { - // Guard parameters - if (GuardNullParam(inputObject, nameof(inputObject), out var result) || - GuardNullOrEmptyParam(field, nameof(field), out result)) - return result; + ObjectHelper.GetPath( + bindingContext: PipelineContext.CurrentThread, + targetObject: inputObject, + path: field, + caseSensitive: false, + value: out object fieldValue); + return fieldValue == null ? Pass() : Fail(Operand.FromPath(field), ReasonStrings.NotNull, field); + } - ObjectHelper.GetPath( - bindingContext: PipelineContext.CurrentThread, - targetObject: inputObject, - path: field, - caseSensitive: false, - value: out object fieldValue); - return fieldValue == null ? Pass() : Fail(Operand.FromPath(field), ReasonStrings.NotNull, field); - } + /// + /// The object field value must not be null. + /// + public AssertResult NotNull(PSObject inputObject, string field) + { + // Guard parameters + if (GuardNullParam(inputObject, nameof(inputObject), out var result) || + GuardNullOrEmptyParam(field, nameof(field), out result) || + GuardField(inputObject, field, false, out var fieldValue, out result)) + return result; - /// - /// The object field value must not be null. - /// - public AssertResult NotNull(PSObject inputObject, string field) - { - // Guard parameters - if (GuardNullParam(inputObject, nameof(inputObject), out var result) || - GuardNullOrEmptyParam(field, nameof(field), out result) || - GuardField(inputObject, field, false, out var fieldValue, out result)) - return result; + return fieldValue == null ? Fail(Operand.FromPath(field), ReasonStrings.Null, field) : Pass(); + } - return fieldValue == null ? Fail(Operand.FromPath(field), ReasonStrings.Null, field) : Pass(); - } + /// + /// The object should not have the field or the field value is null or empty. + /// + public AssertResult NullOrEmpty(PSObject inputObject, string field) + { + // Guard parameters + if (GuardNullParam(inputObject, nameof(inputObject), out var result) || + GuardNullOrEmptyParam(field, nameof(field), out result)) + return result; - /// - /// The object should not have the field or the field value is null or empty. - /// - public AssertResult NullOrEmpty(PSObject inputObject, string field) - { - // Guard parameters - if (GuardNullParam(inputObject, nameof(inputObject), out var result) || - GuardNullOrEmptyParam(field, nameof(field), out result)) - return result; + // Assert + return ObjectHelper.GetPath( + bindingContext: PipelineContext.CurrentThread, + targetObject: inputObject, + path: field, + caseSensitive: false, + value: out object fieldValue) && !ExpressionHelpers.NullOrEmpty(fieldValue) + ? Fail(Operand.FromPath(field), ReasonStrings.NullOrEmpty, field) + : Pass(); + } - // Assert - return ObjectHelper.GetPath( - bindingContext: PipelineContext.CurrentThread, - targetObject: inputObject, - path: field, - caseSensitive: false, - value: out object fieldValue) && !ExpressionHelpers.NullOrEmpty(fieldValue) - ? Fail(Operand.FromPath(field), ReasonStrings.NullOrEmpty, field) - : Pass(); - } + /// + /// The value should start with the any of the specified prefixes. Only applies to strings. + /// + public AssertResult StartsWith(PSObject inputObject, string field, string[] prefix, bool caseSensitive = false) + { + // Guard parameters + if (GuardNullParam(inputObject, nameof(inputObject), out var result) || + GuardNullOrEmptyParam(field, nameof(field), out result) || + GuardNullParam(prefix, nameof(prefix), out result) || + GuardField(inputObject, field, false, out var fieldValue, out result) || + GuardStringOrArray(Operand.FromPath(field), fieldValue, out var value, out result)) + return result; - /// - /// The value should start with the any of the specified prefixes. Only applies to strings. - /// - public AssertResult StartsWith(PSObject inputObject, string field, string[] prefix, bool caseSensitive = false) - { - // Guard parameters - if (GuardNullParam(inputObject, nameof(inputObject), out var result) || - GuardNullOrEmptyParam(field, nameof(field), out result) || - GuardNullParam(prefix, nameof(prefix), out result) || - GuardField(inputObject, field, false, out var fieldValue, out result) || - GuardStringOrArray(Operand.FromPath(field), fieldValue, out var value, out result)) - return result; - - if (prefix == null || prefix.Length == 0) - return Pass(); + if (prefix == null || prefix.Length == 0) + return Pass(); - // Assert - for (var i_prefix = 0; i_prefix < prefix.Length; i_prefix++) + // Assert + for (var i_prefix = 0; i_prefix < prefix.Length; i_prefix++) + { + for (var i_value = 0; i_value < value.Length; i_value++) { - for (var i_value = 0; i_value < value.Length; i_value++) - { - if (ExpressionHelpers.StartsWith(value[i_value], prefix[i_prefix], caseSensitive)) - return Pass(); - } + if (ExpressionHelpers.StartsWith(value[i_value], prefix[i_prefix], caseSensitive)) + return Pass(); } - return Fail(Operand.FromPath(field), ReasonStrings.StartsWith, field, FormatArray(prefix)); } + return Fail(Operand.FromPath(field), ReasonStrings.StartsWith, field, FormatArray(prefix)); + } - /// - /// The value should not start with the any of the specified prefixes. Only applies to strings. - /// - /// - /// The parameter 'inputObject' is null. - /// The parameter 'field' is null or empty. - /// The parameter 'prefix' is null. - /// The field '{0}' does not exist. - /// The value '{0}' starts with '{1}'. - /// - public AssertResult NotStartsWith(PSObject inputObject, string field, string[] prefix, bool caseSensitive = false) - { - // Guard parameters - if (GuardNullParam(inputObject, nameof(inputObject), out var result) || - GuardNullOrEmptyParam(field, nameof(field), out result) || - GuardNullParam(prefix, nameof(prefix), out result) || - GuardField(inputObject, field, false, out var fieldValue, out result)) - return result; - - if (prefix == null || prefix.Length == 0 || GuardStringOrArray(Operand.FromPath(field), fieldValue, out var value, out _)) - return Pass(); + /// + /// The value should not start with the any of the specified prefixes. Only applies to strings. + /// + /// + /// The parameter 'inputObject' is null. + /// The parameter 'field' is null or empty. + /// The parameter 'prefix' is null. + /// The field '{0}' does not exist. + /// The value '{0}' starts with '{1}'. + /// + public AssertResult NotStartsWith(PSObject inputObject, string field, string[] prefix, bool caseSensitive = false) + { + // Guard parameters + if (GuardNullParam(inputObject, nameof(inputObject), out var result) || + GuardNullOrEmptyParam(field, nameof(field), out result) || + GuardNullParam(prefix, nameof(prefix), out result) || + GuardField(inputObject, field, false, out var fieldValue, out result)) + return result; - // Assert - for (var i_prefix = 0; i_prefix < prefix.Length; i_prefix++) - { - for (var i_value = 0; i_value < value.Length; i_value++) - { - if (ExpressionHelpers.StartsWith(value[i_value], prefix[i_prefix], caseSensitive)) - return Fail(Operand.FromPath(field), ReasonStrings.Assert_StartsWith, value, prefix[i_prefix]); - } - } + if (prefix == null || prefix.Length == 0 || GuardStringOrArray(Operand.FromPath(field), fieldValue, out var value, out _)) return Pass(); - } - /// - /// The value should end with the any of the specified suffix. Only applies to strings. - /// - public AssertResult EndsWith(PSObject inputObject, string field, string[] suffix, bool caseSensitive = false) + // Assert + for (var i_prefix = 0; i_prefix < prefix.Length; i_prefix++) { - // Guard parameters - if (GuardNullParam(inputObject, nameof(inputObject), out var result) || - GuardNullOrEmptyParam(field, nameof(field), out result) || - GuardNullParam(suffix, nameof(suffix), out result) || - GuardField(inputObject, field, false, out var fieldValue, out result) || - GuardStringOrArray(Operand.FromPath(field), fieldValue, out var value, out result)) - return result; - - if (suffix == null || suffix.Length == 0) - return Pass(); - - // Assert - for (var i_suffix = 0; i_suffix < suffix.Length; i_suffix++) + for (var i_value = 0; i_value < value.Length; i_value++) { - for (var i_value = 0; i_value < value.Length; i_value++) - { - if (ExpressionHelpers.EndsWith(value[i_value], suffix[i_suffix], caseSensitive)) - return Pass(); - } + if (ExpressionHelpers.StartsWith(value[i_value], prefix[i_prefix], caseSensitive)) + return Fail(Operand.FromPath(field), ReasonStrings.Assert_StartsWith, value, prefix[i_prefix]); } - return Fail(Operand.FromPath(field), ReasonStrings.EndsWith, field, FormatArray(suffix)); } + return Pass(); + } - /// - /// The value should not end with the any of the specified suffix. Only applies to strings. - /// - /// - /// The parameter 'inputObject' is null. - /// The parameter 'field' is null or empty. - /// The parameter 'prefix' is null. - /// The field '{0}' does not exist. - /// The value '{0}' ends with '{1}'. - /// - public AssertResult NotEndsWith(PSObject inputObject, string field, string[] suffix, bool caseSensitive = false) - { - // Guard parameters - if (GuardNullParam(inputObject, nameof(inputObject), out var result) || - GuardNullOrEmptyParam(field, nameof(field), out result) || - GuardNullParam(suffix, nameof(suffix), out result) || - GuardField(inputObject, field, false, out var fieldValue, out result)) - return result; - - if (suffix == null || suffix.Length == 0 || GuardStringOrArray(Operand.FromPath(field), fieldValue, out var value, out _)) - return Pass(); + /// + /// The value should end with the any of the specified suffix. Only applies to strings. + /// + public AssertResult EndsWith(PSObject inputObject, string field, string[] suffix, bool caseSensitive = false) + { + // Guard parameters + if (GuardNullParam(inputObject, nameof(inputObject), out var result) || + GuardNullOrEmptyParam(field, nameof(field), out result) || + GuardNullParam(suffix, nameof(suffix), out result) || + GuardField(inputObject, field, false, out var fieldValue, out result) || + GuardStringOrArray(Operand.FromPath(field), fieldValue, out var value, out result)) + return result; - // Assert - for (var i_suffix = 0; i_suffix < suffix.Length; i_suffix++) - { - for (var i_value = 0; i_value < value.Length; i_value++) - { - if (ExpressionHelpers.EndsWith(value[i_value], suffix[i_suffix], caseSensitive)) - return Fail(Operand.FromPath(field), ReasonStrings.Assert_EndsWith, value, suffix[i_suffix]); - } - } + if (suffix == null || suffix.Length == 0) return Pass(); - } - /// - /// The value should contain with the any of the specified text. Only applies to strings. - /// - public AssertResult Contains(PSObject inputObject, string field, string[] text, bool caseSensitive = false) + // Assert + for (var i_suffix = 0; i_suffix < suffix.Length; i_suffix++) { - // Guard parameters - if (GuardNullParam(inputObject, nameof(inputObject), out var result) || - GuardNullOrEmptyParam(field, nameof(field), out result) || - GuardNullParam(text, nameof(text), out result) || - GuardField(inputObject, field, false, out var fieldValue, out result) || - GuardStringOrArray(Operand.FromPath(field), fieldValue, out var value, out result)) - return result; - - if (text == null || text.Length == 0) - return Pass(); - - // Assert - for (var i_text = 0; i_text < text.Length; i_text++) + for (var i_value = 0; i_value < value.Length; i_value++) { - for (var i_value = 0; i_value < value.Length; i_value++) - { - if (ExpressionHelpers.Contains(value[i_value], text[i_text], caseSensitive)) - return Pass(); - } + if (ExpressionHelpers.EndsWith(value[i_value], suffix[i_suffix], caseSensitive)) + return Pass(); } - return Fail(Operand.FromPath(field), ReasonStrings.Contains, field, FormatArray(text)); } + return Fail(Operand.FromPath(field), ReasonStrings.EndsWith, field, FormatArray(suffix)); + } - /// - /// The value should not contain with the any of the specified text. Only applies to strings. - /// - /// - /// The parameter 'inputObject' is null. - /// The parameter 'field' is null or empty. - /// The parameter 'prefix' is null. - /// The field '{0}' does not exist. - /// The value '{0}' contains '{1}'. - /// - public AssertResult NotContains(PSObject inputObject, string field, string[] text, bool caseSensitive = false) - { - // Guard parameters - if (GuardNullParam(inputObject, nameof(inputObject), out var result) || - GuardNullOrEmptyParam(field, nameof(field), out result) || - GuardNullParam(text, nameof(text), out result) || - GuardField(inputObject, field, false, out var fieldValue, out result)) - return result; - - if (text == null || text.Length == 0 || GuardStringOrArray(Operand.FromPath(field), fieldValue, out var value, out _)) - return Pass(); + /// + /// The value should not end with the any of the specified suffix. Only applies to strings. + /// + /// + /// The parameter 'inputObject' is null. + /// The parameter 'field' is null or empty. + /// The parameter 'prefix' is null. + /// The field '{0}' does not exist. + /// The value '{0}' ends with '{1}'. + /// + public AssertResult NotEndsWith(PSObject inputObject, string field, string[] suffix, bool caseSensitive = false) + { + // Guard parameters + if (GuardNullParam(inputObject, nameof(inputObject), out var result) || + GuardNullOrEmptyParam(field, nameof(field), out result) || + GuardNullParam(suffix, nameof(suffix), out result) || + GuardField(inputObject, field, false, out var fieldValue, out result)) + return result; - // Assert - for (var i_text = 0; i_text < text.Length; i_text++) - { - for (var i_value = 0; i_value < value.Length; i_value++) - { - if (ExpressionHelpers.Contains(value[i_value], text[i_text], caseSensitive)) - return Fail(Operand.FromPath(field), ReasonStrings.Assert_Contains, value, text[i_text]); - } - } + if (suffix == null || suffix.Length == 0 || GuardStringOrArray(Operand.FromPath(field), fieldValue, out var value, out _)) return Pass(); - } - /// - /// The object field value should only contain lowercase characters. - /// - public AssertResult IsLower(PSObject inputObject, string field, bool requireLetters = false) + // Assert + for (var i_suffix = 0; i_suffix < suffix.Length; i_suffix++) { - // Guard parameters - if (GuardNullParam(inputObject, nameof(inputObject), out var result) || - GuardNullOrEmptyParam(field, nameof(field), out result) || - GuardField(inputObject, field, false, out var fieldValue, out result) || - GuardString(Operand.FromPath(field), fieldValue, out var value, out result)) - return result; + for (var i_value = 0; i_value < value.Length; i_value++) + { + if (ExpressionHelpers.EndsWith(value[i_value], suffix[i_suffix], caseSensitive)) + return Fail(Operand.FromPath(field), ReasonStrings.Assert_EndsWith, value, suffix[i_suffix]); + } + } + return Pass(); + } - if (!ExpressionHelpers.IsLower(value, requireLetters, out var notLetters)) - return Fail(Operand.FromPath(field), notLetters ? ReasonStrings.IsLetter : ReasonStrings.Assert_IsLower, value); + /// + /// The value should contain with the any of the specified text. Only applies to strings. + /// + public AssertResult Contains(PSObject inputObject, string field, string[] text, bool caseSensitive = false) + { + // Guard parameters + if (GuardNullParam(inputObject, nameof(inputObject), out var result) || + GuardNullOrEmptyParam(field, nameof(field), out result) || + GuardNullParam(text, nameof(text), out result) || + GuardField(inputObject, field, false, out var fieldValue, out result) || + GuardStringOrArray(Operand.FromPath(field), fieldValue, out var value, out result)) + return result; + if (text == null || text.Length == 0) return Pass(); - } - /// - /// The object field value should only contain uppercase characters. - /// - public AssertResult IsUpper(PSObject inputObject, string field, bool requireLetters = false) + // Assert + for (var i_text = 0; i_text < text.Length; i_text++) { - // Guard parameters - if (GuardNullParam(inputObject, nameof(inputObject), out var result) || - GuardNullOrEmptyParam(field, nameof(field), out result) || - GuardField(inputObject, field, false, out var fieldValue, out result) || - GuardString(Operand.FromPath(field), fieldValue, out var value, out result)) - return result; + for (var i_value = 0; i_value < value.Length; i_value++) + { + if (ExpressionHelpers.Contains(value[i_value], text[i_text], caseSensitive)) + return Pass(); + } + } + return Fail(Operand.FromPath(field), ReasonStrings.Contains, field, FormatArray(text)); + } - if (!ExpressionHelpers.IsUpper(value, requireLetters, out var notLetters)) - return Fail(Operand.FromPath(field), notLetters ? ReasonStrings.IsLetter : ReasonStrings.Assert_IsUpper, value); + /// + /// The value should not contain with the any of the specified text. Only applies to strings. + /// + /// + /// The parameter 'inputObject' is null. + /// The parameter 'field' is null or empty. + /// The parameter 'prefix' is null. + /// The field '{0}' does not exist. + /// The value '{0}' contains '{1}'. + /// + public AssertResult NotContains(PSObject inputObject, string field, string[] text, bool caseSensitive = false) + { + // Guard parameters + if (GuardNullParam(inputObject, nameof(inputObject), out var result) || + GuardNullOrEmptyParam(field, nameof(field), out result) || + GuardNullParam(text, nameof(text), out result) || + GuardField(inputObject, field, false, out var fieldValue, out result)) + return result; + if (text == null || text.Length == 0 || GuardStringOrArray(Operand.FromPath(field), fieldValue, out var value, out _)) return Pass(); - } - /// - /// The object field value should be a numeric type. - /// - /// - /// The value '{0}' is not numeric. - /// - public AssertResult IsNumeric(PSObject inputObject, string field, bool convert = false) + // Assert + for (var i_text = 0; i_text < text.Length; i_text++) { - // Guard parameters - if (GuardNullParam(inputObject, nameof(inputObject), out var result) || - GuardNullOrEmptyParam(field, nameof(field), out result) || - GuardField(inputObject, field, false, out var fieldValue, out result) || - GuardNullFieldValue(field, fieldValue, out result)) - return result; - - return ExpressionHelpers.TryInt(fieldValue, convert, out _) || - ExpressionHelpers.TryLong(fieldValue, convert, out _) || - ExpressionHelpers.TryFloat(fieldValue, convert, out _) || - ExpressionHelpers.TryByte(fieldValue, convert, out _) || - ExpressionHelpers.TryDouble(fieldValue, convert, out _) - ? Pass() - : Fail(Operand.FromPath(field), ReasonStrings.Assert_NotNumeric, fieldValue); + for (var i_value = 0; i_value < value.Length; i_value++) + { + if (ExpressionHelpers.Contains(value[i_value], text[i_text], caseSensitive)) + return Fail(Operand.FromPath(field), ReasonStrings.Assert_Contains, value, text[i_text]); + } } + return Pass(); + } - /// - /// The object field value should be an integer type. - /// - /// - /// The value '{0}' is not an integer. - /// - public AssertResult IsInteger(PSObject inputObject, string field, bool convert = false) - { - // Guard parameters - if (GuardNullParam(inputObject, nameof(inputObject), out var result) || - GuardNullOrEmptyParam(field, nameof(field), out result) || - GuardField(inputObject, field, false, out var fieldValue, out result) || - GuardNullFieldValue(field, fieldValue, out result)) - return result; - - return ExpressionHelpers.TryInt(fieldValue, convert, out _) || - ExpressionHelpers.TryLong(fieldValue, convert, out _) || - ExpressionHelpers.TryByte(fieldValue, convert, out _) - ? Pass() - : Fail(Operand.FromPath(field), ReasonStrings.Assert_NotInteger, fieldValue); - } + /// + /// The object field value should only contain lowercase characters. + /// + public AssertResult IsLower(PSObject inputObject, string field, bool requireLetters = false) + { + // Guard parameters + if (GuardNullParam(inputObject, nameof(inputObject), out var result) || + GuardNullOrEmptyParam(field, nameof(field), out result) || + GuardField(inputObject, field, false, out var fieldValue, out result) || + GuardString(Operand.FromPath(field), fieldValue, out var value, out result)) + return result; - /// - /// The object field value should be a boolean. - /// - /// - /// The value '{0}' is not a boolean. - /// - public AssertResult IsBoolean(PSObject inputObject, string field, bool convert = false) - { - // Guard parameters - if (GuardNullParam(inputObject, nameof(inputObject), out var result) || - GuardNullOrEmptyParam(field, nameof(field), out result) || - GuardField(inputObject, field, false, out var fieldValue, out result) || - GuardNullFieldValue(field, fieldValue, out result)) - return result; - - return ExpressionHelpers.TryBool(fieldValue, convert, out _) - ? Pass() - : Fail(Operand.FromPath(field), ReasonStrings.Assert_NotBoolean, fieldValue); - } + if (!ExpressionHelpers.IsLower(value, requireLetters, out var notLetters)) + return Fail(Operand.FromPath(field), notLetters ? ReasonStrings.IsLetter : ReasonStrings.Assert_IsLower, value); - /// - /// The object field value should be an array. - /// - /// - /// The value '{0}' is not an array - /// - public AssertResult IsArray(PSObject inputObject, string field) - { - // Guard parameters - if (GuardNullParam(inputObject, nameof(inputObject), out var result) || - GuardNullOrEmptyParam(field, nameof(field), out result) || - GuardField(inputObject, field, false, out var fieldValue, out result) || - GuardNullFieldValue(field, fieldValue, out result)) - return result; - - return ExpressionHelpers.TryArray(fieldValue, out _) - ? Pass() - : Fail(Operand.FromPath(field), ReasonStrings.Assert_NotArray, fieldValue); - } + return Pass(); + } - /// - /// The object field value should be a string. - /// - /// - /// The value '{0}' is not a string. - /// - public AssertResult IsString(PSObject inputObject, string field) - { - // Guard parameters - if (GuardNullParam(inputObject, nameof(inputObject), out var result) || - GuardNullOrEmptyParam(field, nameof(field), out result) || - GuardField(inputObject, field, false, out var fieldValue, out result) || - GuardNullFieldValue(field, fieldValue, out result)) - return result; - - return ExpressionHelpers.TryString(fieldValue, out _) - ? Pass() - : Fail(Operand.FromPath(field), ReasonStrings.Assert_NotString, fieldValue); - } + /// + /// The object field value should only contain uppercase characters. + /// + public AssertResult IsUpper(PSObject inputObject, string field, bool requireLetters = false) + { + // Guard parameters + if (GuardNullParam(inputObject, nameof(inputObject), out var result) || + GuardNullOrEmptyParam(field, nameof(field), out result) || + GuardField(inputObject, field, false, out var fieldValue, out result) || + GuardString(Operand.FromPath(field), fieldValue, out var value, out result)) + return result; - /// - /// The object field value should be a DateTime. - /// - /// - /// The value '{0}' is not a date. - /// - public AssertResult IsDateTime(PSObject inputObject, string field, bool convert = false) - { - // Guard parameters - if (GuardNullParam(inputObject, nameof(inputObject), out var result) || - GuardNullOrEmptyParam(field, nameof(field), out result) || - GuardField(inputObject, field, false, out var fieldValue, out result) || - GuardNullFieldValue(field, fieldValue, out result)) - return result; - - return ExpressionHelpers.TryDateTime(fieldValue, convert, out _) - ? Pass() - : Fail(Operand.FromPath(field), ReasonStrings.Assert_NotDateTime, fieldValue); - } + if (!ExpressionHelpers.IsUpper(value, requireLetters, out var notLetters)) + return Fail(Operand.FromPath(field), notLetters ? ReasonStrings.IsLetter : ReasonStrings.Assert_IsUpper, value); - /// - /// The object field value should be one of the specified types. - /// - public AssertResult TypeOf(PSObject inputObject, string field, Type[] type) - { - // Guard parameters - if (GuardNullParam(inputObject, nameof(inputObject), out var result) || - GuardNullOrEmptyParam(field, nameof(field), out result) || - GuardNullOrEmptyParam(type, nameof(type), out result) || - GuardField(inputObject, field, false, out var fieldValue, out result) || - GuardNullFieldValue(field, fieldValue, out result)) - return result; - - result = Fail(); - for (var i = 0; type != null && i < type.Length; i++) - { - var o = ExpressionHelpers.GetBaseObject(fieldValue); - if (type[i].IsAssignableFrom(fieldValue.GetType()) || - type[i].IsAssignableFrom(o.GetType()) || - TryTypeName(fieldValue, type[i].FullName)) - return Pass(); + return Pass(); + } - result.AddReason(Operand.FromPath(field), ReasonStrings.Type, type[i].Name, GetTypeName(fieldValue), fieldValue); - } + /// + /// The object field value should be a numeric type. + /// + /// + /// The value '{0}' is not numeric. + /// + public AssertResult IsNumeric(PSObject inputObject, string field, bool convert = false) + { + // Guard parameters + if (GuardNullParam(inputObject, nameof(inputObject), out var result) || + GuardNullOrEmptyParam(field, nameof(field), out result) || + GuardField(inputObject, field, false, out var fieldValue, out result) || + GuardNullFieldValue(field, fieldValue, out result)) return result; - } - /// - /// The object field value should be one of the specified types. - /// - public AssertResult TypeOf(PSObject inputObject, string field, string[] type) - { - // Guard parameters - if (GuardNullParam(inputObject, nameof(inputObject), out var result) || - GuardNullOrEmptyParam(field, nameof(field), out result) || - GuardNullOrEmptyParam(type, nameof(type), out result) || - GuardField(inputObject, field, false, out var fieldValue, out result) || - GuardNullFieldValue(field, fieldValue, out result)) - return result; - - result = Fail(); - for (var i = 0; type != null && i < type.Length; i++) - { - var o = ExpressionHelpers.GetBaseObject(fieldValue); - if (StringComparer.OrdinalIgnoreCase.Equals(fieldValue.GetType().FullName, type[i]) || - StringComparer.OrdinalIgnoreCase.Equals(o.GetType().FullName, type[i]) || - TryTypeName(fieldValue, type[i])) - return Pass(); + return ExpressionHelpers.TryInt(fieldValue, convert, out _) || + ExpressionHelpers.TryLong(fieldValue, convert, out _) || + ExpressionHelpers.TryFloat(fieldValue, convert, out _) || + ExpressionHelpers.TryByte(fieldValue, convert, out _) || + ExpressionHelpers.TryDouble(fieldValue, convert, out _) + ? Pass() + : Fail(Operand.FromPath(field), ReasonStrings.Assert_NotNumeric, fieldValue); + } - result.AddReason(Operand.FromPath(field), ReasonStrings.Type, type[i], GetTypeName(fieldValue), fieldValue); - } + /// + /// The object field value should be an integer type. + /// + /// + /// The value '{0}' is not an integer. + /// + public AssertResult IsInteger(PSObject inputObject, string field, bool convert = false) + { + // Guard parameters + if (GuardNullParam(inputObject, nameof(inputObject), out var result) || + GuardNullOrEmptyParam(field, nameof(field), out result) || + GuardField(inputObject, field, false, out var fieldValue, out result) || + GuardNullFieldValue(field, fieldValue, out result)) + return result; + + return ExpressionHelpers.TryInt(fieldValue, convert, out _) || + ExpressionHelpers.TryLong(fieldValue, convert, out _) || + ExpressionHelpers.TryByte(fieldValue, convert, out _) + ? Pass() + : Fail(Operand.FromPath(field), ReasonStrings.Assert_NotInteger, fieldValue); + } + + /// + /// The object field value should be a boolean. + /// + /// + /// The value '{0}' is not a boolean. + /// + public AssertResult IsBoolean(PSObject inputObject, string field, bool convert = false) + { + // Guard parameters + if (GuardNullParam(inputObject, nameof(inputObject), out var result) || + GuardNullOrEmptyParam(field, nameof(field), out result) || + GuardField(inputObject, field, false, out var fieldValue, out result) || + GuardNullFieldValue(field, fieldValue, out result)) + return result; + + return ExpressionHelpers.TryBool(fieldValue, convert, out _) + ? Pass() + : Fail(Operand.FromPath(field), ReasonStrings.Assert_NotBoolean, fieldValue); + } + + /// + /// The object field value should be an array. + /// + /// + /// The value '{0}' is not an array + /// + public AssertResult IsArray(PSObject inputObject, string field) + { + // Guard parameters + if (GuardNullParam(inputObject, nameof(inputObject), out var result) || + GuardNullOrEmptyParam(field, nameof(field), out result) || + GuardField(inputObject, field, false, out var fieldValue, out result) || + GuardNullFieldValue(field, fieldValue, out result)) + return result; + + return ExpressionHelpers.TryArray(fieldValue, out _) + ? Pass() + : Fail(Operand.FromPath(field), ReasonStrings.Assert_NotArray, fieldValue); + } + + /// + /// The object field value should be a string. + /// + /// + /// The value '{0}' is not a string. + /// + public AssertResult IsString(PSObject inputObject, string field) + { + // Guard parameters + if (GuardNullParam(inputObject, nameof(inputObject), out var result) || + GuardNullOrEmptyParam(field, nameof(field), out result) || + GuardField(inputObject, field, false, out var fieldValue, out result) || + GuardNullFieldValue(field, fieldValue, out result)) + return result; + + return ExpressionHelpers.TryString(fieldValue, out _) + ? Pass() + : Fail(Operand.FromPath(field), ReasonStrings.Assert_NotString, fieldValue); + } + + /// + /// The object field value should be a DateTime. + /// + /// + /// The value '{0}' is not a date. + /// + public AssertResult IsDateTime(PSObject inputObject, string field, bool convert = false) + { + // Guard parameters + if (GuardNullParam(inputObject, nameof(inputObject), out var result) || + GuardNullOrEmptyParam(field, nameof(field), out result) || + GuardField(inputObject, field, false, out var fieldValue, out result) || + GuardNullFieldValue(field, fieldValue, out result)) + return result; + + return ExpressionHelpers.TryDateTime(fieldValue, convert, out _) + ? Pass() + : Fail(Operand.FromPath(field), ReasonStrings.Assert_NotDateTime, fieldValue); + } + + /// + /// The object field value should be one of the specified types. + /// + public AssertResult TypeOf(PSObject inputObject, string field, Type[] type) + { + // Guard parameters + if (GuardNullParam(inputObject, nameof(inputObject), out var result) || + GuardNullOrEmptyParam(field, nameof(field), out result) || + GuardNullOrEmptyParam(type, nameof(type), out result) || + GuardField(inputObject, field, false, out var fieldValue, out result) || + GuardNullFieldValue(field, fieldValue, out result)) return result; - } - /// - /// The Version assertion method checks the field value is a valid semantic version. - /// A constraint can optionally be provided to require the semantic version to be within a range. - /// - /// - /// - /// Only applies to strings. - /// - public AssertResult Version(PSObject inputObject, string field, string constraint = null, bool includePrerelease = false) + result = Fail(); + for (var i = 0; type != null && i < type.Length; i++) { - // Guard parameters - if (GuardNullParam(inputObject, nameof(inputObject), out var result) || - GuardNullOrEmptyParam(field, nameof(field), out result) || - GuardField(inputObject, field, false, out var fieldValue, out result) || - GuardSemanticVersion(Operand.FromPath(field), fieldValue, out var value, out result)) - return result; - - if (!SemanticVersion.TryParseConstraint(constraint, out var c, includePrerelease)) - throw new RuleException(string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.VersionConstraintInvalid, value)); - - // Assert - return c != null && !c.Equals(value) ? Fail(Operand.FromPath(field), ReasonStrings.VersionContraint, value, constraint) : Pass(); + var o = ExpressionHelpers.GetBaseObject(fieldValue); + if (type[i].IsAssignableFrom(fieldValue.GetType()) || + type[i].IsAssignableFrom(o.GetType()) || + TryTypeName(fieldValue, type[i].FullName)) + return Pass(); + + result.AddReason(Operand.FromPath(field), ReasonStrings.Type, type[i].Name, GetTypeName(fieldValue), fieldValue); } + return result; + } + + /// + /// The object field value should be one of the specified types. + /// + public AssertResult TypeOf(PSObject inputObject, string field, string[] type) + { + // Guard parameters + if (GuardNullParam(inputObject, nameof(inputObject), out var result) || + GuardNullOrEmptyParam(field, nameof(field), out result) || + GuardNullOrEmptyParam(type, nameof(type), out result) || + GuardField(inputObject, field, false, out var fieldValue, out result) || + GuardNullFieldValue(field, fieldValue, out result)) + return result; - /// - /// The APIVersion assertion method checks the field value is a valid date version. - /// A constraint can optionally be provided to require the date version to be within a range. - /// - /// - /// - /// Only applies to strings. - /// - public AssertResult APIVersion(PSObject inputObject, string field, string constraint = null, bool includePrerelease = false) + result = Fail(); + for (var i = 0; type != null && i < type.Length; i++) { - // Guard parameters - if (GuardNullParam(inputObject, nameof(inputObject), out var result) || - GuardNullOrEmptyParam(field, nameof(field), out result) || - GuardField(inputObject, field, false, out var fieldValue, out result) || - GuardDateVersion(Operand.FromPath(field), fieldValue, out var value, out result)) - return result; - - if (!DateVersion.TryParseConstraint(constraint, out var c, includePrerelease)) - throw new RuleException(string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.VersionConstraintInvalid, value)); - - // Assert - return c != null && !c.Equals(value) ? Fail(Operand.FromPath(field), ReasonStrings.VersionContraint, value, constraint) : Pass(); + var o = ExpressionHelpers.GetBaseObject(fieldValue); + if (StringComparer.OrdinalIgnoreCase.Equals(fieldValue.GetType().FullName, type[i]) || + StringComparer.OrdinalIgnoreCase.Equals(o.GetType().FullName, type[i]) || + TryTypeName(fieldValue, type[i])) + return Pass(); + + result.AddReason(Operand.FromPath(field), ReasonStrings.Type, type[i], GetTypeName(fieldValue), fieldValue); } + return result; + } - /// - /// The Greater assertion method checks the field value is greater than the specified value. - /// The field value can either be an integer, float, array, or string. - /// - /// - public AssertResult Greater(PSObject inputObject, string field, int value, bool convert = false) - { - // Guard parameters - if (GuardNullParam(inputObject, nameof(inputObject), out var result) || - GuardNullOrEmptyParam(field, nameof(field), out result) || - GuardField(inputObject, field, false, out var fieldValue, out result)) - return result; + /// + /// The Version assertion method checks the field value is a valid semantic version. + /// A constraint can optionally be provided to require the semantic version to be within a range. + /// + /// + /// + /// Only applies to strings. + /// + public AssertResult Version(PSObject inputObject, string field, string constraint = null, bool includePrerelease = false) + { + // Guard parameters + if (GuardNullParam(inputObject, nameof(inputObject), out var result) || + GuardNullOrEmptyParam(field, nameof(field), out result) || + GuardField(inputObject, field, false, out var fieldValue, out result) || + GuardSemanticVersion(Operand.FromPath(field), fieldValue, out var value, out result)) + return result; - if (ExpressionHelpers.CompareNumeric(fieldValue, value, convert, out var compare, out var actual)) - return compare > 0 ? Pass() : Fail(Operand.FromPath(field), ReasonStrings.Greater, actual, value); + if (!SemanticVersion.TryParseConstraint(constraint, out var c, includePrerelease)) + throw new RuleException(string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.VersionConstraintInvalid, value)); - return Fail(Operand.FromPath(field), ReasonStrings.Compare, fieldValue, value); - } + // Assert + return c != null && !c.Equals(value) ? Fail(Operand.FromPath(field), ReasonStrings.VersionContraint, value, constraint) : Pass(); + } - /// - /// The GreaterOrEqual assertion method checks the field value is greater or equal to the specified value. - /// The field value can either be an integer, float, array, or string. - /// - /// - public AssertResult GreaterOrEqual(PSObject inputObject, string field, int value, bool convert = false) - { - // Guard parameters - if (GuardNullParam(inputObject, nameof(inputObject), out var result) || - GuardNullOrEmptyParam(field, nameof(field), out result) || - GuardField(inputObject, field, false, out var fieldValue, out result)) - return result; + /// + /// The APIVersion assertion method checks the field value is a valid date version. + /// A constraint can optionally be provided to require the date version to be within a range. + /// + /// + /// + /// Only applies to strings. + /// + public AssertResult APIVersion(PSObject inputObject, string field, string constraint = null, bool includePrerelease = false) + { + // Guard parameters + if (GuardNullParam(inputObject, nameof(inputObject), out var result) || + GuardNullOrEmptyParam(field, nameof(field), out result) || + GuardField(inputObject, field, false, out var fieldValue, out result) || + GuardDateVersion(Operand.FromPath(field), fieldValue, out var value, out result)) + return result; - if (ExpressionHelpers.CompareNumeric(fieldValue, value, convert, out var compare, out var actual)) - return compare >= 0 ? Pass() : Fail(Operand.FromPath(field), ReasonStrings.GreaterOrEqual, actual, value); + if (!DateVersion.TryParseConstraint(constraint, out var c, includePrerelease)) + throw new RuleException(string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.VersionConstraintInvalid, value)); - return Fail(Operand.FromPath(field), ReasonStrings.Compare, fieldValue, value); - } + // Assert + return c != null && !c.Equals(value) ? Fail(Operand.FromPath(field), ReasonStrings.VersionContraint, value, constraint) : Pass(); + } - /// - /// The Less assertion method checks the field value is less than the specified value. - /// The field value can either be an integer, float, array, or string. - /// - /// - public AssertResult Less(PSObject inputObject, string field, int value, bool convert = false) - { - // Guard parameters - if (GuardNullParam(inputObject, nameof(inputObject), out var result) || - GuardNullOrEmptyParam(field, nameof(field), out result) || - GuardField(inputObject, field, false, out var fieldValue, out result)) - return result; + /// + /// The Greater assertion method checks the field value is greater than the specified value. + /// The field value can either be an integer, float, array, or string. + /// + /// + public AssertResult Greater(PSObject inputObject, string field, int value, bool convert = false) + { + // Guard parameters + if (GuardNullParam(inputObject, nameof(inputObject), out var result) || + GuardNullOrEmptyParam(field, nameof(field), out result) || + GuardField(inputObject, field, false, out var fieldValue, out result)) + return result; - if (ExpressionHelpers.CompareNumeric(fieldValue, value, convert, out var compare, out var actual)) - return compare < 0 ? Pass() : Fail(Operand.FromPath(field), ReasonStrings.Less, actual, value); + if (ExpressionHelpers.CompareNumeric(fieldValue, value, convert, out var compare, out var actual)) + return compare > 0 ? Pass() : Fail(Operand.FromPath(field), ReasonStrings.Greater, actual, value); - return Fail(Operand.FromPath(field), ReasonStrings.Compare, fieldValue, value); - } + return Fail(Operand.FromPath(field), ReasonStrings.Compare, fieldValue, value); + } - /// - /// The LessOrEqual assertion method checks the field value is less or equal to the specified value. - /// The field value can either be an integer, float, array, or string. - /// - /// - public AssertResult LessOrEqual(PSObject inputObject, string field, int value, bool convert = false) - { - // Guard parameters - if (GuardNullParam(inputObject, nameof(inputObject), out var result) || - GuardNullOrEmptyParam(field, nameof(field), out result) || - GuardField(inputObject, field, false, out var fieldValue, out result)) - return result; + /// + /// The GreaterOrEqual assertion method checks the field value is greater or equal to the specified value. + /// The field value can either be an integer, float, array, or string. + /// + /// + public AssertResult GreaterOrEqual(PSObject inputObject, string field, int value, bool convert = false) + { + // Guard parameters + if (GuardNullParam(inputObject, nameof(inputObject), out var result) || + GuardNullOrEmptyParam(field, nameof(field), out result) || + GuardField(inputObject, field, false, out var fieldValue, out result)) + return result; - if (ExpressionHelpers.CompareNumeric(fieldValue, value, convert, out var compare, out var actual)) - return compare <= 0 ? Pass() : Fail(Operand.FromPath(field), ReasonStrings.LessOrEqual, actual, value); + if (ExpressionHelpers.CompareNumeric(fieldValue, value, convert, out var compare, out var actual)) + return compare >= 0 ? Pass() : Fail(Operand.FromPath(field), ReasonStrings.GreaterOrEqual, actual, value); - return Fail(Operand.FromPath(field), ReasonStrings.Compare, fieldValue, value); - } + return Fail(Operand.FromPath(field), ReasonStrings.Compare, fieldValue, value); + } - /// - /// The object field value must be included in the set. - /// - /// - public AssertResult In(PSObject inputObject, string field, Array values, bool caseSensitive = false) - { - // Guard parameters - if (GuardNullParam(inputObject, nameof(inputObject), out var result) || - GuardNullOrEmptyParam(field, nameof(field), out result) || - GuardNullParam(values, nameof(values), out result) || - GuardField(inputObject, field, false, out var fieldValue, out result)) - return result; - - for (var i = 0; values != null && i < values.Length; i++) - { - if (ExpressionHelpers.AnyValue(fieldValue, values.GetValue(i), caseSensitive, out var _)) - return Pass(); - } - return Fail(Operand.FromPath(field), ReasonStrings.In, fieldValue); - } + /// + /// The Less assertion method checks the field value is less than the specified value. + /// The field value can either be an integer, float, array, or string. + /// + /// + public AssertResult Less(PSObject inputObject, string field, int value, bool convert = false) + { + // Guard parameters + if (GuardNullParam(inputObject, nameof(inputObject), out var result) || + GuardNullOrEmptyParam(field, nameof(field), out result) || + GuardField(inputObject, field, false, out var fieldValue, out result)) + return result; - /// - /// The object field value must not be included in the set. - /// - /// - public AssertResult NotIn(PSObject inputObject, string field, Array values, bool caseSensitive = false) - { - // Guard parameters - if (GuardNullParam(inputObject, nameof(inputObject), out var result) || - GuardNullOrEmptyParam(field, nameof(field), out result) || - GuardNullParam(values, nameof(values), out result)) - return result; + if (ExpressionHelpers.CompareNumeric(fieldValue, value, convert, out var compare, out var actual)) + return compare < 0 ? Pass() : Fail(Operand.FromPath(field), ReasonStrings.Less, actual, value); - if (!ObjectHelper.GetPath( - bindingContext: PipelineContext.CurrentThread, - targetObject: inputObject, - path: field, - caseSensitive: caseSensitive, - value: out object fieldValue)) - return Pass(); + return Fail(Operand.FromPath(field), ReasonStrings.Compare, fieldValue, value); + } - for (var i = 0; values != null && i < values.Length; i++) - { - if (ExpressionHelpers.AnyValue(fieldValue, values.GetValue(i), caseSensitive, out var foundValue)) - return Fail(Operand.FromPath(field), ReasonStrings.NotIn, foundValue); - } - return Pass(); - } + /// + /// The LessOrEqual assertion method checks the field value is less or equal to the specified value. + /// The field value can either be an integer, float, array, or string. + /// + /// + public AssertResult LessOrEqual(PSObject inputObject, string field, int value, bool convert = false) + { + // Guard parameters + if (GuardNullParam(inputObject, nameof(inputObject), out var result) || + GuardNullOrEmptyParam(field, nameof(field), out result) || + GuardField(inputObject, field, false, out var fieldValue, out result)) + return result; - /// - /// The Subset assertion method checks the field value includes all of the specified values. - /// The field value may also contain additional values that are not specified in the values parameter. - /// The field value must be an array or collection. - /// Specified values can be included in the field value in any order. - /// - /// - public AssertResult Subset(PSObject inputObject, string field, Array values, bool caseSensitive = false, bool unique = false) + if (ExpressionHelpers.CompareNumeric(fieldValue, value, convert, out var compare, out var actual)) + return compare <= 0 ? Pass() : Fail(Operand.FromPath(field), ReasonStrings.LessOrEqual, actual, value); + + return Fail(Operand.FromPath(field), ReasonStrings.Compare, fieldValue, value); + } + + /// + /// The object field value must be included in the set. + /// + /// + public AssertResult In(PSObject inputObject, string field, Array values, bool caseSensitive = false) + { + // Guard parameters + if (GuardNullParam(inputObject, nameof(inputObject), out var result) || + GuardNullOrEmptyParam(field, nameof(field), out result) || + GuardNullParam(values, nameof(values), out result) || + GuardField(inputObject, field, false, out var fieldValue, out result)) + return result; + + for (var i = 0; values != null && i < values.Length; i++) { - // Guard parameters - if (GuardNullParam(inputObject, nameof(inputObject), out var result) || - GuardNullOrEmptyParam(field, nameof(field), out result) || - GuardNullParam(values, nameof(values), out result) || - GuardField(inputObject, field, false, out var fieldValue, out result) || - GuardFieldEnumerable(fieldValue, field, out _, out result)) - return result; - - for (var i = 0; values != null && i < values.Length; i++) - { - if (!ExpressionHelpers.CountValue(fieldValue, values.GetValue(i), caseSensitive, out var count) || (count > 1 && unique)) - return count == 0 ? Fail(Operand.FromPath(field), ReasonStrings.Subset, field, values.GetValue(i)) : Fail(Operand.FromPath(field), ReasonStrings.SubsetDuplicate, field, values.GetValue(i)); - } - return Pass(); + if (ExpressionHelpers.AnyValue(fieldValue, values.GetValue(i), caseSensitive, out var _)) + return Pass(); } + return Fail(Operand.FromPath(field), ReasonStrings.In, fieldValue); + } - /// - /// The SetOf assertion method checks the field value only includes all of the specified values. - /// The field value must be an array or collection. - /// Specified values can be included in the field value in any order. - /// - /// - public AssertResult SetOf(PSObject inputObject, string field, Array values, bool caseSensitive = false) - { - // Guard parameters - if (GuardNullParam(inputObject, nameof(inputObject), out var result) || - GuardNullOrEmptyParam(field, nameof(field), out result) || - GuardNullParam(values, nameof(values), out result) || - GuardField(inputObject, field, false, out var fieldValue, out result) || - GuardFieldEnumerable(fieldValue, field, out var count, out result)) - return result; - - if (count != values.Length) - return Fail(Operand.FromPath(field), ReasonStrings.Count, field, count, values.Length); - - for (var i = 0; values != null && i < values.Length; i++) - { - if (!ExpressionHelpers.AnyValue(fieldValue, values.GetValue(i), caseSensitive, out _)) - return Fail(Operand.FromPath(field), ReasonStrings.Subset, field, values.GetValue(i)); - } + /// + /// The object field value must not be included in the set. + /// + /// + public AssertResult NotIn(PSObject inputObject, string field, Array values, bool caseSensitive = false) + { + // Guard parameters + if (GuardNullParam(inputObject, nameof(inputObject), out var result) || + GuardNullOrEmptyParam(field, nameof(field), out result) || + GuardNullParam(values, nameof(values), out result)) + return result; + + if (!ObjectHelper.GetPath( + bindingContext: PipelineContext.CurrentThread, + targetObject: inputObject, + path: field, + caseSensitive: caseSensitive, + value: out object fieldValue)) return Pass(); - } - /// - /// The field value must contain the specified number of items. - /// - /// - public AssertResult Count(PSObject inputObject, string field, int count) + for (var i = 0; values != null && i < values.Length; i++) { - // Guard parameters - if (GuardNullParam(inputObject, nameof(inputObject), out var result) || - GuardNullOrEmptyParam(field, nameof(field), out result) || - GuardField(inputObject, field, false, out var fieldValue, out result) || - GuardFieldEnumerable(fieldValue, field, out var actual, out result)) - return result; - - return actual == count ? Pass() : Fail(Operand.FromPath(field), ReasonStrings.Count, field, actual, count); + if (ExpressionHelpers.AnyValue(fieldValue, values.GetValue(i), caseSensitive, out var foundValue)) + return Fail(Operand.FromPath(field), ReasonStrings.NotIn, foundValue); } + return Pass(); + } - /// - /// The field value must not contain the specified number of items. - /// - /// - public AssertResult NotCount(PSObject inputObject, string field, int count) + /// + /// The Subset assertion method checks the field value includes all of the specified values. + /// The field value may also contain additional values that are not specified in the values parameter. + /// The field value must be an array or collection. + /// Specified values can be included in the field value in any order. + /// + /// + public AssertResult Subset(PSObject inputObject, string field, Array values, bool caseSensitive = false, bool unique = false) + { + // Guard parameters + if (GuardNullParam(inputObject, nameof(inputObject), out var result) || + GuardNullOrEmptyParam(field, nameof(field), out result) || + GuardNullParam(values, nameof(values), out result) || + GuardField(inputObject, field, false, out var fieldValue, out result) || + GuardFieldEnumerable(fieldValue, field, out _, out result)) + return result; + + for (var i = 0; values != null && i < values.Length; i++) { - // Guard parameters - if (GuardNullParam(inputObject, nameof(inputObject), out var result) || - GuardNullOrEmptyParam(field, nameof(field), out result) || - GuardField(inputObject, field, false, out var fieldValue, out result) || - GuardFieldEnumerable(fieldValue, field, out var actual, out result)) - return result; - - return actual != count ? Pass() : Fail(Operand.FromPath(field), ReasonStrings.NotCount, field, actual, count); + if (!ExpressionHelpers.CountValue(fieldValue, values.GetValue(i), caseSensitive, out var count) || (count > 1 && unique)) + return count == 0 ? Fail(Operand.FromPath(field), ReasonStrings.Subset, field, values.GetValue(i)) : Fail(Operand.FromPath(field), ReasonStrings.SubsetDuplicate, field, values.GetValue(i)); } + return Pass(); + } - /// - /// The object field value must match the regular expression. - /// - /// - public AssertResult Match(PSObject inputObject, string field, string pattern, bool caseSensitive = false) + /// + /// The SetOf assertion method checks the field value only includes all of the specified values. + /// The field value must be an array or collection. + /// Specified values can be included in the field value in any order. + /// + /// + public AssertResult SetOf(PSObject inputObject, string field, Array values, bool caseSensitive = false) + { + // Guard parameters + if (GuardNullParam(inputObject, nameof(inputObject), out var result) || + GuardNullOrEmptyParam(field, nameof(field), out result) || + GuardNullParam(values, nameof(values), out result) || + GuardField(inputObject, field, false, out var fieldValue, out result) || + GuardFieldEnumerable(fieldValue, field, out var count, out result)) + return result; + + if (count != values.Length) + return Fail(Operand.FromPath(field), ReasonStrings.Count, field, count, values.Length); + + for (var i = 0; values != null && i < values.Length; i++) { - // Guard parameters - if (GuardNullParam(inputObject, nameof(inputObject), out var result) || - GuardNullOrEmptyParam(field, nameof(field), out result) || - GuardField(inputObject, field, false, out var fieldValue, out result) || - GuardString(Operand.FromPath(field), fieldValue, out var value, out result)) - return result; - - return ExpressionHelpers.Match(pattern, value, caseSensitive) ? Pass() : Fail(Operand.FromPath(field), ReasonStrings.MatchPattern, value, pattern); + if (!ExpressionHelpers.AnyValue(fieldValue, values.GetValue(i), caseSensitive, out _)) + return Fail(Operand.FromPath(field), ReasonStrings.Subset, field, values.GetValue(i)); } + return Pass(); + } - /// - /// The object field value must not match the regular expression. - /// - /// - public AssertResult NotMatch(PSObject inputObject, string field, string pattern, bool caseSensitive = false) - { - // Guard parameters - if (GuardNullParam(inputObject, nameof(inputObject), out var result) || - GuardNullOrEmptyParam(field, nameof(field), out result)) - return result; + /// + /// The field value must contain the specified number of items. + /// + /// + public AssertResult Count(PSObject inputObject, string field, int count) + { + // Guard parameters + if (GuardNullParam(inputObject, nameof(inputObject), out var result) || + GuardNullOrEmptyParam(field, nameof(field), out result) || + GuardField(inputObject, field, false, out var fieldValue, out result) || + GuardFieldEnumerable(fieldValue, field, out var actual, out result)) + return result; - if (!ObjectHelper.GetPath( - bindingContext: PipelineContext.CurrentThread, - targetObject: inputObject, - path: field, - caseSensitive: caseSensitive, - value: out object fieldValue)) - return Pass(); + return actual == count ? Pass() : Fail(Operand.FromPath(field), ReasonStrings.Count, field, actual, count); + } - if (GuardString(Operand.FromPath(field), fieldValue, out var value, out result)) - return result; + /// + /// The field value must not contain the specified number of items. + /// + /// + public AssertResult NotCount(PSObject inputObject, string field, int count) + { + // Guard parameters + if (GuardNullParam(inputObject, nameof(inputObject), out var result) || + GuardNullOrEmptyParam(field, nameof(field), out result) || + GuardField(inputObject, field, false, out var fieldValue, out result) || + GuardFieldEnumerable(fieldValue, field, out var actual, out result)) + return result; - if (!ExpressionHelpers.Match(pattern, value, caseSensitive)) - return Pass(); + return actual != count ? Pass() : Fail(Operand.FromPath(field), ReasonStrings.NotCount, field, actual, count); + } - return Fail(Operand.FromPath(field), ReasonStrings.NotMatchPattern, value, pattern); - } + /// + /// The object field value must match the regular expression. + /// + /// + public AssertResult Match(PSObject inputObject, string field, string pattern, bool caseSensitive = false) + { + // Guard parameters + if (GuardNullParam(inputObject, nameof(inputObject), out var result) || + GuardNullOrEmptyParam(field, nameof(field), out result) || + GuardField(inputObject, field, false, out var fieldValue, out result) || + GuardString(Operand.FromPath(field), fieldValue, out var value, out result)) + return result; - /// - /// The FilePath assertion method checks the file exists. - /// Checks use file system case-sensitivity rules. - /// - /// - public AssertResult FilePath(PSObject inputObject, string field, string[] suffix = null) - { - // Guard parameters - if (GuardNullParam(inputObject, nameof(inputObject), out var result) || - GuardNullOrEmptyParam(field, nameof(field), out result) || - GuardField(inputObject, field, false, out var fieldValue, out result) || - GuardString(Operand.FromPath(field), fieldValue, out var value, out result)) - return result; - - if (suffix == null || suffix.Length == 0) - { - return !TryFilePath(value, out _) ? Fail(Operand.FromPath(field), ReasonStrings.FilePath, value) : Pass(); - } + return ExpressionHelpers.Match(pattern, value, caseSensitive) ? Pass() : Fail(Operand.FromPath(field), ReasonStrings.MatchPattern, value, pattern); + } - var reason = Fail(); - for (var i = 0; i < suffix.Length; i++) - { - if (!TryFilePath(Path.Combine(value, suffix[i]), out _)) - reason.AddReason(Operand.FromPath(field), ReasonStrings.FilePath, suffix[i]); - else - return Pass(); - } - return reason; - } + /// + /// The object field value must not match the regular expression. + /// + /// + public AssertResult NotMatch(PSObject inputObject, string field, string pattern, bool caseSensitive = false) + { + // Guard parameters + if (GuardNullParam(inputObject, nameof(inputObject), out var result) || + GuardNullOrEmptyParam(field, nameof(field), out result)) + return result; - /// - /// The FileHeader assertion method checks a file for a comment header. - /// When comparing the file header, the format of line comments are automatically detected by file extension. - /// - /// - public AssertResult FileHeader(PSObject inputObject, string field, string[] header, string prefix = null) - { - // Guard parameters - if (GuardNullParam(inputObject, nameof(inputObject), out var result) || - GuardNullOrEmptyParam(field, nameof(field), out result) || - GuardField(inputObject, field, false, out var fieldValue, out result) || - GuardString(Operand.FromPath(field), fieldValue, out var value, out result)) - return result; - - // File does not exist - if (!TryFilePath(value, out _)) - return Fail(Operand.FromPath(field), ReasonStrings.FilePath, value); - - // No header - if (header == null || header.Length == 0) - return Pass(); + if (!ObjectHelper.GetPath( + bindingContext: PipelineContext.CurrentThread, + targetObject: inputObject, + path: field, + caseSensitive: caseSensitive, + value: out object fieldValue)) + return Pass(); - if (string.IsNullOrEmpty(prefix)) - prefix = DetectLinePrefix(GetFileType(value)); + if (GuardString(Operand.FromPath(field), fieldValue, out var value, out result)) + return result; - var lineNo = 0; - foreach (var content in File.ReadLines(value)) - { - if (lineNo >= header.Length) - break; + if (!ExpressionHelpers.Match(pattern, value, caseSensitive)) + return Pass(); - if (content != string.Concat(prefix, header[lineNo])) - return Fail(Operand.FromPath(field), ReasonStrings.FileHeader); + return Fail(Operand.FromPath(field), ReasonStrings.NotMatchPattern, value, pattern); + } - lineNo++; - } + /// + /// The FilePath assertion method checks the file exists. + /// Checks use file system case-sensitivity rules. + /// + /// + public AssertResult FilePath(PSObject inputObject, string field, string[] suffix = null) + { + // Guard parameters + if (GuardNullParam(inputObject, nameof(inputObject), out var result) || + GuardNullOrEmptyParam(field, nameof(field), out result) || + GuardField(inputObject, field, false, out var fieldValue, out result) || + GuardString(Operand.FromPath(field), fieldValue, out var value, out result)) + return result; - // Catch file has less lines than header - return lineNo < header.Length ? Fail(Operand.FromPath(field), ReasonStrings.FileHeader) : Pass(); + if (suffix == null || suffix.Length == 0) + { + return !TryFilePath(value, out _) ? Fail(Operand.FromPath(field), ReasonStrings.FilePath, value) : Pass(); } - /// - /// The field value must be within the specified path. - /// - public AssertResult WithinPath(PSObject inputObject, string field, string[] path, bool? caseSensitive = null) + var reason = Fail(); + for (var i = 0; i < suffix.Length; i++) { - // Guard parameters - if (GuardNullParam(inputObject, nameof(inputObject), out var result) || - GuardNullOrEmptyParam(field, nameof(field), out result) || - GuardNullOrEmptyParam(path, nameof(path), out result) || - GuardField(inputObject, field, false, out var fieldValue, out result)) - return result; - - var fieldValuePath = ExpressionHelpers.GetObjectOriginPath(fieldValue); - result = Fail(); - for (var i = 0; path != null && i < path.Length; i++) - { - if (ExpressionHelpers.WithinPath(fieldValuePath, path[i], caseSensitive.GetValueOrDefault(PSRuleOption.IsCaseSensitive()))) - return Pass(); + if (!TryFilePath(Path.Combine(value, suffix[i]), out _)) + reason.AddReason(Operand.FromPath(field), ReasonStrings.FilePath, suffix[i]); + else + return Pass(); + } + return reason; + } - result.AddReason(Operand.FromPath(field), ReasonStrings.WithinPath, - ExpressionHelpers.NormalizePath(Environment.GetWorkingPath(), fieldValuePath), - ExpressionHelpers.NormalizePath(Environment.GetWorkingPath(), path[i]) - ); - } + /// + /// The FileHeader assertion method checks a file for a comment header. + /// When comparing the file header, the format of line comments are automatically detected by file extension. + /// + /// + public AssertResult FileHeader(PSObject inputObject, string field, string[] header, string prefix = null) + { + // Guard parameters + if (GuardNullParam(inputObject, nameof(inputObject), out var result) || + GuardNullOrEmptyParam(field, nameof(field), out result) || + GuardField(inputObject, field, false, out var fieldValue, out result) || + GuardString(Operand.FromPath(field), fieldValue, out var value, out result)) return result; - } - /// - /// The field must not be within the specified path. - /// - public AssertResult NotWithinPath(PSObject inputObject, string field, string[] path, bool? caseSensitive = null) - { - // Guard parameters - if (GuardNullParam(inputObject, nameof(inputObject), out var result) || - GuardNullOrEmptyParam(field, nameof(field), out result) || - GuardNullOrEmptyParam(path, nameof(path), out result) || - GuardField(inputObject, field, false, out var fieldValue, out result)) - return result; - - var fieldValuePath = ExpressionHelpers.GetObjectOriginPath(fieldValue); - for (var i = 0; path != null && i < path.Length; i++) - { - if (ExpressionHelpers.WithinPath(fieldValuePath, path[i], caseSensitive.GetValueOrDefault(PSRuleOption.IsCaseSensitive()))) - return Fail(Operand.FromPath(field), ReasonStrings.NotWithinPath, - ExpressionHelpers.NormalizePath(Environment.GetWorkingPath(), fieldValuePath), - ExpressionHelpers.NormalizePath(Environment.GetWorkingPath(), path[i]) - ); - } + // File does not exist + if (!TryFilePath(value, out _)) + return Fail(Operand.FromPath(field), ReasonStrings.FilePath, value); + + // No header + if (header == null || header.Length == 0) return Pass(); - } - /// - /// The value should match at least one of the specified patterns. Only applies to strings. - /// - /// - /// The parameter 'inputObject' is null. - /// The parameter 'field' is null or empty. - /// The parameter 'pattern' is null. - /// The field '{0}' does not exist. - /// The value '{0}' is not like '{1}'. - /// - public AssertResult Like(PSObject inputObject, string field, string[] pattern, bool caseSensitive = false) + if (string.IsNullOrEmpty(prefix)) + prefix = DetectLinePrefix(GetFileType(value)); + + var lineNo = 0; + foreach (var content in File.ReadLines(value)) { - // Guard parameters - if (GuardNullParam(inputObject, nameof(inputObject), out var result) || - GuardNullOrEmptyParam(field, nameof(field), out result) || - GuardNullParam(pattern, nameof(pattern), out result) || - GuardField(inputObject, field, false, out var fieldValue, out result) || - GuardString(Operand.FromPath(field), fieldValue, out var value, out result)) - return result; - - if (pattern == null || pattern.Length == 0) - return Pass(); + if (lineNo >= header.Length) + break; - // Assert - for (var i = 0; i < pattern.Length; i++) - { - if (ExpressionHelpers.Like(value, pattern[i], caseSensitive)) - return Pass(); - } - return Fail(Operand.FromPath(field), ReasonStrings.Assert_NotLike, field, FormatArray(pattern)); + if (content != string.Concat(prefix, header[lineNo])) + return Fail(Operand.FromPath(field), ReasonStrings.FileHeader); + + lineNo++; } - /// - /// The value should not match any of the specified patterns. Only applies to strings. - /// - /// - /// The parameter 'inputObject' is null. - /// The parameter 'field' is null or empty. - /// The parameter 'pattern' is null. - /// The field '{0}' does not exist. - /// The value '{0}' is like '{1}'. - /// - public AssertResult NotLike(PSObject inputObject, string field, string[] pattern, bool caseSensitive = false) + // Catch file has less lines than header + return lineNo < header.Length ? Fail(Operand.FromPath(field), ReasonStrings.FileHeader) : Pass(); + } + + /// + /// The field value must be within the specified path. + /// + public AssertResult WithinPath(PSObject inputObject, string field, string[] path, bool? caseSensitive = null) + { + // Guard parameters + if (GuardNullParam(inputObject, nameof(inputObject), out var result) || + GuardNullOrEmptyParam(field, nameof(field), out result) || + GuardNullOrEmptyParam(path, nameof(path), out result) || + GuardField(inputObject, field, false, out var fieldValue, out result)) + return result; + + var fieldValuePath = ExpressionHelpers.GetObjectOriginPath(fieldValue); + result = Fail(); + for (var i = 0; path != null && i < path.Length; i++) { - // Guard parameters - if (GuardNullParam(inputObject, nameof(inputObject), out var result) || - GuardNullOrEmptyParam(field, nameof(field), out result) || - GuardNullParam(pattern, nameof(pattern), out result) || - GuardField(inputObject, field, false, out var fieldValue, out result)) - return result; - - if (pattern == null || pattern.Length == 0 || GuardString(Operand.FromPath(field), fieldValue, out var value, out _)) + if (ExpressionHelpers.WithinPath(fieldValuePath, path[i], caseSensitive.GetValueOrDefault(PSRuleOption.IsCaseSensitive()))) return Pass(); - // Assert - for (var i = 0; i < pattern.Length; i++) - { - if (ExpressionHelpers.Like(value, pattern[i], caseSensitive)) - return Fail(Operand.FromPath(field), ReasonStrings.Assert_Like, value, pattern[i]); - } - return Pass(); + result.AddReason(Operand.FromPath(field), ReasonStrings.WithinPath, + ExpressionHelpers.NormalizePath(Environment.GetWorkingPath(), fieldValuePath), + ExpressionHelpers.NormalizePath(Environment.GetWorkingPath(), path[i]) + ); } + return result; + } - #endregion Conditions - - #region Helper methods + /// + /// The field must not be within the specified path. + /// + public AssertResult NotWithinPath(PSObject inputObject, string field, string[] path, bool? caseSensitive = null) + { + // Guard parameters + if (GuardNullParam(inputObject, nameof(inputObject), out var result) || + GuardNullOrEmptyParam(field, nameof(field), out result) || + GuardNullOrEmptyParam(path, nameof(path), out result) || + GuardField(inputObject, field, false, out var fieldValue, out result)) + return result; - /// - /// Fails if the value is null. - /// - /// - /// Reason: The parameter '{0}' is null. - /// - private bool GuardNullParam(object value, string parameterName, out AssertResult result) + var fieldValuePath = ExpressionHelpers.GetObjectOriginPath(fieldValue); + for (var i = 0; path != null && i < path.Length; i++) { - result = value == null ? Fail(ReasonStrings.NullParameter, parameterName) : null; - return result != null; + if (ExpressionHelpers.WithinPath(fieldValuePath, path[i], caseSensitive.GetValueOrDefault(PSRuleOption.IsCaseSensitive()))) + return Fail(Operand.FromPath(field), ReasonStrings.NotWithinPath, + ExpressionHelpers.NormalizePath(Environment.GetWorkingPath(), fieldValuePath), + ExpressionHelpers.NormalizePath(Environment.GetWorkingPath(), path[i]) + ); } + return Pass(); + } + + /// + /// The value should match at least one of the specified patterns. Only applies to strings. + /// + /// + /// The parameter 'inputObject' is null. + /// The parameter 'field' is null or empty. + /// The parameter 'pattern' is null. + /// The field '{0}' does not exist. + /// The value '{0}' is not like '{1}'. + /// + public AssertResult Like(PSObject inputObject, string field, string[] pattern, bool caseSensitive = false) + { + // Guard parameters + if (GuardNullParam(inputObject, nameof(inputObject), out var result) || + GuardNullOrEmptyParam(field, nameof(field), out result) || + GuardNullParam(pattern, nameof(pattern), out result) || + GuardField(inputObject, field, false, out var fieldValue, out result) || + GuardString(Operand.FromPath(field), fieldValue, out var value, out result)) + return result; + + if (pattern == null || pattern.Length == 0) + return Pass(); - /// - /// Fails of the value is null or empty. - /// - /// Returns true if the value is null or an empty string. - /// - /// Reason: The parameter '{0}' is null or empty. - /// - private bool GuardNullOrEmptyParam(string value, string parameterName, out AssertResult result) + // Assert + for (var i = 0; i < pattern.Length; i++) { - result = string.IsNullOrEmpty(value) ? Fail(ReasonStrings.NullOrEmptyParameter, parameterName) : null; - return result != null; + if (ExpressionHelpers.Like(value, pattern[i], caseSensitive)) + return Pass(); } + return Fail(Operand.FromPath(field), ReasonStrings.Assert_NotLike, field, FormatArray(pattern)); + } + + /// + /// The value should not match any of the specified patterns. Only applies to strings. + /// + /// + /// The parameter 'inputObject' is null. + /// The parameter 'field' is null or empty. + /// The parameter 'pattern' is null. + /// The field '{0}' does not exist. + /// The value '{0}' is like '{1}'. + /// + public AssertResult NotLike(PSObject inputObject, string field, string[] pattern, bool caseSensitive = false) + { + // Guard parameters + if (GuardNullParam(inputObject, nameof(inputObject), out var result) || + GuardNullOrEmptyParam(field, nameof(field), out result) || + GuardNullParam(pattern, nameof(pattern), out result) || + GuardField(inputObject, field, false, out var fieldValue, out result)) + return result; + + if (pattern == null || pattern.Length == 0 || GuardString(Operand.FromPath(field), fieldValue, out var value, out _)) + return Pass(); - /// - /// Fails of the value is null or empty. - /// - /// Returns true if the value is null or is empty. - /// - /// Reason: The parameter '{0}' is null or empty. - /// - private bool GuardNullOrEmptyParam(Array value, string parameterName, out AssertResult result) + // Assert + for (var i = 0; i < pattern.Length; i++) { - result = value == null || value.Length == 0 ? Fail(ReasonStrings.NullOrEmptyParameter, parameterName) : null; - return result != null; + if (ExpressionHelpers.Like(value, pattern[i], caseSensitive)) + return Fail(Operand.FromPath(field), ReasonStrings.Assert_Like, value, pattern[i]); } + return Pass(); + } - /// - /// Fails if the field does not exist. - /// - /// Returns true if the field does not exist. - /// - /// Reason: The field '{0}' does not exist. - /// - private bool GuardField(PSObject inputObject, string field, bool caseSensitive, out object fieldValue, out AssertResult result) - { - result = null; - if (ObjectHelper.GetPath( - bindingContext: PipelineContext.CurrentThread, - targetObject: inputObject, - path: field, - caseSensitive: caseSensitive, - value: out fieldValue)) - return false; + #endregion Conditions - result = Fail(Operand.FromPath(field), ReasonStrings.Assert_Exists, field); - return true; - } + #region Helper methods - private bool GuardSemanticVersion(IOperand operand, object fieldValue, out SemanticVersion.Version value, out AssertResult result) - { - result = null; - value = null; - if (ExpressionHelpers.TryString(fieldValue, out var sversion) && SemanticVersion.TryParseVersion(sversion, out value)) - return false; + /// + /// Fails if the value is null. + /// + /// + /// Reason: The parameter '{0}' is null. + /// + private bool GuardNullParam(object value, string parameterName, out AssertResult result) + { + result = value == null ? Fail(ReasonStrings.NullParameter, parameterName) : null; + return result != null; + } - result = Fail(operand, ReasonStrings.Version, fieldValue); - return true; - } + /// + /// Fails of the value is null or empty. + /// + /// Returns true if the value is null or an empty string. + /// + /// Reason: The parameter '{0}' is null or empty. + /// + private bool GuardNullOrEmptyParam(string value, string parameterName, out AssertResult result) + { + result = string.IsNullOrEmpty(value) ? Fail(ReasonStrings.NullOrEmptyParameter, parameterName) : null; + return result != null; + } - private bool GuardDateVersion(IOperand operand, object fieldValue, out DateVersion.Version value, out AssertResult result) - { - result = null; - value = null; - if (ExpressionHelpers.TryString(fieldValue, out var sversion) && DateVersion.TryParseVersion(sversion, out value)) - return false; + /// + /// Fails of the value is null or empty. + /// + /// Returns true if the value is null or is empty. + /// + /// Reason: The parameter '{0}' is null or empty. + /// + private bool GuardNullOrEmptyParam(Array value, string parameterName, out AssertResult result) + { + result = value == null || value.Length == 0 ? Fail(ReasonStrings.NullOrEmptyParameter, parameterName) : null; + return result != null; + } - result = Fail(operand, ReasonStrings.Version, fieldValue); - return true; - } + /// + /// Fails if the field does not exist. + /// + /// Returns true if the field does not exist. + /// + /// Reason: The field '{0}' does not exist. + /// + private bool GuardField(PSObject inputObject, string field, bool caseSensitive, out object fieldValue, out AssertResult result) + { + result = null; + if (ObjectHelper.GetPath( + bindingContext: PipelineContext.CurrentThread, + targetObject: inputObject, + path: field, + caseSensitive: caseSensitive, + value: out fieldValue)) + return false; - /// - /// Fails if the field is not enumerable. - /// - /// Returns true of the field value is not enumerable. - /// - /// Reason: The field '{0}' is not enumerable. - /// - private bool GuardFieldEnumerable(object fieldValue, string field, out int count, out AssertResult result) - { - result = null; - if (ExpressionHelpers.TryEnumerableLength(fieldValue, out count)) - return false; + result = Fail(Operand.FromPath(field), ReasonStrings.Assert_Exists, field); + return true; + } - result = Fail(Operand.FromPath(field), ReasonStrings.NotEnumerable, field); - return true; - } + private bool GuardSemanticVersion(IOperand operand, object fieldValue, out SemanticVersion.Version value, out AssertResult result) + { + result = null; + value = null; + if (ExpressionHelpers.TryString(fieldValue, out var sversion) && SemanticVersion.TryParseVersion(sversion, out value)) + return false; - /// - /// Fails if the field value is not a string. - /// - /// Returns true if the field value is not a string. - /// - /// Reason: The field value '{0}' is not a string. - /// - private bool GuardString(IOperand operand, object fieldValue, out string value, out AssertResult result) - { - result = null; - if (ExpressionHelpers.TryString(fieldValue, out value)) - return false; + result = Fail(operand, ReasonStrings.Version, fieldValue); + return true; + } - result = Fail(operand, ReasonStrings.Type, TYPENAME_STRING, GetTypeName(fieldValue), fieldValue); - return true; - } + private bool GuardDateVersion(IOperand operand, object fieldValue, out DateVersion.Version value, out AssertResult result) + { + result = null; + value = null; + if (ExpressionHelpers.TryString(fieldValue, out var sversion) && DateVersion.TryParseVersion(sversion, out value)) + return false; - /// - /// Fails if the field value is not a string or an array of strings. - /// - /// Returns true if the field value is not a string or an array of strings. - /// - /// Reason: The field value '{0}' is not a string. - /// - private bool GuardStringOrArray(IOperand operand, object fieldValue, out string[] value, out AssertResult result) - { - result = null; - if (ExpressionHelpers.TryStringOrArray(fieldValue, convert: false, value: out value)) - return false; + result = Fail(operand, ReasonStrings.Version, fieldValue); + return true; + } - result = Fail(operand, ReasonStrings.Type, TYPENAME_STRING, GetTypeName(fieldValue), fieldValue); - return true; - } + /// + /// Fails if the field is not enumerable. + /// + /// Returns true of the field value is not enumerable. + /// + /// Reason: The field '{0}' is not enumerable. + /// + private bool GuardFieldEnumerable(object fieldValue, string field, out int count, out AssertResult result) + { + result = null; + if (ExpressionHelpers.TryEnumerableLength(fieldValue, out count)) + return false; - /// - /// Fields if the field value is null. - /// - /// Returns true if the field value is null. - /// - /// Reason: The field value '{0}' is null. - /// - private bool GuardNullFieldValue(string field, object fieldValue, out AssertResult result) - { - result = null; - if (fieldValue != null) - return false; + result = Fail(Operand.FromPath(field), ReasonStrings.NotEnumerable, field); + return true; + } - result = Fail(Operand.FromPath(field), ReasonStrings.Null, field); - return true; - } + /// + /// Fails if the field value is not a string. + /// + /// Returns true if the field value is not a string. + /// + /// Reason: The field value '{0}' is not a string. + /// + private bool GuardString(IOperand operand, object fieldValue, out string value, out AssertResult result) + { + result = null; + if (ExpressionHelpers.TryString(fieldValue, out value)) + return false; - private static bool TryReadJson(string uri, out string json) - { - json = null; - if (uri == null) - { - return false; - } - else if (uri.IsURL()) - { - using var webClient = new WebClient(); - json = webClient.DownloadString(uri); - return true; - } - else if (TryFilePath(uri, out var path)) - { - using var reader = new StreamReader(path); - json = reader.ReadToEnd(); - return true; - } + result = Fail(operand, ReasonStrings.Type, TYPENAME_STRING, GetTypeName(fieldValue), fieldValue); + return true; + } + + /// + /// Fails if the field value is not a string or an array of strings. + /// + /// Returns true if the field value is not a string or an array of strings. + /// + /// Reason: The field value '{0}' is not a string. + /// + private bool GuardStringOrArray(IOperand operand, object fieldValue, out string[] value, out AssertResult result) + { + result = null; + if (ExpressionHelpers.TryStringOrArray(fieldValue, convert: false, value: out value)) return false; - } - private static bool TryFilePath(string path, out string rootedPath) - { - rootedPath = Environment.GetRootedPath(path); - return File.Exists(rootedPath); - } + result = Fail(operand, ReasonStrings.Type, TYPENAME_STRING, GetTypeName(fieldValue), fieldValue); + return true; + } + + /// + /// Fields if the field value is null. + /// + /// Returns true if the field value is null. + /// + /// Reason: The field value '{0}' is null. + /// + private bool GuardNullFieldValue(string field, object fieldValue, out AssertResult result) + { + result = null; + if (fieldValue != null) + return false; + + result = Fail(Operand.FromPath(field), ReasonStrings.Null, field); + return true; + } - private static string FormatArray(string[] values) + private static bool TryReadJson(string uri, out string json) + { + json = null; + if (uri == null) { - return string.Join(COMMASEPARATOR, values); + return false; } - - /// - /// Get the file extension of the name of the file if an extension is not set. - /// - private static string GetFileType(string value) + else if (uri.IsURL()) { - var ext = Path.GetExtension(value); - return string.IsNullOrEmpty(ext) ? Path.GetFileNameWithoutExtension(value) : ext; + using var webClient = new WebClient(); + json = webClient.DownloadString(uri); + return true; } - - /// - /// Determine line comment prefix by file extension - /// - private static string DetectLinePrefix(string extension) + else if (TryFilePath(uri, out var path)) { - extension = extension?.ToLower(); - switch (extension) - { - case ".bicep": - case ".bicepparam": - case ".cs": - case ".csx": - case ".ts": - case ".tsp": - case ".tsx": - case ".js": - case ".jsx": - case ".fs": - case ".go": - case ".groovy": - case ".php": - case ".cpp": - case ".h": - case ".java": - case ".json": - case ".jsonc": - case ".scala": - case "jenkinsfile": - return "// "; - - case ".editorconfig": - case ".ipynb": - case ".ps1": - case ".psd1": - case ".psm1": - case ".yaml": - case ".yml": - case ".r": - case ".py": - case ".sh": - case ".tf": - case ".tfvars": - case ".toml": - case ".gitignore": - case ".pl": - case ".rb": - case "dockerfile": - return "# "; - - case ".sql": - case ".lua": - return "-- "; - - case ".bat": - case ".cmd": - return ":: "; - - default: - return string.Empty; - } + using var reader = new StreamReader(path); + json = reader.ReadToEnd(); + return true; } + return false; + } - private static string GetTypeName(object value) - { - return value == null ? TYPENAME_NULL : value.GetType().Name; + private static bool TryFilePath(string path, out string rootedPath) + { + rootedPath = Environment.GetRootedPath(path); + return File.Exists(rootedPath); + } + + private static string FormatArray(string[] values) + { + return string.Join(COMMASEPARATOR, values); + } + + /// + /// Get the file extension of the name of the file if an extension is not set. + /// + private static string GetFileType(string value) + { + var ext = Path.GetExtension(value); + return string.IsNullOrEmpty(ext) ? Path.GetFileNameWithoutExtension(value) : ext; + } + + /// + /// Determine line comment prefix by file extension + /// + private static string DetectLinePrefix(string extension) + { + extension = extension?.ToLower(); + switch (extension) + { + case ".bicep": + case ".bicepparam": + case ".cs": + case ".csx": + case ".ts": + case ".tsp": + case ".tsx": + case ".js": + case ".jsx": + case ".fs": + case ".go": + case ".groovy": + case ".php": + case ".cpp": + case ".h": + case ".java": + case ".json": + case ".jsonc": + case ".scala": + case "jenkinsfile": + return "// "; + + case ".editorconfig": + case ".ipynb": + case ".ps1": + case ".psd1": + case ".psm1": + case ".yaml": + case ".yml": + case ".r": + case ".py": + case ".sh": + case ".tf": + case ".tfvars": + case ".toml": + case ".gitignore": + case ".pl": + case ".rb": + case "dockerfile": + return "# "; + + case ".sql": + case ".lua": + return "-- "; + + case ".bat": + case ".cmd": + return ":: "; + + default: + return string.Empty; } + } - private static bool TryTypeName(object fieldValue, string typeName) - { - if (fieldValue is not PSObject pso) - return false; + private static string GetTypeName(object value) + { + return value == null ? TYPENAME_NULL : value.GetType().Name; + } - for (var i = 0; i < pso.TypeNames.Count; i++) - { - if (StringComparer.OrdinalIgnoreCase.Equals(pso.TypeNames[i], typeName)) - return true; - } + private static bool TryTypeName(object fieldValue, string typeName) + { + if (fieldValue is not PSObject pso) return false; - } - #endregion Helper methods + for (var i = 0; i < pso.TypeNames.Count; i++) + { + if (StringComparer.OrdinalIgnoreCase.Equals(pso.TypeNames[i], typeName)) + return true; + } + return false; } + + #endregion Helper methods } diff --git a/src/PSRule/Runtime/AssertResult.cs b/src/PSRule/Runtime/AssertResult.cs index 53a322c1f7..85735a39f7 100644 --- a/src/PSRule/Runtime/AssertResult.cs +++ b/src/PSRule/Runtime/AssertResult.cs @@ -5,213 +5,212 @@ using PSRule.Pipeline; using PSRule.Resources; -namespace PSRule.Runtime +namespace PSRule.Runtime; + +/// +/// The result of a single assertion. +/// +public sealed class AssertResult : IEquatable { - /// - /// The result of a single assertion. - /// - public sealed class AssertResult : IEquatable - { - private readonly List _Reason; + private readonly List _Reason; - internal AssertResult(IOperand operand, bool value, string reason, object[] args) + internal AssertResult(IOperand operand, bool value, string reason, object[] args) + { + Result = value; + if (!Result) { - Result = value; - if (!Result) - { - _Reason = new List(); - AddReason(operand, reason, args); - } + _Reason = new List(); + AddReason(operand, reason, args); } + } - /// - /// Convert the result into a boolean value. - /// - public static explicit operator bool(AssertResult result) - { - return result != null && result.Result; - } + /// + /// Convert the result into a boolean value. + /// + public static explicit operator bool(AssertResult result) + { + return result != null && result.Result; + } - /// - /// Success of the condition. True indicate pass, false indicates fail. - /// - public bool Result { get; private set; } + /// + /// Success of the condition. True indicate pass, false indicates fail. + /// + public bool Result { get; private set; } - /// - /// Add a reason. - /// - /// The text of a reason to add. This text should already be localized for the currently culture. - public void AddReason(string text) - { - AddReason(null, text, null); - } + /// + /// Add a reason. + /// + /// The text of a reason to add. This text should already be localized for the currently culture. + public void AddReason(string text) + { + AddReason(null, text, null); + } - /// - /// Add a reasons from an existing result. - /// - internal void AddReason(AssertResult result) - { - if (result == null || Result || result.Result || result._Reason == null || result._Reason.Count == 0) - return; + /// + /// Add a reasons from an existing result. + /// + internal void AddReason(AssertResult result) + { + if (result == null || Result || result.Result || result._Reason == null || result._Reason.Count == 0) + return; - _Reason.AddRange(result._Reason); - } + _Reason.AddRange(result._Reason); + } - /// - /// Add a reason. - /// - /// Indentifies the operand that was the reason for the failure. - /// The text of a reason to add. This text should already be localized for the currently culture. - /// Replacement arguments for the format string. - internal void AddReason(IOperand operand, string text, params object[] args) - { - // Ignore reasons if this is a pass. - if (Result || string.IsNullOrEmpty(text)) - return; + /// + /// Add a reason. + /// + /// Indentifies the operand that was the reason for the failure. + /// The text of a reason to add. This text should already be localized for the currently culture. + /// Replacement arguments for the format string. + internal void AddReason(IOperand operand, string text, params object[] args) + { + // Ignore reasons if this is a pass. + if (Result || string.IsNullOrEmpty(text)) + return; - _Reason.Add(new ResultReason(RunspaceContext.CurrentThread?.TargetObject?.Path, operand, text, args)); - } + _Reason.Add(new ResultReason(RunspaceContext.CurrentThread?.TargetObject?.Path, operand, text, args)); + } - /// - /// Adds a reason, and optionally replace existing reasons. - /// - /// The text of a reason to add. This text should already be localized for the currently culture. - /// When set to true, existing reasons are cleared. - public AssertResult WithReason(string text, bool replace = false) - { - if (replace && _Reason != null) - _Reason.Clear(); + /// + /// Adds a reason, and optionally replace existing reasons. + /// + /// The text of a reason to add. This text should already be localized for the currently culture. + /// When set to true, existing reasons are cleared. + public AssertResult WithReason(string text, bool replace = false) + { + if (replace && _Reason != null) + _Reason.Clear(); - AddReason(text); - return this; - } + AddReason(text); + return this; + } - /// - /// Adds a logical path prefix on to each reason path. - /// - /// A string to prefix on each path. - public AssertResult PathPrefix(string prefix) - { - for (var i = 0; _Reason != null && i < _Reason.Count; i++) - _Reason[i].Prefix = prefix; + /// + /// Adds a logical path prefix on to each reason path. + /// + /// A string to prefix on each path. + public AssertResult PathPrefix(string prefix) + { + for (var i = 0; _Reason != null && i < _Reason.Count; i++) + _Reason[i].Prefix = prefix; - return this; - } + return this; + } - /// - /// Replace the existing reason with the supplied format string. - /// - /// The text of a reason to use. This text should already be localized for the currently culture. - /// Replacement arguments for the format string. - public AssertResult Reason(string text, params object[] args) - { - _Reason?.Clear(); + /// + /// Replace the existing reason with the supplied format string. + /// + /// The text of a reason to use. This text should already be localized for the currently culture. + /// Replacement arguments for the format string. + public AssertResult Reason(string text, params object[] args) + { + _Reason?.Clear(); - AddReason(Operand.FromTarget(), text, args); - return this; - } + AddReason(Operand.FromTarget(), text, args); + return this; + } - /// - /// Replace the existing reason with the supplied format string. - /// - /// The object path that affected the reason. - /// The text of a reason to use. This text should already be localized for the currently culture. - /// Replacement arguments for the format string. - public AssertResult ReasonFrom(string path, string text, params object[] args) - { - _Reason?.Clear(); + /// + /// Replace the existing reason with the supplied format string. + /// + /// The object path that affected the reason. + /// The text of a reason to use. This text should already be localized for the currently culture. + /// Replacement arguments for the format string. + public AssertResult ReasonFrom(string path, string text, params object[] args) + { + _Reason?.Clear(); - AddReason(Operand.FromPath(path), text, args); - return this; - } + AddReason(Operand.FromPath(path), text, args); + return this; + } - /// - /// Replace the existing reason with the supplied format string if the condition is true. - /// - /// When true the reason will be used. When false the existing reason will be used. - /// The text of a reason to use. This text should already be localized for the currently culture. - /// Replacement arguments for the format string. - public AssertResult ReasonIf(bool condition, string text, params object[] args) - { - return !condition ? this : Reason(text, args); - } + /// + /// Replace the existing reason with the supplied format string if the condition is true. + /// + /// When true the reason will be used. When false the existing reason will be used. + /// The text of a reason to use. This text should already be localized for the currently culture. + /// Replacement arguments for the format string. + public AssertResult ReasonIf(bool condition, string text, params object[] args) + { + return !condition ? this : Reason(text, args); + } - /// - /// Replace the existing reason with the supplied format string if the condition is true. - /// - /// The object path that affected the reason. - /// When true the reason will be used. When false the existing reason will be used. - /// The text of a reason to use. This text should already be localized for the currently culture. - /// Replacement arguments for the format string. - public AssertResult ReasonIf(string path, bool condition, string text, params object[] args) - { - return !condition ? this : ReasonFrom(path, text, args); - } + /// + /// Replace the existing reason with the supplied format string if the condition is true. + /// + /// The object path that affected the reason. + /// When true the reason will be used. When false the existing reason will be used. + /// The text of a reason to use. This text should already be localized for the currently culture. + /// Replacement arguments for the format string. + public AssertResult ReasonIf(string path, bool condition, string text, params object[] args) + { + return !condition ? this : ReasonFrom(path, text, args); + } - /// - /// Get an reasons that are currently set. - /// - /// Returns an array of reasons. This will always return null when the Value is true. - public string[] GetReason() - { - return (Result || IsNullOrEmptyReason()) ? Array.Empty() : _Reason.GetStrings(); - } + /// + /// Get an reasons that are currently set. + /// + /// Returns an array of reasons. This will always return null when the Value is true. + public string[] GetReason() + { + return (Result || IsNullOrEmptyReason()) ? Array.Empty() : _Reason.GetStrings(); + } - /// - /// Complete an assertion by writing an provided reasons and returning a boolean. - /// - /// Returns true or false. - public bool Complete() - { - // Check that the scope is still valid - if (!RunspaceContext.CurrentThread.IsScope(RunspaceScope.Rule)) - throw new RuleException(string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.VariableConditionScope, "Assert")); + /// + /// Complete an assertion by writing an provided reasons and returning a boolean. + /// + /// Returns true or false. + public bool Complete() + { + // Check that the scope is still valid + if (!RunspaceContext.CurrentThread.IsScope(RunspaceScope.Rule)) + throw new RuleException(string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.VariableConditionScope, "Assert")); - // Continue - for (var i = 0; _Reason != null && i < _Reason.Count; i++) - RunspaceContext.CurrentThread.WriteReason(_Reason[i]); + // Continue + for (var i = 0; _Reason != null && i < _Reason.Count; i++) + RunspaceContext.CurrentThread.WriteReason(_Reason[i]); - return Result; - } + return Result; + } - /// - /// Clear any reasons for this result. - /// - public void Ignore() - { - _Reason.Clear(); - } + /// + /// Clear any reasons for this result. + /// + public void Ignore() + { + _Reason.Clear(); + } - /// - public bool Equals(bool other) - { - return Result == other; - } + /// + public bool Equals(bool other) + { + return Result == other; + } - /// - /// Get a formatted string of the result reasons. - /// - public override string ToString() - { - return IsNullOrEmptyReason() ? string.Empty : string.Join(" ", _Reason.GetStrings()); - } + /// + /// Get a formatted string of the result reasons. + /// + public override string ToString() + { + return IsNullOrEmptyReason() ? string.Empty : string.Join(" ", _Reason.GetStrings()); + } - /// - /// Convert the result into a boolean value. - /// - public bool ToBoolean() - { - return Result; - } + /// + /// Convert the result into a boolean value. + /// + public bool ToBoolean() + { + return Result; + } - internal IResultReasonV2[] ToResultReason() - { - return _Reason == null || _Reason.Count == 0 ? Array.Empty() : _Reason.ToArray(); - } + internal IResultReasonV2[] ToResultReason() + { + return _Reason == null || _Reason.Count == 0 ? Array.Empty() : _Reason.ToArray(); + } - private bool IsNullOrEmptyReason() - { - return _Reason == null || _Reason.Count == 0; - } + private bool IsNullOrEmptyReason() + { + return _Reason == null || _Reason.Count == 0; } } diff --git a/src/PSRule/Runtime/Configuration.cs b/src/PSRule/Runtime/Configuration.cs index 6b52078898..086481da89 100644 --- a/src/PSRule/Runtime/Configuration.cs +++ b/src/PSRule/Runtime/Configuration.cs @@ -4,117 +4,116 @@ using System.Collections; using System.Dynamic; -namespace PSRule.Runtime +namespace PSRule.Runtime; + +/// +/// A set of rule configuration values that are exposed at runtime and automatically failback to defaults when not set in configuration. +/// +public sealed class Configuration : DynamicObject { - /// - /// A set of rule configuration values that are exposed at runtime and automatically failback to defaults when not set in configuration. - /// - public sealed class Configuration : DynamicObject - { - private readonly RunspaceContext _Context; + private readonly RunspaceContext _Context; - internal Configuration(RunspaceContext context) - { - _Context = context; - } + internal Configuration(RunspaceContext context) + { + _Context = context; + } - /// - public override bool TryGetMember(GetMemberBinder binder, out object result) - { - result = null; - if (binder == null || string.IsNullOrEmpty(binder.Name)) - return false; + /// + public override bool TryGetMember(GetMemberBinder binder, out object result) + { + result = null; + if (binder == null || string.IsNullOrEmpty(binder.Name)) + return false; - // Get from configuration - return TryGetValue(binder.Name, out result); - } + // Get from configuration + return TryGetValue(binder.Name, out result); + } - /// - /// Get the specified configuration key as a string array. - /// - /// A key for the configuration value. - /// Returns an array of strings. If the configuration key does not exist and empty array is returned. - public string[] GetStringValues(string configurationKey) - { - if (!TryGetValue(configurationKey, out var value) || value == null) - return Array.Empty(); + /// + /// Get the specified configuration key as a string array. + /// + /// A key for the configuration value. + /// Returns an array of strings. If the configuration key does not exist and empty array is returned. + public string[] GetStringValues(string configurationKey) + { + if (!TryGetValue(configurationKey, out var value) || value == null) + return Array.Empty(); - if (value is string valueT) - return new string[] { valueT }; + if (value is string valueT) + return new string[] { valueT }; - if (value is string[] result) - return result; + if (value is string[] result) + return result; - if (value is IEnumerable c) - { - var cList = new List(); - foreach (var v in c) - cList.Add(v.ToString()); + if (value is IEnumerable c) + { + var cList = new List(); + foreach (var v in c) + cList.Add(v.ToString()); - return cList.ToArray(); - } - return new string[] { value.ToString() }; + return cList.ToArray(); } + return new string[] { value.ToString() }; + } - /// - /// Try to the configuration key or use the specified default value if the key does not exist. - /// - /// A key for the configuration value. - /// The default value to use if the configuration key does not exist. - /// Returns the configured value or the default. - public object GetValueOrDefault(string configurationKey, object defaultValue) - { - return !TryGetValue(configurationKey, out var value) || value == null ? defaultValue : value; - } + /// + /// Try to the configuration key or use the specified default value if the key does not exist. + /// + /// A key for the configuration value. + /// The default value to use if the configuration key does not exist. + /// Returns the configured value or the default. + public object GetValueOrDefault(string configurationKey, object defaultValue) + { + return !TryGetValue(configurationKey, out var value) || value == null ? defaultValue : value; + } - /// - /// Try to get the configuration key as a . - /// - /// A key for the configuration value. - /// The default value to use if the configuration key does not exist. - /// Returns the configured value or the default. - public bool GetBoolOrDefault(string configurationKey, bool defaultValue) - { - return !TryGetValue(configurationKey, out var value) || !TryBool(value, out var result) ? defaultValue : result; - } + /// + /// Try to get the configuration key as a . + /// + /// A key for the configuration value. + /// The default value to use if the configuration key does not exist. + /// Returns the configured value or the default. + public bool GetBoolOrDefault(string configurationKey, bool defaultValue) + { + return !TryGetValue(configurationKey, out var value) || !TryBool(value, out var result) ? defaultValue : result; + } - /// - /// Try to get the configuration key as an . - /// - /// A key for the configuration value. - /// The default value to use if the configuration key does not exist. - /// Returns the configured value or the default. - public int GetIntegerOrDefault(string configurationKey, int defaultValue) - { - return !TryGetValue(configurationKey, out var value) || !TryInt(value, out var result) ? defaultValue : result; - } + /// + /// Try to get the configuration key as an . + /// + /// A key for the configuration value. + /// The default value to use if the configuration key does not exist. + /// Returns the configured value or the default. + public int GetIntegerOrDefault(string configurationKey, int defaultValue) + { + return !TryGetValue(configurationKey, out var value) || !TryInt(value, out var result) ? defaultValue : result; + } - private bool TryGetValue(string name, out object value) - { - value = null; - return _Context != null && _Context.TryGetConfigurationValue(name, out value); - } + private bool TryGetValue(string name, out object value) + { + value = null; + return _Context != null && _Context.TryGetConfigurationValue(name, out value); + } - private static bool TryBool(object o, out bool value) + private static bool TryBool(object o, out bool value) + { + value = default; + if (o is bool result || (o is string svalue && bool.TryParse(svalue, out result))) { - value = default; - if (o is bool result || (o is string svalue && bool.TryParse(svalue, out result))) - { - value = result; - return true; - } - return false; + value = result; + return true; } + return false; + } - private static bool TryInt(object o, out int value) + private static bool TryInt(object o, out int value) + { + value = default; + if (o is int result || (o is string svalue && int.TryParse(svalue, out result))) { - value = default; - if (o is int result || (o is string svalue && int.TryParse(svalue, out result))) - { - value = result; - return true; - } - return false; + value = result; + return true; } + return false; } } diff --git a/src/PSRule/Runtime/IBindingContext.cs b/src/PSRule/Runtime/IBindingContext.cs index a279314d14..8cd9f01a82 100644 --- a/src/PSRule/Runtime/IBindingContext.cs +++ b/src/PSRule/Runtime/IBindingContext.cs @@ -3,12 +3,11 @@ using PSRule.Runtime.ObjectPath; -namespace PSRule.Runtime +namespace PSRule.Runtime; + +internal interface IBindingContext { - internal interface IBindingContext - { - bool GetPathExpression(string path, out PathExpression expression); + bool GetPathExpression(string path, out PathExpression expression); - void CachePathExpression(string path, PathExpression expression); - } + void CachePathExpression(string path, PathExpression expression); } diff --git a/src/PSRule/Runtime/IInputCollection.cs b/src/PSRule/Runtime/IInputCollection.cs index 4bc69cc1c8..30c7ac087b 100644 --- a/src/PSRule/Runtime/IInputCollection.cs +++ b/src/PSRule/Runtime/IInputCollection.cs @@ -1,17 +1,16 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -namespace PSRule.Runtime +namespace PSRule.Runtime; + +/// +/// A collection of input passed to PSRule for anlaysis. +/// +public interface IInputCollection { /// - /// A collection of input passed to PSRule for anlaysis. + /// Add a path to the list of inputs. /// - public interface IInputCollection - { - /// - /// Add a path to the list of inputs. - /// - /// The path of files to add. - void Add(string path); - } + /// The path of files to add. + void Add(string path); } diff --git a/src/PSRule/Runtime/ILogger.cs b/src/PSRule/Runtime/ILogger.cs index 86e3828b0b..833f42f583 100644 --- a/src/PSRule/Runtime/ILogger.cs +++ b/src/PSRule/Runtime/ILogger.cs @@ -1,51 +1,50 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -namespace PSRule.Runtime +namespace PSRule.Runtime; + +/// +/// A set of log levels which indicate different types of diagnostic messages. +/// +[Flags] +internal enum LogLevel { - /// - /// A set of log levels which indicate different types of diagnostic messages. - /// - [Flags] - internal enum LogLevel - { - None = 0, + None = 0, - Error = 1, + Error = 1, - Warning = 2, + Warning = 2, - Info = 4, + Info = 4, - Verbose = 8, + Verbose = 8, - Debug = 16, - } + Debug = 16, +} + +/// +/// A generic interface for diagnostic logging within PSRule. +/// +internal interface ILogger +{ + /// + /// Determines if a specific log level should be written. + /// + /// The level to query. + /// Returns true when the log level should be written or false otherwise. + bool ShouldLog(LogLevel level); + + /// + /// Write a warning. + /// + /// The warning message write. + /// Any arguments to format the string with. + void Warning(string message, params object[] args); /// - /// A generic interface for diagnostic logging within PSRule. + /// Write an error from an exception. /// - internal interface ILogger - { - /// - /// Determines if a specific log level should be written. - /// - /// The level to query. - /// Returns true when the log level should be written or false otherwise. - bool ShouldLog(LogLevel level); - - /// - /// Write a warning. - /// - /// The warning message write. - /// Any arguments to format the string with. - void Warning(string message, params object[] args); - - /// - /// Write an error from an exception. - /// - /// The exception to write. - /// A string identififer for the error. - void Error(Exception exception, string errorId = null); - } + /// The exception to write. + /// A string identififer for the error. + void Error(Exception exception, string errorId = null); } diff --git a/src/PSRule/Runtime/IRepositoryRuntimeInfo.cs b/src/PSRule/Runtime/IRepositoryRuntimeInfo.cs index 2fd3d64240..a2120a7e22 100644 --- a/src/PSRule/Runtime/IRepositoryRuntimeInfo.cs +++ b/src/PSRule/Runtime/IRepositoryRuntimeInfo.cs @@ -3,27 +3,26 @@ using PSRule.Data; -namespace PSRule.Runtime +namespace PSRule.Runtime; + +/// +/// Display information about the current repository at runtime. +/// +public interface IRepositoryRuntimeInfo { /// - /// Display information about the current repository at runtime. + /// A URL to the current repository. /// - public interface IRepositoryRuntimeInfo - { - /// - /// A URL to the current repository. - /// - string Url { get; } + string Url { get; } - /// - /// The base ref for the current repository branch. - /// - string BaseRef { get; } + /// + /// The base ref for the current repository branch. + /// + string BaseRef { get; } - /// - /// Get a list of changed files within the repository. - /// - /// A collection of files. - IInputFileInfoCollection GetChangedFiles(); - } + /// + /// Get a list of changed files within the repository. + /// + /// A collection of files. + IInputFileInfoCollection GetChangedFiles(); } diff --git a/src/PSRule/Runtime/LanguageScope.cs b/src/PSRule/Runtime/LanguageScope.cs index 7f499b9126..bfe59bc1d8 100644 --- a/src/PSRule/Runtime/LanguageScope.cs +++ b/src/PSRule/Runtime/LanguageScope.cs @@ -6,322 +6,321 @@ using PSRule.Definitions; using PSRule.Pipeline; -namespace PSRule.Runtime +namespace PSRule.Runtime; + +/// +/// A named scope for language elements. +/// +internal interface ILanguageScope : IDisposable { /// - /// A named scope for language elements. + /// The name of the scope. /// - internal interface ILanguageScope : IDisposable - { - /// - /// The name of the scope. - /// - string Name { get; } + string Name { get; } - BindingOption Binding { get; } + BindingOption Binding { get; } - /// - /// Get an ordered culture preference list which will be tries for finding help. - /// - string[] Culture { get; } + /// + /// Get an ordered culture preference list which will be tries for finding help. + /// + string[] Culture { get; } - void Configure(OptionContext context); + void Configure(OptionContext context); - /// - /// Try to get a specific configuration value by name. - /// - bool TryConfigurationValue(string key, out object value); + /// + /// Try to get a specific configuration value by name. + /// + bool TryConfigurationValue(string key, out object value); - void WithFilter(IResourceFilter resourceFilter); + void WithFilter(IResourceFilter resourceFilter); - /// - /// Get a filter for a specific resource kind. - /// - IResourceFilter GetFilter(ResourceKind kind); + /// + /// Get a filter for a specific resource kind. + /// + IResourceFilter GetFilter(ResourceKind kind); - /// - /// Add a service to the scope. - /// - void AddService(string name, object service); + /// + /// Add a service to the scope. + /// + void AddService(string name, object service); - /// - /// Get a previously added service. - /// - object GetService(string name); + /// + /// Get a previously added service. + /// + object GetService(string name); - bool TryGetType(object o, out string type, out string path); + bool TryGetType(object o, out string type, out string path); - bool TryGetName(object o, out string name, out string path); + bool TryGetName(object o, out string name, out string path); - bool TryGetScope(object o, out string[] scope); - } + bool TryGetScope(object o, out string[] scope); +} - [DebuggerDisplay("{Name}")] - internal sealed class LanguageScope : ILanguageScope - { - internal const string STANDALONE_SCOPENAME = "."; +[DebuggerDisplay("{Name}")] +internal sealed class LanguageScope : ILanguageScope +{ + internal const string STANDALONE_SCOPENAME = "."; - private readonly RunspaceContext _Context; - private IDictionary _Configuration; - private readonly Dictionary _Service; - private readonly Dictionary _Filter; + private readonly RunspaceContext _Context; + private IDictionary _Configuration; + private readonly Dictionary _Service; + private readonly Dictionary _Filter; - private bool _Disposed; + private bool _Disposed; - public LanguageScope(RunspaceContext context, string name) - { - _Context = context; - Name = Normalize(name); - //_Configuration = new Dictionary(); - _Filter = new Dictionary(); - _Service = new Dictionary(); - } + public LanguageScope(RunspaceContext context, string name) + { + _Context = context; + Name = Normalize(name); + //_Configuration = new Dictionary(); + _Filter = new Dictionary(); + _Service = new Dictionary(); + } - /// - public string Name { [DebuggerStepThrough] get; } + /// + public string Name { [DebuggerStepThrough] get; } - /// - public BindingOption Binding { [DebuggerStepThrough] get; [DebuggerStepThrough] private set; } + /// + public BindingOption Binding { [DebuggerStepThrough] get; [DebuggerStepThrough] private set; } - /// - public string[] Culture { [DebuggerStepThrough] get; [DebuggerStepThrough] private set; } + /// + public string[] Culture { [DebuggerStepThrough] get; [DebuggerStepThrough] private set; } - /// - public void Configure(Dictionary configuration) - { - _Configuration.AddUnique(configuration); - } + /// + 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 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) - { - value = null; - return !string.IsNullOrEmpty(key) && _Configuration.TryGetValue(key, out value); - } + /// + public bool TryConfigurationValue(string key, out object value) + { + value = null; + return !string.IsNullOrEmpty(key) && _Configuration.TryGetValue(key, out value); + } - /// - public void WithFilter(IResourceFilter resourceFilter) - { - _Filter[resourceFilter.Kind] = resourceFilter; - } + /// + public void WithFilter(IResourceFilter resourceFilter) + { + _Filter[resourceFilter.Kind] = resourceFilter; + } - /// - public IResourceFilter GetFilter(ResourceKind kind) - { - return _Filter.TryGetValue(kind, out var filter) ? filter : null; - } + /// + public IResourceFilter GetFilter(ResourceKind kind) + { + return _Filter.TryGetValue(kind, out var filter) ? filter : null; + } - /// - public void AddService(string name, object service) - { - if (_Service.ContainsKey(name)) - return; + /// + public void AddService(string name, object service) + { + if (_Service.ContainsKey(name)) + return; - _Service.Add(name, service); - } + _Service.Add(name, service); + } + + /// + public object GetService(string name) + { + return _Service.TryGetValue(name, out var service) ? service : null; + } - /// - public object GetService(string name) + public bool TryGetType(object o, out string type, out string path) + { + if (_Context != null && _Context.TargetObject.Value == o) { - return _Service.TryGetValue(name, out var service) ? service : null; + var binding = _Context.TargetBinder.Result(Name); + type = binding.TargetType; + path = binding.TargetTypePath; + return true; } - - public bool TryGetType(object o, out string type, out string path) + else if (_Context != null) { - if (_Context != null && _Context.TargetObject.Value == o) - { - var binding = _Context.TargetBinder.Result(Name); - type = binding.TargetType; - path = binding.TargetTypePath; - return true; - } - else if (_Context != null) - { - var binding = _Context.TargetBinder.Using(Name).Bind(o); - type = binding.TargetType; - path = binding.TargetTypePath; - return true; - } - type = null; - path = null; - return false; + var binding = _Context.TargetBinder.Using(Name).Bind(o); + type = binding.TargetType; + path = binding.TargetTypePath; + return true; } + type = null; + path = null; + return false; + } - public bool TryGetName(object o, out string name, out string path) + public bool TryGetName(object o, out string name, out string path) + { + if (_Context != null && _Context.TargetObject.Value == o) { - if (_Context != null && _Context.TargetObject.Value == o) - { - var binding = _Context.TargetBinder.Result(Name); - name = binding.TargetName; - path = binding.TargetNamePath; - return true; - } - else if (_Context != null) - { - var binding = _Context.TargetBinder.Using(Name).Bind(o); - name = binding.TargetName; - path = binding.TargetNamePath; - return true; - } - name = null; - path = null; - return false; + var binding = _Context.TargetBinder.Result(Name); + name = binding.TargetName; + path = binding.TargetNamePath; + return true; } - - public bool TryGetScope(object o, out string[] scope) + else if (_Context != null) { - if (_Context != null && _Context.TargetObject.Value == o) - { - scope = _Context.TargetObject.Scope; - return true; - } - scope = null; - return false; + var binding = _Context.TargetBinder.Using(Name).Bind(o); + name = binding.TargetName; + path = binding.TargetNamePath; + return true; } + name = null; + path = null; + return false; + } - internal static string Normalize(string scope) + public bool TryGetScope(object o, out string[] scope) + { + if (_Context != null && _Context.TargetObject.Value == o) { - return string.IsNullOrEmpty(scope) ? STANDALONE_SCOPENAME : scope; + scope = _Context.TargetObject.Scope; + return true; } + scope = null; + return false; + } - private void Dispose(bool disposing) + internal static string Normalize(string scope) + { + return string.IsNullOrEmpty(scope) ? STANDALONE_SCOPENAME : scope; + } + + private void Dispose(bool disposing) + { + if (!_Disposed) { - if (!_Disposed) + if (disposing) { - if (disposing) + // Release and dispose services + if (_Service != null && _Service.Count > 0) { - // Release and dispose services - if (_Service != null && _Service.Count > 0) + foreach (var kv in _Service) { - foreach (var kv in _Service) - { - if (kv.Value is IDisposable d) - d.Dispose(); - } - _Service.Clear(); + if (kv.Value is IDisposable d) + d.Dispose(); } + _Service.Clear(); } - _Disposed = true; } - } - - public void Dispose() - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(disposing: true); - GC.SuppressFinalize(this); + _Disposed = true; } } - /// - /// A collection of . - /// - internal sealed class LanguageScopeSet : IDisposable + public void Dispose() { - private readonly RunspaceContext _Context; - private readonly Dictionary _Scopes; + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } +} + +/// +/// A collection of . +/// +internal sealed class LanguageScopeSet : IDisposable +{ + private readonly RunspaceContext _Context; + private readonly Dictionary _Scopes; - private ILanguageScope _Current; - private bool _Disposed; + private ILanguageScope _Current; + private bool _Disposed; - public LanguageScopeSet(RunspaceContext context) - { - _Context = context; - _Scopes = new Dictionary(StringComparer.OrdinalIgnoreCase); - Import(null, out _Current); - } + public LanguageScopeSet(RunspaceContext context) + { + _Context = context; + _Scopes = new Dictionary(StringComparer.OrdinalIgnoreCase); + Import(null, out _Current); + } - public ILanguageScope Current + public ILanguageScope Current + { + get { - get - { - return _Current; - } + return _Current; } + } - #region IDisposable + #region IDisposable - private void Dispose(bool disposing) + private void Dispose(bool disposing) + { + if (!_Disposed) { - if (!_Disposed) + if (disposing) { - if (disposing) + // Release and dispose scopes + if (_Scopes != null && _Scopes.Count > 0) { - // Release and dispose scopes - if (_Scopes != null && _Scopes.Count > 0) - { - foreach (var kv in _Scopes) - kv.Value.Dispose(); + foreach (var kv in _Scopes) + kv.Value.Dispose(); - _Scopes.Clear(); - } + _Scopes.Clear(); } - _Disposed = true; } + _Disposed = true; } + } - public void Dispose() - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(disposing: true); - GC.SuppressFinalize(this); - } + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } - #endregion IDisposable + #endregion IDisposable - internal void Add(ILanguageScope languageScope) - { - _Scopes.Add(languageScope.Name, languageScope); - } + internal void Add(ILanguageScope languageScope) + { + _Scopes.Add(languageScope.Name, languageScope); + } - internal IEnumerable Get() - { - return _Scopes.Values; - } + internal IEnumerable Get() + { + return _Scopes.Values; + } - /// - /// Switch to a specific language scope by name. - /// - /// The name of the language scope to switch to. - internal void UseScope(string name) - { - if (!_Scopes.TryGetValue(GetScopeName(name), out var scope)) - throw new Exception($"The specified scope '{name}' was not found."); + /// + /// Switch to a specific language scope by name. + /// + /// The name of the language scope to switch to. + internal void UseScope(string name) + { + if (!_Scopes.TryGetValue(GetScopeName(name), out var scope)) + throw new Exception($"The specified scope '{name}' was not found."); - _Current = scope; - } + _Current = scope; + } - internal bool TryScope(string name, out ILanguageScope scope) - { - return _Scopes.TryGetValue(GetScopeName(name), out scope); - } + internal bool TryScope(string name, out ILanguageScope scope) + { + return _Scopes.TryGetValue(GetScopeName(name), out scope); + } - internal bool Import(string name, out ILanguageScope scope) - { - if (_Scopes.TryGetValue(GetScopeName(name), out scope)) - return false; + internal bool Import(string name, out ILanguageScope scope) + { + if (_Scopes.TryGetValue(GetScopeName(name), out scope)) + return false; - scope = new LanguageScope(_Context, name); - Add(scope); - return true; - } + scope = new LanguageScope(_Context, name); + Add(scope); + return true; + } - private static string GetScopeName(string name) - { - return LanguageScope.Normalize(name); - } + private static string GetScopeName(string name) + { + return LanguageScope.Normalize(name); } } diff --git a/src/PSRule/Runtime/LanguageScriptBlock.cs b/src/PSRule/Runtime/LanguageScriptBlock.cs index cae792f2c5..2695037dc5 100644 --- a/src/PSRule/Runtime/LanguageScriptBlock.cs +++ b/src/PSRule/Runtime/LanguageScriptBlock.cs @@ -3,46 +3,45 @@ using System.Management.Automation; -namespace PSRule.Runtime +namespace PSRule.Runtime; + +internal sealed class LanguageScriptBlock : IDisposable { - internal sealed class LanguageScriptBlock : IDisposable - { - private readonly PowerShell _Block; + private readonly PowerShell _Block; - private bool _Disposed; + private bool _Disposed; - public LanguageScriptBlock(PowerShell block) - { - _Block = block; - } + public LanguageScriptBlock(PowerShell block) + { + _Block = block; + } - public void Invoke() - { - _Block.Invoke(); - } + public void Invoke() + { + _Block.Invoke(); + } - #region IDisposable + #region IDisposable - private void Dispose(bool disposing) + private void Dispose(bool disposing) + { + if (!_Disposed) { - if (!_Disposed) + if (disposing) { - if (disposing) - { - _Block.Runspace = null; - _Block.Dispose(); - } - _Disposed = true; + _Block.Runspace = null; + _Block.Dispose(); } + _Disposed = true; } + } - public void Dispose() - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(disposing: true); - GC.SuppressFinalize(this); - } - - #endregion IDisposable + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); } + + #endregion IDisposable } diff --git a/src/PSRule/Runtime/LocalizedData.cs b/src/PSRule/Runtime/LocalizedData.cs index fb7e528a48..aa8fe8f48a 100644 --- a/src/PSRule/Runtime/LocalizedData.cs +++ b/src/PSRule/Runtime/LocalizedData.cs @@ -5,53 +5,52 @@ using System.Dynamic; using System.Management.Automation.Language; -namespace PSRule.Runtime +namespace PSRule.Runtime; + +/// +/// A PSRule built-in variable that is used to reference localized strings from rules. +/// +public sealed class LocalizedData : DynamicObject { - /// - /// A PSRule built-in variable that is used to reference localized strings from rules. - /// - public sealed class LocalizedData : DynamicObject - { - private const string DATA_FILENAME = "PSRule-rules.psd1"; + private const string DATA_FILENAME = "PSRule-rules.psd1"; - private static readonly Hashtable Empty = new(); + private static readonly Hashtable Empty = new(); - /// - public override bool TryGetMember(GetMemberBinder binder, out object result) + /// + public override bool TryGetMember(GetMemberBinder binder, out object result) + { + var hashtable = TryGetLocalized(); + if (hashtable.Count > 0 && binder != null && !string.IsNullOrEmpty(binder.Name) && hashtable.ContainsKey(binder.Name)) { - var hashtable = TryGetLocalized(); - if (hashtable.Count > 0 && binder != null && !string.IsNullOrEmpty(binder.Name) && hashtable.ContainsKey(binder.Name)) - { - result = hashtable[binder.Name]; - return true; - } - result = null; - return false; + result = hashtable[binder.Name]; + return true; } + result = null; + return false; + } - private static Hashtable TryGetLocalized() - { - var path = GetFilePath(); - if (path == null) - return Empty; - - if (RunspaceContext.CurrentThread.Pipeline.LocalizedDataCache.TryGetValue(path, out var value)) - return value; - - var ast = Parser.ParseFile(path, out var tokens, out var errors); - var data = ast.Find(a => a is HashtableAst, false); - if (data != null) - { - var result = (Hashtable)data.SafeGetValue(); - RunspaceContext.CurrentThread.Pipeline.LocalizedDataCache[path] = result; - return result; - } + private static Hashtable TryGetLocalized() + { + var path = GetFilePath(); + if (path == null) return Empty; - } - private static string GetFilePath() + if (RunspaceContext.CurrentThread.Pipeline.LocalizedDataCache.TryGetValue(path, out var value)) + return value; + + var ast = Parser.ParseFile(path, out var tokens, out var errors); + var data = ast.Find(a => a is HashtableAst, false); + if (data != null) { - return RunspaceContext.CurrentThread.GetLocalizedPath(DATA_FILENAME, out _); + var result = (Hashtable)data.SafeGetValue(); + RunspaceContext.CurrentThread.Pipeline.LocalizedDataCache[path] = result; + return result; } + return Empty; + } + + private static string GetFilePath() + { + return RunspaceContext.CurrentThread.GetLocalizedPath(DATA_FILENAME, out _); } } diff --git a/src/PSRule/Runtime/NameToken.cs b/src/PSRule/Runtime/NameToken.cs index 653bd383ce..8885ea3c38 100644 --- a/src/PSRule/Runtime/NameToken.cs +++ b/src/PSRule/Runtime/NameToken.cs @@ -3,53 +3,52 @@ using System.Diagnostics; -namespace PSRule.Runtime +namespace PSRule.Runtime; + +/// +/// The type of NameToken. +/// +internal enum NameTokenType +{ + /// + /// The token represents a field/ property of an object. + /// + Field = 0, + + /// + /// The token is an index in an object. + /// + Index = 1, + + /// + /// The token is a reference to the parent object. Can only be the first token. + /// + Self = 2 +} + +/// +/// A token for expressing a path through a tree of fields. +/// +[DebuggerDisplay("{Type}, Name = {Name}, Index = {Index}")] +internal sealed class NameToken { /// - /// The type of NameToken. + /// The name of the field if the token type if Field. + /// + public string Name; + + /// + /// The index if the token type if Index. + /// + public int Index; + + /// + /// The next token. /// - internal enum NameTokenType - { - /// - /// The token represents a field/ property of an object. - /// - Field = 0, - - /// - /// The token is an index in an object. - /// - Index = 1, - - /// - /// The token is a reference to the parent object. Can only be the first token. - /// - Self = 2 - } + public NameToken Next; /// - /// A token for expressing a path through a tree of fields. + /// The type of the token. /// - [DebuggerDisplay("{Type}, Name = {Name}, Index = {Index}")] - internal sealed class NameToken - { - /// - /// The name of the field if the token type if Field. - /// - public string Name; - - /// - /// The index if the token type if Index. - /// - public int Index; - - /// - /// The next token. - /// - public NameToken Next; - - /// - /// The type of the token. - /// - public NameTokenType Type = NameTokenType.Field; - } + public NameTokenType Type = NameTokenType.Field; } diff --git a/src/PSRule/Runtime/ObjectHelper.cs b/src/PSRule/Runtime/ObjectHelper.cs index 99f90247d9..08cd873205 100644 --- a/src/PSRule/Runtime/ObjectHelper.cs +++ b/src/PSRule/Runtime/ObjectHelper.cs @@ -5,43 +5,42 @@ using System.Management.Automation; using PSRule.Runtime.ObjectPath; -namespace PSRule.Runtime +namespace PSRule.Runtime; + +/// +/// A helper class to traverse object properties. +/// +internal static class ObjectHelper { - /// - /// A helper class to traverse object properties. - /// - internal static class ObjectHelper + public static bool GetPath(PSObject targetObject, string path, bool caseSensitive, out object value) { - public static bool GetPath(PSObject targetObject, string path, bool caseSensitive, out object value) - { - return GetPath(null, targetObject, path, caseSensitive, out value); - } + return GetPath(null, targetObject, path, caseSensitive, out value); + } - public static bool GetPath(IBindingContext bindingContext, object targetObject, string path, bool caseSensitive, out object value) - { - var expression = GetPathExpression(bindingContext, path); - return expression.TryGet(targetObject, caseSensitive, out value); - } + public static bool GetPath(IBindingContext bindingContext, object targetObject, string path, bool caseSensitive, out object value) + { + var expression = GetPathExpression(bindingContext, path); + return expression.TryGet(targetObject, caseSensitive, out value); + } - public static bool GetPath(IBindingContext bindingContext, object targetObject, string path, bool caseSensitive, out object[] value) - { - var expression = GetPathExpression(bindingContext, path); - return expression.TryGet(targetObject, caseSensitive, out value); - } + public static bool GetPath(IBindingContext bindingContext, object targetObject, string path, bool caseSensitive, out object[] value) + { + var expression = GetPathExpression(bindingContext, path); + return expression.TryGet(targetObject, caseSensitive, out value); + } - /// - /// Get a token for the specified name either by creating or reading from cache. - /// - [DebuggerStepThrough] - private static PathExpression GetPathExpression(IBindingContext bindingContext, string path) + /// + /// Get a token for the specified name either by creating or reading from cache. + /// + [DebuggerStepThrough] + private static PathExpression GetPathExpression(IBindingContext bindingContext, string path) + { + // Try to load nameToken from cache + if (bindingContext == null || !bindingContext.GetPathExpression(path, out var expression)) { - // Try to load nameToken from cache - if (bindingContext == null || !bindingContext.GetPathExpression(path, out var expression)) - { - expression = PathExpression.Create(path); - bindingContext?.CachePathExpression(path, expression); - } - return expression; + expression = PathExpression.Create(path); + bindingContext?.CachePathExpression(path, expression); } + return expression; } } diff --git a/src/PSRule/Runtime/ObjectPath/Exceptions.cs b/src/PSRule/Runtime/ObjectPath/Exceptions.cs index 9e5e99fe08..78b1e281e8 100644 --- a/src/PSRule/Runtime/ObjectPath/Exceptions.cs +++ b/src/PSRule/Runtime/ObjectPath/Exceptions.cs @@ -5,42 +5,41 @@ using System.Security.Permissions; using PSRule.Pipeline; -namespace PSRule.Runtime.ObjectPath +namespace PSRule.Runtime.ObjectPath; + +/// +/// An exception thrown by PSRule when evaluating an object path. +/// +[Serializable] +public sealed class ObjectPathEvaluateException : PipelineException { - /// - /// An exception thrown by PSRule when evaluating an object path. - /// - [Serializable] - public sealed class ObjectPathEvaluateException : PipelineException + /// + public ObjectPathEvaluateException() + { + } + + /// + public ObjectPathEvaluateException(string message) : base(message) + { + } + + /// + public ObjectPathEvaluateException(string message, Exception innerException) : base(message, innerException) { - /// - public ObjectPathEvaluateException() - { - } - - /// - public ObjectPathEvaluateException(string message) : base(message) - { - } - - /// - public ObjectPathEvaluateException(string message, Exception innerException) : base(message, innerException) - { - } - - /// - private ObjectPathEvaluateException(SerializationInfo info, StreamingContext context) : base(info, context) - { - } - - /// - [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)] - public override void GetObjectData(SerializationInfo info, StreamingContext context) - { - if (info == null) - throw new ArgumentNullException(nameof(info)); - - base.GetObjectData(info, context); - } + } + + /// + private ObjectPathEvaluateException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + + /// + [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)] + public override void GetObjectData(SerializationInfo info, StreamingContext context) + { + if (info == null) + throw new ArgumentNullException(nameof(info)); + + base.GetObjectData(info, context); } } diff --git a/src/PSRule/Runtime/ObjectPath/PathExpression.cs b/src/PSRule/Runtime/ObjectPath/PathExpression.cs index f02b62b178..851868c883 100644 --- a/src/PSRule/Runtime/ObjectPath/PathExpression.cs +++ b/src/PSRule/Runtime/ObjectPath/PathExpression.cs @@ -3,127 +3,126 @@ using System.Diagnostics; -namespace PSRule.Runtime.ObjectPath +namespace PSRule.Runtime.ObjectPath; + +/// +/// An expression function that returns one or more values when successful. +/// +internal delegate bool PathExpressionFn(IPathExpressionContext context, object input, out IEnumerable value, out bool enumerable); + +/// +/// A function for filter objects that simply returns true or false. +/// +internal delegate bool PathExpressionFilterFn(IPathExpressionContext context, object input); + +/// +/// A context ojbect used using evaluating a path expression. +/// +internal interface IPathExpressionContext { + object Input { get; } + + bool CaseSensitive { get; } +} + +/// +/// The default context object used using evaluating a path expression. +/// +internal sealed class PathExpressionContext : IPathExpressionContext +{ + public PathExpressionContext(object input, bool caseSensitive) + { + Input = input; + CaseSensitive = caseSensitive; + } + /// - /// An expression function that returns one or more values when successful. + /// The original root object passed into the expression. /// - internal delegate bool PathExpressionFn(IPathExpressionContext context, object input, out IEnumerable value, out bool enumerable); + public object Input { get; } /// - /// A function for filter objects that simply returns true or false. + /// Determines if member name matching is case-sensitive. /// - internal delegate bool PathExpressionFilterFn(IPathExpressionContext context, object input); + public bool CaseSensitive { get; } +} + +/// +/// A path expression using JSONPath inspired syntax. +/// +[DebuggerDisplay("{Path}")] +internal sealed class PathExpression +{ + private readonly PathExpressionFn _Expression; + + [DebuggerStepThrough] + private PathExpression(string path, PathExpressionFn expression, bool isArray) + { + Path = path; + IsArray = isArray; + _Expression = expression; + } /// - /// A context ojbect used using evaluating a path expression. + /// The path expression. /// - internal interface IPathExpressionContext + public string Path { get; } + + /// + /// Specifies if the result is an array. + /// + public bool IsArray { get; } + + /// + /// Create the expression from the specified path. + /// + public static PathExpression Create(string path) { - object Input { get; } + var tokens = PathTokenizer.Get(path); + var builder = new PathExpressionBuilder(); + return new PathExpression(path, builder.Build(tokens), builder.IsArray); + } + + /// + /// Use the expression to return an array of results. + /// + [DebuggerStepThrough] + public bool TryGet(object o, bool caseSensitive, out object[] value) + { + value = null; + if (!TryGet(o, caseSensitive, out var result, out _)) + return false; - bool CaseSensitive { get; } + value = result.ToArray(); + return true; } /// - /// The default context object used using evaluating a path expression. + /// Use the expression to return a single or an array of results. /// - internal sealed class PathExpressionContext : IPathExpressionContext + [DebuggerStepThrough] + public bool TryGet(object o, bool caseSensitive, out object value) { - public PathExpressionContext(object input, bool caseSensitive) - { - Input = input; - CaseSensitive = caseSensitive; - } - - /// - /// The original root object passed into the expression. - /// - public object Input { get; } - - /// - /// Determines if member name matching is case-sensitive. - /// - public bool CaseSensitive { get; } + value = null; + if (!TryGet(o, caseSensitive, out var result, out var enumerable)) + return false; + + var items = result.ToArray(); + value = IsArray || enumerable ? items : items[0]; + return true; } /// - /// A path expression using JSONPath inspired syntax. + /// Use the path to selector one or more values from the object. /// - [DebuggerDisplay("{Path}")] - internal sealed class PathExpression + /// The object to navigate the path for. + /// Determines if member name matching is case-sensitive. + /// The values selected from the object. + /// Determines if is enumerable. + /// Returns true when the path exists within the object. Returns false if the path does not exist. + private bool TryGet(object o, bool caseSensitive, out IEnumerable value, out bool enumerable) { - private readonly PathExpressionFn _Expression; - - [DebuggerStepThrough] - private PathExpression(string path, PathExpressionFn expression, bool isArray) - { - Path = path; - IsArray = isArray; - _Expression = expression; - } - - /// - /// The path expression. - /// - public string Path { get; } - - /// - /// Specifies if the result is an array. - /// - public bool IsArray { get; } - - /// - /// Create the expression from the specified path. - /// - public static PathExpression Create(string path) - { - var tokens = PathTokenizer.Get(path); - var builder = new PathExpressionBuilder(); - return new PathExpression(path, builder.Build(tokens), builder.IsArray); - } - - /// - /// Use the expression to return an array of results. - /// - [DebuggerStepThrough] - public bool TryGet(object o, bool caseSensitive, out object[] value) - { - value = null; - if (!TryGet(o, caseSensitive, out var result, out _)) - return false; - - value = result.ToArray(); - return true; - } - - /// - /// Use the expression to return a single or an array of results. - /// - [DebuggerStepThrough] - public bool TryGet(object o, bool caseSensitive, out object value) - { - value = null; - if (!TryGet(o, caseSensitive, out var result, out var enumerable)) - return false; - - var items = result.ToArray(); - value = IsArray || enumerable ? items : items[0]; - return true; - } - - /// - /// Use the path to selector one or more values from the object. - /// - /// The object to navigate the path for. - /// Determines if member name matching is case-sensitive. - /// The values selected from the object. - /// Determines if is enumerable. - /// Returns true when the path exists within the object. Returns false if the path does not exist. - private bool TryGet(object o, bool caseSensitive, out IEnumerable value, out bool enumerable) - { - var context = new PathExpressionContext(o, caseSensitive); - return _Expression.Invoke(context, o, out value, out enumerable); - } + var context = new PathExpressionContext(o, caseSensitive); + return _Expression.Invoke(context, o, out value, out enumerable); } } diff --git a/src/PSRule/Runtime/ObjectPath/PathExpressionBuilder.cs b/src/PSRule/Runtime/ObjectPath/PathExpressionBuilder.cs index d0ef2413ab..6d15703390 100644 --- a/src/PSRule/Runtime/ObjectPath/PathExpressionBuilder.cs +++ b/src/PSRule/Runtime/ObjectPath/PathExpressionBuilder.cs @@ -9,718 +9,717 @@ using Newtonsoft.Json.Linq; using PSRule.Resources; -namespace PSRule.Runtime.ObjectPath +namespace PSRule.Runtime.ObjectPath; + +/// +/// A helper class to build an expression tree from path tokens. +/// +internal sealed class PathExpressionBuilder { - /// - /// A helper class to build an expression tree from path tokens. - /// - internal sealed class PathExpressionBuilder + private const int DEFAULT_RECURSE_MAX_DEPTH = 100; + private static readonly object[] DEFAULT_EMPTY_ARRAY = new object[] { }; + + private readonly int _RecurseMaxDepth; + + public PathExpressionBuilder() { - private const int DEFAULT_RECURSE_MAX_DEPTH = 100; - private static readonly object[] DEFAULT_EMPTY_ARRAY = new object[] { }; + _RecurseMaxDepth = DEFAULT_RECURSE_MAX_DEPTH; + } - private readonly int _RecurseMaxDepth; + private sealed class DynamicPropertyBinder : GetMemberBinder + { + internal DynamicPropertyBinder(string name, bool ignoreCase) + : base(name, ignoreCase) { } - public PathExpressionBuilder() + public override DynamicMetaObject FallbackGetMember(DynamicMetaObject target, DynamicMetaObject errorSuggestion) { - _RecurseMaxDepth = DEFAULT_RECURSE_MAX_DEPTH; + return null; } + } - private sealed class DynamicPropertyBinder : GetMemberBinder - { - internal DynamicPropertyBinder(string name, bool ignoreCase) - : base(name, ignoreCase) { } - - public override DynamicMetaObject FallbackGetMember(DynamicMetaObject target, DynamicMetaObject errorSuggestion) - { - return null; - } - } + /// + /// Determines if the output should be an array. + /// + public bool IsArray { get; private set; } - /// - /// Determines if the output should be an array. - /// - public bool IsArray { get; private set; } + /// + /// Build a delegate function to evaluate the object path. + /// + public PathExpressionFn Build(IPathToken[] tokens) + { + return BuildSelector(new TokenReader(tokens)); + } - /// - /// Build a delegate function to evaluate the object path. - /// - public PathExpressionFn Build(IPathToken[] tokens) - { - return BuildSelector(new TokenReader(tokens)); - } + private void UseArray() + { + IsArray = true; + } - private void UseArray() - { - IsArray = true; - } + private PathExpressionFn BuildSelector(ITokenReader reader) + { + if (!reader.Peak(out var token) || token.Type == PathTokenType.EndFilter || token.Type == PathTokenType.EndGroup) + return Return; - private PathExpressionFn BuildSelector(ITokenReader reader) + reader.Next(out token); + switch (token.Type) { - if (!reader.Peak(out var token) || token.Type == PathTokenType.EndFilter || token.Type == PathTokenType.EndGroup) - return Return; + case PathTokenType.DotSelector: + return DotSelector(reader, token.As(), token.Option); - reader.Next(out token); - switch (token.Type) - { - case PathTokenType.DotSelector: - return DotSelector(reader, token.As(), token.Option); + case PathTokenType.DescendantSelector: + return DescendantSelector(reader, token.As(), token.Option); - case PathTokenType.DescendantSelector: - return DescendantSelector(reader, token.As(), token.Option); + case PathTokenType.RootRef: + return RootRef(reader); - case PathTokenType.RootRef: - return RootRef(reader); + case PathTokenType.CurrentRef: + return CurrentRef(reader); - case PathTokenType.CurrentRef: - return CurrentRef(reader); + case PathTokenType.IndexWildSelector: + return IndexWildSelector(reader); - case PathTokenType.IndexWildSelector: - return IndexWildSelector(reader); + case PathTokenType.IndexSelector: + return IndexSelector(reader, token.As()); - case PathTokenType.IndexSelector: - return IndexSelector(reader, token.As()); + case PathTokenType.StartFilter: + return FilterSelector(reader); - case PathTokenType.StartFilter: - return FilterSelector(reader); + case PathTokenType.ArraySliceSelector: + return ArraySliceSelector(reader, token.As()); - case PathTokenType.ArraySliceSelector: - return ArraySliceSelector(reader, token.As()); + case PathTokenType.Boolean: + case PathTokenType.Integer: + case PathTokenType.String: + return Literal(token.Arg); - case PathTokenType.Boolean: - case PathTokenType.Integer: - case PathTokenType.String: - return Literal(token.Arg); - - default: - return Return; - } + default: + return Return; } + } - private PathExpressionFn FilterSelector(ITokenReader reader) + private PathExpressionFn FilterSelector(ITokenReader reader) + { + UseArray(); + var filter = BuildExpression(reader, PathTokenType.EndFilter); + var next = BuildSelector(reader); + return (IPathExpressionContext context, object input, out IEnumerable value, out bool enumerable) => { - UseArray(); - var filter = BuildExpression(reader, PathTokenType.EndFilter); - var next = BuildSelector(reader); - return (IPathExpressionContext context, object input, out IEnumerable value, out bool enumerable) => + var result = new List(); + var success = 0; + foreach (var i in GetAll(input)) { - var result = new List(); - var success = 0; - foreach (var i in GetAll(input)) - { - if (!filter(context, i)) - continue; + if (!filter(context, i)) + continue; - if (!next(context, i, out var items, out _)) - continue; + if (!next(context, i, out var items, out _)) + continue; - success++; - result.AddRange(items); - } - value = success > 0 ? result.ToArray() : null; - enumerable = value != null; - return success > 0; - }; - } + success++; + result.AddRange(items); + } + value = success > 0 ? result.ToArray() : null; + enumerable = value != null; + return success > 0; + }; + } - private PathExpressionFn IndexSelector(ITokenReader reader, int index) + private PathExpressionFn IndexSelector(ITokenReader reader, int index) + { + var next = BuildSelector(reader); + return (IPathExpressionContext context, object input, out IEnumerable value, out bool enumerable) => { - var next = BuildSelector(reader); - return (IPathExpressionContext context, object input, out IEnumerable value, out bool enumerable) => - { - value = null; - enumerable = false; - return TryGetIndex(input, index, out var item) && next(context, item, out value, out enumerable); - }; - } + value = null; + enumerable = false; + return TryGetIndex(input, index, out var item) && next(context, item, out value, out enumerable); + }; + } - private PathExpressionFn IndexWildSelector(ITokenReader reader) + private PathExpressionFn IndexWildSelector(ITokenReader reader) + { + UseArray(); + var next = BuildSelector(reader); + return (IPathExpressionContext context, object input, out IEnumerable value, out bool enumerable) => { - UseArray(); - var next = BuildSelector(reader); - return (IPathExpressionContext context, object input, out IEnumerable value, out bool enumerable) => + var result = new List(); + var success = 0; + foreach (var i in GetAll(input)) { - var result = new List(); - var success = 0; - foreach (var i in GetAll(input)) - { - if (!next(context, i, out var items, out _)) - continue; + if (!next(context, i, out var items, out _)) + continue; - success++; - result.AddRange(items); - } - value = success > 0 ? result.ToArray() : null; - enumerable = value != null; - return success > 0; - }; - } + success++; + result.AddRange(items); + } + value = success > 0 ? result.ToArray() : null; + enumerable = value != null; + return success > 0; + }; + } - private PathExpressionFn ArraySliceSelector(ITokenReader reader, int?[] arg) - { - UseArray(); - var next = BuildSelector(reader); - var step = arg[2].GetValueOrDefault(1); - var start = arg[0].GetValueOrDefault(step >= 0 ? 0 : -1); - var end = arg[1]; - return (IPathExpressionContext context, object input, out IEnumerable value, out bool enumerable) => - { - var result = new List(); - var currentIndex = start; - while ((!end.HasValue || (step > 0 && currentIndex < end) || (step < 0 && currentIndex > end)) && TryGetIndex(input, currentIndex, out var slice)) - { - currentIndex += step; - if (!next(context, slice, out var items, out _)) - continue; + private PathExpressionFn ArraySliceSelector(ITokenReader reader, int?[] arg) + { + UseArray(); + var next = BuildSelector(reader); + var step = arg[2].GetValueOrDefault(1); + var start = arg[0].GetValueOrDefault(step >= 0 ? 0 : -1); + var end = arg[1]; + return (IPathExpressionContext context, object input, out IEnumerable value, out bool enumerable) => + { + var result = new List(); + var currentIndex = start; + while ((!end.HasValue || (step > 0 && currentIndex < end) || (step < 0 && currentIndex > end)) && TryGetIndex(input, currentIndex, out var slice)) + { + currentIndex += step; + if (!next(context, slice, out var items, out _)) + continue; - result.AddRange(items); - } - enumerable = true; - value = result.ToArray(); - return true; - }; - } + result.AddRange(items); + } + enumerable = true; + value = result.ToArray(); + return true; + }; + } - private PathExpressionFn DotSelector(ITokenReader reader, string memberName, PathTokenOption option) + private PathExpressionFn DotSelector(ITokenReader reader, string memberName, PathTokenOption option) + { + var caseSensitiveFlag = option == PathTokenOption.CaseSensitive; + var next = BuildSelector(reader); + return (IPathExpressionContext context, object input, out IEnumerable value, out bool enumerable) => { - var caseSensitiveFlag = option == PathTokenOption.CaseSensitive; - var next = BuildSelector(reader); - return (IPathExpressionContext context, object input, out IEnumerable value, out bool enumerable) => - { - value = null; - enumerable = false; - var caseSensitive = context.CaseSensitive != caseSensitiveFlag; - return TryGetField(input, memberName, caseSensitive, out var item) && next(context, item, out value, out enumerable); - }; - } + value = null; + enumerable = false; + var caseSensitive = context.CaseSensitive != caseSensitiveFlag; + return TryGetField(input, memberName, caseSensitive, out var item) && next(context, item, out value, out enumerable); + }; + } - private PathExpressionFn DescendantSelector(ITokenReader reader, string memberName, PathTokenOption option) + private PathExpressionFn DescendantSelector(ITokenReader reader, string memberName, PathTokenOption option) + { + var caseSensitiveFlag = option == PathTokenOption.CaseSensitive; + var next = BuildSelector(reader); + return (IPathExpressionContext context, object input, out IEnumerable value, out bool enumerable) => { - var caseSensitiveFlag = option == PathTokenOption.CaseSensitive; - var next = BuildSelector(reader); - return (IPathExpressionContext context, object input, out IEnumerable value, out bool enumerable) => + var caseSensitive = context.CaseSensitive != caseSensitiveFlag; + var result = new List(); + var success = 0; + foreach (var i in GetAllRecurse(input, memberName, caseSensitive, 0)) { - var caseSensitive = context.CaseSensitive != caseSensitiveFlag; - var result = new List(); - var success = 0; - foreach (var i in GetAllRecurse(input, memberName, caseSensitive, 0)) - { - if (!next(context, i, out var items, out _)) - continue; + if (!next(context, i, out var items, out _)) + continue; - success++; - result.AddRange(items); - } - value = success > 0 ? result.ToArray() : null; - enumerable = value != null; - return success > 0; - }; - } + success++; + result.AddRange(items); + } + value = success > 0 ? result.ToArray() : null; + enumerable = value != null; + return success > 0; + }; + } - private PathExpressionFn CurrentRef(ITokenReader reader) - { - var next = BuildSelector(reader); - return (IPathExpressionContext context, object input, out IEnumerable value, out bool enumerable) => next(context, input, out value, out enumerable); - } + private PathExpressionFn CurrentRef(ITokenReader reader) + { + var next = BuildSelector(reader); + return (IPathExpressionContext context, object input, out IEnumerable value, out bool enumerable) => next(context, input, out value, out enumerable); + } - private PathExpressionFn RootRef(ITokenReader reader) - { - var next = BuildSelector(reader); - return (IPathExpressionContext context, object input, out IEnumerable value, out bool enumerable) => next(context, context.Input, out value, out enumerable); - } + private PathExpressionFn RootRef(ITokenReader reader) + { + var next = BuildSelector(reader); + return (IPathExpressionContext context, object input, out IEnumerable value, out bool enumerable) => next(context, context.Input, out value, out enumerable); + } - private PathExpressionFilterFn BuildExpression(ITokenReader reader, PathTokenType stop) + private PathExpressionFilterFn BuildExpression(ITokenReader reader, PathTokenType stop) + { + var result = new Stack(4); + while (reader.Next(out var token) && token.Type != stop) { - var result = new Stack(4); - while (reader.Next(out var token) && token.Type != stop) - { - if (token.Type == PathTokenType.LogicalOperator && token.As() == FilterOperator.Or) - continue; + if (token.Type == PathTokenType.LogicalOperator && token.As() == FilterOperator.Or) + continue; - if (token.Type == PathTokenType.LogicalOperator && token.As() == FilterOperator.And) + if (token.Type == PathTokenType.LogicalOperator && token.As() == FilterOperator.And) + { + var left = result.Pop(); + var right = BuildBasicExpression(reader); + result.Push((IPathExpressionContext context, object input) => { - var left = result.Pop(); - var right = BuildBasicExpression(reader); - result.Push((IPathExpressionContext context, object input) => - { - // All expression must return true - return left(context, input) && right(context, input); - }); - continue; - } - result.Push(BuildBasicExpression(reader)); + // All expression must return true + return left(context, input) && right(context, input); + }); + continue; } - var expressions = result.ToArray(); - return (IPathExpressionContext context, object input) => - { - // Any one expression returns true - for (var i = 0; i < expressions.Length; i++) - if (expressions[i](context, input)) - return true; - - return false; - }; + result.Push(BuildBasicExpression(reader)); } - - private PathExpressionFilterFn BuildBasicExpression(ITokenReader reader) + var expressions = result.ToArray(); + return (IPathExpressionContext context, object input) => { - var token = reader.Current; - switch (token.Type) - { - case PathTokenType.NotOperator: - if (reader.Consume(PathTokenType.StartGroup)) - return NotCondition(BuildExpression(reader, PathTokenType.EndGroup)); + // Any one expression returns true + for (var i = 0; i < expressions.Length; i++) + if (expressions[i](context, input)) + return true; - return NotCondition(ExistCondition(BuildSelector(reader))); + return false; + }; + } - case PathTokenType.StartGroup: - return BuildExpression(reader, PathTokenType.EndGroup); + private PathExpressionFilterFn BuildBasicExpression(ITokenReader reader) + { + var token = reader.Current; + switch (token.Type) + { + case PathTokenType.NotOperator: + if (reader.Consume(PathTokenType.StartGroup)) + return NotCondition(BuildExpression(reader, PathTokenType.EndGroup)); - default: - return BuildRelationExpression(reader); - } - } + return NotCondition(ExistCondition(BuildSelector(reader))); - private PathExpressionFilterFn BuildRelationExpression(ITokenReader reader) - { - var left = BuildSelector(reader); - if (reader.Current.Type == PathTokenType.ComparisonOperator) - { - var op = reader.Current; - var right = BuildSelector(reader); - return BinaryCondition(left, right, op.As()); - } - return ExistCondition(left); - } + case PathTokenType.StartGroup: + return BuildExpression(reader, PathTokenType.EndGroup); - private static PathExpressionFilterFn ExistCondition(PathExpressionFn next) - { - return (IPathExpressionContext context, object input) => next(context, input, out _, out _); + default: + return BuildRelationExpression(reader); } + } - private static PathExpressionFilterFn NotCondition(PathExpressionFilterFn next) + private PathExpressionFilterFn BuildRelationExpression(ITokenReader reader) + { + var left = BuildSelector(reader); + if (reader.Current.Type == PathTokenType.ComparisonOperator) { - return (IPathExpressionContext context, object input) => !next(context, input); + var op = reader.Current; + var right = BuildSelector(reader); + return BinaryCondition(left, right, op.As()); } + return ExistCondition(left); + } - private static PathExpressionFilterFn BinaryCondition(PathExpressionFn left, PathExpressionFn right, FilterOperator op) - { - return (IPathExpressionContext context, object input) => - { - if (!left(context, input, out var leftValue, out _) || !right(context, input, out var rightValue, out _)) - return false; - - var operand1 = leftValue.FirstOrDefault(); - var operand2 = rightValue.FirstOrDefault(); + private static PathExpressionFilterFn ExistCondition(PathExpressionFn next) + { + return (IPathExpressionContext context, object input) => next(context, input, out _, out _); + } - // Get the specific operator - switch (op) - { - case FilterOperator.Equal: - return ExpressionHelpers.Equal(operand1, operand2, context.CaseSensitive); + private static PathExpressionFilterFn NotCondition(PathExpressionFilterFn next) + { + return (IPathExpressionContext context, object input) => !next(context, input); + } - case FilterOperator.NotEqual: - return !ExpressionHelpers.Equal(operand1, operand2, context.CaseSensitive); + private static PathExpressionFilterFn BinaryCondition(PathExpressionFn left, PathExpressionFn right, FilterOperator op) + { + return (IPathExpressionContext context, object input) => + { + if (!left(context, input, out var leftValue, out _) || !right(context, input, out var rightValue, out _)) + return false; - case FilterOperator.Less: - return ExpressionHelpers.CompareNumeric(operand1, operand2, convert: false, compare: out var compare, value: out _) && compare < 0; + var operand1 = leftValue.FirstOrDefault(); + var operand2 = rightValue.FirstOrDefault(); - case FilterOperator.LessOrEqual: - return ExpressionHelpers.CompareNumeric(operand1, operand2, convert: false, compare: out compare, value: out _) && compare <= 0; + // Get the specific operator + switch (op) + { + case FilterOperator.Equal: + return ExpressionHelpers.Equal(operand1, operand2, context.CaseSensitive); - case FilterOperator.Greater: - return ExpressionHelpers.CompareNumeric(operand1, operand2, convert: false, compare: out compare, value: out _) && compare > 0; + case FilterOperator.NotEqual: + return !ExpressionHelpers.Equal(operand1, operand2, context.CaseSensitive); - case FilterOperator.GreaterOrEqual: - return ExpressionHelpers.CompareNumeric(operand1, operand2, convert: false, compare: out compare, value: out _) && compare >= 0; + case FilterOperator.Less: + return ExpressionHelpers.CompareNumeric(operand1, operand2, convert: false, compare: out var compare, value: out _) && compare < 0; - case FilterOperator.RegEx: - return ExpressionHelpers.Match(operand1, operand2, context.CaseSensitive); - } - return false; - }; - } + case FilterOperator.LessOrEqual: + return ExpressionHelpers.CompareNumeric(operand1, operand2, convert: false, compare: out compare, value: out _) && compare <= 0; - private static bool Return(IPathExpressionContext context, object input, out IEnumerable value, out bool enumerable) - { - // Unwrap primitive types - if (input is JValue jValue && (jValue.Type == JTokenType.String || jValue.Type == JTokenType.Integer || jValue.Type == JTokenType.Boolean)) - input = jValue.Value; + case FilterOperator.Greater: + return ExpressionHelpers.CompareNumeric(operand1, operand2, convert: false, compare: out compare, value: out _) && compare > 0; - if (input is PSObject pso && pso.BaseObject is IEnumerable e && pso.BaseObject is not string) - input = e.Cast().ToArray(); + case FilterOperator.GreaterOrEqual: + return ExpressionHelpers.CompareNumeric(operand1, operand2, convert: false, compare: out compare, value: out _) && compare >= 0; - enumerable = false; - if (input is object[] eo) - { - enumerable = true; - value = eo; + case FilterOperator.RegEx: + return ExpressionHelpers.Match(operand1, operand2, context.CaseSensitive); } - else - value = new object[] { input }; + return false; + }; + } - return true; - } + private static bool Return(IPathExpressionContext context, object input, out IEnumerable value, out bool enumerable) + { + // Unwrap primitive types + if (input is JValue jValue && (jValue.Type == JTokenType.String || jValue.Type == JTokenType.Integer || jValue.Type == JTokenType.Boolean)) + input = jValue.Value; - private static PathExpressionFn Literal(object arg) + if (input is PSObject pso && pso.BaseObject is IEnumerable e && pso.BaseObject is not string) + input = e.Cast().ToArray(); + + enumerable = false; + if (input is object[] eo) { - var isEnumerable = arg is object[]; - var result = isEnumerable ? arg as object[] : new object[] { arg }; - return (IPathExpressionContext context, object input, out IEnumerable value, out bool enumerable) => - { - value = result; - enumerable = isEnumerable; - return true; - }; + enumerable = true; + value = eo; } + else + value = new object[] { input }; - #region Enumerators + return true; + } - private static IEnumerable GetAll(object o) + private static PathExpressionFn Literal(object arg) + { + var isEnumerable = arg is object[]; + var result = isEnumerable ? arg as object[] : new object[] { arg }; + return (IPathExpressionContext context, object input, out IEnumerable value, out bool enumerable) => { - var baseObject = ExpressionHelpers.GetBaseObject(o); - if (IsSimpleType(baseObject)) - return DEFAULT_EMPTY_ARRAY; + value = result; + enumerable = isEnumerable; + return true; + }; + } - if (baseObject is JObject jObject) - return GetAllField(jObject); + #region Enumerators - if (baseObject is JArray jArray) - return GetAllIndex(jArray); + private static IEnumerable GetAll(object o) + { + var baseObject = ExpressionHelpers.GetBaseObject(o); + if (IsSimpleType(baseObject)) + return DEFAULT_EMPTY_ARRAY; - return baseObject is IEnumerable ? GetAllIndex(baseObject) : GetAllField(baseObject); - } + if (baseObject is JObject jObject) + return GetAllField(jObject); - private static bool IsSimpleType(object o) - { - return o is string || (o != null && o.GetType().IsValueType); - } + if (baseObject is JArray jArray) + return GetAllIndex(jArray); - private IEnumerable GetAllRecurse(object o, string fieldName, bool caseSensitive, int depth) - { - if (depth > _RecurseMaxDepth) - throw new ObjectPathEvaluateException(string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.ObjectPathRecurseMaxDepth, _RecurseMaxDepth, fieldName)); + return baseObject is IEnumerable ? GetAllIndex(baseObject) : GetAllField(baseObject); + } - foreach (var i in GetAll(o)) - { - if (TryGetField(i, fieldName, caseSensitive, out var value)) - { - yield return value; - } - else - { - foreach (var c in GetAllRecurse(i, fieldName, caseSensitive, depth + 1)) - yield return c; - } - } - } + private static bool IsSimpleType(object o) + { + return o is string || (o != null && o.GetType().IsValueType); + } - private static IEnumerable GetAllIndex(object o) - { - if (o is IEnumerable enumerable) - foreach (var i in enumerable) - yield return i; - } + private IEnumerable GetAllRecurse(object o, string fieldName, bool caseSensitive, int depth) + { + if (depth > _RecurseMaxDepth) + throw new ObjectPathEvaluateException(string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.ObjectPathRecurseMaxDepth, _RecurseMaxDepth, fieldName)); - private static IEnumerable GetAllField(object o) + foreach (var i in GetAll(o)) { - var baseObject = ExpressionHelpers.GetBaseObject(o); - if (baseObject == null) - yield break; - - // Handle dictionaries and hashtables - if (baseObject is IDictionary dictionary) - { - foreach (var value in dictionary.Values) - yield return value; - } - // Handle PSObjects - else if (o is PSObject psObject) - { - foreach (var property in psObject.Properties) - yield return property.Value; - } - // Handle JObject - else if (o is JObject jObject) + if (TryGetField(i, fieldName, caseSensitive, out var value)) { - foreach (var property in jObject.Properties()) - yield return property.Value; + yield return value; } - // Handle DynamicObjects - else if (o is DynamicObject dynamicObject) - { - - } - // Handle all other CLR types else { - var baseType = baseObject.GetType(); - var properties = baseType.GetProperties(bindingAttr: BindingFlags.Instance | BindingFlags.Public); - for (var i = 0; properties != null && i < properties.Length; i++) - yield return properties[i].GetValue(baseObject); - - var fields = baseType.GetFields(bindingAttr: BindingFlags.Instance | BindingFlags.Public); - for (var i = 0; fields != null && i < fields.Length; i++) - yield return fields[i].GetValue(baseObject); + foreach (var c in GetAllRecurse(i, fieldName, caseSensitive, depth + 1)) + yield return c; } } + } - #endregion Enumerators + private static IEnumerable GetAllIndex(object o) + { + if (o is IEnumerable enumerable) + foreach (var i in enumerable) + yield return i; + } - #region Lookup + private static IEnumerable GetAllField(object o) + { + var baseObject = ExpressionHelpers.GetBaseObject(o); + if (baseObject == null) + yield break; - private static bool TryGetField(object o, string fieldName, bool caseSensitive, out object value) + // Handle dictionaries and hashtables + if (baseObject is IDictionary dictionary) { - value = null; - var baseObject = ExpressionHelpers.GetBaseObject(o); - if (baseObject == null || (baseObject is JValue jValue && jValue.Type == JTokenType.Null)) - return false; - - // Handle dictionaries and hashtables - if (baseObject is IDictionary dictionary) - { - return TryDictionary(dictionary, fieldName, caseSensitive, out value); - } - // Handle JToken - else if (baseObject is JObject jObject) - { - return TryPropertyValue(jObject, fieldName, caseSensitive, out value); - } - // Handle PSObjects - else if (o is PSObject psObject) - { - return TryPropertyValue(psObject, fieldName, caseSensitive, out value); - } - // Handle DynamicObjects - else if (o is DynamicObject dynamicObject) - { - return TryPropertyValue(dynamicObject, fieldName, caseSensitive, out value); - } - // Handle all other CLR types - var baseType = baseObject.GetType(); - return TryPropertyValue(o, fieldName, baseType, caseSensitive, out value) || - TryFieldValue(o, fieldName, baseType, caseSensitive, out value) || - TryIndexerProperty(o, fieldName, baseType, out value); + foreach (var value in dictionary.Values) + yield return value; + } + // Handle PSObjects + else if (o is PSObject psObject) + { + foreach (var property in psObject.Properties) + yield return property.Value; + } + // Handle JObject + else if (o is JObject jObject) + { + foreach (var property in jObject.Properties()) + yield return property.Value; } + // Handle DynamicObjects + else if (o is DynamicObject dynamicObject) + { - private static bool TryGetIndex(object o, int index, out object value) + } + // Handle all other CLR types + else { - value = null; - var baseObject = ExpressionHelpers.GetBaseObject(o); - if (baseObject == null) - return false; + var baseType = baseObject.GetType(); + var properties = baseType.GetProperties(bindingAttr: BindingFlags.Instance | BindingFlags.Public); + for (var i = 0; properties != null && i < properties.Length; i++) + yield return properties[i].GetValue(baseObject); - // Handle array indexes - if (baseObject is Array array && index < array.Length) - { - if (index < 0) - index = array.Length + index; + var fields = baseType.GetFields(bindingAttr: BindingFlags.Instance | BindingFlags.Public); + for (var i = 0; fields != null && i < fields.Length; i++) + yield return fields[i].GetValue(baseObject); + } + } - if (index < 0 || index >= array.Length) - return false; + #endregion Enumerators - value = array.GetValue(index); - return true; - } - // Handle IList - else if (baseObject is IList list && index < list.Count) - { - if (index < 0) - index = list.Count + index; + #region Lookup - if (index < 0 || index >= list.Count) - return false; + private static bool TryGetField(object o, string fieldName, bool caseSensitive, out object value) + { + value = null; + var baseObject = ExpressionHelpers.GetBaseObject(o); + if (baseObject == null || (baseObject is JValue jValue && jValue.Type == JTokenType.Null)) + return false; - value = list[index]; - return true; - } - // Handle IEnumerable - else if (baseObject is IEnumerable enumerable) - { - return TryEnumerableIndex(enumerable, index, out value); - } - // Handle all other CLR types - return TryIndexerProperty(o, index, baseObject.GetType(), out value); + // Handle dictionaries and hashtables + if (baseObject is IDictionary dictionary) + { + return TryDictionary(dictionary, fieldName, caseSensitive, out value); } - - #endregion Lookup - - private static bool TryEnumerableIndex(IEnumerable o, int index, out object value) + // Handle JToken + else if (baseObject is JObject jObject) { - value = null; - var e = o.GetEnumerator(); - if (index < 0) - { - var items = new List(); - while (e.MoveNext()) - items.Add(e.Current); - - index = items.Count + index; - if (index < 0 || index >= items.Count) - return false; - - value = items[index]; - return true; - } - - for (var i = 0; e.MoveNext(); i++) - { - if (i == index) - { - value = e.Current; - return true; - } - } - return false; + return TryPropertyValue(jObject, fieldName, caseSensitive, out value); } - - private static bool TryDictionary(IDictionary dictionary, string key, bool caseSensitive, out object value) + // Handle PSObjects + else if (o is PSObject psObject) { - value = null; - var comparer = caseSensitive ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase; - foreach (var k in dictionary.Keys) - { - if (comparer.Equals(key, k)) - { - value = dictionary[k]; - return true; - } - } - return false; + return TryPropertyValue(psObject, fieldName, caseSensitive, out value); } + // Handle DynamicObjects + else if (o is DynamicObject dynamicObject) + { + return TryPropertyValue(dynamicObject, fieldName, caseSensitive, out value); + } + // Handle all other CLR types + var baseType = baseObject.GetType(); + return TryPropertyValue(o, fieldName, baseType, caseSensitive, out value) || + TryFieldValue(o, fieldName, baseType, caseSensitive, out value) || + TryIndexerProperty(o, fieldName, baseType, out value); + } + + private static bool TryGetIndex(object o, int index, out object value) + { + value = null; + var baseObject = ExpressionHelpers.GetBaseObject(o); + if (baseObject == null) + return false; - private static bool TryPropertyValue(object targetObject, string propertyName, Type baseType, bool caseSensitive, out object value) + // Handle array indexes + if (baseObject is Array array && index < array.Length) { - value = null; - var bindingFlags = caseSensitive ? BindingFlags.Default : BindingFlags.IgnoreCase; - var propertyInfo = baseType.GetProperty(propertyName, bindingAttr: bindingFlags | BindingFlags.Instance | BindingFlags.Public); - if (propertyInfo == null) + if (index < 0) + index = array.Length + index; + + if (index < 0 || index >= array.Length) return false; - value = propertyInfo.GetValue(targetObject); + value = array.GetValue(index); return true; } - - private static bool TryPropertyValue(PSObject targetObject, string propertyName, bool caseSensitive, out object value) + // Handle IList + else if (baseObject is IList list && index < list.Count) { - value = null; - var p = targetObject.Properties[propertyName]; - if (p == null) - return false; + if (index < 0) + index = list.Count + index; - if (caseSensitive && !StringComparer.Ordinal.Equals(p.Name, propertyName)) + if (index < 0 || index >= list.Count) return false; - value = p.Value; + value = list[index]; return true; } + // Handle IEnumerable + else if (baseObject is IEnumerable enumerable) + { + return TryEnumerableIndex(enumerable, index, out value); + } + // Handle all other CLR types + return TryIndexerProperty(o, index, baseObject.GetType(), out value); + } + + #endregion Lookup - private static bool TryPropertyValue(JObject targetObject, string propertyName, bool caseSensitive, out object value) + private static bool TryEnumerableIndex(IEnumerable o, int index, out object value) + { + value = null; + var e = o.GetEnumerator(); + if (index < 0) { - value = null; - if (!targetObject.TryGetValue(propertyName, caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase, out var result)) + var items = new List(); + while (e.MoveNext()) + items.Add(e.Current); + + index = items.Count + index; + if (index < 0 || index >= items.Count) return false; - value = GetTokenValue(result); + value = items[index]; return true; } - private static bool TryPropertyValue(DynamicObject targetObject, string propertyName, bool caseSensitive, out object value) + for (var i = 0; e.MoveNext(); i++) { - return targetObject.TryGetMember(new DynamicPropertyBinder(propertyName, !caseSensitive), out value); + if (i == index) + { + value = e.Current; + return true; + } } + return false; + } - private static object GetTokenValue(JToken o) + private static bool TryDictionary(IDictionary dictionary, string key, bool caseSensitive, out object value) + { + value = null; + var comparer = caseSensitive ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase; + foreach (var k in dictionary.Keys) { - if (o == null || o.Type == JTokenType.Null) - return null; + if (comparer.Equals(key, k)) + { + value = dictionary[k]; + return true; + } + } + return false; + } - if (o.Type == JTokenType.String) - return o.Value(); + private static bool TryPropertyValue(object targetObject, string propertyName, Type baseType, bool caseSensitive, out object value) + { + value = null; + var bindingFlags = caseSensitive ? BindingFlags.Default : BindingFlags.IgnoreCase; + var propertyInfo = baseType.GetProperty(propertyName, bindingAttr: bindingFlags | BindingFlags.Instance | BindingFlags.Public); + if (propertyInfo == null) + return false; - if (o.Type == JTokenType.Boolean) - return o.Value(); + value = propertyInfo.GetValue(targetObject); + return true; + } - if (o.Type == JTokenType.Integer) - return o.Value(); + private static bool TryPropertyValue(PSObject targetObject, string propertyName, bool caseSensitive, out object value) + { + value = null; + var p = targetObject.Properties[propertyName]; + if (p == null) + return false; - return o; - } + if (caseSensitive && !StringComparer.Ordinal.Equals(p.Name, propertyName)) + return false; - private static bool TryFieldValue(object targetObject, string fieldName, Type baseType, bool caseSensitive, out object value) - { - value = null; - var bindingFlags = caseSensitive ? BindingFlags.Default : BindingFlags.IgnoreCase; - var fieldInfo = baseType.GetField(fieldName, bindingAttr: bindingFlags | BindingFlags.Instance | BindingFlags.Public); - if (fieldInfo == null) - return false; + value = p.Value; + return true; + } - value = fieldInfo.GetValue(targetObject); - return true; - } + private static bool TryPropertyValue(JObject targetObject, string propertyName, bool caseSensitive, out object value) + { + value = null; + if (!targetObject.TryGetValue(propertyName, caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase, out var result)) + return false; + + value = GetTokenValue(result); + return true; + } + + private static bool TryPropertyValue(DynamicObject targetObject, string propertyName, bool caseSensitive, out object value) + { + return targetObject.TryGetMember(new DynamicPropertyBinder(propertyName, !caseSensitive), out value); + } - private static bool TryIndexerProperty(object targetObject, object index, Type baseType, out object value) + private static object GetTokenValue(JToken o) + { + if (o == null || o.Type == JTokenType.Null) + return null; + + if (o.Type == JTokenType.String) + return o.Value(); + + if (o.Type == JTokenType.Boolean) + return o.Value(); + + if (o.Type == JTokenType.Integer) + return o.Value(); + + return o; + } + + private static bool TryFieldValue(object targetObject, string fieldName, Type baseType, bool caseSensitive, out object value) + { + value = null; + var bindingFlags = caseSensitive ? BindingFlags.Default : BindingFlags.IgnoreCase; + var fieldInfo = baseType.GetField(fieldName, bindingAttr: bindingFlags | BindingFlags.Instance | BindingFlags.Public); + if (fieldInfo == null) + return false; + + value = fieldInfo.GetValue(targetObject); + return true; + } + + private static bool TryIndexerProperty(object targetObject, object index, Type baseType, out object value) + { + value = null; + var properties = baseType.GetProperties(); + foreach (var property in GetIndexerProperties(baseType)) { - value = null; - var properties = baseType.GetProperties(); - foreach (var property in GetIndexerProperties(baseType)) + var parameters = property.GetIndexParameters(); + if (parameters.Length > 0) { - var parameters = property.GetIndexParameters(); - if (parameters.Length > 0) + try { - try - { - var converter = GetConverter(parameters[0].ParameterType); - var p1 = converter(index); - value = property.GetValue(targetObject, new object[] { p1 }); - return true; - } - catch - { - // Discard converter exceptions - } + var converter = GetConverter(parameters[0].ParameterType); + var p1 = converter(index); + value = property.GetValue(targetObject, new object[] { p1 }); + return true; + } + catch + { + // Discard converter exceptions } } - return false; } + return false; + } - private static Converter GetConverter(Type targetType) + private static Converter GetConverter(Type targetType) + { + var convertAtribute = targetType.GetCustomAttribute(); + if (convertAtribute != null) { - var convertAtribute = targetType.GetCustomAttribute(); - if (convertAtribute != null) + var converterType = Type.GetType(convertAtribute.ConverterTypeName); + if (converterType.IsSubclassOf(typeof(TypeConverter))) { - var converterType = Type.GetType(convertAtribute.ConverterTypeName); - if (converterType.IsSubclassOf(typeof(TypeConverter))) - { - var converter = (TypeConverter)Activator.CreateInstance(converterType); - return s => converter.ConvertFrom(s); - } - else if (converterType.IsSubclassOf(typeof(PSTypeConverter))) - { - var converter = (PSTypeConverter)Activator.CreateInstance(converterType); - return s => converter.ConvertFrom(s, targetType, Thread.CurrentThread.CurrentCulture, true); - } + var converter = (TypeConverter)Activator.CreateInstance(converterType); + return s => converter.ConvertFrom(s); + } + else if (converterType.IsSubclassOf(typeof(PSTypeConverter))) + { + var converter = (PSTypeConverter)Activator.CreateInstance(converterType); + return s => converter.ConvertFrom(s, targetType, Thread.CurrentThread.CurrentCulture, true); } - return s => Convert.ChangeType(s, targetType); } + return s => Convert.ChangeType(s, targetType); + } - private static IEnumerable GetIndexerProperties(Type baseType) + private static IEnumerable GetIndexerProperties(Type baseType) + { + var attribute = baseType.GetCustomAttribute(); + var properties = baseType.GetProperties(); + foreach (var property in properties) { - var attribute = baseType.GetCustomAttribute(); - var properties = baseType.GetProperties(); - foreach (var property in properties) + if (attribute != null) { - if (attribute != null) - { - if (property.Name == attribute.MemberName) - yield return property; - } - else - { - var parameters = property.GetIndexParameters(); - if (parameters.Length > 0) - yield return property; - } + if (property.Name == attribute.MemberName) + yield return property; + } + else + { + var parameters = property.GetIndexParameters(); + if (parameters.Length > 0) + yield return property; } } } diff --git a/src/PSRule/Runtime/ObjectPath/PathTokenizer.cs b/src/PSRule/Runtime/ObjectPath/PathTokenizer.cs index 52623e7d05..6da527b9f2 100644 --- a/src/PSRule/Runtime/ObjectPath/PathTokenizer.cs +++ b/src/PSRule/Runtime/ObjectPath/PathTokenizer.cs @@ -3,691 +3,690 @@ using System.Diagnostics; -namespace PSRule.Runtime.ObjectPath +namespace PSRule.Runtime.ObjectPath; + +/// +/// A helper to tokenize an object path expression. +/// +internal static class PathTokenizer { - /// - /// A helper to tokenize an object path expression. - /// - internal static class PathTokenizer + private sealed class TokenStream : ITokenWriter { - private sealed class TokenStream : ITokenWriter - { - private readonly List _Items; + private readonly List _Items; - public TokenStream() - { - _Items = new List(); - } + public TokenStream() + { + _Items = new List(); + } - public IPathToken Last + public IPathToken Last + { + get { - get - { - return _Items.Count > 0 ? _Items[_Items.Count - 1] : null; - } + return _Items.Count > 0 ? _Items[_Items.Count - 1] : null; } + } - public void Add(IPathToken token) - { - _Items.Add(token); - } + public void Add(IPathToken token) + { + _Items.Add(token); + } - public IPathToken[] ToArray() - { - return _Items.ToArray(); - } + public IPathToken[] ToArray() + { + return _Items.ToArray(); } + } - [DebuggerDisplay("Position = {Position}")] - private sealed class PathStream - { - private const char ROOTREF = '$'; - private const char CURRENTREF = '@'; - private const char DOT = '.'; - private const char QUOTED_SINGLE = '\''; - private const char QUOTED_DOUBLE = '"'; - private const char INDEX_OPEN = '['; - private const char INDEX_CLOSE = ']'; - private const char ANY = '*'; - private const char DASH = '-'; - private const char QUERY = '?'; - private const char GROUP_OPEN = '('; - private const char GROUP_CLOSE = ')'; - private const char EQUALS = '='; - private const char NOT = '!'; - private const char LESSTHAN = '<'; - private const char GREATERTHAN = '>'; - private const char TILDA = '~'; - private const char NULL = '\0'; - private const char COLON = ':'; - private const char COMMA = ','; - private const char OR = '|'; - private const char AND = '&'; - private const char UNDERSCORE = '_'; - private const char PLUS = '+'; - - private readonly string _Path; - private readonly int _Last; - - public PathStream(string path) - { - _Path = path; - _Last = _Path.Length - 1; - } + [DebuggerDisplay("Position = {Position}")] + private sealed class PathStream + { + private const char ROOTREF = '$'; + private const char CURRENTREF = '@'; + private const char DOT = '.'; + private const char QUOTED_SINGLE = '\''; + private const char QUOTED_DOUBLE = '"'; + private const char INDEX_OPEN = '['; + private const char INDEX_CLOSE = ']'; + private const char ANY = '*'; + private const char DASH = '-'; + private const char QUERY = '?'; + private const char GROUP_OPEN = '('; + private const char GROUP_CLOSE = ')'; + private const char EQUALS = '='; + private const char NOT = '!'; + private const char LESSTHAN = '<'; + private const char GREATERTHAN = '>'; + private const char TILDA = '~'; + private const char NULL = '\0'; + private const char COLON = ':'; + private const char COMMA = ','; + private const char OR = '|'; + private const char AND = '&'; + private const char UNDERSCORE = '_'; + private const char PLUS = '+'; + + private readonly string _Path; + private readonly int _Last; + + public PathStream(string path) + { + _Path = path; + _Last = _Path.Length - 1; + } - public bool EOF(int position) - { - return position > _Last; - } + public bool EOF(int position) + { + return position > _Last; + } - public char Current(int pos) - { - return pos > _Last ? NULL : _Path[pos]; - } + public char Current(int pos) + { + return pos > _Last ? NULL : _Path[pos]; + } - public bool Current(int pos, char c) - { - return pos <= _Last && _Path[pos] == c; - } + public bool Current(int pos, char c) + { + return pos <= _Last && _Path[pos] == c; + } - /// - /// Find the start of the sequence. - /// - /// Return true when more characters follow. - public void Next(ref int position) - { - if (position <= _Last) - position++; - } + /// + /// Find the start of the sequence. + /// + /// Return true when more characters follow. + public void Next(ref int position) + { + if (position <= _Last) + position++; + } - /// - /// Capture a token for $ and @. - /// - internal bool TryConsumeRef(ref int position, ITokenWriter tokens) + /// + /// Capture a token for $ and @. + /// + internal bool TryConsumeRef(ref int position, ITokenWriter tokens) + { + if ((Current(position) == ROOTREF && !IsMemberName(position)) || (position == 0 && Current(position) == DOT)) { - if ((Current(position) == ROOTREF && !IsMemberName(position)) || (position == 0 && Current(position) == DOT)) - { - tokens.Add(PathToken.RootRef); - Next(ref position); - return true; - } - if (Current(position) == CURRENTREF) - { - tokens.Add(position == 0 ? PathToken.RootRef : PathToken.CurrentRef); - Next(ref position); - return true; - } - return false; + tokens.Add(PathToken.RootRef); + Next(ref position); + return true; } - - internal bool TryConsumeChild(ref int position, ITokenWriter tokens) + if (Current(position) == CURRENTREF) { - return TryConsumeDotWildSelector(ref position, tokens) || - TryConsumeDotSelector(ref position, tokens) || - TryConsumeIndexSelector(ref position, tokens) || - TryConsumeDescendantSelector(ref position, tokens); + tokens.Add(position == 0 ? PathToken.RootRef : PathToken.CurrentRef); + Next(ref position); + return true; } + return false; + } - /// - /// Capture a token for "[?(@.enabled==true)]". - /// - internal bool TryConsumeFilter(ref int position, ITokenWriter tokens) - { - if (Current(position) != INDEX_OPEN || position + 5 >= _Last || _Path[position + 1] != QUERY) - return false; - - var groupOpen = _Path[position + 2] == GROUP_OPEN; - var pos = groupOpen ? position + 3 : position + 2; - tokens.Add(new PathToken(PathTokenType.StartFilter)); - if (groupOpen) - tokens.Add(new PathToken(PathTokenType.StartGroup)); + internal bool TryConsumeChild(ref int position, ITokenWriter tokens) + { + return TryConsumeDotWildSelector(ref position, tokens) || + TryConsumeDotSelector(ref position, tokens) || + TryConsumeIndexSelector(ref position, tokens) || + TryConsumeDescendantSelector(ref position, tokens); + } - while (!EOF(pos) && Current(pos) != INDEX_CLOSE) - { - if (!TryConsumeBooleanExpression(ref pos, tokens) && !TryConsumeGroup(ref pos, tokens)) - Next(ref pos); + /// + /// Capture a token for "[?(@.enabled==true)]". + /// + internal bool TryConsumeFilter(ref int position, ITokenWriter tokens) + { + if (Current(position) != INDEX_OPEN || position + 5 >= _Last || _Path[position + 1] != QUERY) + return false; - pos = SkipPadding(pos); - } + var groupOpen = _Path[position + 2] == GROUP_OPEN; + var pos = groupOpen ? position + 3 : position + 2; + tokens.Add(new PathToken(PathTokenType.StartFilter)); + if (groupOpen) + tokens.Add(new PathToken(PathTokenType.StartGroup)); - if (Current(pos) == INDEX_CLOSE) + while (!EOF(pos) && Current(pos) != INDEX_CLOSE) + { + if (!TryConsumeBooleanExpression(ref pos, tokens) && !TryConsumeGroup(ref pos, tokens)) Next(ref pos); - tokens.Add(new PathToken(PathTokenType.EndFilter)); - position = SkipPadding(pos); - return true; + pos = SkipPadding(pos); } - private bool TryConsumeGroup(ref int position, ITokenWriter tokens) - { - if (Current(position) != GROUP_OPEN && Current(position) != GROUP_CLOSE) - return false; + if (Current(pos) == INDEX_CLOSE) + Next(ref pos); - tokens.Add(new PathToken(Current(position) == GROUP_OPEN ? PathTokenType.StartGroup : PathTokenType.EndGroup)); - position++; - TryConsumeLogicalOperator(ref position, tokens); - return true; - } + tokens.Add(new PathToken(PathTokenType.EndFilter)); + position = SkipPadding(pos); + return true; + } - private bool TryConsumeBooleanExpression(ref int position, ITokenWriter tokens) - { - if (!TryConsumeRef(ref position, tokens) && !TryConsumeDescendantSelector(ref position, tokens) && !TryConsumeDotSelector(ref position, tokens) && !TryConsumeNot(ref position, tokens)) - return false; + private bool TryConsumeGroup(ref int position, ITokenWriter tokens) + { + if (Current(position) != GROUP_OPEN && Current(position) != GROUP_CLOSE) + return false; - if (tokens.Last.Type == PathTokenType.NotOperator) - TryConsumeRef(ref position, tokens); + tokens.Add(new PathToken(Current(position) == GROUP_OPEN ? PathTokenType.StartGroup : PathTokenType.EndGroup)); + position++; + TryConsumeLogicalOperator(ref position, tokens); + return true; + } - TryConsumeDescendantSelector(ref position, tokens); - TryConsumeDotSelector(ref position, tokens); - TryConsumeComparisonOperator(ref position, tokens); - TryConsumePrimitive(ref position, tokens); - TryConsumeLogicalOperator(ref position, tokens); - return true; - } + private bool TryConsumeBooleanExpression(ref int position, ITokenWriter tokens) + { + if (!TryConsumeRef(ref position, tokens) && !TryConsumeDescendantSelector(ref position, tokens) && !TryConsumeDotSelector(ref position, tokens) && !TryConsumeNot(ref position, tokens)) + return false; - private bool TryConsumeNot(ref int position, ITokenWriter tokens) - { - if (Current(position) != NOT) - return false; + if (tokens.Last.Type == PathTokenType.NotOperator) + TryConsumeRef(ref position, tokens); - tokens.Add(new PathToken(PathTokenType.NotOperator)); - position += 1; - return true; - } + TryConsumeDescendantSelector(ref position, tokens); + TryConsumeDotSelector(ref position, tokens); + TryConsumeComparisonOperator(ref position, tokens); + TryConsumePrimitive(ref position, tokens); + TryConsumeLogicalOperator(ref position, tokens); + return true; + } - private bool TryConsumePrimitive(ref int position, ITokenWriter tokens) - { - return TryConsumeNumberLiteral(ref position, tokens) || - TryConsumeStringLiteral(ref position, tokens) || - TryConsumeBooleanLiteral(ref position, tokens); - } + private bool TryConsumeNot(ref int position, ITokenWriter tokens) + { + if (Current(position) != NOT) + return false; - private bool TryConsumeStringLiteral(ref int position, ITokenWriter tokens) - { - if (!IsQuoted(Current(position))) - return false; + tokens.Add(new PathToken(PathTokenType.NotOperator)); + position += 1; + return true; + } - if (!UntilQuote(ref position, out var value)) - return false; + private bool TryConsumePrimitive(ref int position, ITokenWriter tokens) + { + return TryConsumeNumberLiteral(ref position, tokens) || + TryConsumeStringLiteral(ref position, tokens) || + TryConsumeBooleanLiteral(ref position, tokens); + } - tokens.Add(new PathToken(PathTokenType.String, value)); - position = SkipPadding(position); - return true; - } + private bool TryConsumeStringLiteral(ref int position, ITokenWriter tokens) + { + if (!IsQuoted(Current(position))) + return false; - private bool TryConsumeNumberLiteral(ref int position, ITokenWriter tokens) - { - var pos = SkipPadding(position); - if (!TryInteger(pos, out var value)) - return false; + if (!UntilQuote(ref position, out var value)) + return false; - tokens.Add(new PathToken(PathTokenType.Integer, value)); - position = pos + value.ToString().Length; - return true; - } + tokens.Add(new PathToken(PathTokenType.String, value)); + position = SkipPadding(position); + return true; + } - private bool TryConsumeBooleanLiteral(ref int position, ITokenWriter tokens) - { - var pos = SkipPadding(position); - if (!TryBoolean(pos, out var value)) - return false; + private bool TryConsumeNumberLiteral(ref int position, ITokenWriter tokens) + { + var pos = SkipPadding(position); + if (!TryInteger(pos, out var value)) + return false; - tokens.Add(new PathToken(PathTokenType.Boolean, value)); - position = pos + value.ToString().Length; - return true; - } + tokens.Add(new PathToken(PathTokenType.Integer, value)); + position = pos + value.ToString().Length; + return true; + } - private void TryConsumeComparisonOperator(ref int position, ITokenWriter tokens) - { - var pos = SkipPadding(position); - if (!IsComparisonOperator(Current(pos))) - return; + private bool TryConsumeBooleanLiteral(ref int position, ITokenWriter tokens) + { + var pos = SkipPadding(position); + if (!TryBoolean(pos, out var value)) + return false; - var op = FilterOperator.None; - var c1 = ConsumeChar(ref pos); - var c2 = Current(pos); + tokens.Add(new PathToken(PathTokenType.Boolean, value)); + position = pos + value.ToString().Length; + return true; + } - if (c1 == EQUALS && c2 == EQUALS) - op = FilterOperator.Equal; + private void TryConsumeComparisonOperator(ref int position, ITokenWriter tokens) + { + var pos = SkipPadding(position); + if (!IsComparisonOperator(Current(pos))) + return; - if (c1 == NOT && c2 == EQUALS) - op = FilterOperator.NotEqual; + var op = FilterOperator.None; + var c1 = ConsumeChar(ref pos); + var c2 = Current(pos); - if (c1 == LESSTHAN && c2 == EQUALS) - op = FilterOperator.LessOrEqual; + if (c1 == EQUALS && c2 == EQUALS) + op = FilterOperator.Equal; - if (c1 == LESSTHAN && !IsComparisonOperator(c2)) - op = FilterOperator.Less; + if (c1 == NOT && c2 == EQUALS) + op = FilterOperator.NotEqual; - if (c1 == GREATERTHAN && c2 == EQUALS) - op = FilterOperator.GreaterOrEqual; + if (c1 == LESSTHAN && c2 == EQUALS) + op = FilterOperator.LessOrEqual; - if (c1 == GREATERTHAN && !IsComparisonOperator(c2)) - op = FilterOperator.Greater; + if (c1 == LESSTHAN && !IsComparisonOperator(c2)) + op = FilterOperator.Less; - if (c1 == TILDA && c2 == EQUALS) - op = FilterOperator.RegEx; + if (c1 == GREATERTHAN && c2 == EQUALS) + op = FilterOperator.GreaterOrEqual; - if (op != FilterOperator.None) - { - position = SkipPadding(op == FilterOperator.Less || op == FilterOperator.Greater ? pos : pos + 1); - tokens.Add(new PathToken(PathTokenType.ComparisonOperator, op)); - } - } + if (c1 == GREATERTHAN && !IsComparisonOperator(c2)) + op = FilterOperator.Greater; - private void TryConsumeLogicalOperator(ref int position, ITokenWriter tokens) - { - var pos = SkipPadding(position); - if (!IsLogicalOperator(Current(pos))) - return; + if (c1 == TILDA && c2 == EQUALS) + op = FilterOperator.RegEx; - IPathToken token = null; - var c1 = ConsumeChar(ref pos); - var c2 = ConsumeChar(ref pos); + if (op != FilterOperator.None) + { + position = SkipPadding(op == FilterOperator.Less || op == FilterOperator.Greater ? pos : pos + 1); + tokens.Add(new PathToken(PathTokenType.ComparisonOperator, op)); + } + } - if (c1 == OR && c2 == OR) - token = new PathToken(PathTokenType.LogicalOperator, FilterOperator.Or); + private void TryConsumeLogicalOperator(ref int position, ITokenWriter tokens) + { + var pos = SkipPadding(position); + if (!IsLogicalOperator(Current(pos))) + return; - if (c1 == AND && c2 == AND) - token = new PathToken(PathTokenType.LogicalOperator, FilterOperator.And); + IPathToken token = null; + var c1 = ConsumeChar(ref pos); + var c2 = ConsumeChar(ref pos); - if (token != null) - { - position = SkipPadding(pos); - tokens.Add(token); - } - } + if (c1 == OR && c2 == OR) + token = new PathToken(PathTokenType.LogicalOperator, FilterOperator.Or); - private char ConsumeChar(ref int position) - { - return position > _Last ? NULL : _Path[position++]; - } + if (c1 == AND && c2 == AND) + token = new PathToken(PathTokenType.LogicalOperator, FilterOperator.And); - /// - /// Check if current is a property operator. - /// - /// - /// "." or "+" but not ".." - /// - private bool IsDotSelector(int position) + if (token != null) { - return (Current(position, DOT) && !Current(position + 1, DOT)) || Current(position, PLUS); + position = SkipPadding(pos); + tokens.Add(token); } + } - private bool IsDotWildcardSelector(int position) - { - return Current(position, DOT) && Current(position + 1, ANY); - } + private char ConsumeChar(ref int position) + { + return position > _Last ? NULL : _Path[position++]; + } - private bool IsDescendantSelector(int position) - { - return Current(position, DOT) && Current(position + 1, DOT); - } + /// + /// Check if current is a property operator. + /// + /// + /// "." or "+" but not ".." + /// + private bool IsDotSelector(int position) + { + return (Current(position, DOT) && !Current(position + 1, DOT)) || Current(position, PLUS); + } - private static bool IsComparisonOperator(char c) - { - return c == EQUALS || c == NOT || c == LESSTHAN || c == GREATERTHAN || c == TILDA; - } + private bool IsDotWildcardSelector(int position) + { + return Current(position, DOT) && Current(position + 1, ANY); + } - private static bool IsLogicalOperator(char c) - { - return c == OR || c == AND; - } + private bool IsDescendantSelector(int position) + { + return Current(position, DOT) && Current(position + 1, DOT); + } - private static bool IsMemberNameCharacter(char c) - { - return char.IsLetterOrDigit(c) || c == UNDERSCORE || c == DASH; - } + private static bool IsComparisonOperator(char c) + { + return c == EQUALS || c == NOT || c == LESSTHAN || c == GREATERTHAN || c == TILDA; + } - private static bool IsQuoted(char c) - { - return c == QUOTED_SINGLE || c == QUOTED_DOUBLE; - } + private static bool IsLogicalOperator(char c) + { + return c == OR || c == AND; + } - private bool TryConsumeDotSelector(ref int position, ITokenWriter tokens) - { - if (!IsDotSelector(position) && !Current(position, QUOTED_SINGLE) && !Current(position, QUOTED_DOUBLE) && !IsMemberName(position)) - return false; + private static bool IsMemberNameCharacter(char c) + { + return char.IsLetterOrDigit(c) || c == UNDERSCORE || c == DASH; + } - var pos = IsDotSelector(position) ? position + 1 : position; - var option = Current(position, PLUS) ? PathTokenOption.CaseSensitive : PathTokenOption.None; - var field = CaptureMemberName(ref pos); - if (string.IsNullOrEmpty(field)) - return false; + private static bool IsQuoted(char c) + { + return c == QUOTED_SINGLE || c == QUOTED_DOUBLE; + } - tokens.Add(new PathToken(PathTokenType.DotSelector, field, option)); - position = pos; - return true; - } + private bool TryConsumeDotSelector(ref int position, ITokenWriter tokens) + { + if (!IsDotSelector(position) && !Current(position, QUOTED_SINGLE) && !Current(position, QUOTED_DOUBLE) && !IsMemberName(position)) + return false; - private bool TryConsumeDotWildSelector(ref int position, ITokenWriter tokens) - { - if (!IsDotWildcardSelector(position)) - return false; + var pos = IsDotSelector(position) ? position + 1 : position; + var option = Current(position, PLUS) ? PathTokenOption.CaseSensitive : PathTokenOption.None; + var field = CaptureMemberName(ref pos); + if (string.IsNullOrEmpty(field)) + return false; - tokens.Add(new PathToken(PathTokenType.DotWildSelector)); - position += 2; - return true; - } + tokens.Add(new PathToken(PathTokenType.DotSelector, field, option)); + position = pos; + return true; + } - private bool TryConsumeDescendantSelector(ref int position, ITokenWriter tokens) - { - if (!IsDescendantSelector(position)) - return false; + private bool TryConsumeDotWildSelector(ref int position, ITokenWriter tokens) + { + if (!IsDotWildcardSelector(position)) + return false; - var pos = position + 2; - var field = CaptureMemberName(ref pos); - if (string.IsNullOrEmpty(field)) - return false; + tokens.Add(new PathToken(PathTokenType.DotWildSelector)); + position += 2; + return true; + } - tokens.Add(new PathToken(PathTokenType.DescendantSelector, field)); - position = pos; - return true; - } + private bool TryConsumeDescendantSelector(ref int position, ITokenWriter tokens) + { + if (!IsDescendantSelector(position)) + return false; - private bool TryConsumeIndexSelector(ref int position, ITokenWriter tokens) - { - if (!(Current(position) == INDEX_OPEN && Current(position + 1) != QUERY && position + 1 < _Last)) - return false; + var pos = position + 2; + var field = CaptureMemberName(ref pos); + if (string.IsNullOrEmpty(field)) + return false; - // Move past "[" - position++; - return TryConsumeArraySliceSelector(ref position, tokens) || - TryConsumeUnionSelector(ref position, tokens) || - TryConsumeNumericIndex(ref position, tokens) || - TryConsumeIndexWildSelector(ref position, tokens) || - TryConsumeStringIndex(ref position, tokens); - } + tokens.Add(new PathToken(PathTokenType.DescendantSelector, field)); + position = pos; + return true; + } - /// - /// Capture a token for: [::1] - /// - private bool TryConsumeArraySliceSelector(ref int position, ITokenWriter tokens) - { - if (!AnyUntilIndexClose(position, COLON)) - return false; + private bool TryConsumeIndexSelector(ref int position, ITokenWriter tokens) + { + if (!(Current(position) == INDEX_OPEN && Current(position + 1) != QUERY && position + 1 < _Last)) + return false; - var pos = SkipPadding(position); - var slice = new int?[] { null, null, null }; - for (var i = 0; i <= 2 && pos <= _Last && _Path[pos] != INDEX_CLOSE; i++) - { - if (WhileNumeric(pos, out var end) && end > pos) - { - slice[i] = int.Parse(Substring(pos, end)); - pos = Current(end, COLON) ? end + 1 : end; - } - else - { - pos++; - } - } - position = ++pos; - tokens.Add(new PathToken(PathTokenType.ArraySliceSelector, slice)); - return true; - } + // Move past "[" + position++; + return TryConsumeArraySliceSelector(ref position, tokens) || + TryConsumeUnionSelector(ref position, tokens) || + TryConsumeNumericIndex(ref position, tokens) || + TryConsumeIndexWildSelector(ref position, tokens) || + TryConsumeStringIndex(ref position, tokens); + } - /// - /// Capture a token for: [,] - /// - private bool TryConsumeUnionSelector(ref int position, ITokenWriter tokens) - { - return AnyUntilIndexClose(position, COMMA) && - (TryConsumeUnionQuotedMemberSelector(ref position, tokens) || TryConsumeUnionIndexSelector(ref position, tokens)); - } + /// + /// Capture a token for: [::1] + /// + private bool TryConsumeArraySliceSelector(ref int position, ITokenWriter tokens) + { + if (!AnyUntilIndexClose(position, COLON)) + return false; - private bool TryConsumeUnionIndexSelector(ref int position, ITokenWriter tokens) + var pos = SkipPadding(position); + var slice = new int?[] { null, null, null }; + for (var i = 0; i <= 2 && pos <= _Last && _Path[pos] != INDEX_CLOSE; i++) { - var pos = SkipPadding(position); - if (pos + 2 >= _Last || !WhileNumeric(pos, out var end) || end == pos) - return false; - - var members = new List(); - while (pos <= _Last && _Path[pos] != INDEX_CLOSE) + if (WhileNumeric(pos, out var end) && end > pos) { - pos = SkipPadding(pos); - if (!WhileNumeric(pos, out end) || !int.TryParse(Substring(pos, end), out var member)) - break; - - members.Add(member); - pos = SkipPadding(end); - if (Current(pos, COMMA)) - pos++; + slice[i] = int.Parse(Substring(pos, end)); + pos = Current(end, COLON) ? end + 1 : end; + } + else + { + pos++; } - position = pos + 1; - tokens.Add(new PathToken(PathTokenType.UnionIndexSelector, members.ToArray())); - return true; } + position = ++pos; + tokens.Add(new PathToken(PathTokenType.ArraySliceSelector, slice)); + return true; + } - private bool TryConsumeUnionQuotedMemberSelector(ref int position, ITokenWriter tokens) + /// + /// Capture a token for: [,] + /// + private bool TryConsumeUnionSelector(ref int position, ITokenWriter tokens) + { + return AnyUntilIndexClose(position, COMMA) && + (TryConsumeUnionQuotedMemberSelector(ref position, tokens) || TryConsumeUnionIndexSelector(ref position, tokens)); + } + + private bool TryConsumeUnionIndexSelector(ref int position, ITokenWriter tokens) + { + var pos = SkipPadding(position); + if (pos + 2 >= _Last || !WhileNumeric(pos, out var end) || end == pos) + return false; + + var members = new List(); + while (pos <= _Last && _Path[pos] != INDEX_CLOSE) { - var pos = SkipPadding(position); - if (pos + 3 >= _Last || !IsQuoted(_Path[pos])) - return false; + pos = SkipPadding(pos); + if (!WhileNumeric(pos, out end) || !int.TryParse(Substring(pos, end), out var member)) + break; - var members = new List(); - while (pos <= _Last && _Path[pos] != INDEX_CLOSE) - { - pos = SkipPadding(pos); - var member = CaptureMemberName(ref pos); - if (string.IsNullOrEmpty(member)) - break; - - members.Add(member); - pos = SkipPadding(pos); - if (Current(pos, COMMA)) - pos++; - } - position = pos + 1; - tokens.Add(new PathToken(PathTokenType.UnionQuotedMemberSelector, members.ToArray())); - return true; + members.Add(member); + pos = SkipPadding(end); + if (Current(pos, COMMA)) + pos++; } + position = pos + 1; + tokens.Add(new PathToken(PathTokenType.UnionIndexSelector, members.ToArray())); + return true; + } - /// - /// Capture a token for "['store']". - /// - private bool TryConsumeStringIndex(ref int position, ITokenWriter tokens) - { - var pos = SkipPadding(position); - if (pos + 3 >= _Last || !IsQuoted(_Path[pos])) - return false; + private bool TryConsumeUnionQuotedMemberSelector(ref int position, ITokenWriter tokens) + { + var pos = SkipPadding(position); + if (pos + 3 >= _Last || !IsQuoted(_Path[pos])) + return false; - var field = CaptureMemberName(ref pos); - if (string.IsNullOrEmpty(field)) - return false; + var members = new List(); + while (pos <= _Last && _Path[pos] != INDEX_CLOSE) + { + pos = SkipPadding(pos); + var member = CaptureMemberName(ref pos); + if (string.IsNullOrEmpty(member)) + break; - tokens.Add(new PathToken(PathTokenType.DotSelector, field)); - position = pos + 1; - return true; + members.Add(member); + pos = SkipPadding(pos); + if (Current(pos, COMMA)) + pos++; } + position = pos + 1; + tokens.Add(new PathToken(PathTokenType.UnionQuotedMemberSelector, members.ToArray())); + return true; + } - /// - /// Capture a token for "[*]". - /// - private bool TryConsumeIndexWildSelector(ref int position, ITokenWriter tokens) - { - var pos = SkipPadding(position); - if (pos >= _Last || _Path[pos] != ANY) - return false; + /// + /// Capture a token for "['store']". + /// + private bool TryConsumeStringIndex(ref int position, ITokenWriter tokens) + { + var pos = SkipPadding(position); + if (pos + 3 >= _Last || !IsQuoted(_Path[pos])) + return false; - pos = SkipPadding(pos + 1); - if (pos > _Last || _Path[pos] != INDEX_CLOSE) - return false; + var field = CaptureMemberName(ref pos); + if (string.IsNullOrEmpty(field)) + return false; - pos++; - tokens.Add(new PathToken(PathTokenType.IndexWildSelector)); - position = pos; - return true; - } + tokens.Add(new PathToken(PathTokenType.DotSelector, field)); + position = pos + 1; + return true; + } - /// - /// Capture a token for "[0]". - /// - private bool TryConsumeNumericIndex(ref int position, ITokenWriter tokens) - { - var pos = SkipPadding(position); - if (!WhileNumeric(pos, out var end) || !int.TryParse(Substring(pos, end), out var index)) - return false; + /// + /// Capture a token for "[*]". + /// + private bool TryConsumeIndexWildSelector(ref int position, ITokenWriter tokens) + { + var pos = SkipPadding(position); + if (pos >= _Last || _Path[pos] != ANY) + return false; - pos = SkipPadding(end); - if (pos > _Last || _Path[pos] != INDEX_CLOSE) - return false; + pos = SkipPadding(pos + 1); + if (pos > _Last || _Path[pos] != INDEX_CLOSE) + return false; - tokens.Add(new PathToken(PathTokenType.IndexSelector, index)); - position = pos; - return true; - } + pos++; + tokens.Add(new PathToken(PathTokenType.IndexWildSelector)); + position = pos; + return true; + } - private string CaptureMemberName(ref int position) - { - return UntilQuote(ref position, out var value) || WhileMemberName(ref position, out value) ? value : null; - } + /// + /// Capture a token for "[0]". + /// + private bool TryConsumeNumericIndex(ref int position, ITokenWriter tokens) + { + var pos = SkipPadding(position); + if (!WhileNumeric(pos, out var end) || !int.TryParse(Substring(pos, end), out var index)) + return false; - private bool TryBoolean(int position, out bool value) - { - value = default; - if (IsSequence(position, bool.FalseString)) - { - value = false; - return true; - } - if (IsSequence(position, bool.TrueString)) - { - value = true; - return true; - } + pos = SkipPadding(end); + if (pos > _Last || _Path[pos] != INDEX_CLOSE) return false; - } - private bool TryInteger(int position, out int value) - { - value = default; - return WhileNumeric(position, out var end) && int.TryParse(Substring(position, end), out value); - } + tokens.Add(new PathToken(PathTokenType.IndexSelector, index)); + position = pos; + return true; + } - private bool IsSequence(int position, string sequence) + private string CaptureMemberName(ref int position) + { + return UntilQuote(ref position, out var value) || WhileMemberName(ref position, out value) ? value : null; + } + + private bool TryBoolean(int position, out bool value) + { + value = default; + if (IsSequence(position, bool.FalseString)) { - return position + sequence.Length <= _Last && string.Compare(_Path, position, sequence, 0, sequence.Length, StringComparison.OrdinalIgnoreCase) == 0; + value = false; + return true; } - - private bool IsMemberName(int position) + if (IsSequence(position, bool.TrueString)) { - var p = Current(position); - var p1 = Current(position + 1); - return IsMemberNameCharacter(p) || (p == ROOTREF && IsMemberNameCharacter(p1)); + value = true; + return true; } + return false; + } - /// - /// Skip whitespace. - /// - private int SkipPadding(int pos) - { - while (pos < _Last && char.IsWhiteSpace(_Path[pos])) - pos++; + private bool TryInteger(int position, out int value) + { + value = default; + return WhileNumeric(position, out var end) && int.TryParse(Substring(position, end), out value); + } - return pos; - } + private bool IsSequence(int position, string sequence) + { + return position + sequence.Length <= _Last && string.Compare(_Path, position, sequence, 0, sequence.Length, StringComparison.OrdinalIgnoreCase) == 0; + } - private string Substring(int pos, int end) - { - return pos == end ? null : _Path.Substring(pos, end - pos); - } + private bool IsMemberName(int position) + { + var p = Current(position); + var p1 = Current(position + 1); + return IsMemberNameCharacter(p) || (p == ROOTREF && IsMemberNameCharacter(p1)); + } - /// - /// Continue while the character is a member name. - /// - private bool WhileMemberName(ref int position, out string value) - { - value = null; - if (position >= _Last) - return false; + /// + /// Skip whitespace. + /// + private int SkipPadding(int pos) + { + while (pos < _Last && char.IsWhiteSpace(_Path[pos])) + pos++; - var end = _Path[position] == ROOTREF ? position + 1 : position; - while (end <= _Last && IsMemberNameCharacter(_Path[end]) && (end != position || _Path[end] != DASH)) - end++; + return pos; + } - if (end > position && Current(end - 1) == DASH) - end--; + private string Substring(int pos, int end) + { + return pos == end ? null : _Path.Substring(pos, end - pos); + } - if (position == end) - return false; + /// + /// Continue while the character is a member name. + /// + private bool WhileMemberName(ref int position, out string value) + { + value = null; + if (position >= _Last) + return false; - value = Substring(position, end); - position = end; - return true; - } + var end = _Path[position] == ROOTREF ? position + 1 : position; + while (end <= _Last && IsMemberNameCharacter(_Path[end]) && (end != position || _Path[end] != DASH)) + end++; - /// - /// Continue while the character is numeric. - /// - private bool WhileNumeric(int position, out int end) - { - end = position; - if (position >= _Last) - return false; + if (end > position && Current(end - 1) == DASH) + end--; - var i = position; - if (i <= _Last && _Path[i] == DASH) - i++; + if (position == end) + return false; - while (i <= _Last && (char.IsDigit(_Path[i]) || (_Path[i] == DASH && i + 1 < _Last && char.IsDigit(_Path[i + 1])))) - end = ++i; + value = Substring(position, end); + position = end; + return true; + } - return end > position; - } + /// + /// Continue while the character is numeric. + /// + private bool WhileNumeric(int position, out int end) + { + end = position; + if (position >= _Last) + return false; - /// - /// Find the end of the quote ('). - /// - private bool UntilQuote(ref int position, out string value) - { - value = null; - if (position >= _Last || !IsQuoted(_Path[position])) - return false; + var i = position; + if (i <= _Last && _Path[i] == DASH) + i++; - var endQuote = _Path[position]; - var pos = position + 1; - var end = pos; - while (end <= _Last && _Path[end] != endQuote) - end++; + while (i <= _Last && (char.IsDigit(_Path[i]) || (_Path[i] == DASH && i + 1 < _Last && char.IsDigit(_Path[i + 1])))) + end = ++i; - if (pos == end) - return false; + return end > position; + } - value = Substring(pos, end); - position = end + 1; - return true; - } + /// + /// Find the end of the quote ('). + /// + private bool UntilQuote(ref int position, out string value) + { + value = null; + if (position >= _Last || !IsQuoted(_Path[position])) + return false; - private bool AnyUntilIndexClose(int position, char c) - { - for (var i = position; i <= _Last && _Path[i] != INDEX_CLOSE; i++) - if (_Path[i] == c) - return true; + var endQuote = _Path[position]; + var pos = position + 1; + var end = pos; + while (end <= _Last && _Path[end] != endQuote) + end++; + if (pos == end) return false; - } + + value = Substring(pos, end); + position = end + 1; + return true; } - /// - /// Get path tokens for a specific object path expression. - /// - /// The object path expression. - /// One or more path tokens. - internal static IPathToken[] Get(string path) - { - var stream = new PathStream(path); - var tokens = new TokenStream(); - var position = 0; - while (!stream.EOF(position)) + private bool AnyUntilIndexClose(int position, char c) + { + for (var i = position; i <= _Last && _Path[i] != INDEX_CLOSE; i++) + if (_Path[i] == c) + return true; + + return false; + } + } + + /// + /// Get path tokens for a specific object path expression. + /// + /// The object path expression. + /// One or more path tokens. + internal static IPathToken[] Get(string path) + { + var stream = new PathStream(path); + var tokens = new TokenStream(); + var position = 0; + while (!stream.EOF(position)) + { + if (!(stream.TryConsumeRef(ref position, tokens) || + stream.TryConsumeChild(ref position, tokens) || + stream.TryConsumeFilter(ref position, tokens))) { - if (!(stream.TryConsumeRef(ref position, tokens) || - stream.TryConsumeChild(ref position, tokens) || - stream.TryConsumeFilter(ref position, tokens))) - { - stream.Next(ref position); - } + stream.Next(ref position); } - return tokens.ToArray(); } + return tokens.ToArray(); } } diff --git a/src/PSRule/Runtime/ObjectPath/Tokens.cs b/src/PSRule/Runtime/ObjectPath/Tokens.cs index 53eee45387..bda73d360d 100644 --- a/src/PSRule/Runtime/ObjectPath/Tokens.cs +++ b/src/PSRule/Runtime/ObjectPath/Tokens.cs @@ -3,193 +3,192 @@ using System.Diagnostics; -namespace PSRule.Runtime.ObjectPath +namespace PSRule.Runtime.ObjectPath; + +internal enum PathTokenType +{ + None = 0, + + /// + /// Token: $ + /// + RootRef, + + /// + /// Token: @ + /// + CurrentRef, + + /// + /// Token: .Name + /// + DotSelector, + + /// + /// Token: [index] + /// + IndexSelector, + + /// + /// Token: [*] + /// + IndexWildSelector, + + StartFilter, + ComparisonOperator, + Boolean, + EndFilter, + String, + Integer, + LogicalOperator, + + StartGroup, + EndGroup, + + /// + /// Token: ! + /// + NotOperator, + + /// + /// Token: .. + /// + DescendantSelector, + + /// + /// Token: .* + /// + DotWildSelector, + + ArraySliceSelector, + UnionIndexSelector, + UnionQuotedMemberSelector, +} + +internal enum PathTokenOption +{ + None = 0, + + CaseSensitive +} + +internal enum FilterOperator { - internal enum PathTokenType + None = 0, + + // Comparison + Equal, + NotEqual, + LessOrEqual, + Less, + GreaterOrEqual, + Greater, + RegEx, + + // Logical + Or, + And, +} + +internal interface IPathToken +{ + PathTokenType Type { get; } + + PathTokenOption Option { get; } + + object Arg { get; } + + T As(); +} + +[DebuggerDisplay("Type = {Type}, Arg = {Arg}")] +internal sealed class PathToken : IPathToken +{ + public static readonly PathToken RootRef = new(PathTokenType.RootRef); + public static readonly PathToken CurrentRef = new(PathTokenType.CurrentRef); + + public PathTokenType Type { get; } + + public PathTokenOption Option { get; } + + public PathToken(PathTokenType type) { - None = 0, - - /// - /// Token: $ - /// - RootRef, - - /// - /// Token: @ - /// - CurrentRef, - - /// - /// Token: .Name - /// - DotSelector, - - /// - /// Token: [index] - /// - IndexSelector, - - /// - /// Token: [*] - /// - IndexWildSelector, - - StartFilter, - ComparisonOperator, - Boolean, - EndFilter, - String, - Integer, - LogicalOperator, - - StartGroup, - EndGroup, - - /// - /// Token: ! - /// - NotOperator, - - /// - /// Token: .. - /// - DescendantSelector, - - /// - /// Token: .* - /// - DotWildSelector, - - ArraySliceSelector, - UnionIndexSelector, - UnionQuotedMemberSelector, + Type = type; } - internal enum PathTokenOption + public PathToken(PathTokenType type, object arg, PathTokenOption option = PathTokenOption.None) { - None = 0, - - CaseSensitive + Type = type; + Arg = arg; + Option = option; } - internal enum FilterOperator + public object Arg { get; } + + public T As() { - None = 0, - - // Comparison - Equal, - NotEqual, - LessOrEqual, - Less, - GreaterOrEqual, - Greater, - RegEx, - - // Logical - Or, - And, + return Arg is T result ? result : default; } +} - internal interface IPathToken - { - PathTokenType Type { get; } +internal interface ITokenWriter +{ + IPathToken Last { get; } - PathTokenOption Option { get; } + void Add(IPathToken token); +} - object Arg { get; } +internal interface ITokenReader +{ + IPathToken Current { get; } - T As(); - } + bool Next(out IPathToken token); - [DebuggerDisplay("Type = {Type}, Arg = {Arg}")] - internal sealed class PathToken : IPathToken - { - public static readonly PathToken RootRef = new(PathTokenType.RootRef); - public static readonly PathToken CurrentRef = new(PathTokenType.CurrentRef); + bool Consume(PathTokenType type); - public PathTokenType Type { get; } + bool Peak(out IPathToken token); +} - public PathTokenOption Option { get; } +internal sealed class TokenReader : ITokenReader +{ + private readonly IPathToken[] _Tokens; + private readonly int _Last; - public PathToken(PathTokenType type) - { - Type = type; - } + private int _Index; - public PathToken(PathTokenType type, object arg, PathTokenOption option = PathTokenOption.None) - { - Type = type; - Arg = arg; - Option = option; - } + public TokenReader(IPathToken[] tokens) + { + _Tokens = tokens; + _Last = tokens.Length - 1; + _Index = -1; + } - public object Arg { get; } + public IPathToken Current { get; private set; } - public T As() - { - return Arg is T result ? result : default; - } + public bool Consume(PathTokenType type) + { + return Peak(out var token) && token.Type == type && Next(); } - internal interface ITokenWriter + public bool Next(out IPathToken token) { - IPathToken Last { get; } + token = null; + if (!Next()) + return false; - void Add(IPathToken token); + token = Current; + return true; } - internal interface ITokenReader + private bool Next() { - IPathToken Current { get; } - - bool Next(out IPathToken token); - - bool Consume(PathTokenType type); - - bool Peak(out IPathToken token); + Current = _Index < _Last ? _Tokens[++_Index] : null; + return Current != null; } - internal sealed class TokenReader : ITokenReader + public bool Peak(out IPathToken token) { - private readonly IPathToken[] _Tokens; - private readonly int _Last; - - private int _Index; - - public TokenReader(IPathToken[] tokens) - { - _Tokens = tokens; - _Last = tokens.Length - 1; - _Index = -1; - } - - public IPathToken Current { get; private set; } - - public bool Consume(PathTokenType type) - { - return Peak(out var token) && token.Type == type && Next(); - } - - public bool Next(out IPathToken token) - { - token = null; - if (!Next()) - return false; - - token = Current; - return true; - } - - private bool Next() - { - Current = _Index < _Last ? _Tokens[++_Index] : null; - return Current != null; - } - - public bool Peak(out IPathToken token) - { - token = _Index < _Last ? _Tokens[_Index + 1] : null; - return token != null; - } + token = _Index < _Last ? _Tokens[_Index + 1] : null; + return token != null; } } diff --git a/src/PSRule/Runtime/Operand.cs b/src/PSRule/Runtime/Operand.cs index 4734fe2159..fa51e2c49a 100644 --- a/src/PSRule/Runtime/Operand.cs +++ b/src/PSRule/Runtime/Operand.cs @@ -1,164 +1,163 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -namespace PSRule.Runtime +namespace PSRule.Runtime; + +/// +/// The type of operand that is compared with the expression. +/// +public enum OperandKind { /// - /// The type of operand that is compared with the expression. + /// Unknown. /// - public enum OperandKind - { - /// - /// Unknown. - /// - None = 0, - - /// - /// An object path. - /// - Path = 1, - - /// - /// The object target type. - /// - Type = 2, - - /// - /// The object target name. - /// - Name = 3, - - /// - /// The object source information. - /// - Source = 4, - - /// - /// The target object itself. - /// - Target = 5, - - /// - /// A literal value or function. - /// - Value = 6, - - /// - /// The object scope. - /// - Scope = 7 - } + None = 0, + + /// + /// An object path. + /// + Path = 1, + + /// + /// The object target type. + /// + Type = 2, + + /// + /// The object target name. + /// + Name = 3, + + /// + /// The object source information. + /// + Source = 4, /// - /// An operand that is compared with PSRule expressions. + /// The target object itself. /// - public interface IOperand + Target = 5, + + /// + /// A literal value or function. + /// + Value = 6, + + /// + /// The object scope. + /// + Scope = 7 +} + +/// +/// An operand that is compared with PSRule expressions. +/// +public interface IOperand +{ + /// + /// The value of the operand. + /// + object Value { get; } + + /// + /// The type of operand. + /// + OperandKind Kind { get; } + + /// + /// The object path to the operand. + /// + string Path { get; } + + /// + /// A logical prefix to add to the object path. + /// + string Prefix { get; set; } +} + +internal sealed class Operand : IOperand +{ + private const string Dot = "."; + private const string Space = " "; + + private Operand(OperandKind kind, object value) + { + Kind = kind; + Value = value; + } + + private Operand(OperandKind kind, string path, object value) + : this(kind, value) + { + Path = path; + } + + public object Value { get; } + + public string Path { get; } + + public string Prefix { get; set; } + + public OperandKind Kind { get; } + + internal static IOperand FromName(string name, string path) + { + return new Operand(OperandKind.Name, path, name); + } + + internal static IOperand FromType(string type, string path) + { + return new Operand(OperandKind.Type, path, type); + } + + internal static IOperand FromPath(string path, object value = null) + { + return new Operand(OperandKind.Path, path, value); + } + + internal static IOperand FromSource(string source) + { + return new Operand(OperandKind.Source, source); + } + + internal static IOperand FromTarget() + { + return new Operand(OperandKind.Target, null, null); + } + + internal static IOperand FromValue(object value) + { + return new Operand(OperandKind.Value, null, value); + } + + internal static IOperand FromScope(string[] scope) + { + return new Operand(OperandKind.Scope, scope); + } + + internal static string JoinPath(string p1, string p2) + { + if (IsEmptyPath(p1)) + return p2; + + return IsEmptyPath(p2) ? p1 : string.Concat(p1, Dot, p2); + } + + public override string ToString() + { + return string.IsNullOrEmpty(Path) || Kind == OperandKind.Target ? null : OperandString(); + } + + private string OperandString() { - /// - /// The value of the operand. - /// - object Value { get; } - - /// - /// The type of operand. - /// - OperandKind Kind { get; } - - /// - /// The object path to the operand. - /// - string Path { get; } - - /// - /// A logical prefix to add to the object path. - /// - string Prefix { get; set; } + var kind = Enum.GetName(typeof(OperandKind), Kind); + return IsEmptyPath(Prefix) ? string.Concat(kind, Space, Path, ": ") : string.Concat(kind, Space, Prefix, Dot, Path, ": "); } - internal sealed class Operand : IOperand + private static bool IsEmptyPath(string s) { - private const string Dot = "."; - private const string Space = " "; - - private Operand(OperandKind kind, object value) - { - Kind = kind; - Value = value; - } - - private Operand(OperandKind kind, string path, object value) - : this(kind, value) - { - Path = path; - } - - public object Value { get; } - - public string Path { get; } - - public string Prefix { get; set; } - - public OperandKind Kind { get; } - - internal static IOperand FromName(string name, string path) - { - return new Operand(OperandKind.Name, path, name); - } - - internal static IOperand FromType(string type, string path) - { - return new Operand(OperandKind.Type, path, type); - } - - internal static IOperand FromPath(string path, object value = null) - { - return new Operand(OperandKind.Path, path, value); - } - - internal static IOperand FromSource(string source) - { - return new Operand(OperandKind.Source, source); - } - - internal static IOperand FromTarget() - { - return new Operand(OperandKind.Target, null, null); - } - - internal static IOperand FromValue(object value) - { - return new Operand(OperandKind.Value, null, value); - } - - internal static IOperand FromScope(string[] scope) - { - return new Operand(OperandKind.Scope, scope); - } - - internal static string JoinPath(string p1, string p2) - { - if (IsEmptyPath(p1)) - return p2; - - return IsEmptyPath(p2) ? p1 : string.Concat(p1, Dot, p2); - } - - public override string ToString() - { - return string.IsNullOrEmpty(Path) || Kind == OperandKind.Target ? null : OperandString(); - } - - private string OperandString() - { - var kind = Enum.GetName(typeof(OperandKind), Kind); - return IsEmptyPath(Prefix) ? string.Concat(kind, Space, Path, ": ") : string.Concat(kind, Space, Prefix, Dot, Path, ": "); - } - - private static bool IsEmptyPath(string s) - { - return string.IsNullOrEmpty(s) || - string.IsNullOrWhiteSpace(s) || - s == Dot; - } + return string.IsNullOrEmpty(s) || + string.IsNullOrWhiteSpace(s) || + s == Dot; } } diff --git a/src/PSRule/Runtime/PSRule.cs b/src/PSRule/Runtime/PSRule.cs index 0d4f7078f8..8d1960c5e4 100644 --- a/src/PSRule/Runtime/PSRule.cs +++ b/src/PSRule/Runtime/PSRule.cs @@ -7,389 +7,388 @@ using PSRule.Data; using PSRule.Pipeline; -namespace PSRule.Runtime +namespace PSRule.Runtime; + +/// +/// A set of context properties that are exposed at runtime through the $PSRule variable. +/// +public sealed class PSRule : ScopedItem { + private ITargetSourceCollection _Source; + private IInputCollection _Input; + private ITargetIssueCollection _Issue; + private IBadgeBuilder _BadgeBuilder; + private IRepositoryRuntimeInfo _Repository; + /// - /// A set of context properties that are exposed at runtime through the $PSRule variable. + /// Create an empty instance. /// - public sealed class PSRule : ScopedItem + public PSRule() { } + + internal PSRule(RunspaceContext context) + : base(context) { } + + private sealed class PSRuleSource : ScopedItem, ITargetSourceCollection { - private ITargetSourceCollection _Source; - private IInputCollection _Input; - private ITargetIssueCollection _Issue; - private IBadgeBuilder _BadgeBuilder; - private IRepositoryRuntimeInfo _Repository; - - /// - /// Create an empty instance. - /// - public PSRule() { } - - internal PSRule(RunspaceContext context) + internal PSRuleSource(RunspaceContext context) : base(context) { } - private sealed class PSRuleSource : ScopedItem, ITargetSourceCollection + public TargetSourceInfo this[string type] { - internal PSRuleSource(RunspaceContext context) - : base(context) { } - - public TargetSourceInfo this[string type] + get { - get - { - return GetContext().TargetObject?.Source[type]; - } + return GetContext().TargetObject?.Source[type]; } } + } - private sealed class PSRuleIssue : ScopedItem, ITargetIssueCollection - { - internal PSRuleIssue(RunspaceContext context) - : base(context) { } - - public TargetIssueInfo[] Get(string type = null) - { - return GetContext().TargetObject.Issue.Get(type); - } + private sealed class PSRuleIssue : ScopedItem, ITargetIssueCollection + { + internal PSRuleIssue(RunspaceContext context) + : base(context) { } - public bool Any(string type = null) - { - return GetContext().TargetObject.Issue.Any(type); - } + public TargetIssueInfo[] Get(string type = null) + { + return GetContext().TargetObject.Issue.Get(type); } - private sealed class PSRuleInput : ScopedItem, IInputCollection + public bool Any(string type = null) { - internal PSRuleInput(RunspaceContext context) - : base(context) { } - - /// - public void Add(string path) - { - var context = GetContext(); - context.Writer.VerboseInputAdded(path); - context.Pipeline.Reader.Add(path); - } + return GetContext().TargetObject.Issue.Any(type); } + } + + private sealed class PSRuleInput : ScopedItem, IInputCollection + { + internal PSRuleInput(RunspaceContext context) + : base(context) { } - private sealed class PSRuleRepository : ScopedItem, IRepositoryRuntimeInfo + /// + public void Add(string path) { - private InputFileInfoCollection _ChangedFiles; + var context = GetContext(); + context.Writer.VerboseInputAdded(path); + context.Pipeline.Reader.Add(path); + } + } - internal PSRuleRepository(RunspaceContext context) - : base(context) - { - Url = GetContext().Pipeline.Option.Repository.Url; - BaseRef = GetContext().Pipeline.Option.Repository.BaseRef; - } + private sealed class PSRuleRepository : ScopedItem, IRepositoryRuntimeInfo + { + private InputFileInfoCollection _ChangedFiles; - /// - public string Url { get; } + internal PSRuleRepository(RunspaceContext context) + : base(context) + { + Url = GetContext().Pipeline.Option.Repository.Url; + BaseRef = GetContext().Pipeline.Option.Repository.BaseRef; + } - /// - public string BaseRef { get; } + /// + public string Url { get; } - /// - public IInputFileInfoCollection GetChangedFiles() - { - _ChangedFiles ??= new InputFileInfoCollection(Environment.GetWorkingPath(), GitHelper.TryGetChangedFiles(BaseRef, "d", null, out var files) ? files : null); - return _ChangedFiles; - } - } + /// + public string BaseRef { get; } - /// - /// Exposes the badge API for used within conventions. - /// - public IBadgeBuilder Badges + /// + public IInputFileInfoCollection GetChangedFiles() { - get - { - RequireScope(RunspaceScope.ConventionEnd); - _BadgeBuilder ??= new BadgeBuilder(); - return _BadgeBuilder; - } + _ChangedFiles ??= new InputFileInfoCollection(Environment.GetWorkingPath(), GitHelper.TryGetChangedFiles(BaseRef, "d", null, out var files) ? files : null); + return _ChangedFiles; } + } - /// - /// Custom data set by the rule for this target object. - /// - public Hashtable Data + /// + /// Exposes the badge API for used within conventions. + /// + public IBadgeBuilder Badges + { + get { - get - { - RequireScope(RunspaceScope.Rule | RunspaceScope.Precondition | RunspaceScope.ConventionBegin | RunspaceScope.ConventionProcess); - return GetContext().TargetObject.RequireData(); - } + RequireScope(RunspaceScope.ConventionEnd); + _BadgeBuilder ??= new BadgeBuilder(); + return _BadgeBuilder; } + } - /// - /// A set of custom fields bound for the target object. - /// - /// - /// This property can only be accessed from a rule or pre-condition. - /// - /// - /// Thrown when accessing this property outside of a rule or pre-condition. - /// - public Hashtable Field + /// + /// Custom data set by the rule for this target object. + /// + public Hashtable Data + { + get { - get - { - RequireScope(RunspaceScope.Rule | RunspaceScope.Precondition); - return GetContext().RuleRecord.Field; - } + RequireScope(RunspaceScope.Rule | RunspaceScope.Precondition | RunspaceScope.ConventionBegin | RunspaceScope.ConventionProcess); + return GetContext().TargetObject.RequireData(); } + } - /// - /// A list of pre-defined input path that are included. - /// - /// - /// This property can only be accessed from an initialize convention block. - /// - /// - /// Thrown when accessing this property outside of an initialize convention block. - /// - public IInputCollection Input => GetInput(); - - /// - /// Information about the repository that is currently being used. - /// - public IRepositoryRuntimeInfo Repository => GetRepository(); - - /// - /// An aggregated set of results from executing PSRule rules. - /// - /// - /// This property can only be accessed from an end convention block. - /// - /// - /// Thrown when accessing this property outside of an end convention block. - /// - public IEnumerable Output + /// + /// A set of custom fields bound for the target object. + /// + /// + /// This property can only be accessed from a rule or pre-condition. + /// + /// + /// Thrown when accessing this property outside of a rule or pre-condition. + /// + public Hashtable Field + { + get { - get - { - RequireScope(RunspaceScope.ConventionEnd); - return GetContext().Output; - } + RequireScope(RunspaceScope.Rule | RunspaceScope.Precondition); + return GetContext().RuleRecord.Field; } + } - /// - /// The source information for the location the target object originated from. - /// - public ITargetSourceCollection Source => GetSource(); - - /// - /// Any issues reported by downstream tools and annotated to the target object. - /// - public ITargetIssueCollection Issue => GetIssue(); - - /// - /// The current target object. - /// - public PSObject TargetObject => GetContext().TargetObject.Value; - - /// - /// The bound name of the target object. - /// - public string TargetName => GetContext().RuleRecord.TargetName; - - /// - /// The bound type of the target object. - /// - public string TargetType => GetContext().RuleRecord.TargetType; - - /// - /// The bound scope of the target object. - /// - public string[] Scope => GetContext().TargetObject.Scope; - - /// - /// Attempts to read content from disk. - /// - public PSObject[] GetContent(PSObject sourceObject) + /// + /// A list of pre-defined input path that are included. + /// + /// + /// This property can only be accessed from an initialize convention block. + /// + /// + /// Thrown when accessing this property outside of an initialize convention block. + /// + public IInputCollection Input => GetInput(); + + /// + /// Information about the repository that is currently being used. + /// + public IRepositoryRuntimeInfo Repository => GetRepository(); + + /// + /// An aggregated set of results from executing PSRule rules. + /// + /// + /// This property can only be accessed from an end convention block. + /// + /// + /// Thrown when accessing this property outside of an end convention block. + /// + public IEnumerable Output + { + get { - if (sourceObject == null) - return Array.Empty(); + RequireScope(RunspaceScope.ConventionEnd); + return GetContext().Output; + } + } + + /// + /// The source information for the location the target object originated from. + /// + public ITargetSourceCollection Source => GetSource(); + + /// + /// Any issues reported by downstream tools and annotated to the target object. + /// + public ITargetIssueCollection Issue => GetIssue(); + + /// + /// The current target object. + /// + public PSObject TargetObject => GetContext().TargetObject.Value; - if (!(sourceObject.BaseObject is InputFileInfo || sourceObject.BaseObject is FileInfo || sourceObject.BaseObject is Uri)) - return new PSObject[] { sourceObject }; + /// + /// The bound name of the target object. + /// + public string TargetName => GetContext().RuleRecord.TargetName; + + /// + /// The bound type of the target object. + /// + public string TargetType => GetContext().RuleRecord.TargetType; - var cacheKey = sourceObject.BaseObject.ToString(); - if (GetContext().Pipeline.ContentCache.TryGetValue(cacheKey, out var result)) - return result; + /// + /// The bound scope of the target object. + /// + public string[] Scope => GetContext().TargetObject.Scope; + + /// + /// Attempts to read content from disk. + /// + public PSObject[] GetContent(PSObject sourceObject) + { + if (sourceObject == null) + return Array.Empty(); - var items = PipelineReceiverActions.DetectInputFormat(new TargetObject(sourceObject), PipelineReceiverActions.PassThru).ToArray(); - result = new PSObject[items.Length]; - for (var i = 0; i < items.Length; i++) - result[i] = items[i].Value; + if (!(sourceObject.BaseObject is InputFileInfo || sourceObject.BaseObject is FileInfo || sourceObject.BaseObject is Uri)) + return new PSObject[] { sourceObject }; - GetContext().Pipeline.ContentCache.Add(cacheKey, result); + var cacheKey = sourceObject.BaseObject.ToString(); + if (GetContext().Pipeline.ContentCache.TryGetValue(cacheKey, out var result)) return result; - } - /// - /// Attempts to read content from disk and extract a field from each object. - /// - public PSObject[] GetContentField(PSObject sourceObject, string field) - { - var content = GetContent(sourceObject); - if (IsEmptyContent(content) || string.IsNullOrEmpty(field)) - return Array.Empty(); + var items = PipelineReceiverActions.DetectInputFormat(new TargetObject(sourceObject), PipelineReceiverActions.PassThru).ToArray(); + result = new PSObject[items.Length]; + for (var i = 0; i < items.Length; i++) + result[i] = items[i].Value; + + GetContext().Pipeline.ContentCache.Add(cacheKey, result); + return result; + } - var result = new List(); - for (var i = 0; i < content.Length; i++) + /// + /// Attempts to read content from disk and extract a field from each object. + /// + public PSObject[] GetContentField(PSObject sourceObject, string field) + { + var content = GetContent(sourceObject); + if (IsEmptyContent(content) || string.IsNullOrEmpty(field)) + return Array.Empty(); + + var result = new List(); + for (var i = 0; i < content.Length; i++) + { + if (ObjectHelper.GetPath(content[i], field, false, out var value) && value != null) { - if (ObjectHelper.GetPath(content[i], field, false, out var value) && value != null) + if (value is IEnumerable evalue) { - if (value is IEnumerable evalue) - { - foreach (var item in evalue) - result.Add(PSObject.AsPSObject(item)); - } - else - result.Add(PSObject.AsPSObject(value)); + foreach (var item in evalue) + result.Add(PSObject.AsPSObject(item)); } - + else + result.Add(PSObject.AsPSObject(value)); } - return result.ToArray(); - } - - /// - /// Attempts to read content from disk and return the first object or null. - /// - public PSObject GetContentFirstOrDefault(PSObject sourceObject) - { - var content = GetContent(sourceObject); - return IsEmptyContent(content) ? null : content[0]; - } - private static bool IsEmptyContent(PSObject[] content) - { - return content == null || content.Length == 0; } + return result.ToArray(); + } - /// - /// Evalute an object path expression and returns the resulting objects. - /// - public object[] GetPath(object sourceObject, string path) - { - return (!ObjectHelper.GetPath( - bindingContext: GetContext()?.Pipeline, - targetObject: sourceObject, - path: path, - caseSensitive: false, - out object[] value)) ? Array.Empty() : value; - } + /// + /// Attempts to read content from disk and return the first object or null. + /// + public PSObject GetContentFirstOrDefault(PSObject sourceObject) + { + var content = GetContent(sourceObject); + return IsEmptyContent(content) ? null : content[0]; + } - /// - /// Imports source objects into the pipeline for processing. - /// - /// - /// This method can only be called from a convention initialize or begin block. - /// - /// One or more objects to import into the pipeline. - /// - /// Thrown when accessing this method outside of a convention initialize or begin block. - /// - public void Import(PSObject[] sourceObject) - { - ImportWithType(null, sourceObject); - } + private static bool IsEmptyContent(PSObject[] content) + { + return content == null || content.Length == 0; + } - /// - /// Imports source objects into the pipeline for processing. - /// - /// - /// This method can only be called from a convention initialize or begin block. - /// - /// The target type to assign to any objects imported into the pipeline. - /// One or more objects to import into the pipeline. - /// - /// Thrown when accessing this method outside of a convention initialize or begin block. - /// - public void ImportWithType(string type, PSObject[] sourceObject) - { - if (sourceObject == null || sourceObject.Length == 0) - return; + /// + /// Evalute an object path expression and returns the resulting objects. + /// + public object[] GetPath(object sourceObject, string path) + { + return (!ObjectHelper.GetPath( + bindingContext: GetContext()?.Pipeline, + targetObject: sourceObject, + path: path, + caseSensitive: false, + out object[] value)) ? Array.Empty() : value; + } - RequireScope(RunspaceScope.ConventionInitialize | RunspaceScope.ConventionBegin); - for (var i = 0; i < sourceObject.Length; i++) - { - if (sourceObject[i] == null) - continue; + /// + /// Imports source objects into the pipeline for processing. + /// + /// + /// This method can only be called from a convention initialize or begin block. + /// + /// One or more objects to import into the pipeline. + /// + /// Thrown when accessing this method outside of a convention initialize or begin block. + /// + public void Import(PSObject[] sourceObject) + { + ImportWithType(null, sourceObject); + } - GetContext().Pipeline.Reader.Enqueue(sourceObject[i], type, skipExpansion: true); - } - } + /// + /// Imports source objects into the pipeline for processing. + /// + /// + /// This method can only be called from a convention initialize or begin block. + /// + /// The target type to assign to any objects imported into the pipeline. + /// One or more objects to import into the pipeline. + /// + /// Thrown when accessing this method outside of a convention initialize or begin block. + /// + public void ImportWithType(string type, PSObject[] sourceObject) + { + if (sourceObject == null || sourceObject.Length == 0) + return; - /// - /// Add a reusable singleton object into PSRule runtime that can be reference across multiple rules or conventions. To retrieve the singleton call . - /// - /// A unique identifier for the object. - /// A instance of the singleton. - /// - /// If either or is null or empty the singleton is ignored. - /// This method can only be called from a convention initialize block. - /// - /// - /// Thrown when accessing this method outside of a convention initialize block. - /// - public void AddService(string id, object service) + RequireScope(RunspaceScope.ConventionInitialize | RunspaceScope.ConventionBegin); + for (var i = 0; i < sourceObject.Length; i++) { - if (service == null || string.IsNullOrEmpty(id)) - return; + if (sourceObject[i] == null) + continue; - RequireScope(RunspaceScope.ConventionInitialize); - GetContext().AddService(id, service); + GetContext().Pipeline.Reader.Enqueue(sourceObject[i], type, skipExpansion: true); } + } - /// - /// Retrieve a reusable singleton object from the PSRule runtime that has previously been stored with . - /// - /// The unique identifier for the object. - /// The singleton instance or null if an object with the specified was not found. - /// - /// Thrown when accessing this method outside of PSRule. - /// - public object GetService(string id) - { - if (string.IsNullOrEmpty(id)) - return null; + /// + /// Add a reusable singleton object into PSRule runtime that can be reference across multiple rules or conventions. To retrieve the singleton call . + /// + /// A unique identifier for the object. + /// A instance of the singleton. + /// + /// If either or is null or empty the singleton is ignored. + /// This method can only be called from a convention initialize block. + /// + /// + /// Thrown when accessing this method outside of a convention initialize block. + /// + public void AddService(string id, object service) + { + if (service == null || string.IsNullOrEmpty(id)) + return; - RequireScope(RunspaceScope.Runtime); - return GetContext().GetService(id); - } + RequireScope(RunspaceScope.ConventionInitialize); + GetContext().AddService(id, service); + } + + /// + /// Retrieve a reusable singleton object from the PSRule runtime that has previously been stored with . + /// + /// The unique identifier for the object. + /// The singleton instance or null if an object with the specified was not found. + /// + /// Thrown when accessing this method outside of PSRule. + /// + public object GetService(string id) + { + if (string.IsNullOrEmpty(id)) + return null; - #region Helper methods + RequireScope(RunspaceScope.Runtime); + return GetContext().GetService(id); + } - private ITargetSourceCollection GetSource() - { - RequireScope(RunspaceScope.Target); - _Source ??= new PSRuleSource(GetContext()); - return _Source; - } + #region Helper methods - private IInputCollection GetInput() - { - RequireScope(RunspaceScope.ConventionInitialize); - _Input ??= new PSRuleInput(GetContext()); - return _Input; - } + private ITargetSourceCollection GetSource() + { + RequireScope(RunspaceScope.Target); + _Source ??= new PSRuleSource(GetContext()); + return _Source; + } - private ITargetIssueCollection GetIssue() - { - RequireScope(RunspaceScope.Target); - _Issue ??= new PSRuleIssue(GetContext()); - return _Issue; - } + private IInputCollection GetInput() + { + RequireScope(RunspaceScope.ConventionInitialize); + _Input ??= new PSRuleInput(GetContext()); + return _Input; + } - private IRepositoryRuntimeInfo GetRepository() - { - _Repository ??= new PSRuleRepository(GetContext()); - return _Repository; - } + private ITargetIssueCollection GetIssue() + { + RequireScope(RunspaceScope.Target); + _Issue ??= new PSRuleIssue(GetContext()); + return _Issue; + } - #endregion Helper methods + private IRepositoryRuntimeInfo GetRepository() + { + _Repository ??= new PSRuleRepository(GetContext()); + return _Repository; } + + #endregion Helper methods } diff --git a/src/PSRule/Runtime/PSRuleMemberInfo.cs b/src/PSRule/Runtime/PSRuleMemberInfo.cs index 00b1333f9f..00ac1dfde8 100644 --- a/src/PSRule/Runtime/PSRuleMemberInfo.cs +++ b/src/PSRule/Runtime/PSRuleMemberInfo.cs @@ -7,135 +7,134 @@ using PSRule.Data; using PSRule.Resources; -namespace PSRule.Runtime +namespace PSRule.Runtime; + +[DebuggerDisplay("Path = {Path}, File = {File}")] +internal sealed class PSRuleTargetInfo : PSMemberInfo { - [DebuggerDisplay("Path = {Path}, File = {File}")] - internal sealed class PSRuleTargetInfo : PSMemberInfo + internal const string PropertyName = "_PSRule"; + + public PSRuleTargetInfo() { - internal const string PropertyName = "_PSRule"; + SetMemberName(PropertyName); + Source ??= new List(); + Issue ??= new List(); + } - public PSRuleTargetInfo() - { - SetMemberName(PropertyName); - Source ??= new List(); - Issue ??= new List(); - } + private PSRuleTargetInfo(PSRuleTargetInfo targetInfo) + : this() + { + if (targetInfo == null) + return; + + TargetName = targetInfo.TargetName; + TargetType = targetInfo.TargetType; + Scope = targetInfo.Scope; + Path = targetInfo.Path; + Source = targetInfo.Source; + Issue = targetInfo.Issue; + } - private PSRuleTargetInfo(PSRuleTargetInfo targetInfo) - : this() + public string File + { + get { - if (targetInfo == null) - return; - - TargetName = targetInfo.TargetName; - TargetType = targetInfo.TargetType; - Scope = targetInfo.Scope; - Path = targetInfo.Path; - Source = targetInfo.Source; - Issue = targetInfo.Issue; + return Source.Count == 0 ? null : Source[0].File; } + } - public string File - { - get - { - return Source.Count == 0 ? null : Source[0].File; - } - } + [JsonProperty(PropertyName = "name")] + public string TargetName { get; set; } - [JsonProperty(PropertyName = "name")] - public string TargetName { get; set; } + [JsonProperty(PropertyName = "type")] + public string TargetType { get; set; } - [JsonProperty(PropertyName = "type")] - public string TargetType { get; set; } + [JsonProperty(PropertyName = "scope")] + public string[] Scope { get; set; } - [JsonProperty(PropertyName = "scope")] - public string[] Scope { get; set; } + [JsonProperty(PropertyName = "path")] + public string Path { get; set; } - [JsonProperty(PropertyName = "path")] - public string Path { get; set; } + [JsonProperty(PropertyName = "source")] + public List Source { get; internal set; } - [JsonProperty(PropertyName = "source")] - public List Source { get; internal set; } + [JsonProperty(PropertyName = "issue")] + public List Issue { get; internal set; } - [JsonProperty(PropertyName = "issue")] - public List Issue { get; internal set; } + [JsonIgnore] + public override PSMemberTypes MemberType => PSMemberTypes.PropertySet; - [JsonIgnore] - public override PSMemberTypes MemberType => PSMemberTypes.PropertySet; + [JsonIgnore] + public override string TypeNameOfValue => typeof(PSRuleTargetInfo).FullName; - [JsonIgnore] - public override string TypeNameOfValue => typeof(PSRuleTargetInfo).FullName; + [JsonIgnore] + public override object Value + { + get => this; + set { } + } - [JsonIgnore] - public override object Value - { - get => this; - set { } - } + public override PSMemberInfo Copy() + { + return new PSRuleTargetInfo(this); + } - public override PSMemberInfo Copy() - { - return new PSRuleTargetInfo(this); - } + internal void Combine(PSRuleTargetInfo targetInfo) + { + if (targetInfo == null) + return; + + TargetName = targetInfo.TargetName; + TargetType = targetInfo.TargetType; + Scope = targetInfo.Scope; + Path = targetInfo.Path; + Source.AddUnique(targetInfo?.Source); + Issue.AddUnique(targetInfo?.Issue); + } - internal void Combine(PSRuleTargetInfo targetInfo) - { - if (targetInfo == null) - return; - - TargetName = targetInfo.TargetName; - TargetType = targetInfo.TargetType; - Scope = targetInfo.Scope; - Path = targetInfo.Path; - Source.AddUnique(targetInfo?.Source); - Issue.AddUnique(targetInfo?.Issue); - } + internal void WithSource(TargetSourceInfo source) + { + if (source == null) + return; - internal void WithSource(TargetSourceInfo source) - { - if (source == null) - return; + Source.Add(source); + } - Source.Add(source); - } + internal void WithIssue(TargetIssueInfo issue) + { + if (issue == null) + return; - internal void WithIssue(TargetIssueInfo issue) - { - if (issue == null) - return; + Issue.Add(issue); + } - Issue.Add(issue); - } + internal void UpdateSource(TargetSourceInfo source) + { + if (source == null) + return; - internal void UpdateSource(TargetSourceInfo source) + if (Source.Count == 0) { - if (source == null) - return; + Source.Add(source); + return; + } - if (Source.Count == 0) - { - Source.Add(source); - return; - } + if (Source[0].File == null) + Source[0].File = source.File; + } - if (Source[0].File == null) - Source[0].File = source.File; - } + internal void SetSource(string file, int lineNumber, int linePosition) + { + if (Source.Count > 0) + return; - internal void SetSource(string file, int lineNumber, int linePosition) + var s = new TargetSourceInfo { - if (Source.Count > 0) - return; - - var s = new TargetSourceInfo - { - File = file, - Type = PSRuleResources.FileSourceType, - Line = lineNumber, - Position = linePosition - }; - Source.Add(s); - } + File = file, + Type = PSRuleResources.FileSourceType, + Line = lineNumber, + Position = linePosition + }; + Source.Add(s); } } diff --git a/src/PSRule/Runtime/Rule.cs b/src/PSRule/Runtime/Rule.cs index 1c57d568a4..d59fafa8e3 100644 --- a/src/PSRule/Runtime/Rule.cs +++ b/src/PSRule/Runtime/Rule.cs @@ -1,25 +1,25 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -namespace PSRule.Runtime -{ +namespace PSRule.Runtime; + #pragma warning disable CA1822 // Mark members as static +/// +/// A set of rule properties that are exposed at runtime through the $Rule variable. +/// +public sealed class Rule +{ /// - /// A set of rule properties that are exposed at runtime through the $Rule variable. + /// The name of the currently executing rule. /// - public sealed class Rule - { - /// - /// The name of the currently executing rule. - /// - public string RuleName => RunspaceContext.CurrentThread.RuleRecord.RuleName; + public string RuleName => RunspaceContext.CurrentThread.RuleRecord.RuleName; - /// - /// A unique identifer of the currently executing rule. - /// - public string RuleId => RunspaceContext.CurrentThread.RuleRecord.RuleId; - } + /// + /// A unique identifer of the currently executing rule. + /// + public string RuleId => RunspaceContext.CurrentThread.RuleRecord.RuleId; +} #pragma warning restore CA1822 // Mark members as static -} + diff --git a/src/PSRule/Runtime/RuleConditionResult.cs b/src/PSRule/Runtime/RuleConditionResult.cs index a6baf29000..5e401c9a52 100644 --- a/src/PSRule/Runtime/RuleConditionResult.cs +++ b/src/PSRule/Runtime/RuleConditionResult.cs @@ -3,82 +3,81 @@ using PSRule.Definitions; -namespace PSRule.Runtime +namespace PSRule.Runtime; + +internal static class RuleConditionHelper { - internal static class RuleConditionHelper + private static readonly RuleConditionResult Empty = new(pass: 0, count: 0, hadErrors: false); + + internal static RuleConditionResult Create(IEnumerable value) { - private static readonly RuleConditionResult Empty = new(pass: 0, count: 0, hadErrors: false); + if (value == null) + return Empty; - internal static RuleConditionResult Create(IEnumerable value) + var count = 0; + var pass = 0; + var hasErrors = false; + foreach (var v in value) { - if (value == null) - return Empty; + count++; + if (v == null) + continue; - var count = 0; - var pass = 0; - var hasErrors = false; - foreach (var v in value) + var baseObject = ExpressionHelpers.GetBaseObject(v); + if (!(TryAssertResult(baseObject, out var result) || TryBoolean(baseObject, out result))) { - count++; - if (v == null) - continue; - - var baseObject = ExpressionHelpers.GetBaseObject(v); - if (!(TryAssertResult(baseObject, out var result) || TryBoolean(baseObject, out result))) - { - RunspaceContext.CurrentThread.ErrorInvaildRuleResult(); - hasErrors = true; - } - else if (result) - { - pass++; - } + RunspaceContext.CurrentThread.ErrorInvaildRuleResult(); + hasErrors = true; + } + else if (result) + { + pass++; } - return new RuleConditionResult(pass, count, hasErrors); } + return new RuleConditionResult(pass, count, hasErrors); + } - private static bool TryBoolean(object o, out bool result) - { - result = false; - if (o is not bool bresult) - return false; + private static bool TryBoolean(object o, out bool result) + { + result = false; + if (o is not bool bresult) + return false; - result = bresult; - return true; - } + result = bresult; + return true; + } - private static bool TryAssertResult(object o, out bool result) - { - result = false; - if (o is not AssertResult assert) - return false; + private static bool TryAssertResult(object o, out bool result) + { + result = false; + if (o is not AssertResult assert) + return false; - result = assert.Result; + result = assert.Result; - // Complete results - if (RunspaceContext.CurrentThread.IsScope(RunspaceScope.Rule)) - assert.Complete(); + // Complete results + if (RunspaceContext.CurrentThread.IsScope(RunspaceScope.Rule)) + assert.Complete(); - return true; - } + return true; } +} - internal sealed class RuleConditionResult : IConditionResult +internal sealed class RuleConditionResult : IConditionResult +{ + internal RuleConditionResult(int pass, int count, bool hadErrors) { - internal RuleConditionResult(int pass, int count, bool hadErrors) - { - Pass = pass; - Count = count; - HadErrors = hadErrors; - } + Pass = pass; + Count = count; + HadErrors = hadErrors; + } - /// - public int Pass { get; } + /// + public int Pass { get; } - /// - public int Count { get; } + /// + public int Count { get; } - /// - public bool HadErrors { get; } - } + /// + public bool HadErrors { get; } } diff --git a/src/PSRule/Runtime/RunspaceContext.cs b/src/PSRule/Runtime/RunspaceContext.cs index 3c9d81c23f..56951d56ef 100644 --- a/src/PSRule/Runtime/RunspaceContext.cs +++ b/src/PSRule/Runtime/RunspaceContext.cs @@ -12,939 +12,938 @@ using PSRule.Rules; using static PSRule.Pipeline.PipelineContext; -namespace PSRule.Runtime +namespace PSRule.Runtime; + +/// +/// The available language scope types. +/// +[Flags] +internal enum RunspaceScope { + None = 0, + + Source = 1, + /// - /// The available language scope types. + /// Executing a rule. /// - [Flags] - internal enum RunspaceScope - { - None = 0, + Rule = 2, - Source = 1, + /// + /// Executing a rule precondition. + /// + Precondition = 4, - /// - /// Executing a rule. - /// - Rule = 2, + /// + /// Execution is currently parsing YAML objects. + /// + Resource = 8, - /// - /// Executing a rule precondition. - /// - Precondition = 4, + ConventionBegin = 16, + ConventionProcess = 32, + ConventionEnd = 64, + ConventionInitialize = 128, - /// - /// Execution is currently parsing YAML objects. - /// - Resource = 8, + Convention = ConventionInitialize | ConventionBegin | ConventionProcess | ConventionEnd, + Target = Rule | Precondition | ConventionBegin | ConventionProcess, + Runtime = Rule | Precondition | Convention, + All = Source | Rule | Precondition | Resource | Convention, +} - ConventionBegin = 16, - ConventionProcess = 32, - ConventionEnd = 64, - ConventionInitialize = 128, +/// +/// A context for a PSRule runspace. +/// +internal sealed class RunspaceContext : IDisposable, ILogger +{ + private const string SOURCE_OUTCOME_FAIL = "Rule.Outcome.Fail"; + private const string SOURCE_OUTCOME_PASS = "Rule.Outcome.Pass"; + private const string ERRORID_INVALIDRULERESULT = "PSRule.Runtime.InvalidRuleResult"; + private const string WARN_KEY_SEPARATOR = "_"; - Convention = ConventionInitialize | ConventionBegin | ConventionProcess | ConventionEnd, - Target = Rule | Precondition | ConventionBegin | ConventionProcess, - Runtime = Rule | Precondition | Convention, - All = Source | Rule | Precondition | Resource | Convention, - } + [ThreadStatic] + internal static RunspaceContext CurrentThread; + + internal readonly PipelineContext Pipeline; + internal readonly IPipelineWriter Writer; + + // Fields exposed to engine + internal RuleRecord RuleRecord; + internal RuleBlock RuleBlock; + internal ITargetBindingResult Binding; + + private readonly ExecutionActionPreference _RuleInconclusive; + private readonly ExecutionActionPreference _UnprocessedObject; + private readonly ExecutionActionPreference _RuleSuppressed; + private readonly ExecutionActionPreference _InvariantCulture; + private readonly OutcomeLogStream _FailStream; + private readonly OutcomeLogStream _PassStream; /// - /// A context for a PSRule runspace. + /// Track the current runspace scope. /// - internal sealed class RunspaceContext : IDisposable, ILogger - { - private const string SOURCE_OUTCOME_FAIL = "Rule.Outcome.Fail"; - private const string SOURCE_OUTCOME_PASS = "Rule.Outcome.Pass"; - private const string ERRORID_INVALIDRULERESULT = "PSRule.Runtime.InvalidRuleResult"; - private const string WARN_KEY_SEPARATOR = "_"; + private readonly Stack _Scope; - [ThreadStatic] - internal static RunspaceContext CurrentThread; + /// + /// Track common warnings, to only raise once. + /// + private readonly HashSet _WarnOnce; - internal readonly PipelineContext Pipeline; - internal readonly IPipelineWriter Writer; + private bool _RaisedUsingInvariantCulture; - // Fields exposed to engine - internal RuleRecord RuleRecord; - internal RuleBlock RuleBlock; - internal ITargetBindingResult Binding; + // Pipeline logging + private string _LogPrefix; + private int _ObjectNumber; + private int _RuleErrors; - private readonly ExecutionActionPreference _RuleInconclusive; - private readonly ExecutionActionPreference _UnprocessedObject; - private readonly ExecutionActionPreference _RuleSuppressed; - private readonly ExecutionActionPreference _InvariantCulture; - private readonly OutcomeLogStream _FailStream; - private readonly OutcomeLogStream _PassStream; + private readonly Stopwatch _RuleTimer; + private readonly List _Reason; + private readonly List _Conventions; - /// - /// Track the current runspace scope. - /// - private readonly Stack _Scope; + /// + /// A collection of languages scopes for this pipeline. + /// + private readonly LanguageScopeSet _LanguageScopes; + + // Track whether Dispose has been called. + private bool _Disposed; - /// - /// Track common warnings, to only raise once. - /// - private readonly HashSet _WarnOnce; + internal RunspaceContext(PipelineContext pipeline, IPipelineWriter writer) + { + Writer = writer; + CurrentThread = this; + Pipeline = pipeline; + + _RuleInconclusive = Pipeline.Option.Execution.RuleInconclusive.GetValueOrDefault(ExecutionOption.Default.RuleInconclusive.Value); + _UnprocessedObject = Pipeline.Option.Execution.UnprocessedObject.GetValueOrDefault(ExecutionOption.Default.UnprocessedObject.Value); + _RuleSuppressed = Pipeline.Option.Execution.RuleSuppressed.GetValueOrDefault(ExecutionOption.Default.RuleSuppressed.Value); + _InvariantCulture = Pipeline.Option.Execution.InvariantCulture.GetValueOrDefault(ExecutionOption.Default.InvariantCulture.Value); + + _FailStream = Pipeline.Option.Logging.RuleFail ?? LoggingOption.Default.RuleFail.Value; + _PassStream = Pipeline.Option.Logging.RulePass ?? LoggingOption.Default.RulePass.Value; + _WarnOnce = new HashSet(); + + _ObjectNumber = -1; + _RuleTimer = new Stopwatch(); + _Reason = new List(); + _Conventions = new List(); + _LanguageScopes = new LanguageScopeSet(this); + _Scope = new Stack(); + } - private bool _RaisedUsingInvariantCulture; + internal bool HadErrors => _RuleErrors > 0; - // Pipeline logging - private string _LogPrefix; - private int _ObjectNumber; - private int _RuleErrors; + internal IEnumerable Output { get; private set; } - private readonly Stopwatch _RuleTimer; - private readonly List _Reason; - private readonly List _Conventions; + internal TargetObject TargetObject { get; private set; } - /// - /// A collection of languages scopes for this pipeline. - /// - private readonly LanguageScopeSet _LanguageScopes; + internal ITargetBinder TargetBinder { get; private set; } - // Track whether Dispose has been called. - private bool _Disposed; + internal SourceScope Source { get; private set; } - internal RunspaceContext(PipelineContext pipeline, IPipelineWriter writer) + internal ILanguageScope LanguageScope + { + [DebuggerStepThrough] + get { - Writer = writer; - CurrentThread = this; - Pipeline = pipeline; - - _RuleInconclusive = Pipeline.Option.Execution.RuleInconclusive.GetValueOrDefault(ExecutionOption.Default.RuleInconclusive.Value); - _UnprocessedObject = Pipeline.Option.Execution.UnprocessedObject.GetValueOrDefault(ExecutionOption.Default.UnprocessedObject.Value); - _RuleSuppressed = Pipeline.Option.Execution.RuleSuppressed.GetValueOrDefault(ExecutionOption.Default.RuleSuppressed.Value); - _InvariantCulture = Pipeline.Option.Execution.InvariantCulture.GetValueOrDefault(ExecutionOption.Default.InvariantCulture.Value); - - _FailStream = Pipeline.Option.Logging.RuleFail ?? LoggingOption.Default.RuleFail.Value; - _PassStream = Pipeline.Option.Logging.RulePass ?? LoggingOption.Default.RulePass.Value; - _WarnOnce = new HashSet(); - - _ObjectNumber = -1; - _RuleTimer = new Stopwatch(); - _Reason = new List(); - _Conventions = new List(); - _LanguageScopes = new LanguageScopeSet(this); - _Scope = new Stack(); + return _LanguageScopes.Current; } + } - internal bool HadErrors => _RuleErrors > 0; - - internal IEnumerable Output { get; private set; } + internal bool IsScope(RunspaceScope scope) + { + if (scope == RunspaceScope.None && (_Scope == null || _Scope.Count == 0)) + return true; - internal TargetObject TargetObject { get; private set; } + if (_Scope == null || _Scope.Count == 0) + return false; - internal ITargetBinder TargetBinder { get; private set; } + var current = _Scope.Peek(); + return scope.HasFlag(current); + } - internal SourceScope Source { get; private set; } + internal void PushScope(RunspaceScope scope) + { + _Scope.Push(scope); + } - internal ILanguageScope LanguageScope - { - [DebuggerStepThrough] - get - { - return _LanguageScopes.Current; - } - } + internal void PopScope(RunspaceScope scope) + { + var current = _Scope.Peek(); + if (current != scope) + throw new RuntimeScopeException(); - internal bool IsScope(RunspaceScope scope) - { - if (scope == RunspaceScope.None && (_Scope == null || _Scope.Count == 0)) - return true; + _Scope.Pop(); + } - if (_Scope == null || _Scope.Count == 0) - return false; + public void Pass() + { + if (Writer == null || _PassStream == OutcomeLogStream.None) + return; - var current = _Scope.Peek(); - return scope.HasFlag(current); - } + if (_PassStream == OutcomeLogStream.Warning && Writer.ShouldWriteWarning()) + Writer.WriteWarning(PSRuleResources.OutcomeRulePass, RuleRecord.RuleName, Binding.TargetName); - internal void PushScope(RunspaceScope scope) - { - _Scope.Push(scope); - } + if (_PassStream == OutcomeLogStream.Error && Writer.ShouldWriteError()) + Writer.WriteError(new ErrorRecord( + new RuleException(string.Format( + Thread.CurrentThread.CurrentCulture, + PSRuleResources.OutcomeRulePass, + RuleRecord.RuleName, + Binding.TargetName)), + SOURCE_OUTCOME_PASS, + ErrorCategory.InvalidData, + null)); + + if (_PassStream == OutcomeLogStream.Information && Writer.ShouldWriteInformation()) + Writer.WriteInformation(new InformationRecord( + messageData: string.Format( + Thread.CurrentThread.CurrentCulture, + PSRuleResources.OutcomeRulePass, + RuleRecord.RuleName, + Binding.TargetName), + source: SOURCE_OUTCOME_PASS)); + } - internal void PopScope(RunspaceScope scope) - { - var current = _Scope.Peek(); - if (current != scope) - throw new RuntimeScopeException(); + public void Fail() + { + if (Writer == null || _FailStream == OutcomeLogStream.None) + return; - _Scope.Pop(); - } + if (_FailStream == OutcomeLogStream.Warning && Writer.ShouldWriteWarning()) + Writer.WriteWarning(PSRuleResources.OutcomeRuleFail, RuleRecord.RuleName, Binding.TargetName); - public void Pass() - { - if (Writer == null || _PassStream == OutcomeLogStream.None) - return; - - if (_PassStream == OutcomeLogStream.Warning && Writer.ShouldWriteWarning()) - Writer.WriteWarning(PSRuleResources.OutcomeRulePass, RuleRecord.RuleName, Binding.TargetName); - - if (_PassStream == OutcomeLogStream.Error && Writer.ShouldWriteError()) - Writer.WriteError(new ErrorRecord( - new RuleException(string.Format( - Thread.CurrentThread.CurrentCulture, - PSRuleResources.OutcomeRulePass, - RuleRecord.RuleName, - Binding.TargetName)), - SOURCE_OUTCOME_PASS, - ErrorCategory.InvalidData, - null)); - - if (_PassStream == OutcomeLogStream.Information && Writer.ShouldWriteInformation()) - Writer.WriteInformation(new InformationRecord( - messageData: string.Format( - Thread.CurrentThread.CurrentCulture, - PSRuleResources.OutcomeRulePass, - RuleRecord.RuleName, - Binding.TargetName), - source: SOURCE_OUTCOME_PASS)); - } + if (_FailStream == OutcomeLogStream.Error && Writer.ShouldWriteError()) + Writer.WriteError(new ErrorRecord( + new RuleException(string.Format( + Thread.CurrentThread.CurrentCulture, + PSRuleResources.OutcomeRuleFail, + RuleRecord.RuleName, + Binding.TargetName)), + SOURCE_OUTCOME_FAIL, + ErrorCategory.InvalidData, + null)); + + if (_FailStream == OutcomeLogStream.Information && Writer.ShouldWriteInformation()) + Writer.WriteInformation(new InformationRecord( + messageData: string.Format( + Thread.CurrentThread.CurrentCulture, + PSRuleResources.OutcomeRuleFail, + RuleRecord.RuleName, + Binding.TargetName), + source: SOURCE_OUTCOME_FAIL)); + } - public void Fail() - { - if (Writer == null || _FailStream == OutcomeLogStream.None) - return; - - if (_FailStream == OutcomeLogStream.Warning && Writer.ShouldWriteWarning()) - Writer.WriteWarning(PSRuleResources.OutcomeRuleFail, RuleRecord.RuleName, Binding.TargetName); - - if (_FailStream == OutcomeLogStream.Error && Writer.ShouldWriteError()) - Writer.WriteError(new ErrorRecord( - new RuleException(string.Format( - Thread.CurrentThread.CurrentCulture, - PSRuleResources.OutcomeRuleFail, - RuleRecord.RuleName, - Binding.TargetName)), - SOURCE_OUTCOME_FAIL, - ErrorCategory.InvalidData, - null)); - - if (_FailStream == OutcomeLogStream.Information && Writer.ShouldWriteInformation()) - Writer.WriteInformation(new InformationRecord( - messageData: string.Format( - Thread.CurrentThread.CurrentCulture, - PSRuleResources.OutcomeRuleFail, - RuleRecord.RuleName, - Binding.TargetName), - source: SOURCE_OUTCOME_FAIL)); - } + public void WarnRuleInconclusive(string ruleId) + { + this.Throw(_RuleInconclusive, PSRuleResources.RuleInconclusive, ruleId, Binding.TargetName); + } - public void WarnRuleInconclusive(string ruleId) - { - this.Throw(_RuleInconclusive, PSRuleResources.RuleInconclusive, ruleId, Binding.TargetName); - } + public void WarnObjectNotProcessed() + { + this.Throw(_UnprocessedObject, PSRuleResources.ObjectNotProcessed, Binding.TargetName); + } - public void WarnObjectNotProcessed() - { - this.Throw(_UnprocessedObject, PSRuleResources.ObjectNotProcessed, Binding.TargetName); - } + public void RuleSuppressed(string ruleId) + { + this.Throw(_RuleSuppressed, PSRuleResources.RuleSuppressed, ruleId, Binding.TargetName); + } - public void RuleSuppressed(string ruleId) - { - this.Throw(_RuleSuppressed, PSRuleResources.RuleSuppressed, ruleId, Binding.TargetName); - } + public void WarnRuleCountSuppressed(int ruleCount) + { + this.Throw(_RuleSuppressed, PSRuleResources.RuleCountSuppressed, ruleCount, Binding.TargetName); + } - public void WarnRuleCountSuppressed(int ruleCount) - { - this.Throw(_RuleSuppressed, PSRuleResources.RuleCountSuppressed, ruleCount, Binding.TargetName); - } + public void RuleSuppressionGroup(string ruleId, ISuppressionInfo suppression) + { + if (suppression == null) + return; - public void RuleSuppressionGroup(string ruleId, ISuppressionInfo suppression) - { - if (suppression == null) - return; + if (suppression.Synopsis != null && suppression.Synopsis.HasValue) + this.Throw(_RuleSuppressed, PSRuleResources.RuleSuppressionGroupExtended, ruleId, suppression.Id, Binding.TargetName, suppression.Synopsis.Text); + else + this.Throw(_RuleSuppressed, PSRuleResources.RuleSuppressionGroup, ruleId, suppression.Id, Binding.TargetName); + } - if (suppression.Synopsis != null && suppression.Synopsis.HasValue) - this.Throw(_RuleSuppressed, PSRuleResources.RuleSuppressionGroupExtended, ruleId, suppression.Id, Binding.TargetName, suppression.Synopsis.Text); - else - this.Throw(_RuleSuppressed, PSRuleResources.RuleSuppressionGroup, ruleId, suppression.Id, Binding.TargetName); - } + public void RuleSuppressionGroupCount(ISuppressionInfo suppression, int count) + { + if (suppression == null) + return; - public void RuleSuppressionGroupCount(ISuppressionInfo suppression, int count) - { - if (suppression == null) - return; + if (suppression.Synopsis != null && suppression.Synopsis.HasValue) + this.Throw(_RuleSuppressed, PSRuleResources.RuleSuppressionGroupExtendedCount, count, suppression.Id, Binding.TargetName, suppression.Synopsis.Text); + else + this.Throw(_RuleSuppressed, PSRuleResources.RuleSuppressionGroupCount, count, suppression.Id, Binding.TargetName); + } - if (suppression.Synopsis != null && suppression.Synopsis.HasValue) - this.Throw(_RuleSuppressed, PSRuleResources.RuleSuppressionGroupExtendedCount, count, suppression.Id, Binding.TargetName, suppression.Synopsis.Text); - else - this.Throw(_RuleSuppressed, PSRuleResources.RuleSuppressionGroupCount, count, suppression.Id, Binding.TargetName); - } + public void ErrorInvaildRuleResult() + { + if (Writer == null || !Writer.ShouldWriteError()) + return; + + Writer.WriteError(new ErrorRecord( + exception: new RuleException(message: string.Format( + Thread.CurrentThread.CurrentCulture, + PSRuleResources.InvalidRuleResult, + RuleBlock.Id + )), + errorId: ERRORID_INVALIDRULERESULT, + errorCategory: ErrorCategory.InvalidResult, + targetObject: null + )); + } - public void ErrorInvaildRuleResult() - { - if (Writer == null || !Writer.ShouldWriteError()) - return; + public void VerboseRuleDiscovery(string path) + { + if (Writer == null || !Writer.ShouldWriteVerbose() || string.IsNullOrEmpty(path)) + return; - Writer.WriteError(new ErrorRecord( - exception: new RuleException(message: string.Format( - Thread.CurrentThread.CurrentCulture, - PSRuleResources.InvalidRuleResult, - RuleBlock.Id - )), - errorId: ERRORID_INVALIDRULERESULT, - errorCategory: ErrorCategory.InvalidResult, - targetObject: null - )); - } + Writer.WriteVerbose($"[PSRule][D] -- Discovering rules in: {path}"); + } - public void VerboseRuleDiscovery(string path) - { - if (Writer == null || !Writer.ShouldWriteVerbose() || string.IsNullOrEmpty(path)) - return; + public void VerboseFoundResource(string name, string moduleName, string scriptName) + { + if (Writer == null || !Writer.ShouldWriteVerbose()) + return; - Writer.WriteVerbose($"[PSRule][D] -- Discovering rules in: {path}"); - } + var m = string.IsNullOrEmpty(moduleName) ? "." : moduleName; + Writer.WriteVerbose($"[PSRule][D] -- Found {m}\\{name} in {scriptName}"); + } - public void VerboseFoundResource(string name, string moduleName, string scriptName) - { - if (Writer == null || !Writer.ShouldWriteVerbose()) - return; + public void VerboseObjectStart() + { + if (Writer == null || !Writer.ShouldWriteVerbose()) + return; - var m = string.IsNullOrEmpty(moduleName) ? "." : moduleName; - Writer.WriteVerbose($"[PSRule][D] -- Found {m}\\{name} in {scriptName}"); - } + Writer.WriteVerbose(string.Concat(GetLogPrefix(), " :: ", Binding.TargetName)); + } - public void VerboseObjectStart() - { - if (Writer == null || !Writer.ShouldWriteVerbose()) - return; + public void VerboseConditionMessage(string condition, string message, params object[] args) + { + if (Writer == null || !Writer.ShouldWriteVerbose()) + return; + + Writer.WriteVerbose(string.Concat( + GetLogPrefix(), + "[", + condition, + "] -- ", + string.Format(Thread.CurrentThread.CurrentCulture, message, args))); + } - Writer.WriteVerbose(string.Concat(GetLogPrefix(), " :: ", Binding.TargetName)); - } + public void VerboseConditionResult(string condition, int pass, int count, bool outcome) + { + if (Writer == null || !Writer.ShouldWriteVerbose()) + return; - public void VerboseConditionMessage(string condition, string message, params object[] args) - { - if (Writer == null || !Writer.ShouldWriteVerbose()) - return; - - Writer.WriteVerbose(string.Concat( - GetLogPrefix(), - "[", - condition, - "] -- ", - string.Format(Thread.CurrentThread.CurrentCulture, message, args))); - } + Writer.WriteVerbose(string.Concat(GetLogPrefix(), "[", condition, "] -- [", pass, "/", count, "] [", outcome, "]")); + } - public void VerboseConditionResult(string condition, int pass, int count, bool outcome) - { - if (Writer == null || !Writer.ShouldWriteVerbose()) - return; + public void VerboseConditionResult(string condition, bool outcome) + { + if (Writer == null || !Writer.ShouldWriteVerbose()) + return; - Writer.WriteVerbose(string.Concat(GetLogPrefix(), "[", condition, "] -- [", pass, "/", count, "] [", outcome, "]")); - } + Writer.WriteVerbose(string.Concat(GetLogPrefix(), "[", condition, "] -- [", outcome, "]")); + } - public void VerboseConditionResult(string condition, bool outcome) - { - if (Writer == null || !Writer.ShouldWriteVerbose()) - return; + public void VerboseConditionResult(int pass, int count, RuleOutcome outcome) + { + if (Writer == null || !Writer.ShouldWriteVerbose()) + return; - Writer.WriteVerbose(string.Concat(GetLogPrefix(), "[", condition, "] -- [", outcome, "]")); - } + Writer.WriteVerbose(string.Concat(GetLogPrefix(), " -- [", pass, "/", count, "] [", outcome, "]")); + } - public void VerboseConditionResult(int pass, int count, RuleOutcome outcome) - { - if (Writer == null || !Writer.ShouldWriteVerbose()) - return; + public void WriteError(ErrorRecord record) + { + if (Writer == null || !Writer.ShouldWriteError()) + return; - Writer.WriteVerbose(string.Concat(GetLogPrefix(), " -- [", pass, "/", count, "] [", outcome, "]")); - } + Writer.WriteError(errorRecord: record); + } - public void WriteError(ErrorRecord record) - { - if (Writer == null || !Writer.ShouldWriteError()) - return; + public void WriteError(ParseError error) + { + if (Writer == null || !Writer.ShouldWriteError()) + return; + + var record = new ErrorRecord + ( + exception: new Pipeline.ParseException(message: error.Message, errorId: error.ErrorId), + errorId: error.ErrorId, + errorCategory: ErrorCategory.InvalidOperation, + targetObject: null + ); + Writer.WriteError(errorRecord: record); + } - Writer.WriteError(errorRecord: record); - } + internal PowerShell GetPowerShell() + { + var result = PowerShell.Create(); + result.Runspace = Pipeline.GetRunspace(); + EnableLogging(result); + return result; + } - public void WriteError(ParseError error) - { - if (Writer == null || !Writer.ShouldWriteError()) - return; - - var record = new ErrorRecord - ( - exception: new Pipeline.ParseException(message: error.Message, errorId: error.ErrorId), - errorId: error.ErrorId, - errorCategory: ErrorCategory.InvalidOperation, - targetObject: null - ); - Writer.WriteError(errorRecord: record); - } + private static void EnableLogging(PowerShell ps) + { + ps.Streams.Error.DataAdded += Error_DataAdded; + ps.Streams.Warning.DataAdded += Warning_DataAdded; + ps.Streams.Verbose.DataAdded += Verbose_DataAdded; + ps.Streams.Information.DataAdded += Information_DataAdded; + ps.Streams.Debug.DataAdded += Debug_DataAdded; + } - internal PowerShell GetPowerShell() - { - var result = PowerShell.Create(); - result.Runspace = Pipeline.GetRunspace(); - EnableLogging(result); - return result; - } + private static void Debug_DataAdded(object sender, DataAddedEventArgs e) + { + if (CurrentThread.Writer == null) + return; - private static void EnableLogging(PowerShell ps) - { - ps.Streams.Error.DataAdded += Error_DataAdded; - ps.Streams.Warning.DataAdded += Warning_DataAdded; - ps.Streams.Verbose.DataAdded += Verbose_DataAdded; - ps.Streams.Information.DataAdded += Information_DataAdded; - ps.Streams.Debug.DataAdded += Debug_DataAdded; - } + var collection = sender as PSDataCollection; + var record = collection[e.Index]; + CurrentThread.Writer.WriteDebug(debugRecord: record); + } - private static void Debug_DataAdded(object sender, DataAddedEventArgs e) - { - if (CurrentThread.Writer == null) - return; + private static void Information_DataAdded(object sender, DataAddedEventArgs e) + { + if (CurrentThread.Writer == null) + return; - var collection = sender as PSDataCollection; - var record = collection[e.Index]; - CurrentThread.Writer.WriteDebug(debugRecord: record); - } + var collection = sender as PSDataCollection; + var record = collection[e.Index]; + CurrentThread.Writer.WriteInformation(informationRecord: record); + } - private static void Information_DataAdded(object sender, DataAddedEventArgs e) - { - if (CurrentThread.Writer == null) - return; + private static void Verbose_DataAdded(object sender, DataAddedEventArgs e) + { + if (CurrentThread.Writer == null) + return; - var collection = sender as PSDataCollection; - var record = collection[e.Index]; - CurrentThread.Writer.WriteInformation(informationRecord: record); - } + var collection = sender as PSDataCollection; + var record = collection[e.Index]; + CurrentThread.Writer.WriteVerbose(record.Message); + } - private static void Verbose_DataAdded(object sender, DataAddedEventArgs e) - { - if (CurrentThread.Writer == null) - return; + private static void Warning_DataAdded(object sender, DataAddedEventArgs e) + { + if (CurrentThread.Writer == null) + return; - var collection = sender as PSDataCollection; - var record = collection[e.Index]; - CurrentThread.Writer.WriteVerbose(record.Message); - } + var collection = sender as PSDataCollection; + var record = collection[e.Index]; + CurrentThread.Writer.WriteWarning(message: record.Message); + } - private static void Warning_DataAdded(object sender, DataAddedEventArgs e) - { - if (CurrentThread.Writer == null) - return; + private static void Error_DataAdded(object sender, DataAddedEventArgs e) + { + CurrentThread._RuleErrors++; + if (CurrentThread.Writer == null) + return; - var collection = sender as PSDataCollection; - var record = collection[e.Index]; - CurrentThread.Writer.WriteWarning(message: record.Message); - } + var collection = sender as PSDataCollection; + var record = collection[e.Index]; + CurrentThread.Error(record); + } - private static void Error_DataAdded(object sender, DataAddedEventArgs e) - { - CurrentThread._RuleErrors++; - if (CurrentThread.Writer == null) - return; + public void Error(ActionPreferenceStopException ex) + { + if (ex == null) + return; - var collection = sender as PSDataCollection; - var record = collection[e.Index]; - CurrentThread.Error(record); - } + Error(ex.ErrorRecord); + } - public void Error(ActionPreferenceStopException ex) - { - if (ex == null) - return; + public void Error(Exception ex) + { + if (ex == null) + return; + + var errorRecord = ex is IContainsErrorRecord error ? error.ErrorRecord : null; + var scriptStackTrace = errorRecord != null ? GetStackTrace(errorRecord) : null; + var category = errorRecord != null ? errorRecord.CategoryInfo.Category : ErrorCategory.NotSpecified; + var errorId = errorRecord != null ? GetErrorId(errorRecord) : null; + if (RuleRecord == null) + { + Writer.WriteError(errorRecord); + return; + } + RuleRecord.Outcome = RuleOutcome.Error; + RuleRecord.Error = new ErrorInfo( + message: ex.Message, + scriptStackTrace: scriptStackTrace, + errorId: errorId, + exception: ex, + category: category, + positionMessage: GetPositionMessage(errorRecord), + scriptExtent: GetErrorScriptExtent(errorRecord) + ); + } - Error(ex.ErrorRecord); - } + public void Error(ErrorRecord error) + { + if (RuleRecord == null) + { + Writer.WriteError(error); + return; + } + RuleRecord.Outcome = RuleOutcome.Error; + RuleRecord.Error = new ErrorInfo( + message: error.Exception?.Message, + scriptStackTrace: GetStackTrace(error), + errorId: GetErrorId(error), + exception: error.Exception, + category: error.CategoryInfo.Category, + positionMessage: GetPositionMessage(error), + scriptExtent: GetErrorScriptExtent(error) + ); + } - public void Error(Exception ex) - { - if (ex == null) - return; - - var errorRecord = ex is IContainsErrorRecord error ? error.ErrorRecord : null; - var scriptStackTrace = errorRecord != null ? GetStackTrace(errorRecord) : null; - var category = errorRecord != null ? errorRecord.CategoryInfo.Category : ErrorCategory.NotSpecified; - var errorId = errorRecord != null ? GetErrorId(errorRecord) : null; - if (RuleRecord == null) - { - Writer.WriteError(errorRecord); - return; - } - RuleRecord.Outcome = RuleOutcome.Error; - RuleRecord.Error = new ErrorInfo( - message: ex.Message, - scriptStackTrace: scriptStackTrace, - errorId: errorId, - exception: ex, - category: category, - positionMessage: GetPositionMessage(errorRecord), - scriptExtent: GetErrorScriptExtent(errorRecord) + private string GetStackTrace(ErrorRecord record) + { + return RuleBlock == null + ? record.ScriptStackTrace + : string.Concat( + record.ScriptStackTrace, + System.Environment.NewLine, + string.Format( + Thread.CurrentThread.CurrentCulture, + PSRuleResources.RuleStackTrace, + RuleBlock.Name, + RuleBlock.Extent.File, + RuleBlock.Extent.Line) ); - } + } - public void Error(ErrorRecord error) - { - if (RuleRecord == null) - { - Writer.WriteError(error); - return; - } - RuleRecord.Outcome = RuleOutcome.Error; - RuleRecord.Error = new ErrorInfo( - message: error.Exception?.Message, - scriptStackTrace: GetStackTrace(error), - errorId: GetErrorId(error), - exception: error.Exception, - category: error.CategoryInfo.Category, - positionMessage: GetPositionMessage(error), - scriptExtent: GetErrorScriptExtent(error) + private string GetErrorId(ErrorRecord record) + { + return RuleBlock == null + ? record.FullyQualifiedErrorId + : string.Concat( + record.FullyQualifiedErrorId, + ",", + RuleBlock.Name ); - } - - private string GetStackTrace(ErrorRecord record) - { - return RuleBlock == null - ? record.ScriptStackTrace - : string.Concat( - record.ScriptStackTrace, - System.Environment.NewLine, - string.Format( - Thread.CurrentThread.CurrentCulture, - PSRuleResources.RuleStackTrace, - RuleBlock.Name, - RuleBlock.Extent.File, - RuleBlock.Extent.Line) - ); - } - - private string GetErrorId(ErrorRecord record) - { - return RuleBlock == null - ? record.FullyQualifiedErrorId - : string.Concat( - record.FullyQualifiedErrorId, - ",", - RuleBlock.Name - ); - } + } - private static string GetPositionMessage(ErrorRecord errorRecord) - { - return errorRecord?.InvocationInfo?.PositionMessage; - } + private static string GetPositionMessage(ErrorRecord errorRecord) + { + return errorRecord?.InvocationInfo?.PositionMessage; + } - private static IScriptExtent GetErrorScriptExtent(ErrorRecord errorRecord) - { - if (errorRecord == null) - return null; - - var startPos = new ScriptPosition( - errorRecord.InvocationInfo.ScriptName, - errorRecord.InvocationInfo.ScriptLineNumber, - errorRecord.InvocationInfo.OffsetInLine, - errorRecord.InvocationInfo.Line - ); - var endPos = new ScriptPosition( - errorRecord.InvocationInfo.ScriptName, - errorRecord.InvocationInfo.ScriptLineNumber, - GetPositionMessageOffset(errorRecord.InvocationInfo.PositionMessage), - errorRecord.InvocationInfo.Line - ); - return new ScriptExtent(startPos, endPos); - } + private static IScriptExtent GetErrorScriptExtent(ErrorRecord errorRecord) + { + if (errorRecord == null) + return null; - private static int GetPositionMessageOffset(string positionMessage) - { - if (string.IsNullOrEmpty(positionMessage)) - return 0; + var startPos = new ScriptPosition( + errorRecord.InvocationInfo.ScriptName, + errorRecord.InvocationInfo.ScriptLineNumber, + errorRecord.InvocationInfo.OffsetInLine, + errorRecord.InvocationInfo.Line + ); + var endPos = new ScriptPosition( + errorRecord.InvocationInfo.ScriptName, + errorRecord.InvocationInfo.ScriptLineNumber, + GetPositionMessageOffset(errorRecord.InvocationInfo.PositionMessage), + errorRecord.InvocationInfo.Line + ); + return new ScriptExtent(startPos, endPos); + } - var lines = positionMessage.Split(new string[] { System.Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries); - return lines.Length != 3 ? 0 : lines[2].LastIndexOf('~') - 1; - } + private static int GetPositionMessageOffset(string positionMessage) + { + if (string.IsNullOrEmpty(positionMessage)) + return 0; - private string GetLogPrefix() - { - _LogPrefix ??= $"[PSRule][R][{_ObjectNumber}][{RuleRecord?.RuleId}]"; - return _LogPrefix ?? string.Empty; - } + var lines = positionMessage.Split(new string[] { System.Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries); + return lines.Length != 3 ? 0 : lines[2].LastIndexOf('~') - 1; + } - internal void EnterLanguageScope(SourceFile file) - { - // TODO: Look at scope caching, and a scope stack. + private string GetLogPrefix() + { + _LogPrefix ??= $"[PSRule][R][{_ObjectNumber}][{RuleRecord?.RuleId}]"; + return _LogPrefix ?? string.Empty; + } - if (Source != null && Source.File == file) - return; + internal void EnterLanguageScope(SourceFile file) + { + // TODO: Look at scope caching, and a scope stack. - if (!file.Exists()) - throw new FileNotFoundException(PSRuleResources.ScriptNotFound, file.Path); + if (Source != null && Source.File == file) + return; - _LanguageScopes.UseScope(file.Module); + if (!file.Exists()) + throw new FileNotFoundException(PSRuleResources.ScriptNotFound, file.Path); - // Change scope - //Pipeline.Baseline.ResetScope(moduleName: source.Module); - Source = new SourceScope(file); - } + _LanguageScopes.UseScope(file.Module); - internal void ExitLanguageScope(SourceFile file) - { - // Look at scope poping and validation. + // Change scope + //Pipeline.Baseline.ResetScope(moduleName: source.Module); + Source = new SourceScope(file); + } - Source = null; - } + internal void ExitLanguageScope(SourceFile file) + { + // Look at scope poping and validation. - /// - /// Increment the pipeline object number. - /// - internal void EnterTargetObject(TargetObject targetObject) - { - _ObjectNumber++; - TargetObject = targetObject; - TargetBinder.Bind(TargetObject); - if (Pipeline.ContentCache.Count > 0) - Pipeline.ContentCache.Clear(); - - // Run conventions - RunConventionBegin(); - } + Source = null; + } - public void ExitTargetObject() - { - RunConventionProcess(); - TargetObject = null; - } + /// + /// Increment the pipeline object number. + /// + internal void EnterTargetObject(TargetObject targetObject) + { + _ObjectNumber++; + TargetObject = targetObject; + TargetBinder.Bind(TargetObject); + if (Pipeline.ContentCache.Count > 0) + Pipeline.ContentCache.Clear(); + + // Run conventions + RunConventionBegin(); + } - public bool TrySelector(string name) - { - return TrySelector(ResourceHelper.GetRuleId(Source.File.Module, name, ResourceIdKind.Unknown)); - } + public void ExitTargetObject() + { + RunConventionProcess(); + TargetObject = null; + } - public bool TrySelector(ResourceId id) - { - if (TargetObject == null || Pipeline == null || !Pipeline.Selector.TryGetValue(id.Value, out var selector)) - return false; + public bool TrySelector(string name) + { + return TrySelector(ResourceHelper.GetRuleId(Source.File.Module, name, ResourceIdKind.Unknown)); + } - var annotation = TargetObject.GetAnnotation(); - if (annotation.TryGetSelectorResult(selector, out var result)) - return result; + public bool TrySelector(ResourceId id) + { + if (TargetObject == null || Pipeline == null || !Pipeline.Selector.TryGetValue(id.Value, out var selector)) + return false; - result = selector.Match(TargetObject.Value); - annotation.SetSelectorResult(selector, result); + var annotation = TargetObject.GetAnnotation(); + if (annotation.TryGetSelectorResult(selector, out var result)) return result; - } - /// - /// Enter the rule block scope. - /// - public RuleRecord EnterRuleBlock(RuleBlock ruleBlock) - { - Binding = TargetBinder.Result(ruleBlock.Info.ModuleName); - - _RuleErrors = 0; - RuleBlock = ruleBlock; - RuleRecord = new RuleRecord( - runId: Pipeline.RunId, - ruleId: ruleBlock.Id, - @ref: ruleBlock.Ref.GetValueOrDefault().Name, - targetObject: TargetObject, - targetName: Binding.TargetName, - targetType: Binding.TargetType, - tag: ruleBlock.Tag, - info: ruleBlock.Info, - field: Binding.Field, - level: ruleBlock.Level, - extent: ruleBlock.Extent - ); - - Writer?.EnterScope(ruleBlock.Name); + result = selector.Match(TargetObject.Value); + annotation.SetSelectorResult(selector, result); + return result; + } - // Starts rule execution timer - _RuleTimer.Restart(); - return RuleRecord; - } + /// + /// Enter the rule block scope. + /// + public RuleRecord EnterRuleBlock(RuleBlock ruleBlock) + { + Binding = TargetBinder.Result(ruleBlock.Info.ModuleName); + + _RuleErrors = 0; + RuleBlock = ruleBlock; + RuleRecord = new RuleRecord( + runId: Pipeline.RunId, + ruleId: ruleBlock.Id, + @ref: ruleBlock.Ref.GetValueOrDefault().Name, + targetObject: TargetObject, + targetName: Binding.TargetName, + targetType: Binding.TargetType, + tag: ruleBlock.Tag, + info: ruleBlock.Info, + field: Binding.Field, + level: ruleBlock.Level, + extent: ruleBlock.Extent + ); + + Writer?.EnterScope(ruleBlock.Name); + + // Starts rule execution timer + _RuleTimer.Restart(); + return RuleRecord; + } - /// - /// Exit the rule block scope. - /// - public void ExitRuleBlock() - { - // Stop rule execution time - _RuleTimer.Stop(); - RuleRecord.Time = _RuleTimer.ElapsedMilliseconds; + /// + /// Exit the rule block scope. + /// + public void ExitRuleBlock() + { + // Stop rule execution time + _RuleTimer.Stop(); + RuleRecord.Time = _RuleTimer.ElapsedMilliseconds; - if (!RuleRecord.IsSuccess()) - for (var i = 0; i < _Reason.Count; i++) - RuleRecord._Detail.Add(_Reason[i]); + if (!RuleRecord.IsSuccess()) + for (var i = 0; i < _Reason.Count; i++) + RuleRecord._Detail.Add(_Reason[i]); - Writer?.ExitScope(); + Writer?.ExitScope(); - _LogPrefix = null; - RuleRecord = null; - RuleBlock = null; - _RuleErrors = 0; - _Reason.Clear(); - } + _LogPrefix = null; + RuleRecord = null; + RuleBlock = null; + _RuleErrors = 0; + _Reason.Clear(); + } - internal void Import(IConvention resource) - { - _Conventions.Add(resource); - } + internal void Import(IConvention resource) + { + _Conventions.Add(resource); + } - internal void AddService(string id, object service) - { - ResourceHelper.ParseIdString(LanguageScope.Name, id, out var scopeName, out var name); - if (!StringComparer.OrdinalIgnoreCase.Equals(LanguageScope.Name, scopeName)) - return; + internal void AddService(string id, object service) + { + ResourceHelper.ParseIdString(LanguageScope.Name, id, out var scopeName, out var name); + if (!StringComparer.OrdinalIgnoreCase.Equals(LanguageScope.Name, scopeName)) + return; - LanguageScope.AddService(name, service); - } + LanguageScope.AddService(name, service); + } - internal object GetService(string id) - { - ResourceHelper.ParseIdString(LanguageScope.Name, id, out var scopeName, out var name); - return !_LanguageScopes.TryScope(scopeName, out var scope) ? null : scope.GetService(name); - } + internal object GetService(string id) + { + ResourceHelper.ParseIdString(LanguageScope.Name, id, out var scopeName, out var name); + return !_LanguageScopes.TryScope(scopeName, out var scope) ? null : scope.GetService(name); + } - private void RunConventionInitialize() - { - if (IsEmptyConventions()) - return; + private void RunConventionInitialize() + { + if (IsEmptyConventions()) + return; - for (var i = 0; i < _Conventions.Count; i++) - _Conventions[i].Initialize(this, null); - } + for (var i = 0; i < _Conventions.Count; i++) + _Conventions[i].Initialize(this, null); + } - private void RunConventionBegin() - { - if (IsEmptyConventions()) - return; + private void RunConventionBegin() + { + if (IsEmptyConventions()) + return; - for (var i = 0; i < _Conventions.Count; i++) - _Conventions[i].Begin(this, null); - } + for (var i = 0; i < _Conventions.Count; i++) + _Conventions[i].Begin(this, null); + } - private void RunConventionProcess() - { - if (IsEmptyConventions()) - return; + private void RunConventionProcess() + { + if (IsEmptyConventions()) + return; - for (var i = 0; i < _Conventions.Count; i++) - _Conventions[i].Process(this, null); - } + for (var i = 0; i < _Conventions.Count; i++) + _Conventions[i].Process(this, null); + } - private void RunConventionEnd() - { - if (IsEmptyConventions()) - return; + private void RunConventionEnd() + { + if (IsEmptyConventions()) + return; - for (var i = 0; i < _Conventions.Count; i++) - _Conventions[i].End(this, null); - } + for (var i = 0; i < _Conventions.Count; i++) + _Conventions[i].End(this, null); + } - private bool IsEmptyConventions() - { - return _Conventions == null || _Conventions.Count == 0; - } + private bool IsEmptyConventions() + { + return _Conventions == null || _Conventions.Count == 0; + } - internal void WriteReason(ResultReason[] reason) - { - for (var i = 0; reason != null && i < reason.Length; i++) - WriteReason(reason[i]); - } + internal void WriteReason(ResultReason[] reason) + { + for (var i = 0; reason != null && i < reason.Length; i++) + WriteReason(reason[i]); + } - internal void WriteReason(ResultReason reason) - { - if (reason == null || string.IsNullOrEmpty(reason.Text) || !IsScope(RunspaceScope.Rule)) - return; + internal void WriteReason(ResultReason reason) + { + if (reason == null || string.IsNullOrEmpty(reason.Text) || !IsScope(RunspaceScope.Rule)) + return; - _Reason.Add(reason); - } + _Reason.Add(reason); + } - public void Init(Source[] source) - { - InitLanguageScopes(source); - var resources = Host.HostHelper.ImportResource(source, this).OfType(); + public void Init(Source[] source) + { + InitLanguageScopes(source); + var resources = Host.HostHelper.ImportResource(source, this).OfType(); - // Process module configurations first - foreach (var resource in resources.Where(r => r.Kind == ResourceKind.ModuleConfig).ToArray()) - Pipeline.Import(this, resource); + // Process module configurations first + foreach (var resource in resources.Where(r => r.Kind == ResourceKind.ModuleConfig).ToArray()) + Pipeline.Import(this, resource); - foreach (var languageScope in _LanguageScopes.Get()) - Pipeline.UpdateLanguageScope(languageScope); + foreach (var languageScope in _LanguageScopes.Get()) + Pipeline.UpdateLanguageScope(languageScope); - foreach (var resource in resources) + foreach (var resource in resources) + { + EnterLanguageScope(resource.Source); + try { - EnterLanguageScope(resource.Source); - try - { - Host.HostHelper.UpdateHelpInfo(this, resource); - } - finally - { - ExitLanguageScope(resource.Source); - } + Host.HostHelper.UpdateHelpInfo(this, resource); + } + finally + { + ExitLanguageScope(resource.Source); } - - // Process other resources - foreach (var resource in resources.Where(r => r.Kind != ResourceKind.ModuleConfig).ToArray()) - Pipeline.Import(this, resource); - - foreach (var languageScope in _LanguageScopes.Get()) - Pipeline.UpdateLanguageScope(languageScope); } - private void InitLanguageScopes(Source[] source) - { - for (var i = 0; source != null && i < source.Length; i++) - _LanguageScopes.Import(source[i].Scope, out _); - } + // Process other resources + foreach (var resource in resources.Where(r => r.Kind != ResourceKind.ModuleConfig).ToArray()) + Pipeline.Import(this, resource); - public void Begin() - { - Pipeline.Begin(this); + foreach (var languageScope in _LanguageScopes.Get()) + Pipeline.UpdateLanguageScope(languageScope); + } + + private void InitLanguageScopes(Source[] source) + { + for (var i = 0; source != null && i < source.Length; i++) + _LanguageScopes.Import(source[i].Scope, out _); + } - var builder = new TargetBinderBuilder( - Pipeline.BindTargetName, - Pipeline.BindTargetType, - Pipeline.BindField, - Pipeline.Option.Input.TargetType); + public void Begin() + { + Pipeline.Begin(this); - HashSet _TypeFilter = null; - if (Pipeline.Option.Input.TargetType != null && Pipeline.Option.Input.TargetType.Length > 0) - _TypeFilter = new HashSet(Pipeline.Option.Input.TargetType, StringComparer.OrdinalIgnoreCase); + var builder = new TargetBinderBuilder( + Pipeline.BindTargetName, + Pipeline.BindTargetType, + Pipeline.BindField, + Pipeline.Option.Input.TargetType); - foreach (var languageScope in _LanguageScopes.Get()) - { - builder.With(new TargetBinder.TargetBindingContext(languageScope.Name, languageScope.Binding, Pipeline.BindTargetName, Pipeline.BindTargetType, Pipeline.BindField, _TypeFilter)); - } - TargetBinder = builder.Build(); - RunConventionInitialize(); - } + HashSet _TypeFilter = null; + if (Pipeline.Option.Input.TargetType != null && Pipeline.Option.Input.TargetType.Length > 0) + _TypeFilter = new HashSet(Pipeline.Option.Input.TargetType, StringComparer.OrdinalIgnoreCase); - public void End(IEnumerable output) + foreach (var languageScope in _LanguageScopes.Get()) { - Output = output; - RunConventionEnd(); + builder.With(new TargetBinder.TargetBindingContext(languageScope.Name, languageScope.Binding, Pipeline.BindTargetName, Pipeline.BindTargetType, Pipeline.BindField, _TypeFilter)); } + TargetBinder = builder.Build(); + RunConventionInitialize(); + } - public string GetLocalizedPath(string file, out string culture) - { - culture = null; - if (string.IsNullOrEmpty(Source.File.HelpPath)) - return null; + public void End(IEnumerable output) + { + Output = output; + RunConventionEnd(); + } - var cultures = LanguageScope.Culture; - if (!_RaisedUsingInvariantCulture && (cultures == null || cultures.Length == 0)) - { - this.Throw(_InvariantCulture, PSRuleResources.UsingInvariantCulture); - _RaisedUsingInvariantCulture = true; - return null; - } + public string GetLocalizedPath(string file, out string culture) + { + culture = null; + if (string.IsNullOrEmpty(Source.File.HelpPath)) + return null; - for (var i = 0; cultures != null && i < cultures.Length; i++) - { - var path = Path.Combine(Source.File.HelpPath, cultures[i], file); - if (File.Exists(path)) - { - culture = cultures[i]; - return path; - } - } + var cultures = LanguageScope.Culture; + if (!_RaisedUsingInvariantCulture && (cultures == null || cultures.Length == 0)) + { + this.Throw(_InvariantCulture, PSRuleResources.UsingInvariantCulture); + _RaisedUsingInvariantCulture = true; return null; } - internal bool ShouldWarnOnce(params string[] key) + for (var i = 0; cultures != null && i < cultures.Length; i++) { - var combinedKey = string.Join(WARN_KEY_SEPARATOR, key); - if (_WarnOnce.Contains(combinedKey)) - return false; - - _WarnOnce.Add(combinedKey); - return true; + var path = Path.Combine(Source.File.HelpPath, cultures[i], file); + if (File.Exists(path)) + { + culture = cultures[i]; + return path; + } } + return null; + } - #region Configuration + internal bool ShouldWarnOnce(params string[] key) + { + var combinedKey = string.Join(WARN_KEY_SEPARATOR, key); + if (_WarnOnce.Contains(combinedKey)) + return false; - internal bool TryGetConfigurationValue(string name, out object value) - { - value = null; - if (string.IsNullOrEmpty(name)) - return false; + _WarnOnce.Add(combinedKey); + return true; + } - // Get from baseline configuration - if (LanguageScope.TryConfigurationValue(name, out var result)) - { - value = result; - return true; - } + #region Configuration - // Check if value exists in Rule definition defaults - if (RuleBlock == null || RuleBlock.Configuration == null || !RuleBlock.Configuration.ContainsKey(name)) - return false; + internal bool TryGetConfigurationValue(string name, out object value) + { + value = null; + if (string.IsNullOrEmpty(name)) + return false; - // Get from rule default - value = RuleBlock.Configuration[name]; + // Get from baseline configuration + if (LanguageScope.TryConfigurationValue(name, out var result)) + { + value = result; return true; } - #endregion Configuration + // Check if value exists in Rule definition defaults + if (RuleBlock == null || RuleBlock.Configuration == null || !RuleBlock.Configuration.ContainsKey(name)) + return false; - #region ILogger + // Get from rule default + value = RuleBlock.Configuration[name]; + return true; + } - /// - public bool ShouldLog(LogLevel level) - { - return Writer != null && ( - (level == LogLevel.Warning && Writer.ShouldWriteWarning()) || - (level == LogLevel.Error && Writer.ShouldWriteError()) || - (level == LogLevel.Info && Writer.ShouldWriteInformation()) || - (level == LogLevel.Verbose && Writer.ShouldWriteVerbose()) || - (level == LogLevel.Debug && Writer.ShouldWriteDebug()) - ); - } + #endregion Configuration - /// - public void Warning(string message, params object[] args) - { - if (Writer == null || string.IsNullOrEmpty(message)) - return; + #region ILogger - Writer.WriteWarning(message, args); - } + /// + public bool ShouldLog(LogLevel level) + { + return Writer != null && ( + (level == LogLevel.Warning && Writer.ShouldWriteWarning()) || + (level == LogLevel.Error && Writer.ShouldWriteError()) || + (level == LogLevel.Info && Writer.ShouldWriteInformation()) || + (level == LogLevel.Verbose && Writer.ShouldWriteVerbose()) || + (level == LogLevel.Debug && Writer.ShouldWriteDebug()) + ); + } - /// - public void Error(Exception exception, string errorId = null) - { - if (Writer == null || exception == null) - return; + /// + public void Warning(string message, params object[] args) + { + if (Writer == null || string.IsNullOrEmpty(message)) + return; - Writer.WriteError(new ErrorRecord(exception, errorId, ErrorCategory.InvalidOperation, null)); - } + Writer.WriteWarning(message, args); + } - #endregion ILogger + /// + public void Error(Exception exception, string errorId = null) + { + if (Writer == null || exception == null) + return; - #region IDisposable + Writer.WriteError(new ErrorRecord(exception, errorId, ErrorCategory.InvalidOperation, null)); + } - public void Dispose() - { - Dispose(true); - } + #endregion ILogger + + #region IDisposable + + public void Dispose() + { + Dispose(true); + } - private void Dispose(bool disposing) + private void Dispose(bool disposing) + { + if (!_Disposed) { - if (!_Disposed) + if (disposing) { - if (disposing) + _RuleTimer.Stop(); + _Reason.Clear(); + for (var i = 0; _Conventions != null && i < _Conventions.Count; i++) { - _RuleTimer.Stop(); - _Reason.Clear(); - for (var i = 0; _Conventions != null && i < _Conventions.Count; i++) - { - if (_Conventions[i] is IDisposable d) - d.Dispose(); - } - _LanguageScopes.Dispose(); + if (_Conventions[i] is IDisposable d) + d.Dispose(); } - _Disposed = true; + _LanguageScopes.Dispose(); } + _Disposed = true; } - - #endregion IDisposable } + + #endregion IDisposable } diff --git a/src/PSRule/Runtime/ScopedItem.cs b/src/PSRule/Runtime/ScopedItem.cs index c9ce859c4a..d8f1a25270 100644 --- a/src/PSRule/Runtime/ScopedItem.cs +++ b/src/PSRule/Runtime/ScopedItem.cs @@ -3,40 +3,39 @@ using PSRule.Pipeline; -namespace PSRule.Runtime -{ - /// - /// A base class for scoped context variables used internally by PSRule. - /// - public abstract class ScopedItem - { - private readonly RunspaceContext _Context; +namespace PSRule.Runtime; - internal ScopedItem() - { +/// +/// A base class for scoped context variables used internally by PSRule. +/// +public abstract class ScopedItem +{ + private readonly RunspaceContext _Context; - } + internal ScopedItem() + { - internal ScopedItem(RunspaceContext context) - { - _Context = context; - } + } - #region Helper methods + internal ScopedItem(RunspaceContext context) + { + _Context = context; + } - internal void RequireScope(RunspaceScope scope) - { - if (GetContext().IsScope(scope)) - return; + #region Helper methods - throw new RuntimeScopeException(); - } + internal void RequireScope(RunspaceScope scope) + { + if (GetContext().IsScope(scope)) + return; - internal RunspaceContext GetContext() - { - return _Context ?? RunspaceContext.CurrentThread; - } + throw new RuntimeScopeException(); + } - #endregion Helper methods + internal RunspaceContext GetContext() + { + return _Context ?? RunspaceContext.CurrentThread; } + + #endregion Helper methods } diff --git a/src/PSRule/packages.lock.json b/src/PSRule/packages.lock.json new file mode 100644 index 0000000000..c1308d52bd --- /dev/null +++ b/src/PSRule/packages.lock.json @@ -0,0 +1,710 @@ +{ + "version": 1, + "dependencies": { + ".NETStandard,Version=v2.0": { + "Manatee.Json": { + "type": "Direct", + "requested": "[13.0.5, )", + "resolved": "13.0.5", + "contentHash": "xCWPlnZbfXrOW28r9Cnw2U+kh1KUL5AkM9raGk/sO5nzIGkwAC04gCRI0S4Gqv63Sm2lPmhLvancxPzT6QE+Mw==", + "dependencies": { + "System.ComponentModel.Annotations": "4.4.0", + "System.Net.Http": "4.3.3", + "System.Reflection.Emit": "4.3.0", + "System.ValueTuple": "4.5.0" + } + }, + "Microsoft.CodeAnalysis.NetAnalyzers": { + "type": "Direct", + "requested": "[8.0.0, )", + "resolved": "8.0.0", + "contentHash": "DxiTgkCl3CGq1rYmBX2wjY7XGbxiBdL4J+/AJIAFLKy5z70NxhnVRnPghnicXZ8oF6JKVXlW3xwznRbI3ioEKg==" + }, + "Microsoft.SourceLink.GitHub": { + "type": "Direct", + "requested": "[8.0.0, )", + "resolved": "8.0.0", + "contentHash": "G5q7OqtwIyGTkeIOAc3u2ZuV/kicQaec5EaRnc0pIeSnh9LUjj+PYQrJYBURvDt7twGl2PKA7nSN0kz1Zw5bnQ==", + "dependencies": { + "Microsoft.Build.Tasks.Git": "8.0.0", + "Microsoft.SourceLink.Common": "8.0.0" + } + }, + "NETStandard.Library": { + "type": "Direct", + "requested": "[2.0.3, )", + "resolved": "2.0.3", + "contentHash": "st47PosZSHrjECdjeIzZQbzivYBJFv6P2nv4cj2ypdI204DO+vZ7l5raGMiX4eXMJ53RfOIg+/s4DHVZ54Nu2A==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0" + } + }, + "PowerShellStandard.Library": { + "type": "Direct", + "requested": "[5.1.1, )", + "resolved": "5.1.1", + "contentHash": "e31xJjG+Kjbv6YF3Yq6D4Dl3or8v7LrNF41k3CXrWozW6hR1zcOe5KYuZJaGSiAgLnwP8wcW+I3+IWEzMPZKXQ==" + }, + "Sarif.Sdk": { + "type": "Direct", + "requested": "[2.4.16, )", + "resolved": "2.4.16", + "contentHash": "ikJcKaMnwEvlqmxD3rtMn4ksYr8tvWdid7uzq4JFPcASwz21ErE1wF2CHbY/orlxt+E7uhZ4zBblgk5rdflfpg==", + "dependencies": { + "Newtonsoft.Json": "13.0.1", + "System.Collections.Immutable": "5.0.0" + } + }, + "Microsoft.Build.Tasks.Git": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ==" + }, + "Microsoft.NETCore.Platforms": { + "type": "Transitive", + "resolved": "1.1.0", + "contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==" + }, + "Microsoft.NETCore.Targets": { + "type": "Transitive", + "resolved": "1.1.0", + "contentHash": "aOZA3BWfz9RXjpzt0sRJJMjAscAUm3Hoa4UWAfceV9UTYxgwZ1lZt5nO2myFf+/jetYQo4uTP7zS8sJY67BBxg==" + }, + "Microsoft.SourceLink.Common": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw==" + }, + "Newtonsoft.Json": { + "type": "Transitive", + "resolved": "13.0.3", + "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==" + }, + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.2", + "contentHash": "7VSGO0URRKoMEAq0Sc9cRz8mb6zbyx/BZDEWhgPdzzpmFhkam3fJ1DAGWFXBI4nGlma+uPKpfuMQP5LXRnOH5g==" + }, + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.2", + "contentHash": "0oAaTAm6e2oVH+/Zttt0cuhGaePQYKII1dY8iaqP7CvOpVKgLybKRFvQjXR2LtxXOXTVPNv14j0ot8uV+HrUmw==" + }, + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.2", + "contentHash": "G24ibsCNi5Kbz0oXWynBoRgtGvsw5ZSVEWjv13/KiCAM8C6wz9zzcCniMeQFIkJ2tasjo2kXlvlBZhplL51kGg==" + }, + "runtime.native.System": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "c/qWt2LieNZIj1jGnVNsE2Kl23Ya2aSTBuXMD6V7k9KWr6l16Tqdwq+hJScEpWER9753NWC8h96PaVNY5Ld7Jw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0" + } + }, + "runtime.native.System.Net.Http": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "ZVuZJqnnegJhd2k/PtAbbIcZ3aZeITq3sj06oKfMBSfphW3HDmk/t4ObvbOk/JA/swGR0LNqMksAh/f7gpTROg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0" + } + }, + "runtime.native.System.Security.Cryptography.Apple": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "DloMk88juo0OuOWr56QG7MNchmafTLYWvABy36izkrLI5VledI0rq28KGs1i9wbpeT9NPQrx/wTf8U2vazqQ3Q==", + "dependencies": { + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "4.3.0" + } + }, + "runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.2", + "contentHash": "QR1OwtwehHxSeQvZKXe+iSd+d3XZNkEcuWMFYa2i0aG1l+lR739HPicKMlTbJst3spmeekDVBUS7SeS26s4U/g==", + "dependencies": { + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.2", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.2", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.2", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.2", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.2", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.2", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.2", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.2", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.2", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.2" + } + }, + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.2", + "contentHash": "I+GNKGg2xCHueRd1m9PzeEW7WLbNNLznmTuEi8/vZX71HudUbx1UTwlGkiwMri7JLl8hGaIAWnA/GONhu+LOyQ==" + }, + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.2", + "contentHash": "1Z3TAq1ytS1IBRtPXJvEUZdVsfWfeNEhBkbiOCGEl9wwAfsjP2lz3ZFDx5tq8p60/EqbS0HItG5piHuB71RjoA==" + }, + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "kVXCuMTrTlxq4XOOMAysuNwsXWpYeboGddNGpIgNSZmv1b6r/s/DPk0fYMB7Q5Qo4bY68o48jt4T4y5BVecbCQ==" + }, + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.2", + "contentHash": "6mU/cVmmHtQiDXhnzUImxIcDL48GbTk+TsptXyJA+MIOG9LRjPoAQC/qBFB7X+UNyK86bmvGwC8t+M66wsYC8w==" + }, + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.2", + "contentHash": "vjwG0GGcTW/PPg6KVud8F9GLWYuAV1rrw1BKAqY0oh4jcUqg15oYF1+qkGR2x2ZHM4DQnWKQ7cJgYbfncz/lYg==" + }, + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.2", + "contentHash": "7KMFpTkHC/zoExs+PwP8jDCWcrK9H6L7soowT80CUx3e+nxP/AFnq0AQAW5W76z2WYbLAYCRyPfwYFG6zkvQRw==" + }, + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.2", + "contentHash": "xrlmRCnKZJLHxyyLIqkZjNXqgxnKdZxfItrPkjI+6pkRo5lHX8YvSZlWrSI5AVwLMi4HbNWP7064hcAWeZKp5w==" + }, + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.2", + "contentHash": "leXiwfiIkW7Gmn7cgnNcdtNAU70SjmKW3jxGj1iKHOvdn0zRWsgv/l2OJUO5zdGdiv2VRFnAsxxhDgMzofPdWg==" + }, + "System.Buffers": { + "type": "Transitive", + "resolved": "4.5.1", + "contentHash": "Rw7ijyl1qqRS0YQD/WycNst8hUUMgrMH4FCn1nNm27M4VxchZ1js3fVjQaANHO5f3sN4isvP4a+Met9Y4YomAg==" + }, + "System.Collections": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "3Dcj85/TBdVpL5Zr+gEEBUuFe2icOnLalmEh9hfck1PTYbbyWuZgh4fmm2ysCLTrqLQw6t3TgTyJ+VLp+Qb+Lw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Collections.Concurrent": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "ztl69Xp0Y/UXCL+3v3tEU+lIy+bvjKNUmopn1wep/a291pVPK7dxBd6T7WnlQqRog+d1a/hSsgRsmFnIBKTPLQ==", + "dependencies": { + "System.Collections": "4.3.0", + "System.Diagnostics.Debug": "4.3.0", + "System.Diagnostics.Tracing": "4.3.0", + "System.Globalization": "4.3.0", + "System.Reflection": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Threading": "4.3.0", + "System.Threading.Tasks": "4.3.0" + } + }, + "System.Collections.Immutable": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "FXkLXiK0sVVewcso0imKQoOxjoPAj42R8HtjjbSjVPAzwDfzoyoznWxgA3c38LDbN9SJux1xXoXYAhz98j7r2g==", + "dependencies": { + "System.Memory": "4.5.4" + } + }, + "System.ComponentModel.Annotations": { + "type": "Transitive", + "resolved": "4.4.0", + "contentHash": "29K3DQ+IGU7LBaMjTo7SI7T7X/tsMtLvz1p56LJ556Iu0Dw3pKZw5g8yCYCWMRxrOF0Hr0FU0FwW0o42y2sb3A==" + }, + "System.Diagnostics.Debug": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "ZUhUOdqmaG5Jk3Xdb8xi5kIyQYAA4PnTNlHx1mu9ZY3qv4ELIdKbnL/akbGaKi2RnNUWaZsAs31rvzFdewTj2g==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Diagnostics.DiagnosticSource": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "tD6kosZnTAGdrEa0tZSuFyunMbt/5KYDnHdndJYGqZoNy00XVXyACd5d6KnE1YgYv3ne2CjtAfNXo/fwEhnKUA==", + "dependencies": { + "System.Collections": "4.3.0", + "System.Diagnostics.Tracing": "4.3.0", + "System.Reflection": "4.3.0", + "System.Runtime": "4.3.0", + "System.Threading": "4.3.0" + } + }, + "System.Diagnostics.Tracing": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "rswfv0f/Cqkh78rA5S8eN8Neocz234+emGCtTF3lxPY96F+mmmUen6tbn0glN6PMvlKQb9bPAY5e9u7fgPTkKw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Globalization": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "kYdVd2f2PAdFGblzFswE4hkNANJBKRmsfa2X5LG2AcWE1c7/4t0pYae1L8vfZ5xvE2nK/R9JprtToA61OSHWIg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Globalization.Calendars": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "GUlBtdOWT4LTV3I+9/PJW+56AnnChTaOqqTLFtdmype/L500M2LIyXgmtd9X2P2VOkmJd5c67H5SaC2QcL1bFA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Globalization": "4.3.0", + "System.Runtime": "4.3.0" + } + }, + "System.Globalization.Extensions": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "FhKmdR6MPG+pxow6wGtNAWdZh7noIOpdD5TwQ3CprzgIE1bBBoim0vbR1+AWsWjQmU7zXHgQo4TWSP6lCeiWcQ==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "System.Globalization": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.InteropServices": "4.3.0" + } + }, + "System.IO": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "3qjaHvxQPDpSOYICjUoTsmoq5u6QJAFRUITgeT/4gqkF1bajbSmb1kwSxEA8AHlofqgcKJcM8udgieRNhaJ5Cg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0", + "System.Text.Encoding": "4.3.0", + "System.Threading.Tasks": "4.3.0" + } + }, + "System.IO.FileSystem": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "3wEMARTnuio+ulnvi+hkRNROYwa1kylvYahhcLk4HSoVdl+xxTFVeVlYOfLwrDPImGls0mDqbMhrza8qnWPTdA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.IO": "4.3.0", + "System.IO.FileSystem.Primitives": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Text.Encoding": "4.3.0", + "System.Threading.Tasks": "4.3.0" + } + }, + "System.IO.FileSystem.Primitives": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "6QOb2XFLch7bEc4lIcJH49nJN2HV+OC3fHDgsLVsBVBk3Y4hFAnOBGzJ2lUu7CyDDFo9IBWkSsnbkT6IBwwiMw==", + "dependencies": { + "System.Runtime": "4.3.0" + } + }, + "System.Linq": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "5DbqIUpsDp0dFftytzuMmc0oeMdQwjcP/EWxsksIz/w1TcFRkZ3yKKz0PqiYFMmEwPSWw+qNVqD7PJ889JzHbw==", + "dependencies": { + "System.Collections": "4.3.0", + "System.Diagnostics.Debug": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0" + } + }, + "System.Memory": { + "type": "Transitive", + "resolved": "4.5.4", + "contentHash": "1MbJTHS1lZ4bS4FmsJjnuGJOu88ZzTT2rLvrhW7Ygic+pC0NWA+3hgAen0HRdsocuQXCkUTdFn9yHJJhsijDXw==", + "dependencies": { + "System.Buffers": "4.5.1", + "System.Numerics.Vectors": "4.4.0", + "System.Runtime.CompilerServices.Unsafe": "4.5.3" + } + }, + "System.Net.Http": { + "type": "Transitive", + "resolved": "4.3.3", + "contentHash": "7rCqIbkC/P2+A00NoDH5gnvFhADmX7Dc4INvsOajbU1MVhktE9vZNrjPtF82N6Uo7obK+yzlrPUv/M+snnN/9w==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "System.Collections": "4.3.0", + "System.Diagnostics.Debug": "4.3.0", + "System.Diagnostics.DiagnosticSource": "4.3.0", + "System.Diagnostics.Tracing": "4.3.0", + "System.Globalization": "4.3.0", + "System.Globalization.Extensions": "4.3.0", + "System.IO": "4.3.0", + "System.IO.FileSystem": "4.3.0", + "System.Net.Primitives": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Security.Cryptography.Algorithms": "4.3.0", + "System.Security.Cryptography.Encoding": "4.3.0", + "System.Security.Cryptography.OpenSsl": "4.3.0", + "System.Security.Cryptography.Primitives": "4.3.0", + "System.Security.Cryptography.X509Certificates": "4.3.0", + "System.Text.Encoding": "4.3.0", + "System.Threading": "4.3.0", + "System.Threading.Tasks": "4.3.0", + "runtime.native.System": "4.3.0", + "runtime.native.System.Net.Http": "4.3.0", + "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.2" + } + }, + "System.Net.Primitives": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "qOu+hDwFwoZPbzPvwut2qATe3ygjeQBDQj91xlsaqGFQUI5i4ZnZb8yyQuLGpDGivEPIt8EJkd1BVzVoP31FXA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0", + "System.Runtime.Handles": "4.3.0" + } + }, + "System.Numerics.Vectors": { + "type": "Transitive", + "resolved": "4.4.0", + "contentHash": "UiLzLW+Lw6HLed1Hcg+8jSRttrbuXv7DANVj0DkL9g6EnnzbL75EB7EWsw5uRbhxd/4YdG8li5XizGWepmG3PQ==" + }, + "System.Reflection": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "KMiAFoW7MfJGa9nDFNcfu+FpEdiHpWgTcS2HdMpDvt9saK3y/G4GwprPyzqjFH9NTaGPQeWNHU+iDlDILj96aQ==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.IO": "4.3.0", + "System.Reflection.Primitives": "4.3.0", + "System.Runtime": "4.3.0" + } + }, + "System.Reflection.Emit": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "228FG0jLcIwTVJyz8CLFKueVqQK36ANazUManGaJHkO0icjiIypKW7YLWLIWahyIkdh5M7mV2dJepllLyA1SKg==", + "dependencies": { + "System.IO": "4.3.0", + "System.Reflection": "4.3.0", + "System.Reflection.Emit.ILGeneration": "4.3.0", + "System.Reflection.Primitives": "4.3.0", + "System.Runtime": "4.3.0" + } + }, + "System.Reflection.Emit.ILGeneration": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "59tBslAk9733NXLrUJrwNZEzbMAcu8k344OYo+wfSVygcgZ9lgBdGIzH/nrg3LYhXceynyvTc8t5/GD4Ri0/ng==", + "dependencies": { + "System.Reflection": "4.3.0", + "System.Reflection.Primitives": "4.3.0", + "System.Runtime": "4.3.0" + } + }, + "System.Reflection.Primitives": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "5RXItQz5As4xN2/YUDxdpsEkMhvw3e6aNveFXUn4Hl/udNTCNhnKp8lT9fnc3MhvGKh1baak5CovpuQUXHAlIA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Resources.ResourceManager": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "/zrcPkkWdZmI4F92gL/TPumP98AVDu/Wxr3CSJGQQ+XN6wbRZcyfSKVoPo17ilb3iOr0cCRqJInGwNMolqhS8A==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Globalization": "4.3.0", + "System.Reflection": "4.3.0", + "System.Runtime": "4.3.0" + } + }, + "System.Runtime": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "JufQi0vPQ0xGnAczR13AUFglDyVYt4Kqnz1AZaiKZ5+GICq0/1MH/mO/eAJHt/mHW1zjKBJd7kV26SrxddAhiw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0" + } + }, + "System.Runtime.CompilerServices.Unsafe": { + "type": "Transitive", + "resolved": "4.5.3", + "contentHash": "3TIsJhD1EiiT0w2CcDMN/iSSwnNnsrnbzeVHSKkaEgV85txMprmuO+Yq2AdSbeVGcg28pdNDTPK87tJhX7VFHw==" + }, + "System.Runtime.Extensions": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "guW0uK0fn5fcJJ1tJVXYd7/1h5F+pea1r7FLSOz/f8vPEqbR2ZAknuRDvTQ8PzAilDveOxNjSfr0CHfIQfFk8g==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Runtime.Handles": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "OKiSUN7DmTWeYb3l51A7EYaeNMnvxwE249YtZz7yooT4gOZhmTjIn48KgSsw2k2lYdLgTKNJw/ZIfSElwDRVgg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Runtime.InteropServices": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "uv1ynXqiMK8mp1GM3jDqPCFN66eJ5w5XNomaK2XD+TuCroNTLFGeZ+WCmBMcBDyTFKou3P6cR6J/QsaqDp7fGQ==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Reflection": "4.3.0", + "System.Reflection.Primitives": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Handles": "4.3.0" + } + }, + "System.Runtime.Numerics": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "yMH+MfdzHjy17l2KESnPiF2dwq7T+xLnSJar7slyimAkUh/gTrS9/UQOtv7xarskJ2/XDSNvfLGOBQPjL7PaHQ==", + "dependencies": { + "System.Globalization": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0" + } + }, + "System.Security.Cryptography.Algorithms": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "W1kd2Y8mYSCgc3ULTAZ0hOP2dSdG5YauTb1089T0/kRcN2MpSAW1izOFROrJgxSlMn3ArsgHXagigyi+ibhevg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "System.Collections": "4.3.0", + "System.IO": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Runtime.Numerics": "4.3.0", + "System.Security.Cryptography.Encoding": "4.3.0", + "System.Security.Cryptography.Primitives": "4.3.0", + "System.Text.Encoding": "4.3.0", + "runtime.native.System.Security.Cryptography.Apple": "4.3.0", + "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" + } + }, + "System.Security.Cryptography.Cng": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "03idZOqFlsKRL4W+LuCpJ6dBYDUWReug6lZjBa3uJWnk5sPCUXckocevTaUA8iT/MFSrY/2HXkOt753xQ/cf8g==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "System.IO": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Security.Cryptography.Algorithms": "4.3.0", + "System.Security.Cryptography.Encoding": "4.3.0", + "System.Security.Cryptography.Primitives": "4.3.0", + "System.Text.Encoding": "4.3.0" + } + }, + "System.Security.Cryptography.Csp": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "X4s/FCkEUnRGnwR3aSfVIkldBmtURMhmexALNTwpjklzxWU7yjMk7GHLKOZTNkgnWnE0q7+BCf9N2LVRWxewaA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "System.IO": "4.3.0", + "System.Reflection": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Security.Cryptography.Algorithms": "4.3.0", + "System.Security.Cryptography.Encoding": "4.3.0", + "System.Security.Cryptography.Primitives": "4.3.0", + "System.Text.Encoding": "4.3.0", + "System.Threading": "4.3.0" + } + }, + "System.Security.Cryptography.Encoding": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "1DEWjZZly9ae9C79vFwqaO5kaOlI5q+3/55ohmq/7dpDyDfc8lYe7YVxJUZ5MF/NtbkRjwFRo14yM4OEo9EmDw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "System.Collections": "4.3.0", + "System.Collections.Concurrent": "4.3.0", + "System.Linq": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Security.Cryptography.Primitives": "4.3.0", + "System.Text.Encoding": "4.3.0", + "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" + } + }, + "System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "h4CEgOgv5PKVF/HwaHzJRiVboL2THYCou97zpmhjghx5frc7fIvlkY1jL+lnIQyChrJDMNEXS6r7byGif8Cy4w==", + "dependencies": { + "System.Collections": "4.3.0", + "System.IO": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Runtime.Numerics": "4.3.0", + "System.Security.Cryptography.Algorithms": "4.3.0", + "System.Security.Cryptography.Encoding": "4.3.0", + "System.Security.Cryptography.Primitives": "4.3.0", + "System.Text.Encoding": "4.3.0", + "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" + } + }, + "System.Security.Cryptography.Primitives": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "7bDIyVFNL/xKeFHjhobUAQqSpJq9YTOpbEs6mR233Et01STBMXNAc/V+BM6dwYGc95gVh/Zf+iVXWzj3mE8DWg==", + "dependencies": { + "System.Diagnostics.Debug": "4.3.0", + "System.Globalization": "4.3.0", + "System.IO": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Threading": "4.3.0", + "System.Threading.Tasks": "4.3.0" + } + }, + "System.Security.Cryptography.X509Certificates": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "t2Tmu6Y2NtJ2um0RtcuhP7ZdNNxXEgUm2JeoA/0NvlMjAhKCnM1NX07TDl3244mVp3QU6LPEhT3HTtH1uF7IYw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "System.Collections": "4.3.0", + "System.Diagnostics.Debug": "4.3.0", + "System.Globalization": "4.3.0", + "System.Globalization.Calendars": "4.3.0", + "System.IO": "4.3.0", + "System.IO.FileSystem": "4.3.0", + "System.IO.FileSystem.Primitives": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Runtime.Numerics": "4.3.0", + "System.Security.Cryptography.Algorithms": "4.3.0", + "System.Security.Cryptography.Cng": "4.3.0", + "System.Security.Cryptography.Csp": "4.3.0", + "System.Security.Cryptography.Encoding": "4.3.0", + "System.Security.Cryptography.OpenSsl": "4.3.0", + "System.Security.Cryptography.Primitives": "4.3.0", + "System.Text.Encoding": "4.3.0", + "System.Threading": "4.3.0", + "runtime.native.System": "4.3.0", + "runtime.native.System.Net.Http": "4.3.0", + "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" + } + }, + "System.Text.Encoding": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "BiIg+KWaSDOITze6jGQynxg64naAPtqGHBwDrLaCtixsa5bKiR8dpPOHA7ge3C0JJQizJE+sfkz1wV+BAKAYZw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Threading": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "VkUS0kOBcUf3Wwm0TSbrevDDZ6BlM+b/HRiapRFWjM5O0NS0LviG0glKmFK+hhPDd1XFeSdU1GmlLhb2CoVpIw==", + "dependencies": { + "System.Runtime": "4.3.0", + "System.Threading.Tasks": "4.3.0" + } + }, + "System.Threading.Tasks": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "LbSxKEdOUhVe8BezB/9uOGGppt+nZf6e1VFyw6v3DN6lqitm0OSn2uXMOdtP0M3W4iMcqcivm2J6UgqiwwnXiA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.ValueTuple": { + "type": "Transitive", + "resolved": "4.5.0", + "contentHash": "okurQJO6NRE/apDIP23ajJ0hpiNmJ+f0BwOlB/cSqTLQlw5upkf+5+96+iG2Jw40G1fCVCyPz/FhIABUjMR+RQ==" + }, + "YamlDotNet": { + "type": "Transitive", + "resolved": "13.7.1", + "contentHash": "X4m1PnFcJwvAj1sCDMntg/eZcX96CJLrWMiYnq41KqhFVZPuw63ZTSxIGqgdCwHWHvCAyTxheELC/VDf1HsU2A==" + }, + "Microsoft.PSRule.Badges": { + "type": "Project", + "dependencies": { + "Newtonsoft.Json": "[13.0.3, )" + } + }, + "Microsoft.PSRule.Types": { + "type": "Project", + "dependencies": { + "Newtonsoft.Json": "[13.0.3, )", + "YamlDotNet": "[13.7.1, )" + } + } + } + } +} \ No newline at end of file diff --git a/tests/PSRule.Tests/AssertFormatterTests.cs b/tests/PSRule.Tests/AssertFormatterTests.cs index bcc6f6b639..1f9e4737c9 100644 --- a/tests/PSRule.Tests/AssertFormatterTests.cs +++ b/tests/PSRule.Tests/AssertFormatterTests.cs @@ -9,44 +9,44 @@ using PSRule.Pipeline.Formatters; using PSRule.Rules; -namespace PSRule +namespace PSRule; + +/// +/// Tests for formatters used by assert pipeline. +/// +public sealed class AssertFormatterTests { - /// - /// Tests for formatters used by assert pipeline. - /// - public sealed class AssertFormatterTests + [Fact] + public void Plain() { - [Fact] - public void Plain() - { - var option = GetOption(); - option.Output.Banner = BannerFormat.None; - var writer = GetWriter(); - - // Check output is empty - var formatter = new PlainFormatter(null, writer, option); - formatter.Begin(); - formatter.End(); - Assert.Equal("", writer.Output); - - // Check pass output - writer.Clear(); - formatter = new PlainFormatter(null, writer, option); - formatter.Begin(); - formatter.Result(GetPassResult()); - formatter.End(); - Assert.Equal(@" -> TestObject : TestType [1/1] + var option = GetOption(); + option.Output.Banner = BannerFormat.None; + var writer = GetWriter(); + + // Check output is empty + var formatter = new PlainFormatter(null, writer, option); + formatter.Begin(); + formatter.End(); + Assert.Equal("", writer.Output); + + // Check pass output + writer.Clear(); + formatter = new PlainFormatter(null, writer, option); + formatter.Begin(); + formatter.Result(GetPassResult()); + formatter.End(); + Assert.Equal(@" -> TestObject : TestType [1/1] [PASS] Test ", writer.Output); - // Check fail output - writer.Clear(); - formatter = new PlainFormatter(null, writer, option); - formatter.Begin(); - formatter.Result(GetFailResult()); - formatter.End(); - Assert.Equal(@" -> TestObject : TestType [0/2] + // Check fail output + writer.Clear(); + formatter = new PlainFormatter(null, writer, option); + formatter.Begin(); + formatter.Result(GetFailResult()); + formatter.End(); + Assert.Equal(@" -> TestObject : TestType [0/2] [FAIL] Test1 @@ -54,13 +54,13 @@ [FAIL] Test2 ", writer.Output); - // Check fail output as warning - writer.Clear(); - formatter = new PlainFormatter(null, writer, option); - formatter.Begin(); - formatter.Result(GetFailResult(SeverityLevel.Warning)); - formatter.End(); - Assert.Equal(@" -> TestObject : TestType [0/2] + // Check fail output as warning + writer.Clear(); + formatter = new PlainFormatter(null, writer, option); + formatter.Begin(); + formatter.Result(GetFailResult(SeverityLevel.Warning)); + formatter.End(); + Assert.Equal(@" -> TestObject : TestType [0/2] [FAIL] Test1 @@ -68,52 +68,52 @@ [FAIL] Test2 ", writer.Output); - // Check fail output as information - writer.Clear(); - formatter = new PlainFormatter(null, writer, option); - formatter.Begin(); - formatter.Result(GetFailResult(SeverityLevel.Information)); - formatter.End(); - Assert.Equal(@" -> TestObject : TestType [0/2] + // Check fail output as information + writer.Clear(); + formatter = new PlainFormatter(null, writer, option); + formatter.Begin(); + formatter.Result(GetFailResult(SeverityLevel.Information)); + formatter.End(); + Assert.Equal(@" -> TestObject : TestType [0/2] [FAIL] Test1 [FAIL] Test2 ", writer.Output); - } - - [Fact] - public void Client() - { - var option = GetOption(); - option.Output.Banner = BannerFormat.None; - var writer = GetWriter(); - - // Check output is empty - var formatter = new ClientFormatter(null, writer, option); - formatter.Begin(); - formatter.End(); - Assert.Equal("", writer.Output); - - // Check pass output - writer.Clear(); - formatter = new ClientFormatter(null, writer, option); - formatter.Begin(); - formatter.Result(GetPassResult()); - formatter.End(); - Assert.Equal(@" -> TestObject : TestType [1/1] + } + + [Fact] + public void Client() + { + var option = GetOption(); + option.Output.Banner = BannerFormat.None; + var writer = GetWriter(); + + // Check output is empty + var formatter = new ClientFormatter(null, writer, option); + formatter.Begin(); + formatter.End(); + Assert.Equal("", writer.Output); + + // Check pass output + writer.Clear(); + formatter = new ClientFormatter(null, writer, option); + formatter.Begin(); + formatter.Result(GetPassResult()); + formatter.End(); + Assert.Equal(@" -> TestObject : TestType [1/1] [PASS] Test ", writer.Output); - // Check fail output - writer.Clear(); - formatter = new ClientFormatter(null, writer, option); - formatter.Begin(); - formatter.Result(GetFailResult()); - formatter.End(); - Assert.Equal(@" -> TestObject : TestType [0/2] + // Check fail output + writer.Clear(); + formatter = new ClientFormatter(null, writer, option); + formatter.Begin(); + formatter.Result(GetFailResult()); + formatter.End(); + Assert.Equal(@" -> TestObject : TestType [0/2] [FAIL] Test1 @@ -121,13 +121,13 @@ [FAIL] Test2 ", writer.Output); - // Check fail output as warning - writer.Clear(); - formatter = new ClientFormatter(null, writer, option); - formatter.Begin(); - formatter.Result(GetFailResult(SeverityLevel.Warning)); - formatter.End(); - Assert.Equal(@" -> TestObject : TestType [0/2] + // Check fail output as warning + writer.Clear(); + formatter = new ClientFormatter(null, writer, option); + formatter.Begin(); + formatter.Result(GetFailResult(SeverityLevel.Warning)); + formatter.End(); + Assert.Equal(@" -> TestObject : TestType [0/2] [FAIL] Test1 @@ -135,52 +135,52 @@ [FAIL] Test2 ", writer.Output); - // Check fail output as information - writer.Clear(); - formatter = new ClientFormatter(null, writer, option); - formatter.Begin(); - formatter.Result(GetFailResult(SeverityLevel.Information)); - formatter.End(); - Assert.Equal(@" -> TestObject : TestType [0/2] + // Check fail output as information + writer.Clear(); + formatter = new ClientFormatter(null, writer, option); + formatter.Begin(); + formatter.Result(GetFailResult(SeverityLevel.Information)); + formatter.End(); + Assert.Equal(@" -> TestObject : TestType [0/2] [FAIL] Test1 [FAIL] Test2 ", writer.Output); - } - - [Fact] - public void AzurePipelines() - { - var option = GetOption(); - option.Output.Banner = BannerFormat.None; - var writer = GetWriter(); - - // Check output is empty - var formatter = new AzurePipelinesFormatter(null, writer, option); - formatter.Begin(); - formatter.End(); - Assert.Equal("", writer.Output); - - // Check pass output - writer.Clear(); - formatter = new AzurePipelinesFormatter(null, writer, option); - formatter.Begin(); - formatter.Result(GetPassResult()); - formatter.End(); - Assert.Equal(@" -> TestObject : TestType [1/1] + } + + [Fact] + public void AzurePipelines() + { + var option = GetOption(); + option.Output.Banner = BannerFormat.None; + var writer = GetWriter(); + + // Check output is empty + var formatter = new AzurePipelinesFormatter(null, writer, option); + formatter.Begin(); + formatter.End(); + Assert.Equal("", writer.Output); + + // Check pass output + writer.Clear(); + formatter = new AzurePipelinesFormatter(null, writer, option); + formatter.Begin(); + formatter.Result(GetPassResult()); + formatter.End(); + Assert.Equal(@" -> TestObject : TestType [1/1] [PASS] Test ", writer.Output); - // Check fail output - writer.Clear(); - formatter = new AzurePipelinesFormatter(null, writer, option); - formatter.Begin(); - formatter.Result(GetFailResult()); - formatter.End(); - Assert.Equal(@" -> TestObject : TestType [0/2] + // Check fail output + writer.Clear(); + formatter = new AzurePipelinesFormatter(null, writer, option); + formatter.Begin(); + formatter.Result(GetFailResult()); + formatter.End(); + Assert.Equal(@" -> TestObject : TestType [0/2] [FAIL] Test1 @@ -192,13 +192,13 @@ [FAIL] Test2 ", writer.Output); - // Check fail output as warning - writer.Clear(); - formatter = new AzurePipelinesFormatter(null, writer, option); - formatter.Begin(); - formatter.Result(GetFailResult(SeverityLevel.Warning)); - formatter.End(); - Assert.Equal(@" -> TestObject : TestType [0/2] + // Check fail output as warning + writer.Clear(); + formatter = new AzurePipelinesFormatter(null, writer, option); + formatter.Begin(); + formatter.Result(GetFailResult(SeverityLevel.Warning)); + formatter.End(); + Assert.Equal(@" -> TestObject : TestType [0/2] [FAIL] Test1 @@ -210,52 +210,52 @@ [FAIL] Test2 ", writer.Output); - // Check fail output as information - writer.Clear(); - formatter = new AzurePipelinesFormatter(null, writer, option); - formatter.Begin(); - formatter.Result(GetFailResult(SeverityLevel.Information)); - formatter.End(); - Assert.Equal(@" -> TestObject : TestType [0/2] + // Check fail output as information + writer.Clear(); + formatter = new AzurePipelinesFormatter(null, writer, option); + formatter.Begin(); + formatter.Result(GetFailResult(SeverityLevel.Information)); + formatter.End(); + Assert.Equal(@" -> TestObject : TestType [0/2] [FAIL] Test1 [FAIL] Test2 ", writer.Output); - } - - [Fact] - public void GitHubActions() - { - var option = GetOption(); - option.Output.Banner = BannerFormat.None; - var writer = GetWriter(); - - // Check output is empty - var formatter = new GitHubActionsFormatter(null, writer, option); - formatter.Begin(); - formatter.End(); - Assert.Equal("", writer.Output); - - // Check pass output - writer.Clear(); - formatter = new GitHubActionsFormatter(null, writer, option); - formatter.Begin(); - formatter.Result(GetPassResult()); - formatter.End(); - Assert.Equal(@" -> TestObject : TestType [1/1] + } + + [Fact] + public void GitHubActions() + { + var option = GetOption(); + option.Output.Banner = BannerFormat.None; + var writer = GetWriter(); + + // Check output is empty + var formatter = new GitHubActionsFormatter(null, writer, option); + formatter.Begin(); + formatter.End(); + Assert.Equal("", writer.Output); + + // Check pass output + writer.Clear(); + formatter = new GitHubActionsFormatter(null, writer, option); + formatter.Begin(); + formatter.Result(GetPassResult()); + formatter.End(); + Assert.Equal(@" -> TestObject : TestType [1/1] [PASS] Test ", writer.Output); - // Check fail output - writer.Clear(); - formatter = new GitHubActionsFormatter(null, writer, option); - formatter.Begin(); - formatter.Result(GetFailResult()); - formatter.End(); - Assert.Equal(@" -> TestObject : TestType [0/2] + // Check fail output + writer.Clear(); + formatter = new GitHubActionsFormatter(null, writer, option); + formatter.Begin(); + formatter.Result(GetFailResult()); + formatter.End(); + Assert.Equal(@" -> TestObject : TestType [0/2] [FAIL] Test1 @@ -267,13 +267,13 @@ [FAIL] Test2 ", writer.Output); - // Check fail output as warning - writer.Clear(); - formatter = new GitHubActionsFormatter(null, writer, option); - formatter.Begin(); - formatter.Result(GetFailResult(SeverityLevel.Warning)); - formatter.End(); - Assert.Equal(@" -> TestObject : TestType [0/2] + // Check fail output as warning + writer.Clear(); + formatter = new GitHubActionsFormatter(null, writer, option); + formatter.Begin(); + formatter.Result(GetFailResult(SeverityLevel.Warning)); + formatter.End(); + Assert.Equal(@" -> TestObject : TestType [0/2] [FAIL] Test1 @@ -285,13 +285,13 @@ [FAIL] Test2 ", writer.Output); - // Check fail output as information - writer.Clear(); - formatter = new GitHubActionsFormatter(null, writer, option); - formatter.Begin(); - formatter.Result(GetFailResult(SeverityLevel.Information)); - formatter.End(); - Assert.Equal(@" -> TestObject : TestType [0/2] + // Check fail output as information + writer.Clear(); + formatter = new GitHubActionsFormatter(null, writer, option); + formatter.Begin(); + formatter.Result(GetFailResult(SeverityLevel.Information)); + formatter.End(); + Assert.Equal(@" -> TestObject : TestType [0/2] [FAIL] Test1 @@ -302,39 +302,39 @@ [FAIL] Test2 ::notice::TestObject failed Test2. ", writer.Output); - } - - [Fact] - public void VisualStudioCode() - { - var option = GetOption(); - option.Output.Banner = BannerFormat.None; - var writer = GetWriter(); - - // Check output is empty - var formatter = new VisualStudioCodeFormatter(null, writer, option); - formatter.Begin(); - formatter.End(); - Assert.Equal("", writer.Output); - - // Check pass output - writer.Clear(); - formatter = new VisualStudioCodeFormatter(null, writer, option); - formatter.Begin(); - formatter.Result(GetPassResult()); - formatter.End(); - Assert.Equal(@"> TestObject : TestType [1/1] + } + + [Fact] + public void VisualStudioCode() + { + var option = GetOption(); + option.Output.Banner = BannerFormat.None; + var writer = GetWriter(); + + // Check output is empty + var formatter = new VisualStudioCodeFormatter(null, writer, option); + formatter.Begin(); + formatter.End(); + Assert.Equal("", writer.Output); + + // Check pass output + writer.Clear(); + formatter = new VisualStudioCodeFormatter(null, writer, option); + formatter.Begin(); + formatter.Result(GetPassResult()); + formatter.End(); + Assert.Equal(@"> TestObject : TestType [1/1] PASS Test ", writer.Output); - // Check fail output - writer.Clear(); - formatter = new VisualStudioCodeFormatter(null, writer, option); - formatter.Begin(); - formatter.Result(GetFailResult()); - formatter.End(); - Assert.Equal(@"> TestObject : TestType [0/2] + // Check fail output + writer.Clear(); + formatter = new VisualStudioCodeFormatter(null, writer, option); + formatter.Begin(); + formatter.Result(GetFailResult()); + formatter.End(); + Assert.Equal(@"> TestObject : TestType [0/2] FAIL Test1 @@ -342,13 +342,13 @@ FAIL Test2 ", writer.Output); - // Check fail output as warning - writer.Clear(); - formatter = new VisualStudioCodeFormatter(null, writer, option); - formatter.Begin(); - formatter.Result(GetFailResult(SeverityLevel.Warning)); - formatter.End(); - Assert.Equal(@"> TestObject : TestType [0/2] + // Check fail output as warning + writer.Clear(); + formatter = new VisualStudioCodeFormatter(null, writer, option); + formatter.Begin(); + formatter.Result(GetFailResult(SeverityLevel.Warning)); + formatter.End(); + Assert.Equal(@"> TestObject : TestType [0/2] FAIL Test1 @@ -356,93 +356,92 @@ FAIL Test2 ", writer.Output); - // Check fail output as information - writer.Clear(); - formatter = new VisualStudioCodeFormatter(null, writer, option); - formatter.Begin(); - formatter.Result(GetFailResult(SeverityLevel.Information)); - formatter.End(); - Assert.Equal(@"> TestObject : TestType [0/2] + // Check fail output as information + writer.Clear(); + formatter = new VisualStudioCodeFormatter(null, writer, option); + formatter.Begin(); + formatter.Result(GetFailResult(SeverityLevel.Information)); + formatter.End(); + Assert.Equal(@"> TestObject : TestType [0/2] FAIL Test1 FAIL Test2 ", writer.Output); - } - - #region Helper methods - - private static InvokeResult GetPassResult() - { - var result = new InvokeResult(); - result.Add(new RuleRecord - ( - runId: "run-001", - ruleId: ResourceId.Parse(".\\Test"), - @ref: "", - targetObject: new TargetObject(new PSObject()), - targetName: "TestObject", - targetType: "TestType", - tag: new ResourceTags(), - info: new RuleHelpInfo("Test", "Test rule", null), - field: null, - level: SeverityLevel.Error, - extent: null, - outcome: RuleOutcome.Pass, - reason: RuleOutcomeReason.Processed - )); - return result; - } - - private static InvokeResult GetFailResult(SeverityLevel level = SeverityLevel.Error) - { - var result = new InvokeResult(); - result.Add(new RuleRecord - ( - runId: "run-001", - ruleId: ResourceId.Parse(".\\Test1"), - @ref: "", - targetObject: new TargetObject(new PSObject()), - targetName: "TestObject", - targetType: "TestType", - tag: new ResourceTags(), - info: new RuleHelpInfo("Test1", "Test rule", null), - field: null, - level: level, - extent: null, - outcome: RuleOutcome.Fail, - reason: RuleOutcomeReason.Processed - )); - result.Add(new RuleRecord - ( - runId: "run-001", - ruleId: ResourceId.Parse(".\\Test2"), - @ref: "", - targetObject: new TargetObject(new PSObject()), - targetName: "TestObject", - targetType: "TestType", - tag: new ResourceTags(), - info: new RuleHelpInfo("Test2", "Test rule", null), - field: null, - level: level, - extent: null, - outcome: RuleOutcome.Fail, - reason: RuleOutcomeReason.Processed - )); - return result; - } - - private static PSRuleOption GetOption() - { - return PSRuleOption.FromDefault(); - } - - private static TestAssertWriter GetWriter() - { - return new TestAssertWriter(GetOption()); - } - - #endregion Helper methods } + + #region Helper methods + + private static InvokeResult GetPassResult() + { + var result = new InvokeResult(); + result.Add(new RuleRecord + ( + runId: "run-001", + ruleId: ResourceId.Parse(".\\Test"), + @ref: "", + targetObject: new TargetObject(new PSObject()), + targetName: "TestObject", + targetType: "TestType", + tag: new ResourceTags(), + info: new RuleHelpInfo("Test", "Test rule", null), + field: null, + level: SeverityLevel.Error, + extent: null, + outcome: RuleOutcome.Pass, + reason: RuleOutcomeReason.Processed + )); + return result; + } + + private static InvokeResult GetFailResult(SeverityLevel level = SeverityLevel.Error) + { + var result = new InvokeResult(); + result.Add(new RuleRecord + ( + runId: "run-001", + ruleId: ResourceId.Parse(".\\Test1"), + @ref: "", + targetObject: new TargetObject(new PSObject()), + targetName: "TestObject", + targetType: "TestType", + tag: new ResourceTags(), + info: new RuleHelpInfo("Test1", "Test rule", null), + field: null, + level: level, + extent: null, + outcome: RuleOutcome.Fail, + reason: RuleOutcomeReason.Processed + )); + result.Add(new RuleRecord + ( + runId: "run-001", + ruleId: ResourceId.Parse(".\\Test2"), + @ref: "", + targetObject: new TargetObject(new PSObject()), + targetName: "TestObject", + targetType: "TestType", + tag: new ResourceTags(), + info: new RuleHelpInfo("Test2", "Test rule", null), + field: null, + level: level, + extent: null, + outcome: RuleOutcome.Fail, + reason: RuleOutcomeReason.Processed + )); + return result; + } + + private static PSRuleOption GetOption() + { + return PSRuleOption.FromDefault(); + } + + private static TestAssertWriter GetWriter() + { + return new TestAssertWriter(GetOption()); + } + + #endregion Helper methods } diff --git a/tests/PSRule.Tests/AssertTests.cs b/tests/PSRule.Tests/AssertTests.cs index 7614d195a9..89f52e2d11 100644 --- a/tests/PSRule.Tests/AssertTests.cs +++ b/tests/PSRule.Tests/AssertTests.cs @@ -11,1529 +11,1528 @@ using PSRule.Runtime; using Xunit.Abstractions; -namespace PSRule +namespace PSRule; + +[Trait(LANGUAGE, LANGUAGEELEMENT)] +public sealed class AssertTests { - [Trait(LANGUAGE, LANGUAGEELEMENT)] - public sealed class AssertTests - { - private const string LANGUAGE = "Language"; - private const string LANGUAGEELEMENT = "Variable"; + private const string LANGUAGE = "Language"; + private const string LANGUAGEELEMENT = "Variable"; - private readonly ITestOutputHelper _Output; + private readonly ITestOutputHelper _Output; - public AssertTests(ITestOutputHelper output) - { - _Output = output; - } + public AssertTests(ITestOutputHelper output) + { + _Output = output; + } - [Fact] - public void Assertion() - { - SetContext(); - var assert = GetAssertionHelper(); - var actual1 = assert.Create(false, "Test reason"); - var actual2 = assert.Create(true, "Test reason"); - var actual3 = assert.Fail("Fail reason"); - var actual4 = assert.Create("Name", false, "Test reason"); - Assert.Equal("Test reason", actual1.ToString()); - Assert.Equal("Test reason", actual1.GetReason()[0]); - Assert.False(actual1.Result); - Assert.Equal(string.Empty, actual2.ToString()); - Assert.Empty(actual2.GetReason()); - Assert.True(actual2.Result); - - // WithReason - actual1.WithReason("Alternate reason"); - Assert.Equal("Test reason Alternate reason", actual1.ToString()); - actual1.WithReason("Alternate reason", true); - Assert.Equal("Alternate reason", actual1.ToString()); - - // Reason - actual1.Reason("New {0}", "Reason"); - actual1.Reason("New New Reason"); - Assert.Equal("New New Reason", actual1.ToString()); - Assert.Equal("New New Reason", actual1.GetReason()[0]); - actual1.ReasonIf(false, "Not a reason"); - Assert.Equal("New New Reason", actual1.ToString()); - Assert.Equal("New New Reason", actual1.GetReason()[0]); - actual1.ReasonIf("valueX", true, "New New New Reason"); - Assert.Equal("Path valueX: New New New Reason", actual1.ToString()); - Assert.Equal("Path valueX: New New New Reason", actual1.GetReason()[0]); - - Assert.Equal("Fail reason", actual3.ToString()); - Assert.Equal("Fail reason", actual3.GetReason()[0]); - actual3 = assert.Fail("Fail {0}", "reason"); - Assert.Equal("Fail reason", actual3.ToString()); - Assert.Equal("Fail reason", actual3.GetReason()[0]); - - var actual4Reasons = actual4.ToResultReason(); - Assert.Equal("Name", actual4Reasons[0].Path); - Assert.Equal("Test reason", actual4Reasons[0].Message); - Assert.Equal("Path Name: Test reason", actual4Reasons[0].Format()); - - // ReasonFrom - actual1.ReasonFrom("value", "New {0}", "Reason"); - actual1.ReasonFrom("value2", "New New Reason"); - Assert.Equal("Path value2: New New Reason", actual1.ToString()); - Assert.Equal("Path value2: New New Reason", actual1.GetReason()[0]); - - // WithPathPrefix - actual1.PathPrefix("resources[0]"); - Assert.Equal("Path resources[0].value2: New New Reason", actual1.GetReason()[0]); - actual4.PathPrefix("resources[0]"); - Assert.Equal("Path resources[0].Name: Test reason", actual4.GetReason()[0]); - actual4.PathPrefix("."); - Assert.Equal("Path Name: Test reason", actual4.GetReason()[0]); - - // Aggregate results - Assert.True(assert.AnyOf(actual2, actual3).Result); - Assert.True(assert.AnyOf(actual2).Result); - Assert.True(assert.AnyOf(actual3, actual2, actual1).Result); - Assert.False(assert.AnyOf().Result); - - Assert.False(assert.AllOf(actual2, actual3).Result); - var test1 = assert.AllOf(actual2, actual3); - Assert.Equal("Fail reason", test1.ToString()); - Assert.Equal("Fail reason", test1.GetReason()[0]); - - var test2 = assert.AllOf(actual1, actual2, actual3); - Assert.Equal("Path resources[0].value2: New New Reason Fail reason", test2.ToString()); - Assert.Equal(new string[] { "Path resources[0].value2: New New Reason", "Fail reason" }, test2.GetReason()); - Assert.True(assert.AllOf(actual2, actual2).Result); - Assert.True(assert.AllOf(actual2).Result); - Assert.False(assert.AllOf().Result); - } + [Fact] + public void Assertion() + { + SetContext(); + var assert = GetAssertionHelper(); + var actual1 = assert.Create(false, "Test reason"); + var actual2 = assert.Create(true, "Test reason"); + var actual3 = assert.Fail("Fail reason"); + var actual4 = assert.Create("Name", false, "Test reason"); + Assert.Equal("Test reason", actual1.ToString()); + Assert.Equal("Test reason", actual1.GetReason()[0]); + Assert.False(actual1.Result); + Assert.Equal(string.Empty, actual2.ToString()); + Assert.Empty(actual2.GetReason()); + Assert.True(actual2.Result); + + // WithReason + actual1.WithReason("Alternate reason"); + Assert.Equal("Test reason Alternate reason", actual1.ToString()); + actual1.WithReason("Alternate reason", true); + Assert.Equal("Alternate reason", actual1.ToString()); + + // Reason + actual1.Reason("New {0}", "Reason"); + actual1.Reason("New New Reason"); + Assert.Equal("New New Reason", actual1.ToString()); + Assert.Equal("New New Reason", actual1.GetReason()[0]); + actual1.ReasonIf(false, "Not a reason"); + Assert.Equal("New New Reason", actual1.ToString()); + Assert.Equal("New New Reason", actual1.GetReason()[0]); + actual1.ReasonIf("valueX", true, "New New New Reason"); + Assert.Equal("Path valueX: New New New Reason", actual1.ToString()); + Assert.Equal("Path valueX: New New New Reason", actual1.GetReason()[0]); + + Assert.Equal("Fail reason", actual3.ToString()); + Assert.Equal("Fail reason", actual3.GetReason()[0]); + actual3 = assert.Fail("Fail {0}", "reason"); + Assert.Equal("Fail reason", actual3.ToString()); + Assert.Equal("Fail reason", actual3.GetReason()[0]); + + var actual4Reasons = actual4.ToResultReason(); + Assert.Equal("Name", actual4Reasons[0].Path); + Assert.Equal("Test reason", actual4Reasons[0].Message); + Assert.Equal("Path Name: Test reason", actual4Reasons[0].Format()); + + // ReasonFrom + actual1.ReasonFrom("value", "New {0}", "Reason"); + actual1.ReasonFrom("value2", "New New Reason"); + Assert.Equal("Path value2: New New Reason", actual1.ToString()); + Assert.Equal("Path value2: New New Reason", actual1.GetReason()[0]); + + // WithPathPrefix + actual1.PathPrefix("resources[0]"); + Assert.Equal("Path resources[0].value2: New New Reason", actual1.GetReason()[0]); + actual4.PathPrefix("resources[0]"); + Assert.Equal("Path resources[0].Name: Test reason", actual4.GetReason()[0]); + actual4.PathPrefix("."); + Assert.Equal("Path Name: Test reason", actual4.GetReason()[0]); + + // Aggregate results + Assert.True(assert.AnyOf(actual2, actual3).Result); + Assert.True(assert.AnyOf(actual2).Result); + Assert.True(assert.AnyOf(actual3, actual2, actual1).Result); + Assert.False(assert.AnyOf().Result); + + Assert.False(assert.AllOf(actual2, actual3).Result); + var test1 = assert.AllOf(actual2, actual3); + Assert.Equal("Fail reason", test1.ToString()); + Assert.Equal("Fail reason", test1.GetReason()[0]); + + var test2 = assert.AllOf(actual1, actual2, actual3); + Assert.Equal("Path resources[0].value2: New New Reason Fail reason", test2.ToString()); + Assert.Equal(new string[] { "Path resources[0].value2: New New Reason", "Fail reason" }, test2.GetReason()); + Assert.True(assert.AllOf(actual2, actual2).Result); + Assert.True(assert.AllOf(actual2).Result); + Assert.False(assert.AllOf().Result); + } - [Fact] - public void WithinRollupBlock() - { - SetContext(); - var assert = GetAssertionHelper(); - var actual1 = RuleConditionHelper.Create(new object[] { PSObject.AsPSObject(assert.Create(true, "Test reason")), PSObject.AsPSObject(assert.Create(false, "Test reason")) }); - Assert.True(actual1.AnyOf()); - Assert.False(actual1.AllOf()); - - var actual2 = RuleConditionHelper.Create(new object[] { assert.Create(true, "Test reason"), assert.Create(false, "Test reason") }); - Assert.True(actual2.AnyOf()); - Assert.False(actual2.AllOf()); - } + [Fact] + public void WithinRollupBlock() + { + SetContext(); + var assert = GetAssertionHelper(); + var actual1 = RuleConditionHelper.Create(new object[] { PSObject.AsPSObject(assert.Create(true, "Test reason")), PSObject.AsPSObject(assert.Create(false, "Test reason")) }); + Assert.True(actual1.AnyOf()); + Assert.False(actual1.AllOf()); + + var actual2 = RuleConditionHelper.Create(new object[] { assert.Create(true, "Test reason"), assert.Create(false, "Test reason") }); + Assert.True(actual2.AnyOf()); + Assert.False(actual2.AllOf()); + } - [Fact] - public void HasFieldValue() - { - SetContext(); - var assert = GetAssertionHelper(); - - var value = GetObject( - (name: "value", value: "Value1"), - (name: "value2", value: null), - (name: "value3", value: ""), - (name: "Value4", value: 0), - (name: "value5", value: GetObject((name: "value", value: 0))) - ); - - Assert.False(assert.HasFieldValue(null, null).Result); - Assert.True(assert.HasFieldValue(value, "value").Result); - Assert.False(assert.HasFieldValue(value, "value2").Result); - Assert.False(assert.HasFieldValue(value, "value3").Result); - Assert.True(assert.HasFieldValue(value, "Value4").Result); - Assert.True(assert.HasFieldValue(value, "value5").Result); - Assert.True(assert.HasFieldValue(value, "value5.value").Result); - - Assert.Empty(assert.HasFieldValue(value, "value").ToResultReason()); - Assert.Equal("value6", assert.HasFieldValue(value, "value6").ToResultReason().FirstOrDefault().Path); - Assert.Equal("value7.value", assert.HasFieldValue(value, "value7.value").ToResultReason().FirstOrDefault().Path); - } + [Fact] + public void HasFieldValue() + { + SetContext(); + var assert = GetAssertionHelper(); + + var value = GetObject( + (name: "value", value: "Value1"), + (name: "value2", value: null), + (name: "value3", value: ""), + (name: "Value4", value: 0), + (name: "value5", value: GetObject((name: "value", value: 0))) + ); + + Assert.False(assert.HasFieldValue(null, null).Result); + Assert.True(assert.HasFieldValue(value, "value").Result); + Assert.False(assert.HasFieldValue(value, "value2").Result); + Assert.False(assert.HasFieldValue(value, "value3").Result); + Assert.True(assert.HasFieldValue(value, "Value4").Result); + Assert.True(assert.HasFieldValue(value, "value5").Result); + Assert.True(assert.HasFieldValue(value, "value5.value").Result); + + Assert.Empty(assert.HasFieldValue(value, "value").ToResultReason()); + Assert.Equal("value6", assert.HasFieldValue(value, "value6").ToResultReason().FirstOrDefault().Path); + Assert.Equal("value7.value", assert.HasFieldValue(value, "value7.value").ToResultReason().FirstOrDefault().Path); + } - [Fact] - public void HasField() - { - SetContext(); - var assert = GetAssertionHelper(); - - var value = GetObject( - (name: "value", value: "Value1"), - (name: "value2", value: null), - (name: "value3", value: ""), - (name: "Value4", value: 0), - (name: "value5", value: GetObject((name: "value", value: 0))) - ); - - Assert.False(assert.HasField(null, null).Result); - Assert.False(assert.HasField(null, Array.Empty()).Result); - Assert.True(assert.HasField(value, new string[] { "value" }).Result); - Assert.True(assert.HasField(value, new string[] { "notValue", "Value" }).Result); - Assert.True(assert.HasField(value, new string[] { "value2" }).Result); - Assert.True(assert.HasField(value, new string[] { "value3" }).Result); - Assert.False(assert.HasField(value, new string[] { "Value3" }, true).Result); - Assert.True(assert.HasField(value, new string[] { "Value3", "Value4" }, true).Result); - Assert.True(assert.HasField(value, new string[] { "value5" }).Result); - Assert.True(assert.HasField(value, new string[] { "value5.value" }).Result); - Assert.False(assert.HasField(value, new string[] { "Value5.value" }, true).Result); - Assert.False(assert.HasField(value, new string[] { "value5.Value" }, true).Result); - - Assert.Empty(assert.HasField(value, new string[] { "value" }).ToResultReason()); - Assert.Equal("value6", assert.HasField(value, new string[] { "value6" }).ToResultReason().FirstOrDefault().Path); - Assert.Equal("value7.value", assert.HasField(value, new string[] { "value7.value" }).ToResultReason().FirstOrDefault().Path); - } + [Fact] + public void HasField() + { + SetContext(); + var assert = GetAssertionHelper(); + + var value = GetObject( + (name: "value", value: "Value1"), + (name: "value2", value: null), + (name: "value3", value: ""), + (name: "Value4", value: 0), + (name: "value5", value: GetObject((name: "value", value: 0))) + ); + + Assert.False(assert.HasField(null, null).Result); + Assert.False(assert.HasField(null, Array.Empty()).Result); + Assert.True(assert.HasField(value, new string[] { "value" }).Result); + Assert.True(assert.HasField(value, new string[] { "notValue", "Value" }).Result); + Assert.True(assert.HasField(value, new string[] { "value2" }).Result); + Assert.True(assert.HasField(value, new string[] { "value3" }).Result); + Assert.False(assert.HasField(value, new string[] { "Value3" }, true).Result); + Assert.True(assert.HasField(value, new string[] { "Value3", "Value4" }, true).Result); + Assert.True(assert.HasField(value, new string[] { "value5" }).Result); + Assert.True(assert.HasField(value, new string[] { "value5.value" }).Result); + Assert.False(assert.HasField(value, new string[] { "Value5.value" }, true).Result); + Assert.False(assert.HasField(value, new string[] { "value5.Value" }, true).Result); + + Assert.Empty(assert.HasField(value, new string[] { "value" }).ToResultReason()); + Assert.Equal("value6", assert.HasField(value, new string[] { "value6" }).ToResultReason().FirstOrDefault().Path); + Assert.Equal("value7.value", assert.HasField(value, new string[] { "value7.value" }).ToResultReason().FirstOrDefault().Path); + } - [Fact] - public void NotHasField() - { - SetContext(); - var assert = GetAssertionHelper(); - - var value = GetObject( - (name: "value", value: "Value1"), - (name: "value2", value: null), - (name: "value3", value: ""), - (name: "Value4", value: 0) - ); - - Assert.False(assert.NotHasField(null, null).Result); - Assert.False(assert.NotHasField(null, Array.Empty()).Result); - Assert.False(assert.NotHasField(value, new string[] { "value" }).Result); - Assert.False(assert.NotHasField(value, new string[] { "notValue", "Value" }).Result); - Assert.True(assert.NotHasField(value, new string[] { "notValue", "Value" }, true).Result); - Assert.False(assert.NotHasField(value, new string[] { "value2" }).Result); - Assert.False(assert.NotHasField(value, new string[] { "value3" }).Result); - Assert.True(assert.NotHasField(value, new string[] { "Value3" }, true).Result); - Assert.False(assert.NotHasField(value, new string[] { "Value3", "Value4" }, true).Result); - - Assert.Empty(assert.NotHasField(value, new string[] { "notValue", "Value" }, true).ToResultReason()); - Assert.Equal("value2", assert.NotHasField(value, new string[] { "value2" }).ToResultReason().FirstOrDefault().Path); - Assert.Equal("Value4", assert.NotHasField(value, new string[] { "Value3", "Value4" }, true).ToResultReason().FirstOrDefault().Path); - } + [Fact] + public void NotHasField() + { + SetContext(); + var assert = GetAssertionHelper(); + + var value = GetObject( + (name: "value", value: "Value1"), + (name: "value2", value: null), + (name: "value3", value: ""), + (name: "Value4", value: 0) + ); + + Assert.False(assert.NotHasField(null, null).Result); + Assert.False(assert.NotHasField(null, Array.Empty()).Result); + Assert.False(assert.NotHasField(value, new string[] { "value" }).Result); + Assert.False(assert.NotHasField(value, new string[] { "notValue", "Value" }).Result); + Assert.True(assert.NotHasField(value, new string[] { "notValue", "Value" }, true).Result); + Assert.False(assert.NotHasField(value, new string[] { "value2" }).Result); + Assert.False(assert.NotHasField(value, new string[] { "value3" }).Result); + Assert.True(assert.NotHasField(value, new string[] { "Value3" }, true).Result); + Assert.False(assert.NotHasField(value, new string[] { "Value3", "Value4" }, true).Result); + + Assert.Empty(assert.NotHasField(value, new string[] { "notValue", "Value" }, true).ToResultReason()); + Assert.Equal("value2", assert.NotHasField(value, new string[] { "value2" }).ToResultReason().FirstOrDefault().Path); + Assert.Equal("Value4", assert.NotHasField(value, new string[] { "Value3", "Value4" }, true).ToResultReason().FirstOrDefault().Path); + } - [Fact] - public void HasJsonSchema() - { - SetContext(); - var assert = GetAssertionHelper(); - - var actual1 = GetObject((name: "$schema", value: "abc")); - var actual2 = GetObject((name: "schema", value: "abc")); - var actual3 = GetObject((name: "$schema", value: "http://json-schema.org/draft-07/schema#")); - var actual4 = GetObject((name: "$schema", value: "http://json-schema.org/draft-07/schema#definition")); - var actual5 = GetObject((name: "$schema", value: "")); - - Assert.True(assert.HasJsonSchema(actual1, null).Result); - Assert.True(assert.HasJsonSchema(actual1, new string[] { "abc" }).Result); - Assert.False(assert.HasJsonSchema(actual2, new string[] { "abc" }).Result); - Assert.True(assert.HasJsonSchema(actual1, new string[] { "efg", "abc" }).Result); - Assert.False(assert.HasJsonSchema(actual3, new string[] { "https://json-schema.org/draft-07/schema" }).Result); - Assert.True(assert.HasJsonSchema(actual3, new string[] { "https://json-schema.org/draft-07/schema#" }, true).Result); - Assert.True(assert.HasJsonSchema(actual3, new string[] { "https://json-schema.org/draft-07/schema#", "http://json-schema.org/draft-07/schema#" }).Result); - Assert.False(assert.HasJsonSchema(actual4, new string[] { "https://json-schema.org/draft-07/schema#" }, true).Result); - Assert.False(assert.HasJsonSchema(actual5, null).Result); - - Assert.Equal("$schema", assert.HasJsonSchema(actual2, new string[] { "abc" }).ToResultReason().FirstOrDefault().Path); - } + [Fact] + public void HasJsonSchema() + { + SetContext(); + var assert = GetAssertionHelper(); + + var actual1 = GetObject((name: "$schema", value: "abc")); + var actual2 = GetObject((name: "schema", value: "abc")); + var actual3 = GetObject((name: "$schema", value: "http://json-schema.org/draft-07/schema#")); + var actual4 = GetObject((name: "$schema", value: "http://json-schema.org/draft-07/schema#definition")); + var actual5 = GetObject((name: "$schema", value: "")); + + Assert.True(assert.HasJsonSchema(actual1, null).Result); + Assert.True(assert.HasJsonSchema(actual1, new string[] { "abc" }).Result); + Assert.False(assert.HasJsonSchema(actual2, new string[] { "abc" }).Result); + Assert.True(assert.HasJsonSchema(actual1, new string[] { "efg", "abc" }).Result); + Assert.False(assert.HasJsonSchema(actual3, new string[] { "https://json-schema.org/draft-07/schema" }).Result); + Assert.True(assert.HasJsonSchema(actual3, new string[] { "https://json-schema.org/draft-07/schema#" }, true).Result); + Assert.True(assert.HasJsonSchema(actual3, new string[] { "https://json-schema.org/draft-07/schema#", "http://json-schema.org/draft-07/schema#" }).Result); + Assert.False(assert.HasJsonSchema(actual4, new string[] { "https://json-schema.org/draft-07/schema#" }, true).Result); + Assert.False(assert.HasJsonSchema(actual5, null).Result); + + Assert.Equal("$schema", assert.HasJsonSchema(actual2, new string[] { "abc" }).ToResultReason().FirstOrDefault().Path); + } - [Fact] - public void StartsWith() - { - SetContext(); - var assert = GetAssertionHelper(); - var value = GetObject((name: "name", value: "abcdefg"), (name: "value", value: 123)); - - // String - Assert.True(assert.StartsWith(value, "name", new string[] { "123", "ab" }).Result); - Assert.True(assert.StartsWith(value, "name", new string[] { "123", "ab" }, caseSensitive: true).Result); - Assert.True(assert.StartsWith(value, "name", new string[] { "ABC" }).Result); - Assert.False(assert.StartsWith(value, "name", new string[] { "123", "cd" }).Result); - Assert.False(assert.StartsWith(value, "name", new string[] { "123", "fg" }).Result); - Assert.False(assert.StartsWith(value, "name", new string[] { "abcdefgh" }).Result); - Assert.False(assert.StartsWith(value, "name", new string[] { "ABC" }, caseSensitive: true).Result); - Assert.False(assert.StartsWith(value, "name", new string[] { "123", "cd" }).Result); - - Assert.Equal("name", assert.StartsWith(value, "name", new string[] { "abcdefgh" }).ToResultReason().FirstOrDefault().Path); - - // Integer - Assert.False(assert.StartsWith(value, "value", new string[] { "123" }).Result); - } + [Fact] + public void StartsWith() + { + SetContext(); + var assert = GetAssertionHelper(); + var value = GetObject((name: "name", value: "abcdefg"), (name: "value", value: 123)); + + // String + Assert.True(assert.StartsWith(value, "name", new string[] { "123", "ab" }).Result); + Assert.True(assert.StartsWith(value, "name", new string[] { "123", "ab" }, caseSensitive: true).Result); + Assert.True(assert.StartsWith(value, "name", new string[] { "ABC" }).Result); + Assert.False(assert.StartsWith(value, "name", new string[] { "123", "cd" }).Result); + Assert.False(assert.StartsWith(value, "name", new string[] { "123", "fg" }).Result); + Assert.False(assert.StartsWith(value, "name", new string[] { "abcdefgh" }).Result); + Assert.False(assert.StartsWith(value, "name", new string[] { "ABC" }, caseSensitive: true).Result); + Assert.False(assert.StartsWith(value, "name", new string[] { "123", "cd" }).Result); + + Assert.Equal("name", assert.StartsWith(value, "name", new string[] { "abcdefgh" }).ToResultReason().FirstOrDefault().Path); + + // Integer + Assert.False(assert.StartsWith(value, "value", new string[] { "123" }).Result); + } - [Fact] - public void NotStartsWith() - { - SetContext(); - var assert = GetAssertionHelper(); - var value = GetObject((name: "name", value: "abcdefg"), (name: "value", value: 123)); - - // String - Assert.False(assert.NotStartsWith(value, "name", new string[] { "123", "ab" }).Result); - Assert.False(assert.NotStartsWith(value, "name", new string[] { "123", "ab" }, caseSensitive: true).Result); - Assert.False(assert.NotStartsWith(value, "name", new string[] { "ABC" }).Result); - Assert.True(assert.NotStartsWith(value, "name", new string[] { "123", "cd" }).Result); - Assert.True(assert.NotStartsWith(value, "name", new string[] { "123", "fg" }).Result); - Assert.True(assert.NotStartsWith(value, "name", new string[] { "abcdefgh" }).Result); - Assert.True(assert.NotStartsWith(value, "name", new string[] { "ABC" }, caseSensitive: true).Result); - Assert.True(assert.NotStartsWith(value, "name", new string[] { "123", "cd" }).Result); - - Assert.Equal("name", assert.NotStartsWith(value, "name", new string[] { "ABC" }).ToResultReason().FirstOrDefault().Path); - - // Integer - Assert.True(assert.NotStartsWith(value, "value", new string[] { "123" }).Result); - } + [Fact] + public void NotStartsWith() + { + SetContext(); + var assert = GetAssertionHelper(); + var value = GetObject((name: "name", value: "abcdefg"), (name: "value", value: 123)); + + // String + Assert.False(assert.NotStartsWith(value, "name", new string[] { "123", "ab" }).Result); + Assert.False(assert.NotStartsWith(value, "name", new string[] { "123", "ab" }, caseSensitive: true).Result); + Assert.False(assert.NotStartsWith(value, "name", new string[] { "ABC" }).Result); + Assert.True(assert.NotStartsWith(value, "name", new string[] { "123", "cd" }).Result); + Assert.True(assert.NotStartsWith(value, "name", new string[] { "123", "fg" }).Result); + Assert.True(assert.NotStartsWith(value, "name", new string[] { "abcdefgh" }).Result); + Assert.True(assert.NotStartsWith(value, "name", new string[] { "ABC" }, caseSensitive: true).Result); + Assert.True(assert.NotStartsWith(value, "name", new string[] { "123", "cd" }).Result); + + Assert.Equal("name", assert.NotStartsWith(value, "name", new string[] { "ABC" }).ToResultReason().FirstOrDefault().Path); + + // Integer + Assert.True(assert.NotStartsWith(value, "value", new string[] { "123" }).Result); + } - [Fact] - public void EndsWith() - { - SetContext(); - var assert = GetAssertionHelper(); - var value = GetObject((name: "name", value: "abcdefg"), (name: "value", value: 123)); - - // String - Assert.True(assert.EndsWith(value, "name", new string[] { "123", "fg" }).Result); - Assert.True(assert.EndsWith(value, "name", new string[] { "123", "fg" }, caseSensitive: true).Result); - Assert.True(assert.EndsWith(value, "name", new string[] { "EFG" }).Result); - Assert.False(assert.EndsWith(value, "name", new string[] { "123", "cd" }).Result); - Assert.False(assert.EndsWith(value, "name", new string[] { "123", "ab" }).Result); - Assert.False(assert.EndsWith(value, "name", new string[] { "abcdefgh" }).Result); - Assert.False(assert.EndsWith(value, "name", new string[] { "EFG" }, caseSensitive: true).Result); - Assert.False(assert.EndsWith(value, "name", new string[] { "123", "cd" }).Result); - - Assert.Equal("name", assert.EndsWith(value, "name", new string[] { "abcdefgh" }).ToResultReason().FirstOrDefault().Path); - - // Integer - Assert.False(assert.EndsWith(value, "value", new string[] { "123" }).Result); - } + [Fact] + public void EndsWith() + { + SetContext(); + var assert = GetAssertionHelper(); + var value = GetObject((name: "name", value: "abcdefg"), (name: "value", value: 123)); + + // String + Assert.True(assert.EndsWith(value, "name", new string[] { "123", "fg" }).Result); + Assert.True(assert.EndsWith(value, "name", new string[] { "123", "fg" }, caseSensitive: true).Result); + Assert.True(assert.EndsWith(value, "name", new string[] { "EFG" }).Result); + Assert.False(assert.EndsWith(value, "name", new string[] { "123", "cd" }).Result); + Assert.False(assert.EndsWith(value, "name", new string[] { "123", "ab" }).Result); + Assert.False(assert.EndsWith(value, "name", new string[] { "abcdefgh" }).Result); + Assert.False(assert.EndsWith(value, "name", new string[] { "EFG" }, caseSensitive: true).Result); + Assert.False(assert.EndsWith(value, "name", new string[] { "123", "cd" }).Result); + + Assert.Equal("name", assert.EndsWith(value, "name", new string[] { "abcdefgh" }).ToResultReason().FirstOrDefault().Path); + + // Integer + Assert.False(assert.EndsWith(value, "value", new string[] { "123" }).Result); + } - [Fact] - public void NotEndsWith() - { - SetContext(); - var assert = GetAssertionHelper(); - var value = GetObject((name: "name", value: "abcdefg"), (name: "value", value: 123)); - - // String - Assert.False(assert.NotEndsWith(value, "name", new string[] { "123", "fg" }).Result); - Assert.False(assert.NotEndsWith(value, "name", new string[] { "123", "fg" }, caseSensitive: true).Result); - Assert.False(assert.NotEndsWith(value, "name", new string[] { "EFG" }).Result); - Assert.True(assert.NotEndsWith(value, "name", new string[] { "123", "cd" }).Result); - Assert.True(assert.NotEndsWith(value, "name", new string[] { "123", "ab" }).Result); - Assert.True(assert.NotEndsWith(value, "name", new string[] { "abcdefgh" }).Result); - Assert.True(assert.NotEndsWith(value, "name", new string[] { "EFG" }, caseSensitive: true).Result); - Assert.True(assert.NotEndsWith(value, "name", new string[] { "123", "cd" }).Result); - - Assert.Equal("name", assert.NotEndsWith(value, "name", new string[] { "EFG" }).ToResultReason().FirstOrDefault().Path); - - // Integer - Assert.True(assert.NotEndsWith(value, "value", new string[] { "123" }).Result); - } + [Fact] + public void NotEndsWith() + { + SetContext(); + var assert = GetAssertionHelper(); + var value = GetObject((name: "name", value: "abcdefg"), (name: "value", value: 123)); + + // String + Assert.False(assert.NotEndsWith(value, "name", new string[] { "123", "fg" }).Result); + Assert.False(assert.NotEndsWith(value, "name", new string[] { "123", "fg" }, caseSensitive: true).Result); + Assert.False(assert.NotEndsWith(value, "name", new string[] { "EFG" }).Result); + Assert.True(assert.NotEndsWith(value, "name", new string[] { "123", "cd" }).Result); + Assert.True(assert.NotEndsWith(value, "name", new string[] { "123", "ab" }).Result); + Assert.True(assert.NotEndsWith(value, "name", new string[] { "abcdefgh" }).Result); + Assert.True(assert.NotEndsWith(value, "name", new string[] { "EFG" }, caseSensitive: true).Result); + Assert.True(assert.NotEndsWith(value, "name", new string[] { "123", "cd" }).Result); + + Assert.Equal("name", assert.NotEndsWith(value, "name", new string[] { "EFG" }).ToResultReason().FirstOrDefault().Path); + + // Integer + Assert.True(assert.NotEndsWith(value, "value", new string[] { "123" }).Result); + } - [Fact] - public void Contains() - { - SetContext(); - var assert = GetAssertionHelper(); - var value = GetObject((name: "name", value: "abcdefg"), (name: "value", value: 123)); - - // String - Assert.True(assert.Contains(value, "name", new string[] { "123", "ab" }).Result); - Assert.True(assert.Contains(value, "name", new string[] { "123", "ab" }, caseSensitive: true).Result); - Assert.True(assert.Contains(value, "name", new string[] { "ABC" }).Result); - Assert.True(assert.Contains(value, "name", new string[] { "123", "cd" }).Result); - Assert.True(assert.Contains(value, "name", new string[] { "123", "fg" }).Result); - Assert.False(assert.Contains(value, "name", new string[] { "abcdefgh" }).Result); - Assert.False(assert.Contains(value, "name", new string[] { "ABC" }, caseSensitive: true).Result); - Assert.True(assert.Contains(value, "name", new string[] { "123", "cd" }).Result); - - Assert.Equal("name", assert.Contains(value, "name", new string[] { "abcdefgh" }).ToResultReason().FirstOrDefault().Path); - - // Integer - Assert.False(assert.Contains(value, "value", new string[] { "123" }).Result); - } + [Fact] + public void Contains() + { + SetContext(); + var assert = GetAssertionHelper(); + var value = GetObject((name: "name", value: "abcdefg"), (name: "value", value: 123)); + + // String + Assert.True(assert.Contains(value, "name", new string[] { "123", "ab" }).Result); + Assert.True(assert.Contains(value, "name", new string[] { "123", "ab" }, caseSensitive: true).Result); + Assert.True(assert.Contains(value, "name", new string[] { "ABC" }).Result); + Assert.True(assert.Contains(value, "name", new string[] { "123", "cd" }).Result); + Assert.True(assert.Contains(value, "name", new string[] { "123", "fg" }).Result); + Assert.False(assert.Contains(value, "name", new string[] { "abcdefgh" }).Result); + Assert.False(assert.Contains(value, "name", new string[] { "ABC" }, caseSensitive: true).Result); + Assert.True(assert.Contains(value, "name", new string[] { "123", "cd" }).Result); + + Assert.Equal("name", assert.Contains(value, "name", new string[] { "abcdefgh" }).ToResultReason().FirstOrDefault().Path); + + // Integer + Assert.False(assert.Contains(value, "value", new string[] { "123" }).Result); + } - [Fact] - public void NotContains() - { - SetContext(); - var assert = GetAssertionHelper(); - var value = GetObject((name: "name", value: "abcdefg"), (name: "value", value: 123)); - - // String - Assert.False(assert.NotContains(value, "name", new string[] { "123", "ab" }).Result); - Assert.False(assert.NotContains(value, "name", new string[] { "123", "ab" }, caseSensitive: true).Result); - Assert.False(assert.NotContains(value, "name", new string[] { "ABC" }).Result); - Assert.False(assert.NotContains(value, "name", new string[] { "123", "cd" }).Result); - Assert.False(assert.NotContains(value, "name", new string[] { "123", "fg" }).Result); - Assert.True(assert.NotContains(value, "name", new string[] { "abcdefgh" }).Result); - Assert.True(assert.NotContains(value, "name", new string[] { "ABC" }, caseSensitive: true).Result); - Assert.False(assert.NotContains(value, "name", new string[] { "123", "cd" }).Result); - - Assert.Equal("name", assert.NotContains(value, "name", new string[] { "ABC" }).ToResultReason().FirstOrDefault().Path); - - // Integer - Assert.True(assert.NotContains(value, "value", new string[] { "123" }).Result); - } + [Fact] + public void NotContains() + { + SetContext(); + var assert = GetAssertionHelper(); + var value = GetObject((name: "name", value: "abcdefg"), (name: "value", value: 123)); + + // String + Assert.False(assert.NotContains(value, "name", new string[] { "123", "ab" }).Result); + Assert.False(assert.NotContains(value, "name", new string[] { "123", "ab" }, caseSensitive: true).Result); + Assert.False(assert.NotContains(value, "name", new string[] { "ABC" }).Result); + Assert.False(assert.NotContains(value, "name", new string[] { "123", "cd" }).Result); + Assert.False(assert.NotContains(value, "name", new string[] { "123", "fg" }).Result); + Assert.True(assert.NotContains(value, "name", new string[] { "abcdefgh" }).Result); + Assert.True(assert.NotContains(value, "name", new string[] { "ABC" }, caseSensitive: true).Result); + Assert.False(assert.NotContains(value, "name", new string[] { "123", "cd" }).Result); + + Assert.Equal("name", assert.NotContains(value, "name", new string[] { "ABC" }).ToResultReason().FirstOrDefault().Path); + + // Integer + Assert.True(assert.NotContains(value, "value", new string[] { "123" }).Result); + } - [Fact] - public void IsLower() - { - SetContext(); - var assert = GetAssertionHelper(); - var value = GetObject( - (name: "name1", value: "abc"), - (name: "name2", value: "aBc"), - (name: "name3", value: "123"), - (name: "name4", value: 123) - ); - - Assert.True(assert.IsLower(value, "name1").Result); - Assert.False(assert.IsLower(value, "name2").Result); - Assert.True(assert.IsLower(value, "name3").Result); - Assert.False(assert.IsLower(value, "name3", requireLetters: true).Result); - Assert.False(assert.IsLower(value, "name4").Result); - - Assert.Equal("name2", assert.IsLower(value, "name2").ToResultReason().FirstOrDefault().Path); - Assert.Equal("name3", assert.IsLower(value, "name3", requireLetters: true).ToResultReason().FirstOrDefault().Path); - Assert.Equal("name4", assert.IsLower(value, "name4").ToResultReason().FirstOrDefault().Path); - } + [Fact] + public void IsLower() + { + SetContext(); + var assert = GetAssertionHelper(); + var value = GetObject( + (name: "name1", value: "abc"), + (name: "name2", value: "aBc"), + (name: "name3", value: "123"), + (name: "name4", value: 123) + ); + + Assert.True(assert.IsLower(value, "name1").Result); + Assert.False(assert.IsLower(value, "name2").Result); + Assert.True(assert.IsLower(value, "name3").Result); + Assert.False(assert.IsLower(value, "name3", requireLetters: true).Result); + Assert.False(assert.IsLower(value, "name4").Result); + + Assert.Equal("name2", assert.IsLower(value, "name2").ToResultReason().FirstOrDefault().Path); + Assert.Equal("name3", assert.IsLower(value, "name3", requireLetters: true).ToResultReason().FirstOrDefault().Path); + Assert.Equal("name4", assert.IsLower(value, "name4").ToResultReason().FirstOrDefault().Path); + } - [Fact] - public void IsUpper() - { - SetContext(); - var assert = GetAssertionHelper(); - var value = GetObject( - (name: "name1", value: "ABC"), - (name: "name2", value: "AbC"), - (name: "name3", value: "123"), - (name: "name4", value: 123) - ); - - Assert.True(assert.IsUpper(value, "name1").Result); - Assert.False(assert.IsUpper(value, "name2").Result); - Assert.True(assert.IsUpper(value, "name3").Result); - Assert.False(assert.IsUpper(value, "name3", requireLetters: true).Result); - Assert.False(assert.IsUpper(value, "name4").Result); - - Assert.Equal("name2", assert.IsUpper(value, "name2").ToResultReason().FirstOrDefault().Path); - Assert.Equal("name3", assert.IsUpper(value, "name3", requireLetters: true).ToResultReason().FirstOrDefault().Path); - Assert.Equal("name4", assert.IsUpper(value, "name4").ToResultReason().FirstOrDefault().Path); - } + [Fact] + public void IsUpper() + { + SetContext(); + var assert = GetAssertionHelper(); + var value = GetObject( + (name: "name1", value: "ABC"), + (name: "name2", value: "AbC"), + (name: "name3", value: "123"), + (name: "name4", value: 123) + ); + + Assert.True(assert.IsUpper(value, "name1").Result); + Assert.False(assert.IsUpper(value, "name2").Result); + Assert.True(assert.IsUpper(value, "name3").Result); + Assert.False(assert.IsUpper(value, "name3", requireLetters: true).Result); + Assert.False(assert.IsUpper(value, "name4").Result); + + Assert.Equal("name2", assert.IsUpper(value, "name2").ToResultReason().FirstOrDefault().Path); + Assert.Equal("name3", assert.IsUpper(value, "name3", requireLetters: true).ToResultReason().FirstOrDefault().Path); + Assert.Equal("name4", assert.IsUpper(value, "name4").ToResultReason().FirstOrDefault().Path); + } - [Fact] - public void IsNumeric() - { - SetContext(); - var assert = GetAssertionHelper(); - var value = GetObject( - (name: "value1", value: 123), - (name: "value2", value: 1.0f), - (name: "value3", value: long.MaxValue), - (name: "value4", value: "123"), - (name: "value5", value: null), - (name: "value6", value: PSObject.AsPSObject(123)), - (name: "value7", value: byte.MaxValue), - (name: "value8", value: double.MaxValue) - ); - - Assert.True(assert.IsNumeric(value, "value1").Result); - Assert.True(assert.IsNumeric(value, "value2").Result); - Assert.True(assert.IsNumeric(value, "value3").Result); - Assert.False(assert.IsNumeric(value, "value4").Result); - Assert.True(assert.IsNumeric(value, "value4", convert: true).Result); - Assert.False(assert.IsNumeric(value, "value5").Result); - Assert.True(assert.IsNumeric(value, "value6").Result); - Assert.True(assert.IsNumeric(value, "value7").Result); - Assert.True(assert.IsNumeric(value, "value8").Result); - - Assert.Equal("value4", assert.IsNumeric(value, "value4").ToResultReason().FirstOrDefault().Path); - Assert.Equal("value5", assert.IsNumeric(value, "value5").ToResultReason().FirstOrDefault().Path); - } + [Fact] + public void IsNumeric() + { + SetContext(); + var assert = GetAssertionHelper(); + var value = GetObject( + (name: "value1", value: 123), + (name: "value2", value: 1.0f), + (name: "value3", value: long.MaxValue), + (name: "value4", value: "123"), + (name: "value5", value: null), + (name: "value6", value: PSObject.AsPSObject(123)), + (name: "value7", value: byte.MaxValue), + (name: "value8", value: double.MaxValue) + ); + + Assert.True(assert.IsNumeric(value, "value1").Result); + Assert.True(assert.IsNumeric(value, "value2").Result); + Assert.True(assert.IsNumeric(value, "value3").Result); + Assert.False(assert.IsNumeric(value, "value4").Result); + Assert.True(assert.IsNumeric(value, "value4", convert: true).Result); + Assert.False(assert.IsNumeric(value, "value5").Result); + Assert.True(assert.IsNumeric(value, "value6").Result); + Assert.True(assert.IsNumeric(value, "value7").Result); + Assert.True(assert.IsNumeric(value, "value8").Result); + + Assert.Equal("value4", assert.IsNumeric(value, "value4").ToResultReason().FirstOrDefault().Path); + Assert.Equal("value5", assert.IsNumeric(value, "value5").ToResultReason().FirstOrDefault().Path); + } - [Fact] - public void IsInteger() - { - SetContext(); - var assert = GetAssertionHelper(); - var value = GetObject( - (name: "value1", value: 123), - (name: "value2", value: 1.0f), - (name: "value3", value: long.MaxValue), - (name: "value4", value: "123"), - (name: "value5", value: null), - (name: "value6", value: PSObject.AsPSObject(123)), - (name: "value7", value: byte.MaxValue) - ); - - Assert.True(assert.IsInteger(value, "value1").Result); - Assert.False(assert.IsInteger(value, "value2").Result); - Assert.True(assert.IsInteger(value, "value3").Result); - Assert.False(assert.IsInteger(value, "value4").Result); - Assert.True(assert.IsInteger(value, "value4", convert: true).Result); - Assert.False(assert.IsInteger(value, "value5").Result); - Assert.True(assert.IsInteger(value, "value6").Result); - Assert.True(assert.IsInteger(value, "value7").Result); - - Assert.Equal("value2", assert.IsInteger(value, "value2").ToResultReason().FirstOrDefault().Path); - Assert.Equal("value4", assert.IsInteger(value, "value4").ToResultReason().FirstOrDefault().Path); - Assert.Equal("value5", assert.IsInteger(value, "value5").ToResultReason().FirstOrDefault().Path); - } + [Fact] + public void IsInteger() + { + SetContext(); + var assert = GetAssertionHelper(); + var value = GetObject( + (name: "value1", value: 123), + (name: "value2", value: 1.0f), + (name: "value3", value: long.MaxValue), + (name: "value4", value: "123"), + (name: "value5", value: null), + (name: "value6", value: PSObject.AsPSObject(123)), + (name: "value7", value: byte.MaxValue) + ); + + Assert.True(assert.IsInteger(value, "value1").Result); + Assert.False(assert.IsInteger(value, "value2").Result); + Assert.True(assert.IsInteger(value, "value3").Result); + Assert.False(assert.IsInteger(value, "value4").Result); + Assert.True(assert.IsInteger(value, "value4", convert: true).Result); + Assert.False(assert.IsInteger(value, "value5").Result); + Assert.True(assert.IsInteger(value, "value6").Result); + Assert.True(assert.IsInteger(value, "value7").Result); + + Assert.Equal("value2", assert.IsInteger(value, "value2").ToResultReason().FirstOrDefault().Path); + Assert.Equal("value4", assert.IsInteger(value, "value4").ToResultReason().FirstOrDefault().Path); + Assert.Equal("value5", assert.IsInteger(value, "value5").ToResultReason().FirstOrDefault().Path); + } - [Fact] - public void IsBool() - { - SetContext(); - var assert = GetAssertionHelper(); - var value = GetObject( - (name: "value1", value: true), - (name: "value2", value: 1), - (name: "value3", value: long.MaxValue), - (name: "value4", value: "true"), - (name: "value5", value: null), - (name: "value6", value: PSObject.AsPSObject(true)) - ); - - Assert.True(assert.IsBoolean(value, "value1").Result); - Assert.False(assert.IsBoolean(value, "value2").Result); - Assert.False(assert.IsBoolean(value, "value3").Result); - Assert.False(assert.IsBoolean(value, "value4").Result); - Assert.True(assert.IsBoolean(value, "value4", convert: true).Result); - Assert.False(assert.IsBoolean(value, "value5").Result); - Assert.True(assert.IsBoolean(value, "value6").Result); - - Assert.Equal("value2", assert.IsBoolean(value, "value2").ToResultReason().FirstOrDefault().Path); - } + [Fact] + public void IsBool() + { + SetContext(); + var assert = GetAssertionHelper(); + var value = GetObject( + (name: "value1", value: true), + (name: "value2", value: 1), + (name: "value3", value: long.MaxValue), + (name: "value4", value: "true"), + (name: "value5", value: null), + (name: "value6", value: PSObject.AsPSObject(true)) + ); + + Assert.True(assert.IsBoolean(value, "value1").Result); + Assert.False(assert.IsBoolean(value, "value2").Result); + Assert.False(assert.IsBoolean(value, "value3").Result); + Assert.False(assert.IsBoolean(value, "value4").Result); + Assert.True(assert.IsBoolean(value, "value4", convert: true).Result); + Assert.False(assert.IsBoolean(value, "value5").Result); + Assert.True(assert.IsBoolean(value, "value6").Result); + + Assert.Equal("value2", assert.IsBoolean(value, "value2").ToResultReason().FirstOrDefault().Path); + } - [Fact] - public void IsArray() - { - SetContext(); - var assert = GetAssertionHelper(); - var value = GetObject( - (name: "value1", value: new string[] { "a" }), - (name: "value2", value: new int[] { 1 }), - (name: "value3", value: PSObject.AsPSObject(new int[] { 1 })), - (name: "value4", value: "true"), - (name: "value5", value: null) - ); - - Assert.True(assert.IsArray(value, "value1").Result); - Assert.True(assert.IsArray(value, "value2").Result); - Assert.True(assert.IsArray(value, "value3").Result); - Assert.False(assert.IsArray(value, "value4").Result); - Assert.False(assert.IsArray(value, "value5").Result); - - Assert.Equal("value4", assert.IsArray(value, "value4").ToResultReason().FirstOrDefault().Path); - } + [Fact] + public void IsArray() + { + SetContext(); + var assert = GetAssertionHelper(); + var value = GetObject( + (name: "value1", value: new string[] { "a" }), + (name: "value2", value: new int[] { 1 }), + (name: "value3", value: PSObject.AsPSObject(new int[] { 1 })), + (name: "value4", value: "true"), + (name: "value5", value: null) + ); + + Assert.True(assert.IsArray(value, "value1").Result); + Assert.True(assert.IsArray(value, "value2").Result); + Assert.True(assert.IsArray(value, "value3").Result); + Assert.False(assert.IsArray(value, "value4").Result); + Assert.False(assert.IsArray(value, "value5").Result); + + Assert.Equal("value4", assert.IsArray(value, "value4").ToResultReason().FirstOrDefault().Path); + } - [Fact] - public void IsString() - { - SetContext(); - var assert = GetAssertionHelper(); - var value = GetObject( - (name: "value1", value: "true"), - (name: "value2", value: PSObject.AsPSObject("true")), - (name: "value3", value: 1), - (name: "value4", value: null) - ); - - Assert.True(assert.IsString(value, "value1").Result); - Assert.True(assert.IsString(value, "value2").Result); - Assert.False(assert.IsString(value, "value3").Result); - Assert.False(assert.IsString(value, "value4").Result); - - Assert.Equal("value4", assert.IsString(value, "value4").ToResultReason().FirstOrDefault().Path); - } + [Fact] + public void IsString() + { + SetContext(); + var assert = GetAssertionHelper(); + var value = GetObject( + (name: "value1", value: "true"), + (name: "value2", value: PSObject.AsPSObject("true")), + (name: "value3", value: 1), + (name: "value4", value: null) + ); + + Assert.True(assert.IsString(value, "value1").Result); + Assert.True(assert.IsString(value, "value2").Result); + Assert.False(assert.IsString(value, "value3").Result); + Assert.False(assert.IsString(value, "value4").Result); + + Assert.Equal("value4", assert.IsString(value, "value4").ToResultReason().FirstOrDefault().Path); + } - [Fact] - public void IsDateTime() - { - SetContext(); - var assert = GetAssertionHelper(); - var value = GetObject( - (name: "value1", value: DateTime.Now), - (name: "value2", value: 1), - (name: "value3", value: long.MaxValue), - (name: "value4", value: "2021-04-03T15:00:00.00+10:00"), - (name: "value5", value: null), - (name: "value6", value: PSObject.AsPSObject(DateTime.Now)), - (name: "value7", value: new JValue(DateTime.Now)), - (name: "value8", value: new JValue("2021-04-03T15:00:00.00+10:00")) - ); - - Assert.True(assert.IsDateTime(value, "value1").Result); - Assert.False(assert.IsDateTime(value, "value2").Result); - Assert.False(assert.IsDateTime(value, "value3").Result); - Assert.False(assert.IsDateTime(value, "value4").Result); - Assert.True(assert.IsDateTime(value, "value4", convert: true).Result); - Assert.False(assert.IsDateTime(value, "value5").Result); - Assert.True(assert.IsDateTime(value, "value6").Result); - Assert.True(assert.IsDateTime(value, "value7").Result); - Assert.True(assert.IsDateTime(value, "value8", convert: true).Result); - - Assert.Equal("value2", assert.IsDateTime(value, "value2").ToResultReason().FirstOrDefault().Path); - Assert.Equal("value5", assert.IsDateTime(value, "value5").ToResultReason().FirstOrDefault().Path); - } + [Fact] + public void IsDateTime() + { + SetContext(); + var assert = GetAssertionHelper(); + var value = GetObject( + (name: "value1", value: DateTime.Now), + (name: "value2", value: 1), + (name: "value3", value: long.MaxValue), + (name: "value4", value: "2021-04-03T15:00:00.00+10:00"), + (name: "value5", value: null), + (name: "value6", value: PSObject.AsPSObject(DateTime.Now)), + (name: "value7", value: new JValue(DateTime.Now)), + (name: "value8", value: new JValue("2021-04-03T15:00:00.00+10:00")) + ); + + Assert.True(assert.IsDateTime(value, "value1").Result); + Assert.False(assert.IsDateTime(value, "value2").Result); + Assert.False(assert.IsDateTime(value, "value3").Result); + Assert.False(assert.IsDateTime(value, "value4").Result); + Assert.True(assert.IsDateTime(value, "value4", convert: true).Result); + Assert.False(assert.IsDateTime(value, "value5").Result); + Assert.True(assert.IsDateTime(value, "value6").Result); + Assert.True(assert.IsDateTime(value, "value7").Result); + Assert.True(assert.IsDateTime(value, "value8", convert: true).Result); + + Assert.Equal("value2", assert.IsDateTime(value, "value2").ToResultReason().FirstOrDefault().Path); + Assert.Equal("value5", assert.IsDateTime(value, "value5").ToResultReason().FirstOrDefault().Path); + } - [Fact] - public void TypeOf() - { - SetContext(); - var assert = GetAssertionHelper(); - var customObect = new PSObject(); - customObect.TypeNames.Insert(0, "CustomTypeObject"); - var value = GetObject( - (name: "value1", value: "true"), - (name: "value2", value: PSObject.AsPSObject("true")), - (name: "value3", value: 1), - (name: "value4", value: null), - (name: "value5", value: customObect) - ); - - // By type - Assert.True(assert.TypeOf(value, "value1", new Type[] { typeof(string) }).Result); - Assert.True(assert.TypeOf(value, "value2", new Type[] { typeof(string) }).Result); - Assert.True(assert.TypeOf(value, "value3", new Type[] { typeof(int) }).Result); - Assert.False(assert.TypeOf(value, "value3", new Type[] { typeof(bool) }).Result); - Assert.True(assert.TypeOf(value, "value3", new Type[] { typeof(bool), typeof(int) }).Result); - Assert.False(assert.TypeOf(value, "value3", new Type[] { typeof(string) }).Result); - Assert.False(assert.TypeOf(value, "value4", new Type[] { typeof(string) }).Result); - - Assert.Equal("value3", assert.TypeOf(value, "value3", new Type[] { typeof(bool) }).ToResultReason().FirstOrDefault().Path); - - // By type name - Assert.True(assert.TypeOf(value, "value1", new string[] { "System.String" }).Result); - Assert.True(assert.TypeOf(value, "value2", new string[] { "System.String" }).Result); - Assert.True(assert.TypeOf(value, "value3", new string[] { "System.Int32" }).Result); - Assert.False(assert.TypeOf(value, "value4", new string[] { "System.Int32" }).Result); - Assert.True(assert.TypeOf(value, "value5", new string[] { "CustomTypeObject" }).Result); - - Assert.Equal("value4", assert.TypeOf(value, "value4", new string[] { "System.Int32" }).ToResultReason().FirstOrDefault().Path); - } + [Fact] + public void TypeOf() + { + SetContext(); + var assert = GetAssertionHelper(); + var customObect = new PSObject(); + customObect.TypeNames.Insert(0, "CustomTypeObject"); + var value = GetObject( + (name: "value1", value: "true"), + (name: "value2", value: PSObject.AsPSObject("true")), + (name: "value3", value: 1), + (name: "value4", value: null), + (name: "value5", value: customObect) + ); + + // By type + Assert.True(assert.TypeOf(value, "value1", new Type[] { typeof(string) }).Result); + Assert.True(assert.TypeOf(value, "value2", new Type[] { typeof(string) }).Result); + Assert.True(assert.TypeOf(value, "value3", new Type[] { typeof(int) }).Result); + Assert.False(assert.TypeOf(value, "value3", new Type[] { typeof(bool) }).Result); + Assert.True(assert.TypeOf(value, "value3", new Type[] { typeof(bool), typeof(int) }).Result); + Assert.False(assert.TypeOf(value, "value3", new Type[] { typeof(string) }).Result); + Assert.False(assert.TypeOf(value, "value4", new Type[] { typeof(string) }).Result); + + Assert.Equal("value3", assert.TypeOf(value, "value3", new Type[] { typeof(bool) }).ToResultReason().FirstOrDefault().Path); + + // By type name + Assert.True(assert.TypeOf(value, "value1", new string[] { "System.String" }).Result); + Assert.True(assert.TypeOf(value, "value2", new string[] { "System.String" }).Result); + Assert.True(assert.TypeOf(value, "value3", new string[] { "System.Int32" }).Result); + Assert.False(assert.TypeOf(value, "value4", new string[] { "System.Int32" }).Result); + Assert.True(assert.TypeOf(value, "value5", new string[] { "CustomTypeObject" }).Result); + + Assert.Equal("value4", assert.TypeOf(value, "value4", new string[] { "System.Int32" }).ToResultReason().FirstOrDefault().Path); + } - [Fact] - public void Version() - { - SetContext(); - var assert = GetAssertionHelper(); - var value = GetObject( - (name: "version", value: "1.2.3"), - (name: "version2", value: "1.2.3-alpha.7"), - (name: "version3", value: "3.4.5-alpha.9"), - (name: "notversion", value: "x.y.z") - ); - - Assert.True(assert.Version(value, "version", "1.2.3").Result); - Assert.False(assert.Version(value, "version", "0.2.3").Result); - Assert.False(assert.Version(value, "version", "2.2.3").Result); - Assert.False(assert.Version(value, "version", "1.1.3").Result); - Assert.False(assert.Version(value, "version", "1.3.3").Result); - Assert.False(assert.Version(value, "version", "1.2.2").Result); - Assert.False(assert.Version(value, "version", "1.2.4").Result); - - Assert.True(assert.Version(value, "version", "v1.2.3").Result); - Assert.True(assert.Version(value, "version", "V1.2.3").Result); - Assert.True(assert.Version(value, "version", "=1.2.3").Result); - Assert.False(assert.Version(value, "version", "=0.2.3").Result); - Assert.False(assert.Version(value, "version", "=2.2.3").Result); - Assert.False(assert.Version(value, "version", "=1.1.3").Result); - Assert.False(assert.Version(value, "version", "=1.3.3").Result); - Assert.False(assert.Version(value, "version", "=1.2.2").Result); - Assert.False(assert.Version(value, "version", "=1.2.4").Result); - - Assert.True(assert.Version(value, "version", "^1.2.3").Result); - Assert.False(assert.Version(value, "version", "^0.2.3").Result); - Assert.False(assert.Version(value, "version", "^2.2.3").Result); - Assert.True(assert.Version(value, "version", "^1.1.3").Result); - Assert.False(assert.Version(value, "version", "^1.3.3").Result); - Assert.True(assert.Version(value, "version", "^1.2.2").Result); - Assert.False(assert.Version(value, "version", "^1.2.4").Result); - - Assert.True(assert.Version(value, "version", "~1.2.3").Result); - Assert.False(assert.Version(value, "version", "~0.2.3").Result); - Assert.False(assert.Version(value, "version", "~2.2.3").Result); - Assert.False(assert.Version(value, "version", "~1.1.3").Result); - Assert.False(assert.Version(value, "version", "~1.3.3").Result); - Assert.True(assert.Version(value, "version", "~1.2.2").Result); - Assert.False(assert.Version(value, "version", "~1.2.4").Result); - - Assert.True(assert.Version(value, "version", "1.x").Result); - Assert.True(assert.Version(value, "version", "1.X.x").Result); - Assert.True(assert.Version(value, "version", "1.*").Result); - Assert.True(assert.Version(value, "version", "*").Result); - Assert.True(assert.Version(value, "version", "").Result); - Assert.True(assert.Version(value, "version").Result); - Assert.False(assert.Version(value, "version", "1.3.x").Result); - - Assert.False(assert.Version(value, "version", ">1.2.3").Result); - Assert.True(assert.Version(value, "version", ">0.2.3").Result); - Assert.False(assert.Version(value, "version", ">2.2.3").Result); - Assert.True(assert.Version(value, "version", ">1.1.3").Result); - Assert.False(assert.Version(value, "version", ">1.3.3").Result); - Assert.True(assert.Version(value, "version", ">1.2.2").Result); - Assert.False(assert.Version(value, "version", ">1.2.4").Result); - - Assert.True(assert.Version(value, "version", ">=1.2.3").Result); - Assert.True(assert.Version(value, "version", ">=0.2.3").Result); - Assert.False(assert.Version(value, "version", ">=2.2.3").Result); - Assert.True(assert.Version(value, "version", ">=1.1.3").Result); - Assert.False(assert.Version(value, "version", ">=1.3.3").Result); - Assert.True(assert.Version(value, "version", ">=1.2.2").Result); - Assert.False(assert.Version(value, "version", ">=1.2.4").Result); - - Assert.False(assert.Version(value, "version", "<1.2.3").Result); - Assert.False(assert.Version(value, "version", "<0.2.3").Result); - Assert.True(assert.Version(value, "version", "<2.2.3").Result); - Assert.False(assert.Version(value, "version", "<1.1.3").Result); - Assert.True(assert.Version(value, "version", "<1.3.3").Result); - Assert.False(assert.Version(value, "version", "<1.2.2").Result); - Assert.True(assert.Version(value, "version", "<1.2.4").Result); - - Assert.True(assert.Version(value, "version", "<=1.2.3").Result); - Assert.False(assert.Version(value, "version", "<=0.2.3").Result); - Assert.True(assert.Version(value, "version", "<=2.2.3").Result); - Assert.False(assert.Version(value, "version", "<=1.1.3").Result); - Assert.True(assert.Version(value, "version", "<=1.3.3").Result); - Assert.False(assert.Version(value, "version", "<=1.2.2").Result); - Assert.True(assert.Version(value, "version", "<=1.2.4").Result); - - Assert.True(assert.Version(value, "version", ">1.0.0").Result); - Assert.True(assert.Version(value, "version", "<2.0.0").Result); - - Assert.True(assert.Version(value, "version", ">1.2.3-alpha.3").Result); - Assert.True(assert.Version(value, "version2", ">1.2.3-alpha.3").Result); - Assert.False(assert.Version(value, "version3", ">1.2.3-alpha.3").Result); - - Assert.True(assert.Version(value, "version", ">=1.2.3-0").Result); - Assert.True(assert.Version(value, "version2", ">=1.2.3-0").Result); - - Assert.True(assert.Version(value, "version", ">=1.2.3", includePrerelease: true).Result); - Assert.False(assert.Version(value, "version2", ">=1.2.3", includePrerelease: true).Result); - Assert.True(assert.Version(value, "version3", ">=1.2.3", includePrerelease: true).Result); - - Assert.False(assert.Version(value, "notversion", null).Result); - Assert.Throws(() => assert.Version(value, "version", "2.0.0<").Result); - Assert.Throws(() => assert.Version(value, "version", "z2.0.0").Result); - - Assert.Equal("version", assert.Version(value, "version", "<=0.2.3").ToResultReason().FirstOrDefault().Path); - Assert.Equal("notversion", assert.Version(value, "notversion", null).ToResultReason().FirstOrDefault().Path); - } + [Fact] + public void Version() + { + SetContext(); + var assert = GetAssertionHelper(); + var value = GetObject( + (name: "version", value: "1.2.3"), + (name: "version2", value: "1.2.3-alpha.7"), + (name: "version3", value: "3.4.5-alpha.9"), + (name: "notversion", value: "x.y.z") + ); + + Assert.True(assert.Version(value, "version", "1.2.3").Result); + Assert.False(assert.Version(value, "version", "0.2.3").Result); + Assert.False(assert.Version(value, "version", "2.2.3").Result); + Assert.False(assert.Version(value, "version", "1.1.3").Result); + Assert.False(assert.Version(value, "version", "1.3.3").Result); + Assert.False(assert.Version(value, "version", "1.2.2").Result); + Assert.False(assert.Version(value, "version", "1.2.4").Result); + + Assert.True(assert.Version(value, "version", "v1.2.3").Result); + Assert.True(assert.Version(value, "version", "V1.2.3").Result); + Assert.True(assert.Version(value, "version", "=1.2.3").Result); + Assert.False(assert.Version(value, "version", "=0.2.3").Result); + Assert.False(assert.Version(value, "version", "=2.2.3").Result); + Assert.False(assert.Version(value, "version", "=1.1.3").Result); + Assert.False(assert.Version(value, "version", "=1.3.3").Result); + Assert.False(assert.Version(value, "version", "=1.2.2").Result); + Assert.False(assert.Version(value, "version", "=1.2.4").Result); + + Assert.True(assert.Version(value, "version", "^1.2.3").Result); + Assert.False(assert.Version(value, "version", "^0.2.3").Result); + Assert.False(assert.Version(value, "version", "^2.2.3").Result); + Assert.True(assert.Version(value, "version", "^1.1.3").Result); + Assert.False(assert.Version(value, "version", "^1.3.3").Result); + Assert.True(assert.Version(value, "version", "^1.2.2").Result); + Assert.False(assert.Version(value, "version", "^1.2.4").Result); + + Assert.True(assert.Version(value, "version", "~1.2.3").Result); + Assert.False(assert.Version(value, "version", "~0.2.3").Result); + Assert.False(assert.Version(value, "version", "~2.2.3").Result); + Assert.False(assert.Version(value, "version", "~1.1.3").Result); + Assert.False(assert.Version(value, "version", "~1.3.3").Result); + Assert.True(assert.Version(value, "version", "~1.2.2").Result); + Assert.False(assert.Version(value, "version", "~1.2.4").Result); + + Assert.True(assert.Version(value, "version", "1.x").Result); + Assert.True(assert.Version(value, "version", "1.X.x").Result); + Assert.True(assert.Version(value, "version", "1.*").Result); + Assert.True(assert.Version(value, "version", "*").Result); + Assert.True(assert.Version(value, "version", "").Result); + Assert.True(assert.Version(value, "version").Result); + Assert.False(assert.Version(value, "version", "1.3.x").Result); + + Assert.False(assert.Version(value, "version", ">1.2.3").Result); + Assert.True(assert.Version(value, "version", ">0.2.3").Result); + Assert.False(assert.Version(value, "version", ">2.2.3").Result); + Assert.True(assert.Version(value, "version", ">1.1.3").Result); + Assert.False(assert.Version(value, "version", ">1.3.3").Result); + Assert.True(assert.Version(value, "version", ">1.2.2").Result); + Assert.False(assert.Version(value, "version", ">1.2.4").Result); + + Assert.True(assert.Version(value, "version", ">=1.2.3").Result); + Assert.True(assert.Version(value, "version", ">=0.2.3").Result); + Assert.False(assert.Version(value, "version", ">=2.2.3").Result); + Assert.True(assert.Version(value, "version", ">=1.1.3").Result); + Assert.False(assert.Version(value, "version", ">=1.3.3").Result); + Assert.True(assert.Version(value, "version", ">=1.2.2").Result); + Assert.False(assert.Version(value, "version", ">=1.2.4").Result); + + Assert.False(assert.Version(value, "version", "<1.2.3").Result); + Assert.False(assert.Version(value, "version", "<0.2.3").Result); + Assert.True(assert.Version(value, "version", "<2.2.3").Result); + Assert.False(assert.Version(value, "version", "<1.1.3").Result); + Assert.True(assert.Version(value, "version", "<1.3.3").Result); + Assert.False(assert.Version(value, "version", "<1.2.2").Result); + Assert.True(assert.Version(value, "version", "<1.2.4").Result); + + Assert.True(assert.Version(value, "version", "<=1.2.3").Result); + Assert.False(assert.Version(value, "version", "<=0.2.3").Result); + Assert.True(assert.Version(value, "version", "<=2.2.3").Result); + Assert.False(assert.Version(value, "version", "<=1.1.3").Result); + Assert.True(assert.Version(value, "version", "<=1.3.3").Result); + Assert.False(assert.Version(value, "version", "<=1.2.2").Result); + Assert.True(assert.Version(value, "version", "<=1.2.4").Result); + + Assert.True(assert.Version(value, "version", ">1.0.0").Result); + Assert.True(assert.Version(value, "version", "<2.0.0").Result); + + Assert.True(assert.Version(value, "version", ">1.2.3-alpha.3").Result); + Assert.True(assert.Version(value, "version2", ">1.2.3-alpha.3").Result); + Assert.False(assert.Version(value, "version3", ">1.2.3-alpha.3").Result); + + Assert.True(assert.Version(value, "version", ">=1.2.3-0").Result); + Assert.True(assert.Version(value, "version2", ">=1.2.3-0").Result); + + Assert.True(assert.Version(value, "version", ">=1.2.3", includePrerelease: true).Result); + Assert.False(assert.Version(value, "version2", ">=1.2.3", includePrerelease: true).Result); + Assert.True(assert.Version(value, "version3", ">=1.2.3", includePrerelease: true).Result); + + Assert.False(assert.Version(value, "notversion", null).Result); + Assert.Throws(() => assert.Version(value, "version", "2.0.0<").Result); + Assert.Throws(() => assert.Version(value, "version", "z2.0.0").Result); + + Assert.Equal("version", assert.Version(value, "version", "<=0.2.3").ToResultReason().FirstOrDefault().Path); + Assert.Equal("notversion", assert.Version(value, "notversion", null).ToResultReason().FirstOrDefault().Path); + } - [Fact] - public void APIVersion() - { - SetContext(); - var assert = GetAssertionHelper(); - var value = GetObject( - (name: "version", value: "2015-10-01"), - (name: "version2", value: "2015-10-01-preview"), - (name: "version3", value: "2022-01-01-preview"), - (name: "notversion", value: "x.y.z") - ); - - Assert.True(assert.APIVersion(value, "version", "2015-10-01").Result); - Assert.True(assert.APIVersion(value, "version", "=2015-10-01").Result); - Assert.True(assert.APIVersion(value, "version", ">=2015-10-01").Result); - Assert.False(assert.APIVersion(value, "version", "<2015-10-01").Result); - Assert.True(assert.APIVersion(value, "version", ">2014-01-01").Result); - Assert.True(assert.APIVersion(value, "version", ">=2015-10-01-preview").Result); - Assert.True(assert.APIVersion(value, "version", ">=2015-10-01-preview", includePrerelease: true).Result); - - Assert.False(assert.APIVersion(value, "version2", "2015-10-01").Result); - Assert.False(assert.APIVersion(value, "version2", "=2015-10-01").Result); - Assert.False(assert.APIVersion(value, "version2", ">=2015-10-01").Result); - Assert.False(assert.APIVersion(value, "version2", "<2015-10-01").Result); - Assert.False(assert.APIVersion(value, "version2", ">2014-01-01").Result); - Assert.True(assert.APIVersion(value, "version2", ">=2015-10-01-preview").Result); - Assert.True(assert.APIVersion(value, "version2", ">=2015-10-01-preview", includePrerelease: true).Result); - - Assert.False(assert.APIVersion(value, "version3", "2015-10-01").Result); - Assert.False(assert.APIVersion(value, "version3", "=2015-10-01").Result); - Assert.False(assert.APIVersion(value, "version3", ">=2015-10-01").Result); - Assert.False(assert.APIVersion(value, "version3", "<2015-10-01").Result); - Assert.False(assert.APIVersion(value, "version3", ">2014-01-01").Result); - Assert.False(assert.APIVersion(value, "version3", ">=2015-10-01-preview").Result); - Assert.True(assert.APIVersion(value, "version3", ">=2015-10-01-preview", includePrerelease: true).Result); - } + [Fact] + public void APIVersion() + { + SetContext(); + var assert = GetAssertionHelper(); + var value = GetObject( + (name: "version", value: "2015-10-01"), + (name: "version2", value: "2015-10-01-preview"), + (name: "version3", value: "2022-01-01-preview"), + (name: "notversion", value: "x.y.z") + ); + + Assert.True(assert.APIVersion(value, "version", "2015-10-01").Result); + Assert.True(assert.APIVersion(value, "version", "=2015-10-01").Result); + Assert.True(assert.APIVersion(value, "version", ">=2015-10-01").Result); + Assert.False(assert.APIVersion(value, "version", "<2015-10-01").Result); + Assert.True(assert.APIVersion(value, "version", ">2014-01-01").Result); + Assert.True(assert.APIVersion(value, "version", ">=2015-10-01-preview").Result); + Assert.True(assert.APIVersion(value, "version", ">=2015-10-01-preview", includePrerelease: true).Result); + + Assert.False(assert.APIVersion(value, "version2", "2015-10-01").Result); + Assert.False(assert.APIVersion(value, "version2", "=2015-10-01").Result); + Assert.False(assert.APIVersion(value, "version2", ">=2015-10-01").Result); + Assert.False(assert.APIVersion(value, "version2", "<2015-10-01").Result); + Assert.False(assert.APIVersion(value, "version2", ">2014-01-01").Result); + Assert.True(assert.APIVersion(value, "version2", ">=2015-10-01-preview").Result); + Assert.True(assert.APIVersion(value, "version2", ">=2015-10-01-preview", includePrerelease: true).Result); + + Assert.False(assert.APIVersion(value, "version3", "2015-10-01").Result); + Assert.False(assert.APIVersion(value, "version3", "=2015-10-01").Result); + Assert.False(assert.APIVersion(value, "version3", ">=2015-10-01").Result); + Assert.False(assert.APIVersion(value, "version3", "<2015-10-01").Result); + Assert.False(assert.APIVersion(value, "version3", ">2014-01-01").Result); + Assert.False(assert.APIVersion(value, "version3", ">=2015-10-01-preview").Result); + Assert.True(assert.APIVersion(value, "version3", ">=2015-10-01-preview", includePrerelease: true).Result); + } - [Fact] - public void Greater() - { - SetContext(); - var assert = GetAssertionHelper(); - var value = GetObject((name: "value", value: 3), (name: "jvalue", value: new JValue(3))); - - // Int - Assert.True(assert.Greater(value, "value", 2).Result); - Assert.False(assert.Greater(value, "value", 3).Result); - Assert.False(assert.Greater(value, "value", 4).Result); - Assert.True(assert.Greater(value, "value", 0).Result); - Assert.True(assert.Greater(value, "value", -1).Result); - Assert.False(assert.Greater(value, "jvalue", 4).Result); - - Assert.Equal("value", assert.Greater(value, "value", 3).ToResultReason().FirstOrDefault().Path); - Assert.Equal("jvalue", assert.Greater(value, "jvalue", 4).ToResultReason().FirstOrDefault().Path); - - // ULong - value = GetObject((name: "value", value: (ulong)3), (name: "jvalue", value: new JValue((ulong)3))); - Assert.True(assert.Greater(value, "value", 2).Result); - Assert.False(assert.Greater(value, "value", 3).Result); - Assert.False(assert.Greater(value, "value", 4).Result); - Assert.True(assert.Greater(value, "value", 0).Result); - Assert.True(assert.Greater(value, "value", -1).Result); - Assert.False(assert.Greater(value, "jvalue", 4).Result); - - Assert.Equal("value", assert.Greater(value, "value", 3).ToResultReason().FirstOrDefault().Path); - - // String - value = GetObject((name: "value", value: "abc")); - Assert.True(assert.Greater(value, "value", 2).Result); - Assert.False(assert.Greater(value, "value", 3).Result); - Assert.False(assert.Greater(value, "value", 4).Result); - Assert.True(assert.Greater(value, "value", 0).Result); - Assert.True(assert.Greater(value, "value", -1).Result); - - Assert.Equal("value", assert.Greater(value, "value", 3).ToResultReason().FirstOrDefault().Path); - - // Array - value = GetObject((name: "value", value: new string[] { "1", "2", "3" })); - Assert.True(assert.Greater(value, "value", 2).Result); - Assert.False(assert.Greater(value, "value", 3).Result); - Assert.False(assert.Greater(value, "value", 4).Result); - Assert.True(assert.Greater(value, "value", 0).Result); - Assert.True(assert.Greater(value, "value", -1).Result); - - // DateTime - value = GetObject((name: "value", value: DateTime.Now.AddDays(4))); - Assert.True(assert.Greater(value, "value", 2).Result); - Assert.True(assert.Greater(value, "value", 3).Result); - Assert.False(assert.Greater(value, "value", 5).Result); - Assert.True(assert.Greater(value, "value", 0).Result); - Assert.True(assert.Greater(value, "value", -1).Result); - - // Self - Assert.True(assert.Greater(3, ".", 2).Result); - - // Convert from string - value = GetObject((name: "value", value: "3")); - Assert.True(assert.Greater(value, "value", 2, convert: true).Result); - Assert.False(assert.Greater(value, "value", 2, convert: false).Result); - value = GetObject((name: "value", value: (4.5).ToString())); - Assert.True(assert.Greater(value, "value", 2, convert: true).Result); - Assert.False(assert.Greater(value, "value", 4, convert: false).Result); - - Assert.Equal("value", assert.Greater(value, "value", 4, convert: false).ToResultReason().FirstOrDefault().Path); - } + [Fact] + public void Greater() + { + SetContext(); + var assert = GetAssertionHelper(); + var value = GetObject((name: "value", value: 3), (name: "jvalue", value: new JValue(3))); + + // Int + Assert.True(assert.Greater(value, "value", 2).Result); + Assert.False(assert.Greater(value, "value", 3).Result); + Assert.False(assert.Greater(value, "value", 4).Result); + Assert.True(assert.Greater(value, "value", 0).Result); + Assert.True(assert.Greater(value, "value", -1).Result); + Assert.False(assert.Greater(value, "jvalue", 4).Result); + + Assert.Equal("value", assert.Greater(value, "value", 3).ToResultReason().FirstOrDefault().Path); + Assert.Equal("jvalue", assert.Greater(value, "jvalue", 4).ToResultReason().FirstOrDefault().Path); + + // ULong + value = GetObject((name: "value", value: (ulong)3), (name: "jvalue", value: new JValue((ulong)3))); + Assert.True(assert.Greater(value, "value", 2).Result); + Assert.False(assert.Greater(value, "value", 3).Result); + Assert.False(assert.Greater(value, "value", 4).Result); + Assert.True(assert.Greater(value, "value", 0).Result); + Assert.True(assert.Greater(value, "value", -1).Result); + Assert.False(assert.Greater(value, "jvalue", 4).Result); + + Assert.Equal("value", assert.Greater(value, "value", 3).ToResultReason().FirstOrDefault().Path); + + // String + value = GetObject((name: "value", value: "abc")); + Assert.True(assert.Greater(value, "value", 2).Result); + Assert.False(assert.Greater(value, "value", 3).Result); + Assert.False(assert.Greater(value, "value", 4).Result); + Assert.True(assert.Greater(value, "value", 0).Result); + Assert.True(assert.Greater(value, "value", -1).Result); + + Assert.Equal("value", assert.Greater(value, "value", 3).ToResultReason().FirstOrDefault().Path); + + // Array + value = GetObject((name: "value", value: new string[] { "1", "2", "3" })); + Assert.True(assert.Greater(value, "value", 2).Result); + Assert.False(assert.Greater(value, "value", 3).Result); + Assert.False(assert.Greater(value, "value", 4).Result); + Assert.True(assert.Greater(value, "value", 0).Result); + Assert.True(assert.Greater(value, "value", -1).Result); + + // DateTime + value = GetObject((name: "value", value: DateTime.Now.AddDays(4))); + Assert.True(assert.Greater(value, "value", 2).Result); + Assert.True(assert.Greater(value, "value", 3).Result); + Assert.False(assert.Greater(value, "value", 5).Result); + Assert.True(assert.Greater(value, "value", 0).Result); + Assert.True(assert.Greater(value, "value", -1).Result); + + // Self + Assert.True(assert.Greater(3, ".", 2).Result); + + // Convert from string + value = GetObject((name: "value", value: "3")); + Assert.True(assert.Greater(value, "value", 2, convert: true).Result); + Assert.False(assert.Greater(value, "value", 2, convert: false).Result); + value = GetObject((name: "value", value: (4.5).ToString())); + Assert.True(assert.Greater(value, "value", 2, convert: true).Result); + Assert.False(assert.Greater(value, "value", 4, convert: false).Result); + + Assert.Equal("value", assert.Greater(value, "value", 4, convert: false).ToResultReason().FirstOrDefault().Path); + } - [Fact] - public void GreaterOrEqual() - { - SetContext(); - var assert = GetAssertionHelper(); - var value = GetObject((name: "value", value: 3), (name: "jvalue", value: new JValue(3))); - - // Int - Assert.True(assert.GreaterOrEqual(value, "value", 2).Result); - Assert.True(assert.GreaterOrEqual(value, "value", 3).Result); - Assert.False(assert.GreaterOrEqual(value, "value", 4).Result); - Assert.True(assert.GreaterOrEqual(value, "value", 0).Result); - Assert.True(assert.GreaterOrEqual(value, "value", -1).Result); - Assert.False(assert.GreaterOrEqual(value, "jvalue", 4).Result); - - Assert.Equal("jvalue", assert.GreaterOrEqual(value, "jvalue", 4).ToResultReason().FirstOrDefault().Path); - - // ULong - value = GetObject((name: "value", value: (ulong)3), (name: "jvalue", value: new JValue((ulong)3))); - Assert.True(assert.GreaterOrEqual(value, "value", 2).Result); - Assert.True(assert.GreaterOrEqual(value, "value", 3).Result); - Assert.False(assert.GreaterOrEqual(value, "value", 4).Result); - Assert.True(assert.GreaterOrEqual(value, "value", 0).Result); - Assert.True(assert.GreaterOrEqual(value, "value", -1).Result); - Assert.False(assert.GreaterOrEqual(value, "jvalue", 4).Result); - - Assert.Equal("jvalue", assert.GreaterOrEqual(value, "jvalue", 4).ToResultReason().FirstOrDefault().Path); - - // String - value = GetObject((name: "value", value: "abc")); - Assert.True(assert.GreaterOrEqual(value, "value", 2).Result); - Assert.True(assert.GreaterOrEqual(value, "value", 3).Result); - Assert.False(assert.GreaterOrEqual(value, "value", 4).Result); - Assert.True(assert.GreaterOrEqual(value, "value", 0).Result); - Assert.True(assert.GreaterOrEqual(value, "value", -1).Result); - - Assert.Equal("value", assert.GreaterOrEqual(value, "value", 4).ToResultReason().FirstOrDefault().Path); - - // Array - value = GetObject((name: "value", value: new string[] { "1", "2", "3" })); - Assert.True(assert.GreaterOrEqual(value, "value", 2).Result); - Assert.True(assert.GreaterOrEqual(value, "value", 3).Result); - Assert.False(assert.GreaterOrEqual(value, "value", 4).Result); - Assert.True(assert.GreaterOrEqual(value, "value", 0).Result); - Assert.True(assert.GreaterOrEqual(value, "value", -1).Result); - - // DateTime - value = GetObject((name: "value", value: DateTime.Now.AddDays(4))); - Assert.True(assert.GreaterOrEqual(value, "value", 2).Result); - Assert.True(assert.GreaterOrEqual(value, "value", 3).Result); - Assert.False(assert.GreaterOrEqual(value, "value", 5).Result); - Assert.True(assert.GreaterOrEqual(value, "value", 0).Result); - Assert.True(assert.GreaterOrEqual(value, "value", -1).Result); - - // Self - Assert.True(assert.GreaterOrEqual(2, ".", 2).Result); - - // Convert from string - value = GetObject((name: "value", value: "3")); - Assert.True(assert.GreaterOrEqual(value, "value", 2, convert: true).Result); - Assert.False(assert.GreaterOrEqual(value, "value", 2, convert: false).Result); - value = GetObject((name: "value", value: (4.5).ToString())); - Assert.True(assert.GreaterOrEqual(value, "value", 2, convert: true).Result); - Assert.False(assert.GreaterOrEqual(value, "value", 4, convert: false).Result); - - Assert.Equal("value", assert.GreaterOrEqual(value, "value", 4, convert: false).ToResultReason().FirstOrDefault().Path); - } + [Fact] + public void GreaterOrEqual() + { + SetContext(); + var assert = GetAssertionHelper(); + var value = GetObject((name: "value", value: 3), (name: "jvalue", value: new JValue(3))); + + // Int + Assert.True(assert.GreaterOrEqual(value, "value", 2).Result); + Assert.True(assert.GreaterOrEqual(value, "value", 3).Result); + Assert.False(assert.GreaterOrEqual(value, "value", 4).Result); + Assert.True(assert.GreaterOrEqual(value, "value", 0).Result); + Assert.True(assert.GreaterOrEqual(value, "value", -1).Result); + Assert.False(assert.GreaterOrEqual(value, "jvalue", 4).Result); + + Assert.Equal("jvalue", assert.GreaterOrEqual(value, "jvalue", 4).ToResultReason().FirstOrDefault().Path); + + // ULong + value = GetObject((name: "value", value: (ulong)3), (name: "jvalue", value: new JValue((ulong)3))); + Assert.True(assert.GreaterOrEqual(value, "value", 2).Result); + Assert.True(assert.GreaterOrEqual(value, "value", 3).Result); + Assert.False(assert.GreaterOrEqual(value, "value", 4).Result); + Assert.True(assert.GreaterOrEqual(value, "value", 0).Result); + Assert.True(assert.GreaterOrEqual(value, "value", -1).Result); + Assert.False(assert.GreaterOrEqual(value, "jvalue", 4).Result); + + Assert.Equal("jvalue", assert.GreaterOrEqual(value, "jvalue", 4).ToResultReason().FirstOrDefault().Path); + + // String + value = GetObject((name: "value", value: "abc")); + Assert.True(assert.GreaterOrEqual(value, "value", 2).Result); + Assert.True(assert.GreaterOrEqual(value, "value", 3).Result); + Assert.False(assert.GreaterOrEqual(value, "value", 4).Result); + Assert.True(assert.GreaterOrEqual(value, "value", 0).Result); + Assert.True(assert.GreaterOrEqual(value, "value", -1).Result); + + Assert.Equal("value", assert.GreaterOrEqual(value, "value", 4).ToResultReason().FirstOrDefault().Path); + + // Array + value = GetObject((name: "value", value: new string[] { "1", "2", "3" })); + Assert.True(assert.GreaterOrEqual(value, "value", 2).Result); + Assert.True(assert.GreaterOrEqual(value, "value", 3).Result); + Assert.False(assert.GreaterOrEqual(value, "value", 4).Result); + Assert.True(assert.GreaterOrEqual(value, "value", 0).Result); + Assert.True(assert.GreaterOrEqual(value, "value", -1).Result); + + // DateTime + value = GetObject((name: "value", value: DateTime.Now.AddDays(4))); + Assert.True(assert.GreaterOrEqual(value, "value", 2).Result); + Assert.True(assert.GreaterOrEqual(value, "value", 3).Result); + Assert.False(assert.GreaterOrEqual(value, "value", 5).Result); + Assert.True(assert.GreaterOrEqual(value, "value", 0).Result); + Assert.True(assert.GreaterOrEqual(value, "value", -1).Result); + + // Self + Assert.True(assert.GreaterOrEqual(2, ".", 2).Result); + + // Convert from string + value = GetObject((name: "value", value: "3")); + Assert.True(assert.GreaterOrEqual(value, "value", 2, convert: true).Result); + Assert.False(assert.GreaterOrEqual(value, "value", 2, convert: false).Result); + value = GetObject((name: "value", value: (4.5).ToString())); + Assert.True(assert.GreaterOrEqual(value, "value", 2, convert: true).Result); + Assert.False(assert.GreaterOrEqual(value, "value", 4, convert: false).Result); + + Assert.Equal("value", assert.GreaterOrEqual(value, "value", 4, convert: false).ToResultReason().FirstOrDefault().Path); + } - [Fact] - public void Less() - { - SetContext(); - var assert = GetAssertionHelper(); - var value = GetObject((name: "value", value: 3), (name: "jvalue", value: new JValue(3))); - - // Int - Assert.False(assert.Less(value, "value", 2).Result); - Assert.False(assert.Less(value, "value", 3).Result); - Assert.True(assert.Less(value, "value", 4).Result); - Assert.False(assert.Less(value, "value", 0).Result); - Assert.False(assert.Less(value, "value", -1).Result); - Assert.True(assert.Less(value, "jvalue", 4).Result); - - Assert.Equal("value", assert.Less(value, "value", -1).ToResultReason().FirstOrDefault().Path); - - // ULong - value = GetObject((name: "value", value: (ulong)3), (name: "jvalue", value: new JValue((ulong)3))); - Assert.False(assert.Less(value, "value", 2).Result); - Assert.False(assert.Less(value, "value", 3).Result); - Assert.True(assert.Less(value, "value", 4).Result); - Assert.False(assert.Less(value, "value", 0).Result); - Assert.False(assert.Less(value, "value", -1).Result); - Assert.True(assert.Less(value, "jvalue", 4).Result); - - Assert.Equal("value", assert.Less(value, "value", -1).ToResultReason().FirstOrDefault().Path); - - // String - value = GetObject((name: "value", value: "abc")); - Assert.False(assert.Less(value, "value", 2).Result); - Assert.False(assert.Less(value, "value", 3).Result); - Assert.True(assert.Less(value, "value", 4).Result); - Assert.False(assert.Less(value, "value", 0).Result); - Assert.False(assert.Less(value, "value", -1).Result); - - Assert.Equal("value", assert.Less(value, "value", -1).ToResultReason().FirstOrDefault().Path); - - // Array - value = GetObject((name: "value", value: new string[] { "1", "2", "3" })); - Assert.False(assert.Less(value, "value", 2).Result); - Assert.False(assert.Less(value, "value", 3).Result); - Assert.True(assert.Less(value, "value", 4).Result); - Assert.False(assert.Less(value, "value", 0).Result); - Assert.False(assert.Less(value, "value", -1).Result); - - // DateTime - value = GetObject((name: "value", value: DateTime.Now.AddDays(4))); - Assert.False(assert.Less(value, "value", 2).Result); - Assert.False(assert.Less(value, "value", 3).Result); - Assert.True(assert.Less(value, "value", 5).Result); - Assert.False(assert.Less(value, "value", 0).Result); - Assert.False(assert.Less(value, "value", -1).Result); - - // Self - Assert.True(assert.Less(1, ".", 2).Result); - - // Convert from string - value = GetObject((name: "value", value: "3")); - Assert.False(assert.Less(value, "value", 2, convert: true).Result); - Assert.True(assert.Less(value, "value", 2, convert: false).Result); - value = GetObject((name: "value", value: (4.5).ToString())); - - Assert.False(assert.Less(value, "value", 4, convert: true).Result); - Assert.True(assert.Less(value, "value", 4, convert: false).Result); - - Assert.Equal("value", assert.Less(value, "value", 4, convert: true).ToResultReason().FirstOrDefault().Path); - } + [Fact] + public void Less() + { + SetContext(); + var assert = GetAssertionHelper(); + var value = GetObject((name: "value", value: 3), (name: "jvalue", value: new JValue(3))); + + // Int + Assert.False(assert.Less(value, "value", 2).Result); + Assert.False(assert.Less(value, "value", 3).Result); + Assert.True(assert.Less(value, "value", 4).Result); + Assert.False(assert.Less(value, "value", 0).Result); + Assert.False(assert.Less(value, "value", -1).Result); + Assert.True(assert.Less(value, "jvalue", 4).Result); + + Assert.Equal("value", assert.Less(value, "value", -1).ToResultReason().FirstOrDefault().Path); + + // ULong + value = GetObject((name: "value", value: (ulong)3), (name: "jvalue", value: new JValue((ulong)3))); + Assert.False(assert.Less(value, "value", 2).Result); + Assert.False(assert.Less(value, "value", 3).Result); + Assert.True(assert.Less(value, "value", 4).Result); + Assert.False(assert.Less(value, "value", 0).Result); + Assert.False(assert.Less(value, "value", -1).Result); + Assert.True(assert.Less(value, "jvalue", 4).Result); + + Assert.Equal("value", assert.Less(value, "value", -1).ToResultReason().FirstOrDefault().Path); + + // String + value = GetObject((name: "value", value: "abc")); + Assert.False(assert.Less(value, "value", 2).Result); + Assert.False(assert.Less(value, "value", 3).Result); + Assert.True(assert.Less(value, "value", 4).Result); + Assert.False(assert.Less(value, "value", 0).Result); + Assert.False(assert.Less(value, "value", -1).Result); + + Assert.Equal("value", assert.Less(value, "value", -1).ToResultReason().FirstOrDefault().Path); + + // Array + value = GetObject((name: "value", value: new string[] { "1", "2", "3" })); + Assert.False(assert.Less(value, "value", 2).Result); + Assert.False(assert.Less(value, "value", 3).Result); + Assert.True(assert.Less(value, "value", 4).Result); + Assert.False(assert.Less(value, "value", 0).Result); + Assert.False(assert.Less(value, "value", -1).Result); + + // DateTime + value = GetObject((name: "value", value: DateTime.Now.AddDays(4))); + Assert.False(assert.Less(value, "value", 2).Result); + Assert.False(assert.Less(value, "value", 3).Result); + Assert.True(assert.Less(value, "value", 5).Result); + Assert.False(assert.Less(value, "value", 0).Result); + Assert.False(assert.Less(value, "value", -1).Result); + + // Self + Assert.True(assert.Less(1, ".", 2).Result); + + // Convert from string + value = GetObject((name: "value", value: "3")); + Assert.False(assert.Less(value, "value", 2, convert: true).Result); + Assert.True(assert.Less(value, "value", 2, convert: false).Result); + value = GetObject((name: "value", value: (4.5).ToString())); + + Assert.False(assert.Less(value, "value", 4, convert: true).Result); + Assert.True(assert.Less(value, "value", 4, convert: false).Result); + + Assert.Equal("value", assert.Less(value, "value", 4, convert: true).ToResultReason().FirstOrDefault().Path); + } - [Fact] - public void LessOrEqual() - { - SetContext(); - var assert = GetAssertionHelper(); - var value = GetObject((name: "value", value: 3), (name: "jvalue", value: new JValue(3))); - - // Int - Assert.False(assert.LessOrEqual(value, "value", 2).Result); - Assert.True(assert.LessOrEqual(value, "value", 3).Result); - Assert.True(assert.LessOrEqual(value, "value", 4).Result); - Assert.False(assert.LessOrEqual(value, "value", 0).Result); - Assert.False(assert.LessOrEqual(value, "value", -1).Result); - Assert.True(assert.LessOrEqual(value, "jvalue", 4).Result); - - Assert.Equal("value", assert.LessOrEqual(value, "value", 0).ToResultReason().FirstOrDefault().Path); - - // ULong - value = GetObject((name: "value", value: (ulong)3), (name: "jvalue", value: new JValue((ulong)3))); - Assert.False(assert.LessOrEqual(value, "value", 2).Result); - Assert.True(assert.LessOrEqual(value, "value", 3).Result); - Assert.True(assert.LessOrEqual(value, "value", 4).Result); - Assert.False(assert.LessOrEqual(value, "value", 0).Result); - Assert.False(assert.LessOrEqual(value, "value", -1).Result); - Assert.True(assert.LessOrEqual(value, "jvalue", 4).Result); - - Assert.Equal("value", assert.LessOrEqual(value, "value", 0).ToResultReason().FirstOrDefault().Path); - - // String - value = GetObject((name: "value", value: "abc")); - Assert.False(assert.LessOrEqual(value, "value", 2).Result); - Assert.True(assert.LessOrEqual(value, "value", 3).Result); - Assert.True(assert.LessOrEqual(value, "value", 4).Result); - Assert.False(assert.LessOrEqual(value, "value", 0).Result); - Assert.False(assert.LessOrEqual(value, "value", -1).Result); - - Assert.Equal("value", assert.LessOrEqual(value, "value", 0).ToResultReason().FirstOrDefault().Path); - - // Array - value = GetObject((name: "value", value: new string[] { "1", "2", "3" })); - Assert.False(assert.LessOrEqual(value, "value", 2).Result); - Assert.True(assert.LessOrEqual(value, "value", 3).Result); - Assert.True(assert.LessOrEqual(value, "value", 4).Result); - Assert.False(assert.LessOrEqual(value, "value", 0).Result); - Assert.False(assert.LessOrEqual(value, "value", -1).Result); - - // DateTime - value = GetObject((name: "value", value: DateTime.Now.AddDays(4))); - Assert.False(assert.LessOrEqual(value, "value", 2).Result); - Assert.False(assert.LessOrEqual(value, "value", 3).Result); - Assert.True(assert.LessOrEqual(value, "value", 5).Result); - Assert.False(assert.LessOrEqual(value, "value", 0).Result); - Assert.False(assert.LessOrEqual(value, "value", -1).Result); - - // Self - Assert.True(assert.LessOrEqual(1, ".", 1).Result); - - // Convert from string - value = GetObject((name: "value", value: "3")); - Assert.False(assert.LessOrEqual(value, "value", 2, convert: true).Result); - Assert.True(assert.LessOrEqual(value, "value", 2, convert: false).Result); - value = GetObject((name: "value", value: (4.5).ToString())); - Assert.False(assert.LessOrEqual(value, "value", 4, convert: true).Result); - Assert.True(assert.LessOrEqual(value, "value", 4, convert: false).Result); - - Assert.Equal("value", assert.LessOrEqual(value, "value", 4, convert: true).ToResultReason().FirstOrDefault().Path); - } + [Fact] + public void LessOrEqual() + { + SetContext(); + var assert = GetAssertionHelper(); + var value = GetObject((name: "value", value: 3), (name: "jvalue", value: new JValue(3))); + + // Int + Assert.False(assert.LessOrEqual(value, "value", 2).Result); + Assert.True(assert.LessOrEqual(value, "value", 3).Result); + Assert.True(assert.LessOrEqual(value, "value", 4).Result); + Assert.False(assert.LessOrEqual(value, "value", 0).Result); + Assert.False(assert.LessOrEqual(value, "value", -1).Result); + Assert.True(assert.LessOrEqual(value, "jvalue", 4).Result); + + Assert.Equal("value", assert.LessOrEqual(value, "value", 0).ToResultReason().FirstOrDefault().Path); + + // ULong + value = GetObject((name: "value", value: (ulong)3), (name: "jvalue", value: new JValue((ulong)3))); + Assert.False(assert.LessOrEqual(value, "value", 2).Result); + Assert.True(assert.LessOrEqual(value, "value", 3).Result); + Assert.True(assert.LessOrEqual(value, "value", 4).Result); + Assert.False(assert.LessOrEqual(value, "value", 0).Result); + Assert.False(assert.LessOrEqual(value, "value", -1).Result); + Assert.True(assert.LessOrEqual(value, "jvalue", 4).Result); + + Assert.Equal("value", assert.LessOrEqual(value, "value", 0).ToResultReason().FirstOrDefault().Path); + + // String + value = GetObject((name: "value", value: "abc")); + Assert.False(assert.LessOrEqual(value, "value", 2).Result); + Assert.True(assert.LessOrEqual(value, "value", 3).Result); + Assert.True(assert.LessOrEqual(value, "value", 4).Result); + Assert.False(assert.LessOrEqual(value, "value", 0).Result); + Assert.False(assert.LessOrEqual(value, "value", -1).Result); + + Assert.Equal("value", assert.LessOrEqual(value, "value", 0).ToResultReason().FirstOrDefault().Path); + + // Array + value = GetObject((name: "value", value: new string[] { "1", "2", "3" })); + Assert.False(assert.LessOrEqual(value, "value", 2).Result); + Assert.True(assert.LessOrEqual(value, "value", 3).Result); + Assert.True(assert.LessOrEqual(value, "value", 4).Result); + Assert.False(assert.LessOrEqual(value, "value", 0).Result); + Assert.False(assert.LessOrEqual(value, "value", -1).Result); + + // DateTime + value = GetObject((name: "value", value: DateTime.Now.AddDays(4))); + Assert.False(assert.LessOrEqual(value, "value", 2).Result); + Assert.False(assert.LessOrEqual(value, "value", 3).Result); + Assert.True(assert.LessOrEqual(value, "value", 5).Result); + Assert.False(assert.LessOrEqual(value, "value", 0).Result); + Assert.False(assert.LessOrEqual(value, "value", -1).Result); + + // Self + Assert.True(assert.LessOrEqual(1, ".", 1).Result); + + // Convert from string + value = GetObject((name: "value", value: "3")); + Assert.False(assert.LessOrEqual(value, "value", 2, convert: true).Result); + Assert.True(assert.LessOrEqual(value, "value", 2, convert: false).Result); + value = GetObject((name: "value", value: (4.5).ToString())); + Assert.False(assert.LessOrEqual(value, "value", 4, convert: true).Result); + Assert.True(assert.LessOrEqual(value, "value", 4, convert: false).Result); + + Assert.Equal("value", assert.LessOrEqual(value, "value", 4, convert: true).ToResultReason().FirstOrDefault().Path); + } - [Fact] - public void In() - { - SetContext(); - var assert = GetAssertionHelper(); - - // Int - var value = GetObject((name: "value", value: 3), (name: "values", value: new int[] { 3, 5 })); - Assert.True(assert.In(value, "value", new int[] { 3 }).Result); - Assert.True(assert.In(value, "value", new int[] { 2, 3, 5 }).Result); - Assert.False(assert.In(value, "value", new int[] { 4 }).Result); - Assert.False(assert.In(value, "value", new int[] { 2, 4, 5 }).Result); - - Assert.True(assert.In(value, "values", new int[] { 3 }).Result); - Assert.True(assert.In(value, "values", new int[] { 2, 3, 5 }).Result); - Assert.False(assert.In(value, "values", new int[] { 4 }).Result); - Assert.False(assert.In(value, "values", new int[] { 4, 2 }).Result); - Assert.True(assert.In(value, "values", new int[] { 2, 4, 5 }).Result); - - // Float - value = GetObject((name: "value", value: 3.0f), (name: "values", value: new float[] { 3f, 5f })); - Assert.True(assert.In(value, "value", new float[] { 3.0f }).Result); - Assert.True(assert.In(value, "value", new float[] { 2f, 3.0f, 5f }).Result); - Assert.False(assert.In(value, "value", new float[] { 4f }).Result); - Assert.False(assert.In(value, "value", new float[] { 2f, 4f, 5f }).Result); - - Assert.True(assert.In(value, "values", new float[] { 3.0f }).Result); - Assert.True(assert.In(value, "values", new float[] { 2f, 3.0f, 5f }).Result); - Assert.False(assert.In(value, "values", new float[] { 4f }).Result); - Assert.False(assert.In(value, "values", new float[] { 4f, 2f }).Result); - Assert.True(assert.In(value, "values", new float[] { 2f, 4f, 5f }).Result); - - // String - value = GetObject((name: "value", value: "value2"), (name: "values", value: new string[] { "value2", "value5" }), (name: "objects", value: new object[] { "value2", "value5" })); - Assert.True(assert.In(value, "value", new string[] { "Value2" }).Result); - Assert.True(assert.In(value, "value", new string[] { "VALUE1", "VALUE2", "VALUE3" }).Result); - Assert.False(assert.In(value, "value", new string[] { "Value3" }).Result); - Assert.False(assert.In(value, "value", new string[] { "VALUE1", "VALUE3" }).Result); - Assert.False(assert.In(value, "value", new string[] { "Value2" }, true).Result); - Assert.False(assert.In(value, "value", new string[] { "VALUE1", "VALUE2", "VALUE3" }, true).Result); - Assert.True(assert.In(value, "value", new string[] { "value2" }, true).Result); - Assert.True(assert.In(value, "value", new string[] { "value1", "value2", "value3" }, true).Result); - - Assert.True(assert.In(value, "values", new string[] { "Value2" }).Result); - Assert.True(assert.In(value, "values", new string[] { "VALUE1", "VALUE2", "VALUE3" }).Result); - Assert.True(assert.In(value, "values", new string[] { "Value3", "Value5" }).Result); - Assert.True(assert.In(value, "values", new object[] { "Value3", "Value5" }).Result); - Assert.False(assert.In(value, "values", new string[] { "Value1", "Value3" }).Result); - Assert.False(assert.In(value, "values", new string[] { "VALUE1", "VALUE2", "VALUE3" }, true).Result); - - Assert.True(assert.In(value, "objects", new object[] { "Value3", "Value5" }).Result); - Assert.True(assert.In(value, "objects", new string[] { "Value3", "Value5" }).Result); - Assert.True(assert.In(PSObject.AsPSObject(new object[] { "value2", "value5" }), ".", new string[] { "Value3", "Value5" }).Result); - - Assert.Equal("value", assert.In(value, "value", new string[] { "Value3" }).ToResultReason().FirstOrDefault().Path); - } + [Fact] + public void In() + { + SetContext(); + var assert = GetAssertionHelper(); + + // Int + var value = GetObject((name: "value", value: 3), (name: "values", value: new int[] { 3, 5 })); + Assert.True(assert.In(value, "value", new int[] { 3 }).Result); + Assert.True(assert.In(value, "value", new int[] { 2, 3, 5 }).Result); + Assert.False(assert.In(value, "value", new int[] { 4 }).Result); + Assert.False(assert.In(value, "value", new int[] { 2, 4, 5 }).Result); + + Assert.True(assert.In(value, "values", new int[] { 3 }).Result); + Assert.True(assert.In(value, "values", new int[] { 2, 3, 5 }).Result); + Assert.False(assert.In(value, "values", new int[] { 4 }).Result); + Assert.False(assert.In(value, "values", new int[] { 4, 2 }).Result); + Assert.True(assert.In(value, "values", new int[] { 2, 4, 5 }).Result); + + // Float + value = GetObject((name: "value", value: 3.0f), (name: "values", value: new float[] { 3f, 5f })); + Assert.True(assert.In(value, "value", new float[] { 3.0f }).Result); + Assert.True(assert.In(value, "value", new float[] { 2f, 3.0f, 5f }).Result); + Assert.False(assert.In(value, "value", new float[] { 4f }).Result); + Assert.False(assert.In(value, "value", new float[] { 2f, 4f, 5f }).Result); + + Assert.True(assert.In(value, "values", new float[] { 3.0f }).Result); + Assert.True(assert.In(value, "values", new float[] { 2f, 3.0f, 5f }).Result); + Assert.False(assert.In(value, "values", new float[] { 4f }).Result); + Assert.False(assert.In(value, "values", new float[] { 4f, 2f }).Result); + Assert.True(assert.In(value, "values", new float[] { 2f, 4f, 5f }).Result); + + // String + value = GetObject((name: "value", value: "value2"), (name: "values", value: new string[] { "value2", "value5" }), (name: "objects", value: new object[] { "value2", "value5" })); + Assert.True(assert.In(value, "value", new string[] { "Value2" }).Result); + Assert.True(assert.In(value, "value", new string[] { "VALUE1", "VALUE2", "VALUE3" }).Result); + Assert.False(assert.In(value, "value", new string[] { "Value3" }).Result); + Assert.False(assert.In(value, "value", new string[] { "VALUE1", "VALUE3" }).Result); + Assert.False(assert.In(value, "value", new string[] { "Value2" }, true).Result); + Assert.False(assert.In(value, "value", new string[] { "VALUE1", "VALUE2", "VALUE3" }, true).Result); + Assert.True(assert.In(value, "value", new string[] { "value2" }, true).Result); + Assert.True(assert.In(value, "value", new string[] { "value1", "value2", "value3" }, true).Result); + + Assert.True(assert.In(value, "values", new string[] { "Value2" }).Result); + Assert.True(assert.In(value, "values", new string[] { "VALUE1", "VALUE2", "VALUE3" }).Result); + Assert.True(assert.In(value, "values", new string[] { "Value3", "Value5" }).Result); + Assert.True(assert.In(value, "values", new object[] { "Value3", "Value5" }).Result); + Assert.False(assert.In(value, "values", new string[] { "Value1", "Value3" }).Result); + Assert.False(assert.In(value, "values", new string[] { "VALUE1", "VALUE2", "VALUE3" }, true).Result); + + Assert.True(assert.In(value, "objects", new object[] { "Value3", "Value5" }).Result); + Assert.True(assert.In(value, "objects", new string[] { "Value3", "Value5" }).Result); + Assert.True(assert.In(PSObject.AsPSObject(new object[] { "value2", "value5" }), ".", new string[] { "Value3", "Value5" }).Result); + + Assert.Equal("value", assert.In(value, "value", new string[] { "Value3" }).ToResultReason().FirstOrDefault().Path); + } - [Fact] - public void NotIn() - { - SetContext(); - var assert = GetAssertionHelper(); - - // Int - var value = GetObject((name: "value", value: 3), (name: "values", value: new int[] { 3, 5 })); - Assert.False(assert.NotIn(value, "value", new int[] { 3 }).Result); - Assert.False(assert.NotIn(value, "value", new int[] { 2, 3, 5 }).Result); - Assert.True(assert.NotIn(value, "value", new int[] { 4 }).Result); - Assert.True(assert.NotIn(value, "value", new int[] { 2, 4, 5 }).Result); - - Assert.False(assert.NotIn(value, "values", new int[] { 3 }).Result); - Assert.False(assert.NotIn(value, "values", new int[] { 2, 3, 5 }).Result); - Assert.True(assert.NotIn(value, "values", new int[] { 4 }).Result); - Assert.True(assert.NotIn(value, "values", new int[] { 4, 2 }).Result); - Assert.False(assert.NotIn(value, "values", new int[] { 2, 4, 5 }).Result); - - // Float - value = GetObject((name: "value", value: 3.0f), (name: "values", value: new float[] { 3f, 5f })); - Assert.False(assert.NotIn(value, "value", new float[] { 3.0f }).Result); - Assert.False(assert.NotIn(value, "value", new float[] { 2f, 3.0f, 5f }).Result); - Assert.True(assert.NotIn(value, "value", new float[] { 4f }).Result); - Assert.True(assert.NotIn(value, "value", new float[] { 2f, 4f, 5f }).Result); - - Assert.False(assert.NotIn(value, "values", new float[] { 3.0f }).Result); - Assert.False(assert.NotIn(value, "values", new float[] { 2f, 3.0f, 5f }).Result); - Assert.True(assert.NotIn(value, "values", new float[] { 4f }).Result); - Assert.True(assert.NotIn(value, "values", new float[] { 4f, 2f }).Result); - Assert.False(assert.NotIn(value, "values", new float[] { 2f, 4f, 5f }).Result); - - // String - value = GetObject((name: "value", value: "value2"), (name: "values", value: new string[] { "value2", "value5" })); - Assert.False(assert.NotIn(value, "value", new string[] { "Value2" }).Result); - Assert.False(assert.NotIn(value, "value", new string[] { "VALUE1", "VALUE2", "VALUE3" }).Result); - Assert.True(assert.NotIn(value, "value", new string[] { "Value3" }).Result); - Assert.True(assert.NotIn(value, "value", new string[] { "VALUE1", "VALUE3" }).Result); - Assert.True(assert.NotIn(value, "value", new string[] { "Value2" }, true).Result); - Assert.True(assert.NotIn(value, "value", new string[] { "VALUE1", "VALUE2", "VALUE3" }, true).Result); - Assert.False(assert.NotIn(value, "value", new string[] { "value2" }, true).Result); - Assert.False(assert.NotIn(value, "value", new string[] { "value1", "value2", "value3" }, true).Result); - - Assert.False(assert.NotIn(value, "values", new string[] { "Value2" }).Result); - Assert.False(assert.NotIn(value, "values", new string[] { "VALUE1", "VALUE2", "VALUE3" }).Result); - Assert.False(assert.NotIn(value, "values", new string[] { "Value3", "Value5" }).Result); - Assert.True(assert.NotIn(value, "values", new string[] { "Value1", "Value3" }).Result); - Assert.True(assert.NotIn(value, "values", new string[] { "VALUE1", "VALUE2", "VALUE3" }, true).Result); - - Assert.Equal("values", assert.NotIn(value, "values", new string[] { "Value2" }).ToResultReason().FirstOrDefault().Path); - - // Empty - value = GetObject((name: "null", value: null), (name: "empty", value: Array.Empty())); - Assert.True(assert.NotIn(value, "null", new string[] { "Value1", "Value3" }).Result); - Assert.True(assert.NotIn(value, "empty", new string[] { "Value1", "Value3" }).Result); - Assert.True(assert.NotIn(value, "notValue", new string[] { "Value1", "Value3" }).Result); - } + [Fact] + public void NotIn() + { + SetContext(); + var assert = GetAssertionHelper(); + + // Int + var value = GetObject((name: "value", value: 3), (name: "values", value: new int[] { 3, 5 })); + Assert.False(assert.NotIn(value, "value", new int[] { 3 }).Result); + Assert.False(assert.NotIn(value, "value", new int[] { 2, 3, 5 }).Result); + Assert.True(assert.NotIn(value, "value", new int[] { 4 }).Result); + Assert.True(assert.NotIn(value, "value", new int[] { 2, 4, 5 }).Result); + + Assert.False(assert.NotIn(value, "values", new int[] { 3 }).Result); + Assert.False(assert.NotIn(value, "values", new int[] { 2, 3, 5 }).Result); + Assert.True(assert.NotIn(value, "values", new int[] { 4 }).Result); + Assert.True(assert.NotIn(value, "values", new int[] { 4, 2 }).Result); + Assert.False(assert.NotIn(value, "values", new int[] { 2, 4, 5 }).Result); + + // Float + value = GetObject((name: "value", value: 3.0f), (name: "values", value: new float[] { 3f, 5f })); + Assert.False(assert.NotIn(value, "value", new float[] { 3.0f }).Result); + Assert.False(assert.NotIn(value, "value", new float[] { 2f, 3.0f, 5f }).Result); + Assert.True(assert.NotIn(value, "value", new float[] { 4f }).Result); + Assert.True(assert.NotIn(value, "value", new float[] { 2f, 4f, 5f }).Result); + + Assert.False(assert.NotIn(value, "values", new float[] { 3.0f }).Result); + Assert.False(assert.NotIn(value, "values", new float[] { 2f, 3.0f, 5f }).Result); + Assert.True(assert.NotIn(value, "values", new float[] { 4f }).Result); + Assert.True(assert.NotIn(value, "values", new float[] { 4f, 2f }).Result); + Assert.False(assert.NotIn(value, "values", new float[] { 2f, 4f, 5f }).Result); + + // String + value = GetObject((name: "value", value: "value2"), (name: "values", value: new string[] { "value2", "value5" })); + Assert.False(assert.NotIn(value, "value", new string[] { "Value2" }).Result); + Assert.False(assert.NotIn(value, "value", new string[] { "VALUE1", "VALUE2", "VALUE3" }).Result); + Assert.True(assert.NotIn(value, "value", new string[] { "Value3" }).Result); + Assert.True(assert.NotIn(value, "value", new string[] { "VALUE1", "VALUE3" }).Result); + Assert.True(assert.NotIn(value, "value", new string[] { "Value2" }, true).Result); + Assert.True(assert.NotIn(value, "value", new string[] { "VALUE1", "VALUE2", "VALUE3" }, true).Result); + Assert.False(assert.NotIn(value, "value", new string[] { "value2" }, true).Result); + Assert.False(assert.NotIn(value, "value", new string[] { "value1", "value2", "value3" }, true).Result); + + Assert.False(assert.NotIn(value, "values", new string[] { "Value2" }).Result); + Assert.False(assert.NotIn(value, "values", new string[] { "VALUE1", "VALUE2", "VALUE3" }).Result); + Assert.False(assert.NotIn(value, "values", new string[] { "Value3", "Value5" }).Result); + Assert.True(assert.NotIn(value, "values", new string[] { "Value1", "Value3" }).Result); + Assert.True(assert.NotIn(value, "values", new string[] { "VALUE1", "VALUE2", "VALUE3" }, true).Result); + + Assert.Equal("values", assert.NotIn(value, "values", new string[] { "Value2" }).ToResultReason().FirstOrDefault().Path); + + // Empty + value = GetObject((name: "null", value: null), (name: "empty", value: Array.Empty())); + Assert.True(assert.NotIn(value, "null", new string[] { "Value1", "Value3" }).Result); + Assert.True(assert.NotIn(value, "empty", new string[] { "Value1", "Value3" }).Result); + Assert.True(assert.NotIn(value, "notValue", new string[] { "Value1", "Value3" }).Result); + } - [Fact] - public void SetOf() - { - SetContext(); - var assert = GetAssertionHelper(); - - // Int - var value = GetObject((name: "value", value: 3), (name: "values", value: new int[] { 3, 5 })); - Assert.False(assert.SetOf(value, "value", new int[] { 3 }).Result); - Assert.False(assert.SetOf(value, "values", new int[] { 3 }).Result); - Assert.True(assert.SetOf(value, "values", new int[] { 5, 3 }).Result); - Assert.False(assert.SetOf(value, "values", new int[] { 4 }).Result); - Assert.False(assert.SetOf(value, "values", new int[] { 2, 3, 5 }).Result); - - // Float - value = GetObject((name: "value", value: 3.0f), (name: "values", value: new float[] { 3f, 5f })); - Assert.False(assert.SetOf(value, "value", new float[] { 3f }).Result); - Assert.False(assert.SetOf(value, "values", new float[] { 3.0f }).Result); - Assert.True(assert.SetOf(value, "values", new float[] { 5.0f, 3f }).Result); - Assert.False(assert.SetOf(value, "values", new float[] { 4f }).Result); - Assert.False(assert.SetOf(value, "values", new float[] { 2f, 3f, 5f }).Result); - - // String - value = GetObject((name: "value", value: "value2"), (name: "values", value: new string[] { "value2", "value5" })); - Assert.False(assert.SetOf(value, "value", new string[] { "value2" }).Result); - Assert.False(assert.SetOf(value, "values", new string[] { "Value5" }).Result); - Assert.True(assert.SetOf(value, "values", new string[] { "Value5", "Value2" }).Result); - Assert.False(assert.SetOf(value, "values", new string[] { "VALUE1", "VALUE2", "VALUE3" }).Result); - Assert.False(assert.SetOf(value, "values", new string[] { "Value3", "Value5" }).Result); - Assert.False(assert.SetOf(value, "values", new string[] { "Value2", "Value5" }, true).Result); - Assert.True(assert.SetOf(value, "values", new string[] { "value2", "value5" }, true).Result); - - Assert.Equal("values", assert.SetOf(value, "values", new string[] { "Value2", "Value5" }, true).ToResultReason().FirstOrDefault().Path); - } + [Fact] + public void SetOf() + { + SetContext(); + var assert = GetAssertionHelper(); + + // Int + var value = GetObject((name: "value", value: 3), (name: "values", value: new int[] { 3, 5 })); + Assert.False(assert.SetOf(value, "value", new int[] { 3 }).Result); + Assert.False(assert.SetOf(value, "values", new int[] { 3 }).Result); + Assert.True(assert.SetOf(value, "values", new int[] { 5, 3 }).Result); + Assert.False(assert.SetOf(value, "values", new int[] { 4 }).Result); + Assert.False(assert.SetOf(value, "values", new int[] { 2, 3, 5 }).Result); + + // Float + value = GetObject((name: "value", value: 3.0f), (name: "values", value: new float[] { 3f, 5f })); + Assert.False(assert.SetOf(value, "value", new float[] { 3f }).Result); + Assert.False(assert.SetOf(value, "values", new float[] { 3.0f }).Result); + Assert.True(assert.SetOf(value, "values", new float[] { 5.0f, 3f }).Result); + Assert.False(assert.SetOf(value, "values", new float[] { 4f }).Result); + Assert.False(assert.SetOf(value, "values", new float[] { 2f, 3f, 5f }).Result); + + // String + value = GetObject((name: "value", value: "value2"), (name: "values", value: new string[] { "value2", "value5" })); + Assert.False(assert.SetOf(value, "value", new string[] { "value2" }).Result); + Assert.False(assert.SetOf(value, "values", new string[] { "Value5" }).Result); + Assert.True(assert.SetOf(value, "values", new string[] { "Value5", "Value2" }).Result); + Assert.False(assert.SetOf(value, "values", new string[] { "VALUE1", "VALUE2", "VALUE3" }).Result); + Assert.False(assert.SetOf(value, "values", new string[] { "Value3", "Value5" }).Result); + Assert.False(assert.SetOf(value, "values", new string[] { "Value2", "Value5" }, true).Result); + Assert.True(assert.SetOf(value, "values", new string[] { "value2", "value5" }, true).Result); + + Assert.Equal("values", assert.SetOf(value, "values", new string[] { "Value2", "Value5" }, true).ToResultReason().FirstOrDefault().Path); + } - [Fact] - public void Subset() - { - SetContext(); - var assert = GetAssertionHelper(); - - // Int - var value = GetObject((name: "value", value: 3), (name: "values", value: new int[] { 3, 5, 3 })); - Assert.False(assert.Subset(value, "value", new int[] { 3 }).Result); - - Assert.True(assert.Subset(value, "values", new int[] { 3 }).Result); - Assert.True(assert.Subset(value, "values", new int[] { 5, 3 }).Result); - Assert.False(assert.Subset(value, "values", new int[] { 5, 3 }, false, true).Result); - Assert.False(assert.Subset(value, "values", new int[] { 4 }).Result); - Assert.False(assert.Subset(value, "values", new int[] { 2, 3, 5 }).Result); - - // Float - value = GetObject((name: "value", value: 3.0f), (name: "values", value: new float[] { 3f, 5f, 5f })); - Assert.False(assert.Subset(value, "value", new float[] { 3f }).Result); - - Assert.True(assert.Subset(value, "values", new float[] { 3.0f }).Result); - Assert.True(assert.Subset(value, "values", new float[] { 5.0f, 3f }).Result); - Assert.False(assert.Subset(value, "values", new float[] { 5.0f, 3f }, false, true).Result); - Assert.False(assert.Subset(value, "values", new float[] { 4f }).Result); - Assert.False(assert.Subset(value, "values", new float[] { 2f, 3f, 5f }).Result); - - // String - value = GetObject((name: "value", value: "value2"), (name: "values", value: new string[] { "value2", "value5", "Value2" })); - Assert.False(assert.Subset(value, "value", new string[] { "value2" }).Result); - - Assert.True(assert.Subset(value, "values", new string[] { "Value5" }).Result); - Assert.True(assert.Subset(value, "values", new string[] { "Value5", "Value2" }).Result); - Assert.False(assert.Subset(value, "values", new string[] { "Value5", "Value2" }, false, true).Result); - Assert.False(assert.Subset(value, "values", new string[] { "VALUE1", "VALUE2", "VALUE3" }).Result); - Assert.False(assert.Subset(value, "values", new string[] { "Value3", "Value5" }).Result); - Assert.False(assert.Subset(value, "values", new string[] { "Value2", "Value5" }, true).Result); - - Assert.Equal("values", assert.Subset(value, "values", new string[] { "Value5", "Value2" }, false, true).ToResultReason().FirstOrDefault().Path); - } + [Fact] + public void Subset() + { + SetContext(); + var assert = GetAssertionHelper(); + + // Int + var value = GetObject((name: "value", value: 3), (name: "values", value: new int[] { 3, 5, 3 })); + Assert.False(assert.Subset(value, "value", new int[] { 3 }).Result); + + Assert.True(assert.Subset(value, "values", new int[] { 3 }).Result); + Assert.True(assert.Subset(value, "values", new int[] { 5, 3 }).Result); + Assert.False(assert.Subset(value, "values", new int[] { 5, 3 }, false, true).Result); + Assert.False(assert.Subset(value, "values", new int[] { 4 }).Result); + Assert.False(assert.Subset(value, "values", new int[] { 2, 3, 5 }).Result); + + // Float + value = GetObject((name: "value", value: 3.0f), (name: "values", value: new float[] { 3f, 5f, 5f })); + Assert.False(assert.Subset(value, "value", new float[] { 3f }).Result); + + Assert.True(assert.Subset(value, "values", new float[] { 3.0f }).Result); + Assert.True(assert.Subset(value, "values", new float[] { 5.0f, 3f }).Result); + Assert.False(assert.Subset(value, "values", new float[] { 5.0f, 3f }, false, true).Result); + Assert.False(assert.Subset(value, "values", new float[] { 4f }).Result); + Assert.False(assert.Subset(value, "values", new float[] { 2f, 3f, 5f }).Result); + + // String + value = GetObject((name: "value", value: "value2"), (name: "values", value: new string[] { "value2", "value5", "Value2" })); + Assert.False(assert.Subset(value, "value", new string[] { "value2" }).Result); + + Assert.True(assert.Subset(value, "values", new string[] { "Value5" }).Result); + Assert.True(assert.Subset(value, "values", new string[] { "Value5", "Value2" }).Result); + Assert.False(assert.Subset(value, "values", new string[] { "Value5", "Value2" }, false, true).Result); + Assert.False(assert.Subset(value, "values", new string[] { "VALUE1", "VALUE2", "VALUE3" }).Result); + Assert.False(assert.Subset(value, "values", new string[] { "Value3", "Value5" }).Result); + Assert.False(assert.Subset(value, "values", new string[] { "Value2", "Value5" }, true).Result); + + Assert.Equal("values", assert.Subset(value, "values", new string[] { "Value5", "Value2" }, false, true).ToResultReason().FirstOrDefault().Path); + } - [Fact] - public void Count() - { - SetContext(); - var assert = GetAssertionHelper(); - - // Array - var value = GetObject((name: "value", value: new string[] { "1", "2", "3" })); - Assert.False(assert.Count(value, "value", 2).Result); - Assert.True(assert.Count(value, "value", 3).Result); - Assert.False(assert.Count(value, "value", 4).Result); - Assert.False(assert.Count(value, "value", 0).Result); - Assert.False(assert.Count(value, "value", -1).Result); - - Assert.Equal("value", assert.Count(value, "value", 2).ToResultReason().FirstOrDefault().Path); - } + [Fact] + public void Count() + { + SetContext(); + var assert = GetAssertionHelper(); + + // Array + var value = GetObject((name: "value", value: new string[] { "1", "2", "3" })); + Assert.False(assert.Count(value, "value", 2).Result); + Assert.True(assert.Count(value, "value", 3).Result); + Assert.False(assert.Count(value, "value", 4).Result); + Assert.False(assert.Count(value, "value", 0).Result); + Assert.False(assert.Count(value, "value", -1).Result); + + Assert.Equal("value", assert.Count(value, "value", 2).ToResultReason().FirstOrDefault().Path); + } - [Fact] - public void NotCount() - { - SetContext(); - var assert = GetAssertionHelper(); - - // Array - var value = GetObject((name: "value", value: new string[] { "1", "2", "3" })); - Assert.True(assert.NotCount(value, "value", 2).Result); - Assert.False(assert.NotCount(value, "value", 3).Result); - Assert.True(assert.NotCount(value, "value", 4).Result); - Assert.True(assert.NotCount(value, "value", 0).Result); - Assert.True(assert.NotCount(value, "value", -1).Result); - - Assert.Equal("value", assert.NotCount(value, "value", 3).ToResultReason().FirstOrDefault().Path); - } + [Fact] + public void NotCount() + { + SetContext(); + var assert = GetAssertionHelper(); + + // Array + var value = GetObject((name: "value", value: new string[] { "1", "2", "3" })); + Assert.True(assert.NotCount(value, "value", 2).Result); + Assert.False(assert.NotCount(value, "value", 3).Result); + Assert.True(assert.NotCount(value, "value", 4).Result); + Assert.True(assert.NotCount(value, "value", 0).Result); + Assert.True(assert.NotCount(value, "value", -1).Result); + + Assert.Equal("value", assert.NotCount(value, "value", 3).ToResultReason().FirstOrDefault().Path); + } - [Fact] - public void Match() - { - SetContext(); - var assert = GetAssertionHelper(); - - var value = GetObject((name: "value", value: "Value1")); - Assert.True(assert.Match(value, "value", "Value1").Result); - Assert.True(assert.Match(value, "value", "Value[0-9]").Result); - Assert.True(assert.Match(value, "value", "value1").Result); - Assert.False(assert.Match(value, "value", "value2").Result); - Assert.False(assert.Match(value, "value", "value1", true).Result); - Assert.True(assert.Match(value, "value", "\\w*1").Result); - - Assert.Equal("value", assert.Match(value, "value", "value2").ToResultReason().FirstOrDefault().Path); - } + [Fact] + public void Match() + { + SetContext(); + var assert = GetAssertionHelper(); + + var value = GetObject((name: "value", value: "Value1")); + Assert.True(assert.Match(value, "value", "Value1").Result); + Assert.True(assert.Match(value, "value", "Value[0-9]").Result); + Assert.True(assert.Match(value, "value", "value1").Result); + Assert.False(assert.Match(value, "value", "value2").Result); + Assert.False(assert.Match(value, "value", "value1", true).Result); + Assert.True(assert.Match(value, "value", "\\w*1").Result); + + Assert.Equal("value", assert.Match(value, "value", "value2").ToResultReason().FirstOrDefault().Path); + } - [Fact] - public void NotMatch() - { - SetContext(); - var assert = GetAssertionHelper(); - - var value = GetObject((name: "value", value: "Value2")); - Assert.True(assert.NotMatch(value, "value", "Value1").Result); - Assert.False(assert.NotMatch(value, "value", "Value[0-9]").Result); - Assert.True(assert.NotMatch(value, "value", "value1").Result); - Assert.False(assert.NotMatch(value, "value", "value2").Result); - Assert.True(assert.NotMatch(value, "value", "value2", true).Result); - Assert.False(assert.NotMatch(value, "value", "\\w*2").Result); - Assert.True(assert.NotMatch(value, "notValue", "\\w*2").Result); - - Assert.Equal("value", assert.NotMatch(value, "value", "Value[0-9]").ToResultReason().FirstOrDefault().Path); - } + [Fact] + public void NotMatch() + { + SetContext(); + var assert = GetAssertionHelper(); + + var value = GetObject((name: "value", value: "Value2")); + Assert.True(assert.NotMatch(value, "value", "Value1").Result); + Assert.False(assert.NotMatch(value, "value", "Value[0-9]").Result); + Assert.True(assert.NotMatch(value, "value", "value1").Result); + Assert.False(assert.NotMatch(value, "value", "value2").Result); + Assert.True(assert.NotMatch(value, "value", "value2", true).Result); + Assert.False(assert.NotMatch(value, "value", "\\w*2").Result); + Assert.True(assert.NotMatch(value, "notValue", "\\w*2").Result); + + Assert.Equal("value", assert.NotMatch(value, "value", "Value[0-9]").ToResultReason().FirstOrDefault().Path); + } - [Fact] - public void Null() - { - SetContext(); - var assert = GetAssertionHelper(); - - var value = GetObject( - (name: "value", value: "Value1"), - (name: "value2", value: null), - (name: "value3", value: ""), - (name: "value4", value: 0) - ); - Assert.False(assert.Null(value, "value").Result); - Assert.True(assert.Null(value, "notValue").Result); - Assert.True(assert.Null(value, "value2").Result); - Assert.False(assert.Null(value, "value3").Result); - Assert.False(assert.Null(value, "value4").Result); - - Assert.Equal("value", assert.Null(value, "value").ToResultReason().FirstOrDefault().Path); - } + [Fact] + public void Null() + { + SetContext(); + var assert = GetAssertionHelper(); + + var value = GetObject( + (name: "value", value: "Value1"), + (name: "value2", value: null), + (name: "value3", value: ""), + (name: "value4", value: 0) + ); + Assert.False(assert.Null(value, "value").Result); + Assert.True(assert.Null(value, "notValue").Result); + Assert.True(assert.Null(value, "value2").Result); + Assert.False(assert.Null(value, "value3").Result); + Assert.False(assert.Null(value, "value4").Result); + + Assert.Equal("value", assert.Null(value, "value").ToResultReason().FirstOrDefault().Path); + } - [Fact] - public void NotNull() - { - SetContext(); - var assert = GetAssertionHelper(); - - var value = GetObject( - (name: "value", value: "Value1"), - (name: "value2", value: null), - (name: "value3", value: ""), - (name: "value4", value: 0) - ); - Assert.True(assert.NotNull(value, "value").Result); - Assert.False(assert.NotNull(value, "value2").Result); - Assert.True(assert.NotNull(value, "value3").Result); - Assert.True(assert.NotNull(value, "value4").Result); - Assert.False(assert.NotNull(value, "notValue").Result); - - Assert.Equal("value2", assert.NotNull(value, "value2").ToResultReason().FirstOrDefault().Path); - } + [Fact] + public void NotNull() + { + SetContext(); + var assert = GetAssertionHelper(); + + var value = GetObject( + (name: "value", value: "Value1"), + (name: "value2", value: null), + (name: "value3", value: ""), + (name: "value4", value: 0) + ); + Assert.True(assert.NotNull(value, "value").Result); + Assert.False(assert.NotNull(value, "value2").Result); + Assert.True(assert.NotNull(value, "value3").Result); + Assert.True(assert.NotNull(value, "value4").Result); + Assert.False(assert.NotNull(value, "notValue").Result); + + Assert.Equal("value2", assert.NotNull(value, "value2").ToResultReason().FirstOrDefault().Path); + } - [Fact] - public void NullOrEmpty() - { - SetContext(); - var assert = GetAssertionHelper(); - - var value = GetObject( - (name: "value", value: "Value1"), - (name: "value2", value: null), - (name: "value3", value: ""), - (name: "value4", value: 0), - (name: "value5", value: Array.Empty()) - ); - Assert.False(assert.NullOrEmpty(value, "value").Result); - Assert.True(assert.NullOrEmpty(value, "value2").Result); - Assert.True(assert.NullOrEmpty(value, "value3").Result); - Assert.False(assert.NullOrEmpty(value, "value4").Result); - Assert.True(assert.NullOrEmpty(value, "value5").Result); - Assert.True(assert.NullOrEmpty(value, "notValue").Result); - - Assert.Equal("value", assert.NullOrEmpty(value, "value").ToResultReason().FirstOrDefault().Path); - } + [Fact] + public void NullOrEmpty() + { + SetContext(); + var assert = GetAssertionHelper(); + + var value = GetObject( + (name: "value", value: "Value1"), + (name: "value2", value: null), + (name: "value3", value: ""), + (name: "value4", value: 0), + (name: "value5", value: Array.Empty()) + ); + Assert.False(assert.NullOrEmpty(value, "value").Result); + Assert.True(assert.NullOrEmpty(value, "value2").Result); + Assert.True(assert.NullOrEmpty(value, "value3").Result); + Assert.False(assert.NullOrEmpty(value, "value4").Result); + Assert.True(assert.NullOrEmpty(value, "value5").Result); + Assert.True(assert.NullOrEmpty(value, "notValue").Result); + + Assert.Equal("value", assert.NullOrEmpty(value, "value").ToResultReason().FirstOrDefault().Path); + } - [Fact] - public void FileHeader() - { - SetContext(); - var assert = GetAssertionHelper(); - var header = new string[] { "Copyright (c) Microsoft Corporation.", "Licensed under the MIT License." }; + [Fact] + public void FileHeader() + { + SetContext(); + var assert = GetAssertionHelper(); + var header = new string[] { "Copyright (c) Microsoft Corporation.", "Licensed under the MIT License." }; - // .ps1 - var value = GetObject((name: "FullName", value: GetSourcePath("FromFile.Rule.ps1"))); - Assert.True(assert.FileHeader(value, "FullName", header).Result); + // .ps1 + var value = GetObject((name: "FullName", value: GetSourcePath("FromFile.Rule.ps1"))); + Assert.True(assert.FileHeader(value, "FullName", header).Result); - // .yaml - value = GetObject((name: "FullName", value: GetSourcePath("Baseline.Rule.yaml"))); - Assert.False(assert.FileHeader(value, "FullName", header).Result); + // .yaml + value = GetObject((name: "FullName", value: GetSourcePath("Baseline.Rule.yaml"))); + Assert.False(assert.FileHeader(value, "FullName", header).Result); - Assert.Equal("FullName", assert.FileHeader(value, "FullName", header).ToResultReason().FirstOrDefault().Path); + Assert.Equal("FullName", assert.FileHeader(value, "FullName", header).ToResultReason().FirstOrDefault().Path); - // Dockerfile - value = GetObject((name: "FullName", value: GetSourcePath("Dockerfile"))); - Assert.True(assert.FileHeader(value, "FullName", header).Result); + // Dockerfile + value = GetObject((name: "FullName", value: GetSourcePath("Dockerfile"))); + Assert.True(assert.FileHeader(value, "FullName", header).Result); - // Jenkinsfile - value = GetObject((name: "FullName", value: GetSourcePath("jenkinsfile"))); - Assert.True(assert.FileHeader(value, "FullName", header).Result); - } + // Jenkinsfile + value = GetObject((name: "FullName", value: GetSourcePath("jenkinsfile"))); + Assert.True(assert.FileHeader(value, "FullName", header).Result); + } - [Fact] - public void FilePath() - { - SetContext(); - var assert = GetAssertionHelper(); + [Fact] + public void FilePath() + { + SetContext(); + var assert = GetAssertionHelper(); - var value = GetObject((name: "FullName", value: GetSourcePath("Baseline.Rule.yaml"))); - Assert.True(assert.FilePath(value, "FullName").Result); - value = GetObject((name: "FullName", value: GetSourcePath("README.zz"))); - Assert.False(assert.FilePath(value, "FullName").Result); + var value = GetObject((name: "FullName", value: GetSourcePath("Baseline.Rule.yaml"))); + Assert.True(assert.FilePath(value, "FullName").Result); + value = GetObject((name: "FullName", value: GetSourcePath("README.zz"))); + Assert.False(assert.FilePath(value, "FullName").Result); - Assert.Equal("FullName", assert.FilePath(value, "FullName").ToResultReason().FirstOrDefault().Path); - } + Assert.Equal("FullName", assert.FilePath(value, "FullName").ToResultReason().FirstOrDefault().Path); + } - [Fact] - public void WithinPath() - { - SetContext(); - var assert = GetAssertionHelper(); - - // String - var value = GetObject((name: "FullName", value: GetSourcePath("deployments/path/template.json"))); - Assert.True(AssertionResult(assert.WithinPath(value, "FullName", new string[] { "deployments/path/" }, caseSensitive: false))); - Assert.True(AssertionResult(assert.WithinPath(value, "FullName", new string[] { "deployments\\path\\" }, caseSensitive: false))); - Assert.False(AssertionResult(assert.WithinPath(value, "FullName", new string[] { "deployments/other/" }, caseSensitive: false))); - Assert.False(AssertionResult(assert.WithinPath(value, "FullName", new string[] { "deployments/Path/" }, caseSensitive: true))); - - // InputFileInfo - value = GetObject((name: "FullName", value: new InputFileInfo(AppDomain.CurrentDomain.BaseDirectory, Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "deployments/path/template.json")))); - Assert.True(assert.WithinPath(value, "FullName", new string[] { "deployments/path/" }).Result); - Assert.True(assert.WithinPath(value, "FullName", new string[] { "deployments\\path\\" }).Result); - Assert.False(assert.WithinPath(value, "FullName", new string[] { "deployments/other/" }).Result); - Assert.False(assert.WithinPath(value, "FullName", new string[] { "deployments/Path/" }, caseSensitive: true).Result); - - // FileInfo - value = GetObject((name: "FullName", value: new FileInfo(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "deployments/path/template.json")))); - Assert.True(assert.WithinPath(value, "FullName", new string[] { "deployments/path/" }).Result); - Assert.True(assert.WithinPath(value, "FullName", new string[] { "deployments\\path\\" }).Result); - Assert.False(assert.WithinPath(value, "FullName", new string[] { "deployments/other/" }).Result); - Assert.False(assert.WithinPath(value, "FullName", new string[] { "deployments/Path/" }, caseSensitive: true).Result); - - Assert.Equal("FullName", assert.WithinPath(value, "FullName", new string[] { "deployments/Path/" }, caseSensitive: true).ToResultReason().FirstOrDefault().Path); - } + [Fact] + public void WithinPath() + { + SetContext(); + var assert = GetAssertionHelper(); + + // String + var value = GetObject((name: "FullName", value: GetSourcePath("deployments/path/template.json"))); + Assert.True(AssertionResult(assert.WithinPath(value, "FullName", new string[] { "deployments/path/" }, caseSensitive: false))); + Assert.True(AssertionResult(assert.WithinPath(value, "FullName", new string[] { "deployments\\path\\" }, caseSensitive: false))); + Assert.False(AssertionResult(assert.WithinPath(value, "FullName", new string[] { "deployments/other/" }, caseSensitive: false))); + Assert.False(AssertionResult(assert.WithinPath(value, "FullName", new string[] { "deployments/Path/" }, caseSensitive: true))); + + // InputFileInfo + value = GetObject((name: "FullName", value: new InputFileInfo(AppDomain.CurrentDomain.BaseDirectory, Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "deployments/path/template.json")))); + Assert.True(assert.WithinPath(value, "FullName", new string[] { "deployments/path/" }).Result); + Assert.True(assert.WithinPath(value, "FullName", new string[] { "deployments\\path\\" }).Result); + Assert.False(assert.WithinPath(value, "FullName", new string[] { "deployments/other/" }).Result); + Assert.False(assert.WithinPath(value, "FullName", new string[] { "deployments/Path/" }, caseSensitive: true).Result); + + // FileInfo + value = GetObject((name: "FullName", value: new FileInfo(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "deployments/path/template.json")))); + Assert.True(assert.WithinPath(value, "FullName", new string[] { "deployments/path/" }).Result); + Assert.True(assert.WithinPath(value, "FullName", new string[] { "deployments\\path\\" }).Result); + Assert.False(assert.WithinPath(value, "FullName", new string[] { "deployments/other/" }).Result); + Assert.False(assert.WithinPath(value, "FullName", new string[] { "deployments/Path/" }, caseSensitive: true).Result); + + Assert.Equal("FullName", assert.WithinPath(value, "FullName", new string[] { "deployments/Path/" }, caseSensitive: true).ToResultReason().FirstOrDefault().Path); + } - [Fact] - public void NotWithinPath() - { - SetContext(); - var assert = GetAssertionHelper(); - - // String - var value = GetObject((name: "FullName", value: GetSourcePath("deployments/path/template.json"))); - Assert.False(AssertionResult(assert.NotWithinPath(value, "FullName", new string[] { "deployments/path/" }, caseSensitive: false))); - Assert.False(AssertionResult(assert.NotWithinPath(value, "FullName", new string[] { "deployments\\path\\" }, caseSensitive: false))); - Assert.True(AssertionResult(assert.NotWithinPath(value, "FullName", new string[] { "deployments/other/" }, caseSensitive: false))); - Assert.True(AssertionResult(assert.NotWithinPath(value, "FullName", new string[] { "deployments/Path/" }, caseSensitive: true))); - - // InputFileInfo - value = GetObject((name: "FullName", value: new InputFileInfo(AppDomain.CurrentDomain.BaseDirectory, Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "deployments/path/template.json")))); - Assert.False(assert.NotWithinPath(value, "FullName", new string[] { "deployments/path/" }).Result); - Assert.False(assert.NotWithinPath(value, "FullName", new string[] { "deployments\\path\\" }).Result); - Assert.True(assert.NotWithinPath(value, "FullName", new string[] { "deployments/other/" }).Result); - Assert.True(assert.NotWithinPath(value, "FullName", new string[] { "deployments/Path/" }, caseSensitive: true).Result); - - // InputFileInfo - value = GetObject((name: "FullName", value: new FileInfo(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "deployments/path/template.json")))); - Assert.False(assert.NotWithinPath(value, "FullName", new string[] { "deployments/path/" }).Result); - Assert.False(assert.NotWithinPath(value, "FullName", new string[] { "deployments\\path\\" }).Result); - Assert.True(assert.NotWithinPath(value, "FullName", new string[] { "deployments/other/" }).Result); - Assert.True(assert.NotWithinPath(value, "FullName", new string[] { "deployments/Path/" }, caseSensitive: true).Result); - - Assert.Equal("FullName", assert.NotWithinPath(value, "FullName", new string[] { "deployments/path/" }).ToResultReason().FirstOrDefault().Path); - } + [Fact] + public void NotWithinPath() + { + SetContext(); + var assert = GetAssertionHelper(); + + // String + var value = GetObject((name: "FullName", value: GetSourcePath("deployments/path/template.json"))); + Assert.False(AssertionResult(assert.NotWithinPath(value, "FullName", new string[] { "deployments/path/" }, caseSensitive: false))); + Assert.False(AssertionResult(assert.NotWithinPath(value, "FullName", new string[] { "deployments\\path\\" }, caseSensitive: false))); + Assert.True(AssertionResult(assert.NotWithinPath(value, "FullName", new string[] { "deployments/other/" }, caseSensitive: false))); + Assert.True(AssertionResult(assert.NotWithinPath(value, "FullName", new string[] { "deployments/Path/" }, caseSensitive: true))); + + // InputFileInfo + value = GetObject((name: "FullName", value: new InputFileInfo(AppDomain.CurrentDomain.BaseDirectory, Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "deployments/path/template.json")))); + Assert.False(assert.NotWithinPath(value, "FullName", new string[] { "deployments/path/" }).Result); + Assert.False(assert.NotWithinPath(value, "FullName", new string[] { "deployments\\path\\" }).Result); + Assert.True(assert.NotWithinPath(value, "FullName", new string[] { "deployments/other/" }).Result); + Assert.True(assert.NotWithinPath(value, "FullName", new string[] { "deployments/Path/" }, caseSensitive: true).Result); + + // InputFileInfo + value = GetObject((name: "FullName", value: new FileInfo(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "deployments/path/template.json")))); + Assert.False(assert.NotWithinPath(value, "FullName", new string[] { "deployments/path/" }).Result); + Assert.False(assert.NotWithinPath(value, "FullName", new string[] { "deployments\\path\\" }).Result); + Assert.True(assert.NotWithinPath(value, "FullName", new string[] { "deployments/other/" }).Result); + Assert.True(assert.NotWithinPath(value, "FullName", new string[] { "deployments/Path/" }, caseSensitive: true).Result); + + Assert.Equal("FullName", assert.NotWithinPath(value, "FullName", new string[] { "deployments/path/" }).ToResultReason().FirstOrDefault().Path); + } - [Fact] - public void Like() - { - SetContext(); - var assert = GetAssertionHelper(); - var value = GetObject((name: "name", value: "abcdefg"), (name: "value", value: 123)); - - // String - Assert.True(assert.Like(value, "name", new string[] { "123*", "ab*" }).Result); - Assert.True(assert.Like(value, "name", new string[] { "123*", "ab*" }, caseSensitive: true).Result); - Assert.True(assert.Like(value, "name", new string[] { "AB*" }).Result); - Assert.True(assert.Like(value, "name", new string[] { "*cd*" }).Result); - Assert.True(assert.Like(value, "name", new string[] { "*fg*" }).Result); - Assert.False(assert.Like(value, "name", new string[] { "abcdefgh" }).Result); - Assert.False(assert.Like(value, "name", new string[] { "AB*" }, caseSensitive: true).Result); - Assert.False(assert.Like(value, "name", new string[] { "cd" }).Result); - - // Integer - Assert.False(assert.Like(value, "value", new string[] { "12*" }).Result); - - Assert.Equal("value", assert.Like(value, "value", new string[] { "12*" }).ToResultReason().FirstOrDefault().Path); - } + [Fact] + public void Like() + { + SetContext(); + var assert = GetAssertionHelper(); + var value = GetObject((name: "name", value: "abcdefg"), (name: "value", value: 123)); + + // String + Assert.True(assert.Like(value, "name", new string[] { "123*", "ab*" }).Result); + Assert.True(assert.Like(value, "name", new string[] { "123*", "ab*" }, caseSensitive: true).Result); + Assert.True(assert.Like(value, "name", new string[] { "AB*" }).Result); + Assert.True(assert.Like(value, "name", new string[] { "*cd*" }).Result); + Assert.True(assert.Like(value, "name", new string[] { "*fg*" }).Result); + Assert.False(assert.Like(value, "name", new string[] { "abcdefgh" }).Result); + Assert.False(assert.Like(value, "name", new string[] { "AB*" }, caseSensitive: true).Result); + Assert.False(assert.Like(value, "name", new string[] { "cd" }).Result); + + // Integer + Assert.False(assert.Like(value, "value", new string[] { "12*" }).Result); + + Assert.Equal("value", assert.Like(value, "value", new string[] { "12*" }).ToResultReason().FirstOrDefault().Path); + } - [Fact] - public void NotLike() - { - SetContext(); - var assert = GetAssertionHelper(); - var value = GetObject((name: "name", value: "abcdefg"), (name: "value", value: 123)); - - // String - Assert.False(assert.NotLike(value, "name", new string[] { "123*", "ab*" }).Result); - Assert.False(assert.NotLike(value, "name", new string[] { "123*", "ab*" }, caseSensitive: true).Result); - Assert.False(assert.NotLike(value, "name", new string[] { "AB*" }).Result); - Assert.False(assert.NotLike(value, "name", new string[] { "*cd*" }).Result); - Assert.False(assert.NotLike(value, "name", new string[] { "*fg*" }).Result); - Assert.True(assert.NotLike(value, "name", new string[] { "abcdefgh" }).Result); - Assert.True(assert.NotLike(value, "name", new string[] { "AB*" }, caseSensitive: true).Result); - Assert.True(assert.NotLike(value, "name", new string[] { "cd" }).Result); - - // Integer - Assert.True(assert.NotLike(value, "value", new string[] { "12*" }).Result); - - Assert.Equal("name", assert.NotLike(value, "name", new string[] { "123*", "ab*" }).ToResultReason().FirstOrDefault().Path); - } + [Fact] + public void NotLike() + { + SetContext(); + var assert = GetAssertionHelper(); + var value = GetObject((name: "name", value: "abcdefg"), (name: "value", value: 123)); + + // String + Assert.False(assert.NotLike(value, "name", new string[] { "123*", "ab*" }).Result); + Assert.False(assert.NotLike(value, "name", new string[] { "123*", "ab*" }, caseSensitive: true).Result); + Assert.False(assert.NotLike(value, "name", new string[] { "AB*" }).Result); + Assert.False(assert.NotLike(value, "name", new string[] { "*cd*" }).Result); + Assert.False(assert.NotLike(value, "name", new string[] { "*fg*" }).Result); + Assert.True(assert.NotLike(value, "name", new string[] { "abcdefgh" }).Result); + Assert.True(assert.NotLike(value, "name", new string[] { "AB*" }, caseSensitive: true).Result); + Assert.True(assert.NotLike(value, "name", new string[] { "cd" }).Result); + + // Integer + Assert.True(assert.NotLike(value, "value", new string[] { "12*" }).Result); + + Assert.Equal("name", assert.NotLike(value, "name", new string[] { "123*", "ab*" }).ToResultReason().FirstOrDefault().Path); + } - #region Helper methods + #region Helper methods - private static void SetContext() - { - var context = PipelineContext.New(new Configuration.PSRuleOption(), null, null, null, null, null, null, null); - var runspace = new RunspaceContext(context, null); - runspace.PushScope(RunspaceScope.Rule); - } + private static void SetContext() + { + var context = PipelineContext.New(new Configuration.PSRuleOption(), null, null, null, null, null, null, null); + var runspace = new RunspaceContext(context, null); + runspace.PushScope(RunspaceScope.Rule); + } - private static PSObject GetObject(params (string name, object value)[] properties) - { - var result = new PSObject(); - for (var i = 0; properties != null && i < properties.Length; i++) - result.Properties.Add(new PSNoteProperty(properties[i].name, properties[i].value)); + private static PSObject GetObject(params (string name, object value)[] properties) + { + var result = new PSObject(); + for (var i = 0; properties != null && i < properties.Length; i++) + result.Properties.Add(new PSNoteProperty(properties[i].name, properties[i].value)); - return result; - } + return result; + } - private static Runtime.Assert GetAssertionHelper() - { - return new Runtime.Assert(); - } + private static Runtime.Assert GetAssertionHelper() + { + return new Runtime.Assert(); + } - private static string GetSourcePath(string fileName) - { - return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName); - } + private static string GetSourcePath(string fileName) + { + return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName); + } - private bool AssertionResult(AssertResult result) + private bool AssertionResult(AssertResult result) + { + if (!result.Result) { - if (!result.Result) - { - var reasons = result.GetReason(); - for (var i = 0; reasons != null && i < reasons.Length; i++) - _Output.WriteLine(reasons[i]); - } - return result.Result; + var reasons = result.GetReason(); + for (var i = 0; reasons != null && i < reasons.Length; i++) + _Output.WriteLine(reasons[i]); } - - #endregion Helper methods + return result.Result; } + + #endregion Helper methods } diff --git a/tests/PSRule.Tests/BadgeResourcesTests.cs b/tests/PSRule.Tests/BadgeResourcesTests.cs index b3318b3773..a1a95d1baf 100644 --- a/tests/PSRule.Tests/BadgeResourcesTests.cs +++ b/tests/PSRule.Tests/BadgeResourcesTests.cs @@ -3,15 +3,14 @@ using PSRule.Badges; -namespace PSRule +namespace PSRule; + +public sealed class BadgeResourcesTests { - public sealed class BadgeResourcesTests + [Fact] + public void TestResources() { - [Fact] - public void TestResources() - { - var width = BadgeResources.Measure("PSRule"); - Assert.True(width > 0); - } + var width = BadgeResources.Measure("PSRule"); + Assert.True(width > 0); } } diff --git a/tests/PSRule.Tests/BaselineTests.cs b/tests/PSRule.Tests/BaselineTests.cs index 24625c03e4..190ac4579a 100644 --- a/tests/PSRule.Tests/BaselineTests.cs +++ b/tests/PSRule.Tests/BaselineTests.cs @@ -15,212 +15,211 @@ using PSRule.Runtime; using YamlDotNet.Serialization; -namespace PSRule +namespace PSRule; + +public sealed class BaselineTests { - public sealed class BaselineTests + private const string BaselineYamlFileName = "Baseline.Rule.yaml"; + private const string BaselineJsonFileName = "Baseline.Rule.jsonc"; + + [Fact] + public void ReadBaselineYaml() + { + var baseline = GetBaselines(GetSource(BaselineYamlFileName)); + Assert.NotNull(baseline); + Assert.Equal(6, baseline.Length); + + // TestBaseline1 + Assert.Equal("TestBaseline1", baseline[0].Name); + Assert.Equal("github.com/microsoft/PSRule/v1", baseline[0].ApiVersion); + Assert.Equal("value", baseline[0].Metadata.Annotations["key"]); + Assert.False(baseline[0].Obsolete); + Assert.Equal("This is an example baseline", baseline[0].Info.Synopsis.Text); + + var config = baseline[0].Spec.Configuration["key2"] as Array; + Assert.NotNull(config); + Assert.Equal(2, config.Length); + Assert.IsType(config.GetValue(0)); + var pso = config.GetValue(0) as PSObject; + Assert.Equal("abc", pso.PropertyValue("value1")); + pso = config.GetValue(1) as PSObject; + Assert.Equal("def", pso.PropertyValue("value2")); + + // TestBaseline4 + Assert.Equal("TestBaseline4", baseline[3].Name); + Assert.Null(baseline[3].Info.Synopsis.Text); + + // TestBaseline5 + Assert.Equal("TestBaseline5", baseline[4].Name); + Assert.Equal("github.com/microsoft/PSRule/v1", baseline[4].ApiVersion); + Assert.True(baseline[4].Obsolete); + Assert.Equal("This is an example obsolete baseline", baseline[4].Info.Synopsis.Text); + + // TestBaseline6 + Assert.Equal("TestBaseline6", baseline[5].Name); + var labels = baseline[5].Spec.Rule.Labels; + Assert.True(labels.Contains("framework.v1/control", new string[] { "*" })); + Assert.True(labels.Contains("framework.v1/control", new string[] { "c-1" })); + Assert.False(labels.Contains("framework.v1/control", new string[] { "c-3" })); + Assert.False(labels.Contains("framework.v3/control", new string[] { "*" })); + } + + [Fact] + public void ReadBaselineJson() + { + var baseline = GetBaselines(GetSource(BaselineJsonFileName)); + Assert.NotNull(baseline); + Assert.Equal(6, baseline.Length); + + // TestBaseline1 + Assert.Equal("TestBaseline1", baseline[0].Name); + Assert.Equal("github.com/microsoft/PSRule/v1", baseline[0].ApiVersion); + Assert.Equal("value", baseline[0].Metadata.Annotations["key"]); + Assert.False(baseline[0].Obsolete); + Assert.Equal("This is an example baseline", baseline[0].Info.Synopsis.Text); + + var config = (JArray)baseline[0].Spec.Configuration["key2"]; + Assert.NotNull(config); + Assert.Equal(2, config.Count); + Assert.True(config[0].Type == JTokenType.Object); + Assert.Equal("abc", config[0]["value1"]); + Assert.True(config[1].Type == JTokenType.Object); + Assert.Equal("def", config[1]["value2"]); + + // TestBaseline4 + Assert.Equal("TestBaseline4", baseline[3].Name); + Assert.Null(baseline[3].Info.Synopsis.Text); + + // TestBaseline5 + Assert.Equal("TestBaseline5", baseline[4].Name); + Assert.Equal("github.com/microsoft/PSRule/v1", baseline[4].ApiVersion); + Assert.True(baseline[4].Obsolete); + Assert.Equal("This is an example obsolete baseline", baseline[4].Info.Synopsis.Text); + + // TestBaseline6 + Assert.Equal("TestBaseline6", baseline[5].Name); + var labels = baseline[5].Spec.Rule.Labels; + Assert.True(labels.Contains("framework.v1/control", new string[] { "*" })); + Assert.True(labels.Contains("framework.v1/control", new string[] { "c-1" })); + Assert.False(labels.Contains("framework.v1/control", new string[] { "c-3" })); + Assert.False(labels.Contains("framework.v3/control", new string[] { "*" })); + } + + [Theory] + [InlineData(SourceType.Yaml, BaselineYamlFileName)] + [InlineData(SourceType.Json, BaselineJsonFileName)] + public void ReadBaselineInModule(SourceType type, string path) + { + var baseline = GetBaselines(GetSourceInModule(path, "TestModule", type)); + Assert.NotNull(baseline); + Assert.Equal(6, baseline.Length); + + // TestBaseline1 + Assert.Equal("TestBaseline1", baseline[0].Name); + Assert.Equal("github.com/microsoft/PSRule/v1", baseline[0].ApiVersion); + Assert.Equal("value", baseline[0].Metadata.Annotations["key"]); + Assert.False(baseline[0].Obsolete); + } + + [Theory] + [InlineData(BaselineYamlFileName)] + [InlineData(BaselineJsonFileName)] + public void FilterBaseline(string path) + { + var baseline = GetBaselines(GetSource(path)); + Assert.NotNull(baseline); + + var filter = new BaselineFilter(new string[] { "TestBaseline5" }); + var actual = baseline.FirstOrDefault(b => filter.Match(b)); + + Assert.Equal("TestBaseline5", actual.Name); + } + + [Fact] + public void BaselineAsYaml() + { + var expected = GetBaselines(GetSource(BaselineYamlFileName)); + var s = YamlOutputWriter.ToYaml(expected); + var d = new DeserializerBuilder().Build(); + var actual = d.Deserialize(s); + + // TestBaseline1 + Assert.Equal("github.com/microsoft/PSRule/v1", actual[0]["apiVersion"]); + Assert.Equal("Baseline", actual[0]["kind"]); + Assert.Equal("TestBaseline1", actual[0]["metadata"]["name"]); + Assert.NotNull(actual[0]["spec"]); + Assert.Equal("kind", actual[0]["spec"]["binding"]["field"]["kind"][0]); + Assert.Equal("Id", actual[0]["spec"]["binding"]["field"]["uniqueIdentifer"][0]); + Assert.Equal("AlternateName", actual[0]["spec"]["binding"]["field"]["uniqueIdentifer"][1]); + Assert.Equal("AlternateName", actual[0]["spec"]["binding"]["targetName"][0]); + Assert.Equal("kind", actual[0]["spec"]["binding"]["targetType"][0]); + Assert.Equal("WithBaseline", actual[0]["spec"]["rule"]["include"][0]); + Assert.Equal("value1", actual[0]["spec"]["configuration"]["key1"]); + Assert.Equal("abc", actual[0]["spec"]["configuration"]["key2"][0]["value1"]); + Assert.Equal("def", actual[0]["spec"]["configuration"]["key2"][1]["value2"]); + } + + [Fact] + public void BaselineAsJson() + { + var expected = new object[] { GetBaselines(GetSource(BaselineJsonFileName)) }; + var json = JsonOutputWriter.ToJson(expected, null); + var actual = (JArray)JsonConvert.DeserializeObject(json); + + // TestBaseline1 + Assert.Equal("github.com/microsoft/PSRule/v1", actual[0]["apiVersion"]); + Assert.Equal("Baseline", actual[0]["kind"]); + Assert.Equal("TestBaseline1", actual[0]["metadata"]["name"]); + Assert.NotNull(actual[0]["spec"]); + Assert.Equal("kind", actual[0]["spec"]["binding"]["field"]["kind"][0]); + Assert.Equal("AlternateName", actual[0]["spec"]["binding"]["field"]["uniqueIdentifer"][0]); + Assert.Equal("Id", actual[0]["spec"]["binding"]["field"]["uniqueIdentifer"][1]); + Assert.Equal("AlternateName", actual[0]["spec"]["binding"]["targetName"][0]); + Assert.Equal("kind", actual[0]["spec"]["binding"]["targetType"][0]); + Assert.Equal("WithBaseline", actual[0]["spec"]["rule"]["include"][0]); + Assert.Equal("value1", actual[0]["spec"]["configuration"]["key1"]); + Assert.Equal("abc", actual[0]["spec"]["configuration"]["key2"][0]["value1"]); + Assert.Equal("def", actual[0]["spec"]["configuration"]["key2"][1]["value2"]); + } + + #region Helper methods + + private static Baseline[] GetBaselines(Source[] source) + { + 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(); + return baseline; + } + + private static PSRuleOption GetOption() + { + return new PSRuleOption(); + } + + private static Source[] GetSource(string path) + { + var builder = new SourcePipelineBuilder(null, null); + builder.Directory(GetSourcePath(path)); + return builder.Build(); + } + + private static Source[] GetSourceInModule(string path, string moduleName, SourceType type) { - private const string BaselineYamlFileName = "Baseline.Rule.yaml"; - private const string BaselineJsonFileName = "Baseline.Rule.jsonc"; - - [Fact] - public void ReadBaselineYaml() - { - var baseline = GetBaselines(GetSource(BaselineYamlFileName)); - Assert.NotNull(baseline); - Assert.Equal(6, baseline.Length); - - // TestBaseline1 - Assert.Equal("TestBaseline1", baseline[0].Name); - Assert.Equal("github.com/microsoft/PSRule/v1", baseline[0].ApiVersion); - Assert.Equal("value", baseline[0].Metadata.Annotations["key"]); - Assert.False(baseline[0].Obsolete); - Assert.Equal("This is an example baseline", baseline[0].Info.Synopsis.Text); - - var config = baseline[0].Spec.Configuration["key2"] as Array; - Assert.NotNull(config); - Assert.Equal(2, config.Length); - Assert.IsType(config.GetValue(0)); - var pso = config.GetValue(0) as PSObject; - Assert.Equal("abc", pso.PropertyValue("value1")); - pso = config.GetValue(1) as PSObject; - Assert.Equal("def", pso.PropertyValue("value2")); - - // TestBaseline4 - Assert.Equal("TestBaseline4", baseline[3].Name); - Assert.Null(baseline[3].Info.Synopsis.Text); - - // TestBaseline5 - Assert.Equal("TestBaseline5", baseline[4].Name); - Assert.Equal("github.com/microsoft/PSRule/v1", baseline[4].ApiVersion); - Assert.True(baseline[4].Obsolete); - Assert.Equal("This is an example obsolete baseline", baseline[4].Info.Synopsis.Text); - - // TestBaseline6 - Assert.Equal("TestBaseline6", baseline[5].Name); - var labels = baseline[5].Spec.Rule.Labels; - Assert.True(labels.Contains("framework.v1/control", new string[] { "*" })); - Assert.True(labels.Contains("framework.v1/control", new string[] { "c-1" })); - Assert.False(labels.Contains("framework.v1/control", new string[] { "c-3" })); - Assert.False(labels.Contains("framework.v3/control", new string[] { "*" })); - } - - [Fact] - public void ReadBaselineJson() - { - var baseline = GetBaselines(GetSource(BaselineJsonFileName)); - Assert.NotNull(baseline); - Assert.Equal(6, baseline.Length); - - // TestBaseline1 - Assert.Equal("TestBaseline1", baseline[0].Name); - Assert.Equal("github.com/microsoft/PSRule/v1", baseline[0].ApiVersion); - Assert.Equal("value", baseline[0].Metadata.Annotations["key"]); - Assert.False(baseline[0].Obsolete); - Assert.Equal("This is an example baseline", baseline[0].Info.Synopsis.Text); - - var config = (JArray)baseline[0].Spec.Configuration["key2"]; - Assert.NotNull(config); - Assert.Equal(2, config.Count); - Assert.True(config[0].Type == JTokenType.Object); - Assert.Equal("abc", config[0]["value1"]); - Assert.True(config[1].Type == JTokenType.Object); - Assert.Equal("def", config[1]["value2"]); - - // TestBaseline4 - Assert.Equal("TestBaseline4", baseline[3].Name); - Assert.Null(baseline[3].Info.Synopsis.Text); - - // TestBaseline5 - Assert.Equal("TestBaseline5", baseline[4].Name); - Assert.Equal("github.com/microsoft/PSRule/v1", baseline[4].ApiVersion); - Assert.True(baseline[4].Obsolete); - Assert.Equal("This is an example obsolete baseline", baseline[4].Info.Synopsis.Text); - - // TestBaseline6 - Assert.Equal("TestBaseline6", baseline[5].Name); - var labels = baseline[5].Spec.Rule.Labels; - Assert.True(labels.Contains("framework.v1/control", new string[] { "*" })); - Assert.True(labels.Contains("framework.v1/control", new string[] { "c-1" })); - Assert.False(labels.Contains("framework.v1/control", new string[] { "c-3" })); - Assert.False(labels.Contains("framework.v3/control", new string[] { "*" })); - } - - [Theory] - [InlineData(SourceType.Yaml, BaselineYamlFileName)] - [InlineData(SourceType.Json, BaselineJsonFileName)] - public void ReadBaselineInModule(SourceType type, string path) - { - var baseline = GetBaselines(GetSourceInModule(path, "TestModule", type)); - Assert.NotNull(baseline); - Assert.Equal(6, baseline.Length); - - // TestBaseline1 - Assert.Equal("TestBaseline1", baseline[0].Name); - Assert.Equal("github.com/microsoft/PSRule/v1", baseline[0].ApiVersion); - Assert.Equal("value", baseline[0].Metadata.Annotations["key"]); - Assert.False(baseline[0].Obsolete); - } - - [Theory] - [InlineData(BaselineYamlFileName)] - [InlineData(BaselineJsonFileName)] - public void FilterBaseline(string path) - { - var baseline = GetBaselines(GetSource(path)); - Assert.NotNull(baseline); - - var filter = new BaselineFilter(new string[] { "TestBaseline5" }); - var actual = baseline.FirstOrDefault(b => filter.Match(b)); - - Assert.Equal("TestBaseline5", actual.Name); - } - - [Fact] - public void BaselineAsYaml() - { - var expected = GetBaselines(GetSource(BaselineYamlFileName)); - var s = YamlOutputWriter.ToYaml(expected); - var d = new DeserializerBuilder().Build(); - var actual = d.Deserialize(s); - - // TestBaseline1 - Assert.Equal("github.com/microsoft/PSRule/v1", actual[0]["apiVersion"]); - Assert.Equal("Baseline", actual[0]["kind"]); - Assert.Equal("TestBaseline1", actual[0]["metadata"]["name"]); - Assert.NotNull(actual[0]["spec"]); - Assert.Equal("kind", actual[0]["spec"]["binding"]["field"]["kind"][0]); - Assert.Equal("Id", actual[0]["spec"]["binding"]["field"]["uniqueIdentifer"][0]); - Assert.Equal("AlternateName", actual[0]["spec"]["binding"]["field"]["uniqueIdentifer"][1]); - Assert.Equal("AlternateName", actual[0]["spec"]["binding"]["targetName"][0]); - Assert.Equal("kind", actual[0]["spec"]["binding"]["targetType"][0]); - Assert.Equal("WithBaseline", actual[0]["spec"]["rule"]["include"][0]); - Assert.Equal("value1", actual[0]["spec"]["configuration"]["key1"]); - Assert.Equal("abc", actual[0]["spec"]["configuration"]["key2"][0]["value1"]); - Assert.Equal("def", actual[0]["spec"]["configuration"]["key2"][1]["value2"]); - } - - [Fact] - public void BaselineAsJson() - { - var expected = new object[] { GetBaselines(GetSource(BaselineJsonFileName)) }; - var json = JsonOutputWriter.ToJson(expected, null); - var actual = (JArray)JsonConvert.DeserializeObject(json); - - // TestBaseline1 - Assert.Equal("github.com/microsoft/PSRule/v1", actual[0]["apiVersion"]); - Assert.Equal("Baseline", actual[0]["kind"]); - Assert.Equal("TestBaseline1", actual[0]["metadata"]["name"]); - Assert.NotNull(actual[0]["spec"]); - Assert.Equal("kind", actual[0]["spec"]["binding"]["field"]["kind"][0]); - Assert.Equal("AlternateName", actual[0]["spec"]["binding"]["field"]["uniqueIdentifer"][0]); - Assert.Equal("Id", actual[0]["spec"]["binding"]["field"]["uniqueIdentifer"][1]); - Assert.Equal("AlternateName", actual[0]["spec"]["binding"]["targetName"][0]); - Assert.Equal("kind", actual[0]["spec"]["binding"]["targetType"][0]); - Assert.Equal("WithBaseline", actual[0]["spec"]["rule"]["include"][0]); - Assert.Equal("value1", actual[0]["spec"]["configuration"]["key1"]); - Assert.Equal("abc", actual[0]["spec"]["configuration"]["key2"][0]["value1"]); - Assert.Equal("def", actual[0]["spec"]["configuration"]["key2"][1]["value2"]); - } - - #region Helper methods - - private static Baseline[] GetBaselines(Source[] source) - { - 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(); - return baseline; - } - - private static PSRuleOption GetOption() - { - return new PSRuleOption(); - } - - private static Source[] GetSource(string path) - { - var builder = new SourcePipelineBuilder(null, null); - builder.Directory(GetSourcePath(path)); - return builder.Build(); - } - - private static Source[] GetSourceInModule(string path, string moduleName, SourceType type) - { - var file = new SourceFile(GetSourcePath(path), moduleName, type, null); - var source = new Source( - module: new Source.ModuleInfo(AppDomain.CurrentDomain.BaseDirectory, moduleName, "1.0.0", null, "4de0fd26-6aae-401f-a943-b49f082f141e", "Microsoft", null), - file: new SourceFile[] { file }, - dependency: false - ); - return new Source[] { source }; - } - - private static string GetSourcePath(string fileName) - { - return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName); - } - - #endregion Helper methods + var file = new SourceFile(GetSourcePath(path), moduleName, type, null); + var source = new Source( + module: new Source.ModuleInfo(AppDomain.CurrentDomain.BaseDirectory, moduleName, "1.0.0", null, "4de0fd26-6aae-401f-a943-b49f082f141e", "Microsoft", null), + file: new SourceFile[] { file }, + dependency: false + ); + return new Source[] { source }; } + + private static string GetSourcePath(string fileName) + { + return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName); + } + + #endregion Helper methods } diff --git a/tests/PSRule.Tests/ConventionTests.cs b/tests/PSRule.Tests/ConventionTests.cs index 46d74b0186..1949bdc392 100644 --- a/tests/PSRule.Tests/ConventionTests.cs +++ b/tests/PSRule.Tests/ConventionTests.cs @@ -8,105 +8,104 @@ using PSRule.Pipeline; using static PSRule.PipelineTests; -namespace PSRule +namespace PSRule; + +public sealed class ConventionTests { - public sealed class ConventionTests + [Fact] + public void WithConventions() { - [Fact] - public void WithConventions() - { - var testObject1 = new TestObject { Name = "TestObject1" }; - var option = GetOption(); - option.Rule.Include = new string[] { "ConventionTest" }; - option.Convention.Include = new string[] { "Convention1" }; - var builder = PipelineBuilder.Invoke(GetSource(), option, null); - var pipeline = builder.Build(); - - Assert.NotNull(pipeline); - pipeline.Begin(); - pipeline.Process(PSObject.AsPSObject(testObject1)); - pipeline.End(); - } + var testObject1 = new TestObject { Name = "TestObject1" }; + var option = GetOption(); + option.Rule.Include = new string[] { "ConventionTest" }; + option.Convention.Include = new string[] { "Convention1" }; + var builder = PipelineBuilder.Invoke(GetSource(), option, null); + var pipeline = builder.Build(); - [Fact] - public void ConventionOrder() - { - var testObject1 = new TestObject { Name = "TestObject1" }; - var option = GetOption(); - option.Rule.Include = new string[] { "ConventionTest" }; + Assert.NotNull(pipeline); + pipeline.Begin(); + pipeline.Process(PSObject.AsPSObject(testObject1)); + pipeline.End(); + } - // Order 1 - option.Convention.Include = new string[] { "Convention1", "Convention2" }; - var writer = new TestWriter(option); - var builder = PipelineBuilder.Invoke(GetSource(), option, null); - var pipeline = builder.Build(writer); - pipeline.Begin(); - pipeline.Process(PSObject.AsPSObject(testObject1)); - pipeline.End(); - var actual1 = writer.Output[0] as InvokeResult; - var actual2 = actual1.AsRecord()[0].Data["count"]; - Assert.Equal(110, actual2); + [Fact] + public void ConventionOrder() + { + var testObject1 = new TestObject { Name = "TestObject1" }; + var option = GetOption(); + option.Rule.Include = new string[] { "ConventionTest" }; - // Order 2 - option.Convention.Include = new string[] { "Convention2", "Convention1" }; - writer = new TestWriter(option); - builder = PipelineBuilder.Invoke(GetSource(), option, null); - pipeline = builder.Build(writer); - pipeline.Begin(); - pipeline.Process(PSObject.AsPSObject(testObject1)); - pipeline.End(); - actual1 = writer.Output[0] as InvokeResult; - actual2 = actual1.AsRecord()[0].Data["count"]; - Assert.Equal(11, actual2); - } + // Order 1 + option.Convention.Include = new string[] { "Convention1", "Convention2" }; + var writer = new TestWriter(option); + var builder = PipelineBuilder.Invoke(GetSource(), option, null); + var pipeline = builder.Build(writer); + pipeline.Begin(); + pipeline.Process(PSObject.AsPSObject(testObject1)); + pipeline.End(); + var actual1 = writer.Output[0] as InvokeResult; + var actual2 = actual1.AsRecord()[0].Data["count"]; + Assert.Equal(110, actual2); - /// - /// Test that localized data is accessible to conventions at each block. - /// - [Fact] - public void WithLocalizedData() - { - var testObject1 = new TestObject { Name = "TestObject1" }; - var option = GetOption(); - option.Rule.Include = new string[] { "WithLocalizedDataPrecondition" }; - option.Convention.Include = new string[] { "Convention.WithLocalizedData" }; - var writer = new TestWriter(option); - var builder = PipelineBuilder.Invoke(GetSource(), option, null); - var pipeline = builder.Build(writer); + // Order 2 + option.Convention.Include = new string[] { "Convention2", "Convention1" }; + writer = new TestWriter(option); + builder = PipelineBuilder.Invoke(GetSource(), option, null); + pipeline = builder.Build(writer); + pipeline.Begin(); + pipeline.Process(PSObject.AsPSObject(testObject1)); + pipeline.End(); + actual1 = writer.Output[0] as InvokeResult; + actual2 = actual1.AsRecord()[0].Data["count"]; + Assert.Equal(11, actual2); + } - Assert.NotNull(pipeline); - pipeline.Begin(); - pipeline.Process(PSObject.AsPSObject(testObject1)); - pipeline.End(); - Assert.NotEmpty(writer.Information); - Assert.Equal("LocalizedMessage for en. Format=Initialize.", writer.Information[0] as string); - Assert.Equal("LocalizedMessage for en. Format=Begin.", writer.Information[1] as string); - Assert.Equal("LocalizedMessage for en. Format=Precondition.", writer.Information[2] as string); - Assert.Equal("LocalizedMessage for en. Format=Process.", writer.Information[3] as string); - Assert.Equal("LocalizedMessage for en. Format=End.", writer.Information[4] as string); - } + /// + /// Test that localized data is accessible to conventions at each block. + /// + [Fact] + public void WithLocalizedData() + { + var testObject1 = new TestObject { Name = "TestObject1" }; + var option = GetOption(); + option.Rule.Include = new string[] { "WithLocalizedDataPrecondition" }; + option.Convention.Include = new string[] { "Convention.WithLocalizedData" }; + var writer = new TestWriter(option); + var builder = PipelineBuilder.Invoke(GetSource(), option, null); + var pipeline = builder.Build(writer); - #region Helper methods + Assert.NotNull(pipeline); + pipeline.Begin(); + pipeline.Process(PSObject.AsPSObject(testObject1)); + pipeline.End(); + Assert.NotEmpty(writer.Information); + Assert.Equal("LocalizedMessage for en. Format=Initialize.", writer.Information[0] as string); + Assert.Equal("LocalizedMessage for en. Format=Begin.", writer.Information[1] as string); + Assert.Equal("LocalizedMessage for en. Format=Precondition.", writer.Information[2] as string); + Assert.Equal("LocalizedMessage for en. Format=Process.", writer.Information[3] as string); + Assert.Equal("LocalizedMessage for en. Format=End.", writer.Information[4] as string); + } - private static Source[] GetSource() - { - var builder = new SourcePipelineBuilder(null, null); - builder.Directory(GetSourcePath("FromFileConventions.Rule.ps1")); - return builder.Build(); - } + #region Helper methods - private static PSRuleOption GetOption(string path = null) - { - var option = path == null ? new PSRuleOption() : PSRuleOption.FromFile(path); - option.Output.Culture = new[] { "en-US" }; - return option; - } + private static Source[] GetSource() + { + var builder = new SourcePipelineBuilder(null, null); + builder.Directory(GetSourcePath("FromFileConventions.Rule.ps1")); + return builder.Build(); + } - private static string GetSourcePath(string fileName) - { - return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName); - } + private static PSRuleOption GetOption(string path = null) + { + var option = path == null ? new PSRuleOption() : PSRuleOption.FromFile(path); + option.Output.Culture = new[] { "en-US" }; + return option; + } - #endregion Helper methods + private static string GetSourcePath(string fileName) + { + return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName); } + + #endregion Helper methods } diff --git a/tests/PSRule.Tests/DateVersionTests.cs b/tests/PSRule.Tests/DateVersionTests.cs index aa7b95a99f..e23165eb15 100644 --- a/tests/PSRule.Tests/DateVersionTests.cs +++ b/tests/PSRule.Tests/DateVersionTests.cs @@ -3,181 +3,180 @@ using PSRule.Data; -namespace PSRule +namespace PSRule; + +/// +/// Tests for date version comparison. +/// +public sealed class DateVersionTests { /// - /// Tests for date version comparison. + /// Test parsing of versions. /// - public sealed class DateVersionTests + [Fact] + public void Version() { - /// - /// Test parsing of versions. - /// - [Fact] - public void Version() - { - Assert.True(DateVersion.TryParseVersion("2015-10-01", out var actual)); - Assert.Equal(2015, actual.Year); - Assert.Equal(10, actual.Month); - Assert.Equal(1, actual.Day); - Assert.Equal(string.Empty, actual.Prerelease.Value); + Assert.True(DateVersion.TryParseVersion("2015-10-01", out var actual)); + Assert.Equal(2015, actual.Year); + Assert.Equal(10, actual.Month); + Assert.Equal(1, actual.Day); + Assert.Equal(string.Empty, actual.Prerelease.Value); - Assert.True(DateVersion.TryParseVersion("2015-1-01-prerelease", out actual)); - Assert.Equal(2015, actual.Year); - Assert.Equal(1, actual.Month); - Assert.Equal(1, actual.Day); - Assert.Equal("prerelease", actual.Prerelease.Value); - } + Assert.True(DateVersion.TryParseVersion("2015-1-01-prerelease", out actual)); + Assert.Equal(2015, actual.Year); + Assert.Equal(1, actual.Month); + Assert.Equal(1, actual.Day); + Assert.Equal("prerelease", actual.Prerelease.Value); + } - /// - /// Test ordering of versions by comparison. - /// - [Fact] - public void VersionOrder() - { - Assert.True(DateVersion.TryParseVersion("2015-10-01", out var actual1)); - Assert.True(DateVersion.TryParseVersion("2015-10-01-prerelease", out var actual2)); - Assert.True(DateVersion.TryParseVersion("2022-03-01", out var actual3)); - Assert.True(DateVersion.TryParseVersion("2022-01-03", out var actual4)); + /// + /// Test ordering of versions by comparison. + /// + [Fact] + public void VersionOrder() + { + Assert.True(DateVersion.TryParseVersion("2015-10-01", out var actual1)); + Assert.True(DateVersion.TryParseVersion("2015-10-01-prerelease", out var actual2)); + Assert.True(DateVersion.TryParseVersion("2022-03-01", out var actual3)); + Assert.True(DateVersion.TryParseVersion("2022-01-03", out var actual4)); - Assert.True(actual1.CompareTo(actual1) == 0); - Assert.True(actual1.CompareTo(actual2) > 0); - Assert.True(actual1.CompareTo(actual3) < 0); - Assert.True(actual1.CompareTo(actual4) < 0); - Assert.True(actual2.CompareTo(actual2) == 0); - Assert.True(actual2.CompareTo(actual1) < 0); - Assert.True(actual2.CompareTo(actual3) < 0); - Assert.True(actual2.CompareTo(actual4) < 0); - Assert.True(actual3.CompareTo(actual4) > 0); - Assert.True(actual1.CompareTo(actual4) < 0); - } + Assert.True(actual1.CompareTo(actual1) == 0); + Assert.True(actual1.CompareTo(actual2) > 0); + Assert.True(actual1.CompareTo(actual3) < 0); + Assert.True(actual1.CompareTo(actual4) < 0); + Assert.True(actual2.CompareTo(actual2) == 0); + Assert.True(actual2.CompareTo(actual1) < 0); + Assert.True(actual2.CompareTo(actual3) < 0); + Assert.True(actual2.CompareTo(actual4) < 0); + Assert.True(actual3.CompareTo(actual4) > 0); + Assert.True(actual1.CompareTo(actual4) < 0); + } - /// - /// Test parsing of constraints. - /// - [Fact] - public void Constraint() - { - // Versions - Assert.True(DateVersion.TryParseVersion("2015-10-01", out var version1)); - Assert.True(DateVersion.TryParseVersion("2015-10-01-alpha.9", out var version2)); - Assert.True(DateVersion.TryParseVersion("2022-03-01", out var version3)); - Assert.False(DateVersion.TryParseVersion("2022-03-01-", out var _)); - Assert.True(DateVersion.TryParseVersion("2022-03-01-0", out var _)); + /// + /// Test parsing of constraints. + /// + [Fact] + public void Constraint() + { + // Versions + Assert.True(DateVersion.TryParseVersion("2015-10-01", out var version1)); + Assert.True(DateVersion.TryParseVersion("2015-10-01-alpha.9", out var version2)); + Assert.True(DateVersion.TryParseVersion("2022-03-01", out var version3)); + Assert.False(DateVersion.TryParseVersion("2022-03-01-", out var _)); + Assert.True(DateVersion.TryParseVersion("2022-03-01-0", out var _)); - // Constraints - Assert.True(DateVersion.TryParseConstraint("2015-10-01", out var actual1)); - Assert.True(DateVersion.TryParseConstraint("2015-10-01-alpha.3", out var actual2)); - Assert.True(DateVersion.TryParseConstraint(">2015-10-01-alpha.3", out var actual3)); - Assert.True(DateVersion.TryParseConstraint(">2015-10-01-alpha.1", out var actual4)); - Assert.True(DateVersion.TryParseConstraint("<2015-10-01-beta", out var actual5)); - Assert.True(DateVersion.TryParseConstraint("<2022-03-01", out var actual7)); - Assert.True(DateVersion.TryParseConstraint("=2015-10-01", out var actual8)); - Assert.True(DateVersion.TryParseConstraint(">=2015-10-01", out var actual9)); - Assert.True(DateVersion.TryParseConstraint(">=2015-10-01-0", out var actual10)); - Assert.True(DateVersion.TryParseConstraint("<2022-03-01", out var actual11)); - Assert.True(DateVersion.TryParseConstraint("<2022-03-01-9999999999", out var actual12)); - Assert.True(DateVersion.TryParseConstraint("<2015-10-01-0", out var actual14)); - Assert.True(DateVersion.TryParseConstraint("2015-10-01|| >=2022-03-01-0 2022-03-01", out var actual15)); - Assert.True(DateVersion.TryParseConstraint("2015-10-01 ||>=2022-03-01-0 || 2022-03-01", out var actual16)); - Assert.True(DateVersion.TryParseConstraint("2015-10-01||2022-03-01", out var actual17)); - Assert.True(DateVersion.TryParseConstraint(">=2015-09-01", out var actual18, includePrerelease: true)); - Assert.True(DateVersion.TryParseConstraint("<=2022-03-01-0", out var actual19, includePrerelease: true)); - Assert.True(DateVersion.TryParseConstraint("@pre >=2015-09-01", out var actual20)); - Assert.True(DateVersion.TryParseConstraint("@prerelease <=2022-03-01-0", out var actual21)); + // Constraints + Assert.True(DateVersion.TryParseConstraint("2015-10-01", out var actual1)); + Assert.True(DateVersion.TryParseConstraint("2015-10-01-alpha.3", out var actual2)); + Assert.True(DateVersion.TryParseConstraint(">2015-10-01-alpha.3", out var actual3)); + Assert.True(DateVersion.TryParseConstraint(">2015-10-01-alpha.1", out var actual4)); + Assert.True(DateVersion.TryParseConstraint("<2015-10-01-beta", out var actual5)); + Assert.True(DateVersion.TryParseConstraint("<2022-03-01", out var actual7)); + Assert.True(DateVersion.TryParseConstraint("=2015-10-01", out var actual8)); + Assert.True(DateVersion.TryParseConstraint(">=2015-10-01", out var actual9)); + Assert.True(DateVersion.TryParseConstraint(">=2015-10-01-0", out var actual10)); + Assert.True(DateVersion.TryParseConstraint("<2022-03-01", out var actual11)); + Assert.True(DateVersion.TryParseConstraint("<2022-03-01-9999999999", out var actual12)); + Assert.True(DateVersion.TryParseConstraint("<2015-10-01-0", out var actual14)); + Assert.True(DateVersion.TryParseConstraint("2015-10-01|| >=2022-03-01-0 2022-03-01", out var actual15)); + Assert.True(DateVersion.TryParseConstraint("2015-10-01 ||>=2022-03-01-0 || 2022-03-01", out var actual16)); + Assert.True(DateVersion.TryParseConstraint("2015-10-01||2022-03-01", out var actual17)); + Assert.True(DateVersion.TryParseConstraint(">=2015-09-01", out var actual18, includePrerelease: true)); + Assert.True(DateVersion.TryParseConstraint("<=2022-03-01-0", out var actual19, includePrerelease: true)); + Assert.True(DateVersion.TryParseConstraint("@pre >=2015-09-01", out var actual20)); + Assert.True(DateVersion.TryParseConstraint("@prerelease <=2022-03-01-0", out var actual21)); - // Version1 - 2015-10-01 - Assert.True(actual1.Equals(version1)); - Assert.False(actual2.Equals(version1)); - Assert.True(actual3.Equals(version1)); - Assert.True(actual4.Equals(version1)); - Assert.False(actual5.Equals(version1)); - Assert.True(actual7.Equals(version1)); - Assert.True(actual8.Equals(version1)); - Assert.True(actual9.Equals(version1)); - Assert.True(actual10.Equals(version1)); - Assert.True(actual11.Equals(version1)); - Assert.True(actual12.Equals(version1)); - Assert.False(actual14.Equals(version1)); - Assert.True(actual15.Equals(version1)); - Assert.True(actual16.Equals(version1)); - Assert.True(actual17.Equals(version1)); - Assert.True(actual18.Equals(version1)); - Assert.True(actual19.Equals(version1)); - Assert.True(actual20.Equals(version1)); - Assert.True(actual21.Equals(version1)); + // Version1 - 2015-10-01 + Assert.True(actual1.Equals(version1)); + Assert.False(actual2.Equals(version1)); + Assert.True(actual3.Equals(version1)); + Assert.True(actual4.Equals(version1)); + Assert.False(actual5.Equals(version1)); + Assert.True(actual7.Equals(version1)); + Assert.True(actual8.Equals(version1)); + Assert.True(actual9.Equals(version1)); + Assert.True(actual10.Equals(version1)); + Assert.True(actual11.Equals(version1)); + Assert.True(actual12.Equals(version1)); + Assert.False(actual14.Equals(version1)); + Assert.True(actual15.Equals(version1)); + Assert.True(actual16.Equals(version1)); + Assert.True(actual17.Equals(version1)); + Assert.True(actual18.Equals(version1)); + Assert.True(actual19.Equals(version1)); + Assert.True(actual20.Equals(version1)); + Assert.True(actual21.Equals(version1)); - // Version3 - 2015-10-01-alpha.9 - Assert.False(actual1.Equals(version2)); - Assert.False(actual2.Equals(version2)); - Assert.True(actual3.Equals(version2)); - Assert.True(actual4.Equals(version2)); - Assert.True(actual5.Equals(version2)); - Assert.False(actual7.Equals(version2)); - Assert.False(actual8.Equals(version2)); - Assert.False(actual9.Equals(version2)); - Assert.True(actual10.Equals(version2)); - Assert.False(actual11.Equals(version2)); - Assert.False(actual12.Equals(version2)); - Assert.False(actual14.Equals(version2)); - Assert.False(actual15.Equals(version2)); - Assert.False(actual16.Equals(version2)); - Assert.False(actual17.Equals(version2)); - Assert.True(actual18.Equals(version2)); - Assert.True(actual19.Equals(version2)); - Assert.True(actual20.Equals(version2)); - Assert.True(actual21.Equals(version2)); + // Version3 - 2015-10-01-alpha.9 + Assert.False(actual1.Equals(version2)); + Assert.False(actual2.Equals(version2)); + Assert.True(actual3.Equals(version2)); + Assert.True(actual4.Equals(version2)); + Assert.True(actual5.Equals(version2)); + Assert.False(actual7.Equals(version2)); + Assert.False(actual8.Equals(version2)); + Assert.False(actual9.Equals(version2)); + Assert.True(actual10.Equals(version2)); + Assert.False(actual11.Equals(version2)); + Assert.False(actual12.Equals(version2)); + Assert.False(actual14.Equals(version2)); + Assert.False(actual15.Equals(version2)); + Assert.False(actual16.Equals(version2)); + Assert.False(actual17.Equals(version2)); + Assert.True(actual18.Equals(version2)); + Assert.True(actual19.Equals(version2)); + Assert.True(actual20.Equals(version2)); + Assert.True(actual21.Equals(version2)); - // Version4 - 2022-03-01 - Assert.False(actual1.Equals(version3)); - Assert.False(actual2.Equals(version3)); - Assert.True(actual3.Equals(version3)); - Assert.True(actual4.Equals(version3)); - Assert.False(actual5.Equals(version3)); - Assert.False(actual7.Equals(version3)); - Assert.False(actual8.Equals(version3)); - Assert.True(actual9.Equals(version3)); - Assert.True(actual10.Equals(version3)); - Assert.False(actual11.Equals(version3)); - Assert.False(actual12.Equals(version3)); - Assert.False(actual14.Equals(version3)); - Assert.True(actual15.Equals(version3)); - Assert.True(actual16.Equals(version3)); - Assert.True(actual17.Equals(version3)); - Assert.True(actual18.Equals(version3)); - Assert.False(actual19.Equals(version3)); - Assert.True(actual20.Equals(version3)); - Assert.False(actual21.Equals(version3)); - } + // Version4 - 2022-03-01 + Assert.False(actual1.Equals(version3)); + Assert.False(actual2.Equals(version3)); + Assert.True(actual3.Equals(version3)); + Assert.True(actual4.Equals(version3)); + Assert.False(actual5.Equals(version3)); + Assert.False(actual7.Equals(version3)); + Assert.False(actual8.Equals(version3)); + Assert.True(actual9.Equals(version3)); + Assert.True(actual10.Equals(version3)); + Assert.False(actual11.Equals(version3)); + Assert.False(actual12.Equals(version3)); + Assert.False(actual14.Equals(version3)); + Assert.True(actual15.Equals(version3)); + Assert.True(actual16.Equals(version3)); + Assert.True(actual17.Equals(version3)); + Assert.True(actual18.Equals(version3)); + Assert.False(actual19.Equals(version3)); + Assert.True(actual20.Equals(version3)); + Assert.False(actual21.Equals(version3)); + } - /// - /// Test parsing and order of pre-releases. - /// - [Fact] - public void Prerelease() - { - var actual1 = new DateVersion.PR(null); - var actual2 = new DateVersion.PR("alpha"); - var actual3 = new DateVersion.PR("alpha.1"); - var actual4 = new DateVersion.PR("alpha.beta"); - var actual5 = new DateVersion.PR("beta"); - var actual6 = new DateVersion.PR("beta.2"); - var actual7 = new DateVersion.PR("beta.11"); - var actual8 = new DateVersion.PR("rc.1"); - var actual9 = new DateVersion.PR("alpha.9"); + /// + /// Test parsing and order of pre-releases. + /// + [Fact] + public void Prerelease() + { + var actual1 = new DateVersion.PR(null); + var actual2 = new DateVersion.PR("alpha"); + var actual3 = new DateVersion.PR("alpha.1"); + var actual4 = new DateVersion.PR("alpha.beta"); + var actual5 = new DateVersion.PR("beta"); + var actual6 = new DateVersion.PR("beta.2"); + var actual7 = new DateVersion.PR("beta.11"); + var actual8 = new DateVersion.PR("rc.1"); + var actual9 = new DateVersion.PR("alpha.9"); - Assert.True(actual1.CompareTo(actual1) == 0); - Assert.True(actual1.CompareTo(actual2) > 0); - Assert.True(actual1.CompareTo(actual6) > 0); - Assert.True(actual2.CompareTo(actual3) < 0); - Assert.True(actual3.CompareTo(actual4) < 0); - Assert.True(actual4.CompareTo(actual5) < 0); - Assert.True(actual5.CompareTo(actual6) < 0); - Assert.True(actual6.CompareTo(actual7) < 0); - Assert.True(actual7.CompareTo(actual8) < 0); - Assert.True(actual8.CompareTo(actual1) < 0); - Assert.True(actual8.CompareTo(actual2) > 0); - Assert.True(actual9.CompareTo(actual3) > 0); - } + Assert.True(actual1.CompareTo(actual1) == 0); + Assert.True(actual1.CompareTo(actual2) > 0); + Assert.True(actual1.CompareTo(actual6) > 0); + Assert.True(actual2.CompareTo(actual3) < 0); + Assert.True(actual3.CompareTo(actual4) < 0); + Assert.True(actual4.CompareTo(actual5) < 0); + Assert.True(actual5.CompareTo(actual6) < 0); + Assert.True(actual6.CompareTo(actual7) < 0); + Assert.True(actual7.CompareTo(actual8) < 0); + Assert.True(actual8.CompareTo(actual1) < 0); + Assert.True(actual8.CompareTo(actual2) > 0); + Assert.True(actual9.CompareTo(actual3) > 0); } } diff --git a/tests/PSRule.Tests/ExpressionHelpersTests.cs b/tests/PSRule.Tests/ExpressionHelpersTests.cs index 0588e7d156..f62fef9427 100644 --- a/tests/PSRule.Tests/ExpressionHelpersTests.cs +++ b/tests/PSRule.Tests/ExpressionHelpersTests.cs @@ -1,21 +1,20 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -namespace PSRule +namespace PSRule; + +public sealed class ExpressionHelpersTests { - public sealed class ExpressionHelpersTests + [Fact] + public void WithinPath() { - [Fact] - public void WithinPath() - { - Assert.True(ExpressionHelpers.WithinPath("C:\\temp.json", "C:\\", caseSensitive: false)); - Assert.False(ExpressionHelpers.WithinPath("C:\\temp.json", "C:\\temp\\", caseSensitive: false)); - } + Assert.True(ExpressionHelpers.WithinPath("C:\\temp.json", "C:\\", caseSensitive: false)); + Assert.False(ExpressionHelpers.WithinPath("C:\\temp.json", "C:\\temp\\", caseSensitive: false)); + } - [Fact] - public void NormalizePath() - { - Assert.Equal("C:/temp.json", ExpressionHelpers.NormalizePath("C:\\longer\\directory\\name\\", "C:\\temp.json").Replace("/C:/", "C:/")); - } + [Fact] + public void NormalizePath() + { + Assert.Equal("C:/temp.json", ExpressionHelpers.NormalizePath("C:\\longer\\directory\\name\\", "C:\\temp.json").Replace("/C:/", "C:/")); } } diff --git a/tests/PSRule.Tests/FunctionBuilderTests.cs b/tests/PSRule.Tests/FunctionBuilderTests.cs index b5849be1d0..eebe9ef440 100644 --- a/tests/PSRule.Tests/FunctionBuilderTests.cs +++ b/tests/PSRule.Tests/FunctionBuilderTests.cs @@ -10,54 +10,53 @@ using PSRule.Pipeline; using PSRule.Runtime; -namespace PSRule +namespace PSRule; + +public sealed class FunctionBuilderTests { - public sealed class FunctionBuilderTests + private const string FunctionYamlFileName = "Functions.Rule.yaml"; + private const string FunctionJsonFileName = "Functions.Rule.jsonc"; + + [Theory] + [InlineData("Yaml", FunctionYamlFileName)] + [InlineData("Json", FunctionJsonFileName)] + public void Build(string type, string path) + { + Assert.NotNull(GetSelectorVisitor($"{type}.Fn.Example1", GetSource(path), out _)); + Assert.NotNull(GetSelectorVisitor($"{type}.Fn.Example2", GetSource(path), out _)); + Assert.NotNull(GetSelectorVisitor($"{type}.Fn.Example3", GetSource(path), out _)); + Assert.NotNull(GetSelectorVisitor($"{type}.Fn.Example4", GetSource(path), out _)); + Assert.NotNull(GetSelectorVisitor($"{type}.Fn.Example5", GetSource(path), out _)); + Assert.NotNull(GetSelectorVisitor($"{type}.Fn.Example6", GetSource(path), out _)); + } + + #region Helper methods + + private static PSRuleOption GetOption() + { + return new PSRuleOption(); + } + + private static Source[] GetSource(string path) + { + var builder = new SourcePipelineBuilder(null, null); + builder.Directory(GetSourcePath(path)); + return builder.Build(); + } + + private static SelectorVisitor GetSelectorVisitor(string name, Source[] source, out RunspaceContext context) { - private const string FunctionYamlFileName = "Functions.Rule.yaml"; - private const string FunctionJsonFileName = "Functions.Rule.jsonc"; - - [Theory] - [InlineData("Yaml", FunctionYamlFileName)] - [InlineData("Json", FunctionJsonFileName)] - public void Build(string type, string path) - { - Assert.NotNull(GetSelectorVisitor($"{type}.Fn.Example1", GetSource(path), out _)); - Assert.NotNull(GetSelectorVisitor($"{type}.Fn.Example2", GetSource(path), out _)); - Assert.NotNull(GetSelectorVisitor($"{type}.Fn.Example3", GetSource(path), out _)); - Assert.NotNull(GetSelectorVisitor($"{type}.Fn.Example4", GetSource(path), out _)); - Assert.NotNull(GetSelectorVisitor($"{type}.Fn.Example5", GetSource(path), out _)); - Assert.NotNull(GetSelectorVisitor($"{type}.Fn.Example6", GetSource(path), out _)); - } - - #region Helper methods - - private static PSRuleOption GetOption() - { - return new PSRuleOption(); - } - - private static Source[] GetSource(string path) - { - var builder = new SourcePipelineBuilder(null, null); - builder.Directory(GetSourcePath(path)); - return builder.Build(); - } - - 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 OptionContextBuilder(), null), null); - context.Init(source); - context.Begin(); - var selector = HostHelper.GetSelectorForTests(source, context).ToArray().FirstOrDefault(s => s.Name == name); - return new SelectorVisitor(context, selector.Id, selector.Source, selector.Spec.If); - } - - private static string GetSourcePath(string fileName) - { - return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName); - } - - #endregion Helper methods + 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); + return new SelectorVisitor(context, selector.Id, selector.Source, selector.Spec.If); } + + private static string GetSourcePath(string fileName) + { + return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName); + } + + #endregion Helper methods } diff --git a/tests/PSRule.Tests/FunctionTests.cs b/tests/PSRule.Tests/FunctionTests.cs index 25a629453e..b05760bf0e 100644 --- a/tests/PSRule.Tests/FunctionTests.cs +++ b/tests/PSRule.Tests/FunctionTests.cs @@ -8,632 +8,631 @@ using PSRule.Definitions.Expressions; using PSRule.Pipeline; -namespace PSRule +namespace PSRule; + +/// +/// Define tests for expression functions are working correctly. +/// +public sealed class FunctionTests { - /// - /// Define tests for expression functions are working correctly. - /// - public sealed class FunctionTests + [Fact] + public void Concat() + { + var context = GetContext(); + var fn = GetFunction("concat"); + + var properties = new LanguageExpression.PropertyBag + { + { "concat", new object[] { "1", "2", "3" } } + }; + Assert.Equal("123", fn(context, properties)(context)); + + properties = new LanguageExpression.PropertyBag + { + { "concat", new object[] { 1, 2, 3 } } + }; + Assert.Equal("123", fn(context, properties)(context)); + } + + [Fact] + public void Configuration() + { + var context = GetContext(); + var fn = GetFunction("configuration"); + + var properties = new LanguageExpression.PropertyBag + { + { "configuration", "config1" } + }; + Assert.Equal("123", fn(context, properties)(context)); + + properties = new LanguageExpression.PropertyBag + { + { "configuration", "notconfig" } + }; + Assert.Null(fn(context, properties)(context)); + } + + [Fact] + public void Boolean() { - [Fact] - public void Concat() - { - var context = GetContext(); - var fn = GetFunction("concat"); - - var properties = new LanguageExpression.PropertyBag - { - { "concat", new object[] { "1", "2", "3" } } - }; - Assert.Equal("123", fn(context, properties)(context)); - - properties = new LanguageExpression.PropertyBag - { - { "concat", new object[] { 1, 2, 3 } } - }; - Assert.Equal("123", fn(context, properties)(context)); - } - - [Fact] - public void Configuration() - { - var context = GetContext(); - var fn = GetFunction("configuration"); - - var properties = new LanguageExpression.PropertyBag - { - { "configuration", "config1" } - }; - Assert.Equal("123", fn(context, properties)(context)); - - properties = new LanguageExpression.PropertyBag - { - { "configuration", "notconfig" } - }; - Assert.Null(fn(context, properties)(context)); - } - - [Fact] - public void Boolean() - { - var context = GetContext(); - var fn = GetFunction("boolean"); - - var properties = new LanguageExpression.PropertyBag - { - { "boolean", true } - }; - Assert.Equal(true, fn(context, properties)(context)); - - properties = new LanguageExpression.PropertyBag - { - { "boolean", false } - }; - Assert.Equal(false, fn(context, properties)(context)); - - properties = new LanguageExpression.PropertyBag - { - { "boolean", "true" } - }; - Assert.Equal(true, fn(context, properties)(context)); - - properties = new LanguageExpression.PropertyBag - { - { "boolean", "false" } - }; - Assert.Equal(false, fn(context, properties)(context)); - } - - [Fact] - public void Integer() - { - var context = GetContext(); - var fn = GetFunction("integer"); - - var properties = new LanguageExpression.PropertyBag - { - { "integer", 1 } - }; - Assert.Equal(1, fn(context, properties)(context)); - - properties = new LanguageExpression.PropertyBag - { - { "integer", -1 } - }; - Assert.Equal(-1, fn(context, properties)(context)); - - properties = new LanguageExpression.PropertyBag - { - { "integer", 0 } - }; - Assert.Equal(0, fn(context, properties)(context)); - - properties = new LanguageExpression.PropertyBag - { - { "integer", "1" } - }; - Assert.Equal(1, fn(context, properties)(context)); - - properties = new LanguageExpression.PropertyBag - { - { "integer", "-1" } - }; - Assert.Equal(-1, fn(context, properties)(context)); - - properties = new LanguageExpression.PropertyBag - { - { "integer", "0" } - }; - Assert.Equal(0, fn(context, properties)(context)); - - properties = new LanguageExpression.PropertyBag - { - { "integer", "not" } - }; - Assert.Equal(0, fn(context, properties)(context)); - } - - [Fact] - public void String() - { - var context = GetContext(); - var fn = GetFunction("string"); - - var properties = new LanguageExpression.PropertyBag - { - { "string", 1 } - }; - Assert.Equal("1", fn(context, properties)(context)); - - properties = new LanguageExpression.PropertyBag - { - { "string", -1 } - }; - Assert.Equal("-1", fn(context, properties)(context)); - - properties = new LanguageExpression.PropertyBag - { - { "string", "1" } - }; - Assert.Equal("1", fn(context, properties)(context)); - - properties = new LanguageExpression.PropertyBag - { - { "string", "-1" } - }; - Assert.Equal("-1", fn(context, properties)(context)); - - properties = new LanguageExpression.PropertyBag - { - { "string", "0" } - }; - Assert.Equal("0", fn(context, properties)(context)); - - properties = new LanguageExpression.PropertyBag - { - { "string", "abc" } - }; - Assert.Equal("abc", fn(context, properties)(context)); - - properties = new LanguageExpression.PropertyBag - { - { "string", true } - }; - Assert.Equal("True", fn(context, properties)(context)); - } - - [Fact] - public void Substring() - { - var context = GetContext(); - var fn = GetFunction("substring"); - - var properties = new LanguageExpression.PropertyBag - { - { "substring", "TestObject" }, - { "length", 7 } - }; - Assert.Equal("TestObj", fn(context, properties)(context)); - - properties = new LanguageExpression.PropertyBag - { - { "substring", "TestObject" }, - { "length", "7" } - }; - Assert.Equal("TestObj", fn(context, properties)(context)); - - properties = new LanguageExpression.PropertyBag - { - { "substring", 10000 }, - { "length", 2 } - }; - Assert.Null(fn(context, properties)(context)); - - properties = new LanguageExpression.PropertyBag - { - { "substring", "Test" }, - { "length", 100 } - }; - Assert.Equal("Test", fn(context, properties)(context)); - } - - [Fact] - public void Path() - { - var context = GetContext(); - var fn = GetFunction("path"); - - var properties = new LanguageExpression.PropertyBag - { - { "path", "name" } - }; - Assert.Equal("TestObject1", fn(context, properties)(context)); - - properties = new LanguageExpression.PropertyBag - { - { "path", "notProperty" } - }; - Assert.Null(fn(context, properties)(context)); - } - - [Fact] - public void Replace() - { - var context = GetContext(); - var fn = GetFunction("replace"); - - var properties = new LanguageExpression.PropertyBag - { - { "oldString", "12" }, - { "newString", "" }, - { "replace", "Test123" } - }; - Assert.Equal("Test3", fn(context, properties)(context)); - - properties = new LanguageExpression.PropertyBag - { - { "oldString", "456" }, - { "newString", "" }, - { "replace", "Test123" } - }; - Assert.Equal("Test123", fn(context, properties)(context)); - - properties = new LanguageExpression.PropertyBag - { - { "oldString", "456" }, - { "newString", "" }, - { "replace", "" } - }; - Assert.Equal("", fn(context, properties)(context)); - } - - [Fact] - public void Trim() - { - var context = GetContext(); - var fn = GetFunction("trim"); - - var properties = new LanguageExpression.PropertyBag - { - { "trim", " test " } - }; - Assert.Equal("test", fn(context, properties)(context)); - - properties = new LanguageExpression.PropertyBag - { - { "trim", "test" } - }; - Assert.Equal("test", fn(context, properties)(context)); - - properties = new LanguageExpression.PropertyBag - { - { "trim", "\r\ntest\r\n" } - }; - Assert.Equal("test", fn(context, properties)(context)); - } - - [Fact] - public void First() - { - var context = GetContext(); - var fn = GetFunction("first"); - - // String - var properties = new LanguageExpression.PropertyBag - { - { "first", "Test123" } - }; - Assert.Equal("T", fn(context, properties)(context)); - - properties = new LanguageExpression.PropertyBag - { - { "first", "" } - }; - Assert.Null(fn(context, properties)(context)); - - // Array - properties = new LanguageExpression.PropertyBag - { - { "first", new string[] { "one", "two", "three" } } - }; - Assert.Equal("one", fn(context, properties)(context)); - - properties = new LanguageExpression.PropertyBag - { - { "first", new int[] { 1, 2, 3 } } - }; - Assert.Equal(1, fn(context, properties)(context)); - - properties = new LanguageExpression.PropertyBag - { - { "first", Array.Empty() } - }; - Assert.Null(fn(context, properties)(context)); - } - - [Fact] - public void Last() - { - var context = GetContext(); - var fn = GetFunction("last"); - - // String - var properties = new LanguageExpression.PropertyBag - { - { "last", "Test123" } - }; - Assert.Equal("3", fn(context, properties)(context)); - - properties = new LanguageExpression.PropertyBag - { - { "last", "" } - }; - Assert.Null(fn(context, properties)(context)); - - // Array - properties = new LanguageExpression.PropertyBag - { - { "last", new string[] { "one", "two", "three" } } - }; - Assert.Equal("three", fn(context, properties)(context)); - - properties = new LanguageExpression.PropertyBag - { - { "last", new int[] { 1, 2, 3 } } - }; - Assert.Equal(3, fn(context, properties)(context)); - - properties = new LanguageExpression.PropertyBag - { - { "last", Array.Empty() } - }; - Assert.Null(fn(context, properties)(context)); - } - - [Fact] - public void Split() - { - var context = GetContext(); - var fn = GetFunction("split"); - - var properties = new LanguageExpression.PropertyBag - { - { "split", "One Two Three" }, - { "delimiter", " " } - }; - Assert.Equal(new string[] { "One", "Two", "Three" }, fn(context, properties)(context)); - - properties = new LanguageExpression.PropertyBag - { - { "split", "One Two Three" }, - { "delimiter", " Two " } - }; - Assert.Equal(new string[] { "One", "Three" }, fn(context, properties)(context)); - - properties = new LanguageExpression.PropertyBag - { - { "split", "One Two Three" }, - { "delimiter", "/" } - }; - Assert.Equal(new string[] { "One Two Three" }, fn(context, properties)(context)); - - properties = new LanguageExpression.PropertyBag - { - { "split", "" }, - { "delimiter", "/" } - }; - Assert.Equal(new string[] { "" }, fn(context, properties)(context)); - - properties = new LanguageExpression.PropertyBag - { - { "split", "One Two Three" }, - { "delimiter", new string[] { " Two " } } - }; - Assert.Equal(new string[] { "One", "Three" }, fn(context, properties)(context)); - - properties = new LanguageExpression.PropertyBag - { - { "split", "One Two Three" }, - { "delimiter", new string[] { " ", "Two" } } - }; - Assert.Equal(new string[] { "One", "", "", "Three" }, fn(context, properties)(context)); - - properties = new LanguageExpression.PropertyBag - { - { "split", null }, - { "delimiter", new string[] { " ", "Two" } } - }; - Assert.Null(fn(context, properties)(context)); - } - - [Fact] - public void PadLeft() - { - var context = GetContext(); - var fn = GetFunction("padLeft"); - - var properties = new LanguageExpression.PropertyBag - { - { "padLeft", "One" }, - { "totalLength", 5 } - }; - Assert.Equal(" One", fn(context, properties)(context)); - - - properties = new LanguageExpression.PropertyBag - { - { "padLeft", "One" }, - { "totalLength", 3 } - }; - Assert.Equal("One", fn(context, properties)(context)); - - properties = new LanguageExpression.PropertyBag - { - { "padLeft", "One" }, - { "totalLength", 5 }, - { "paddingCharacter", '_' } - }; - Assert.Equal("__One", fn(context, properties)(context)); - - properties = new LanguageExpression.PropertyBag - { - { "padLeft", "One" }, - { "totalLength", 5 }, - { "paddingCharacter", "_" } - }; - Assert.Equal("__One", fn(context, properties)(context)); - - properties = new LanguageExpression.PropertyBag - { - { "padLeft", "One" }, - { "totalLength", 5 }, - { "paddingCharacter", "__" } - }; - Assert.Equal(" One", fn(context, properties)(context)); - - properties = new LanguageExpression.PropertyBag - { - { "padLeft", "One" }, - { "totalLength", 3 }, - { "paddingCharacter", "_" } - }; - Assert.Equal("One", fn(context, properties)(context)); - - properties = new LanguageExpression.PropertyBag - { - { "padLeft", "One" }, - { "totalLength", 1 }, - { "paddingCharacter", "_" } - }; - Assert.Equal("One", fn(context, properties)(context)); - - properties = new LanguageExpression.PropertyBag - { - { "padLeft", "One" }, - { "totalLength", -1 }, - { "paddingCharacter", "_" } - }; - Assert.Equal("One", fn(context, properties)(context)); - - properties = new LanguageExpression.PropertyBag - { - { "padLeft", null }, - { "totalLength", 5 } - }; - Assert.Null(fn(context, properties)(context)); - - properties = new LanguageExpression.PropertyBag - { - { "padLeft", "One" }, - { "totalLength", null } - }; - Assert.Equal("One", fn(context, properties)(context)); - } - - [Fact] - public void PadRight() - { - var context = GetContext(); - var fn = GetFunction("padRight"); - - var properties = new LanguageExpression.PropertyBag - { - { "padRight", "One" }, - { "totalLength", 5 } - }; - Assert.Equal("One ", fn(context, properties)(context)); - - - properties = new LanguageExpression.PropertyBag - { - { "padRight", "One" }, - { "totalLength", 3 } - }; - Assert.Equal("One", fn(context, properties)(context)); - - properties = new LanguageExpression.PropertyBag - { - { "padRight", "One" }, - { "totalLength", 5 }, - { "paddingCharacter", '_' } - }; - Assert.Equal("One__", fn(context, properties)(context)); - - properties = new LanguageExpression.PropertyBag - { - { "padRight", "One" }, - { "totalLength", 5 }, - { "paddingCharacter", "_" } - }; - Assert.Equal("One__", fn(context, properties)(context)); - - properties = new LanguageExpression.PropertyBag - { - { "padRight", "One" }, - { "totalLength", 5 }, - { "paddingCharacter", "__" } - }; - Assert.Equal("One ", fn(context, properties)(context)); - - properties = new LanguageExpression.PropertyBag - { - { "padRight", "One" }, - { "totalLength", 3 }, - { "paddingCharacter", "_" } - }; - Assert.Equal("One", fn(context, properties)(context)); - - properties = new LanguageExpression.PropertyBag - { - { "padRight", "One" }, - { "totalLength", 1 }, - { "paddingCharacter", "_" } - }; - Assert.Equal("One", fn(context, properties)(context)); - - properties = new LanguageExpression.PropertyBag - { - { "padRight", "One" }, - { "totalLength", -1 }, - { "paddingCharacter", "_" } - }; - Assert.Equal("One", fn(context, properties)(context)); - - properties = new LanguageExpression.PropertyBag - { - { "padRight", null }, - { "totalLength", 5 } - }; - Assert.Null(fn(context, properties)(context)); - - properties = new LanguageExpression.PropertyBag - { - { "padRight", "One" }, - { "totalLength", null } - }; - Assert.Equal("One", fn(context, properties)(context)); - } - - #region Helper methods - - private static ExpressionBuilderFn GetFunction(string name) - { - return Functions.Builtin.Single(f => f.Name == name).Fn; - } - - private static PSRuleOption GetOption() - { - var option = new PSRuleOption(); - option.Configuration["config1"] = "123"; - return option; - } - - private static Source[] GetSource() - { - var builder = new SourcePipelineBuilder(null, null); - builder.Directory(GetSourcePath("Selectors.Rule.yaml")); - return builder.Build(); - } - - 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), null), null); - var s = GetSource(); - var result = new ExpressionContext(context, s[0].File[0], Definitions.ResourceKind.Rule, targetObject); - context.Init(s); - context.Begin(); - context.PushScope(Runtime.RunspaceScope.Precondition); - context.EnterTargetObject(new TargetObject(targetObject)); - return result; - } - - private static string GetSourcePath(string fileName) - { - return System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName); - } - - #endregion Helper methods + var context = GetContext(); + var fn = GetFunction("boolean"); + + var properties = new LanguageExpression.PropertyBag + { + { "boolean", true } + }; + Assert.Equal(true, fn(context, properties)(context)); + + properties = new LanguageExpression.PropertyBag + { + { "boolean", false } + }; + Assert.Equal(false, fn(context, properties)(context)); + + properties = new LanguageExpression.PropertyBag + { + { "boolean", "true" } + }; + Assert.Equal(true, fn(context, properties)(context)); + + properties = new LanguageExpression.PropertyBag + { + { "boolean", "false" } + }; + Assert.Equal(false, fn(context, properties)(context)); } + + [Fact] + public void Integer() + { + var context = GetContext(); + var fn = GetFunction("integer"); + + var properties = new LanguageExpression.PropertyBag + { + { "integer", 1 } + }; + Assert.Equal(1, fn(context, properties)(context)); + + properties = new LanguageExpression.PropertyBag + { + { "integer", -1 } + }; + Assert.Equal(-1, fn(context, properties)(context)); + + properties = new LanguageExpression.PropertyBag + { + { "integer", 0 } + }; + Assert.Equal(0, fn(context, properties)(context)); + + properties = new LanguageExpression.PropertyBag + { + { "integer", "1" } + }; + Assert.Equal(1, fn(context, properties)(context)); + + properties = new LanguageExpression.PropertyBag + { + { "integer", "-1" } + }; + Assert.Equal(-1, fn(context, properties)(context)); + + properties = new LanguageExpression.PropertyBag + { + { "integer", "0" } + }; + Assert.Equal(0, fn(context, properties)(context)); + + properties = new LanguageExpression.PropertyBag + { + { "integer", "not" } + }; + Assert.Equal(0, fn(context, properties)(context)); + } + + [Fact] + public void String() + { + var context = GetContext(); + var fn = GetFunction("string"); + + var properties = new LanguageExpression.PropertyBag + { + { "string", 1 } + }; + Assert.Equal("1", fn(context, properties)(context)); + + properties = new LanguageExpression.PropertyBag + { + { "string", -1 } + }; + Assert.Equal("-1", fn(context, properties)(context)); + + properties = new LanguageExpression.PropertyBag + { + { "string", "1" } + }; + Assert.Equal("1", fn(context, properties)(context)); + + properties = new LanguageExpression.PropertyBag + { + { "string", "-1" } + }; + Assert.Equal("-1", fn(context, properties)(context)); + + properties = new LanguageExpression.PropertyBag + { + { "string", "0" } + }; + Assert.Equal("0", fn(context, properties)(context)); + + properties = new LanguageExpression.PropertyBag + { + { "string", "abc" } + }; + Assert.Equal("abc", fn(context, properties)(context)); + + properties = new LanguageExpression.PropertyBag + { + { "string", true } + }; + Assert.Equal("True", fn(context, properties)(context)); + } + + [Fact] + public void Substring() + { + var context = GetContext(); + var fn = GetFunction("substring"); + + var properties = new LanguageExpression.PropertyBag + { + { "substring", "TestObject" }, + { "length", 7 } + }; + Assert.Equal("TestObj", fn(context, properties)(context)); + + properties = new LanguageExpression.PropertyBag + { + { "substring", "TestObject" }, + { "length", "7" } + }; + Assert.Equal("TestObj", fn(context, properties)(context)); + + properties = new LanguageExpression.PropertyBag + { + { "substring", 10000 }, + { "length", 2 } + }; + Assert.Null(fn(context, properties)(context)); + + properties = new LanguageExpression.PropertyBag + { + { "substring", "Test" }, + { "length", 100 } + }; + Assert.Equal("Test", fn(context, properties)(context)); + } + + [Fact] + public void Path() + { + var context = GetContext(); + var fn = GetFunction("path"); + + var properties = new LanguageExpression.PropertyBag + { + { "path", "name" } + }; + Assert.Equal("TestObject1", fn(context, properties)(context)); + + properties = new LanguageExpression.PropertyBag + { + { "path", "notProperty" } + }; + Assert.Null(fn(context, properties)(context)); + } + + [Fact] + public void Replace() + { + var context = GetContext(); + var fn = GetFunction("replace"); + + var properties = new LanguageExpression.PropertyBag + { + { "oldString", "12" }, + { "newString", "" }, + { "replace", "Test123" } + }; + Assert.Equal("Test3", fn(context, properties)(context)); + + properties = new LanguageExpression.PropertyBag + { + { "oldString", "456" }, + { "newString", "" }, + { "replace", "Test123" } + }; + Assert.Equal("Test123", fn(context, properties)(context)); + + properties = new LanguageExpression.PropertyBag + { + { "oldString", "456" }, + { "newString", "" }, + { "replace", "" } + }; + Assert.Equal("", fn(context, properties)(context)); + } + + [Fact] + public void Trim() + { + var context = GetContext(); + var fn = GetFunction("trim"); + + var properties = new LanguageExpression.PropertyBag + { + { "trim", " test " } + }; + Assert.Equal("test", fn(context, properties)(context)); + + properties = new LanguageExpression.PropertyBag + { + { "trim", "test" } + }; + Assert.Equal("test", fn(context, properties)(context)); + + properties = new LanguageExpression.PropertyBag + { + { "trim", "\r\ntest\r\n" } + }; + Assert.Equal("test", fn(context, properties)(context)); + } + + [Fact] + public void First() + { + var context = GetContext(); + var fn = GetFunction("first"); + + // String + var properties = new LanguageExpression.PropertyBag + { + { "first", "Test123" } + }; + Assert.Equal("T", fn(context, properties)(context)); + + properties = new LanguageExpression.PropertyBag + { + { "first", "" } + }; + Assert.Null(fn(context, properties)(context)); + + // Array + properties = new LanguageExpression.PropertyBag + { + { "first", new string[] { "one", "two", "three" } } + }; + Assert.Equal("one", fn(context, properties)(context)); + + properties = new LanguageExpression.PropertyBag + { + { "first", new int[] { 1, 2, 3 } } + }; + Assert.Equal(1, fn(context, properties)(context)); + + properties = new LanguageExpression.PropertyBag + { + { "first", Array.Empty() } + }; + Assert.Null(fn(context, properties)(context)); + } + + [Fact] + public void Last() + { + var context = GetContext(); + var fn = GetFunction("last"); + + // String + var properties = new LanguageExpression.PropertyBag + { + { "last", "Test123" } + }; + Assert.Equal("3", fn(context, properties)(context)); + + properties = new LanguageExpression.PropertyBag + { + { "last", "" } + }; + Assert.Null(fn(context, properties)(context)); + + // Array + properties = new LanguageExpression.PropertyBag + { + { "last", new string[] { "one", "two", "three" } } + }; + Assert.Equal("three", fn(context, properties)(context)); + + properties = new LanguageExpression.PropertyBag + { + { "last", new int[] { 1, 2, 3 } } + }; + Assert.Equal(3, fn(context, properties)(context)); + + properties = new LanguageExpression.PropertyBag + { + { "last", Array.Empty() } + }; + Assert.Null(fn(context, properties)(context)); + } + + [Fact] + public void Split() + { + var context = GetContext(); + var fn = GetFunction("split"); + + var properties = new LanguageExpression.PropertyBag + { + { "split", "One Two Three" }, + { "delimiter", " " } + }; + Assert.Equal(new string[] { "One", "Two", "Three" }, fn(context, properties)(context)); + + properties = new LanguageExpression.PropertyBag + { + { "split", "One Two Three" }, + { "delimiter", " Two " } + }; + Assert.Equal(new string[] { "One", "Three" }, fn(context, properties)(context)); + + properties = new LanguageExpression.PropertyBag + { + { "split", "One Two Three" }, + { "delimiter", "/" } + }; + Assert.Equal(new string[] { "One Two Three" }, fn(context, properties)(context)); + + properties = new LanguageExpression.PropertyBag + { + { "split", "" }, + { "delimiter", "/" } + }; + Assert.Equal(new string[] { "" }, fn(context, properties)(context)); + + properties = new LanguageExpression.PropertyBag + { + { "split", "One Two Three" }, + { "delimiter", new string[] { " Two " } } + }; + Assert.Equal(new string[] { "One", "Three" }, fn(context, properties)(context)); + + properties = new LanguageExpression.PropertyBag + { + { "split", "One Two Three" }, + { "delimiter", new string[] { " ", "Two" } } + }; + Assert.Equal(new string[] { "One", "", "", "Three" }, fn(context, properties)(context)); + + properties = new LanguageExpression.PropertyBag + { + { "split", null }, + { "delimiter", new string[] { " ", "Two" } } + }; + Assert.Null(fn(context, properties)(context)); + } + + [Fact] + public void PadLeft() + { + var context = GetContext(); + var fn = GetFunction("padLeft"); + + var properties = new LanguageExpression.PropertyBag + { + { "padLeft", "One" }, + { "totalLength", 5 } + }; + Assert.Equal(" One", fn(context, properties)(context)); + + + properties = new LanguageExpression.PropertyBag + { + { "padLeft", "One" }, + { "totalLength", 3 } + }; + Assert.Equal("One", fn(context, properties)(context)); + + properties = new LanguageExpression.PropertyBag + { + { "padLeft", "One" }, + { "totalLength", 5 }, + { "paddingCharacter", '_' } + }; + Assert.Equal("__One", fn(context, properties)(context)); + + properties = new LanguageExpression.PropertyBag + { + { "padLeft", "One" }, + { "totalLength", 5 }, + { "paddingCharacter", "_" } + }; + Assert.Equal("__One", fn(context, properties)(context)); + + properties = new LanguageExpression.PropertyBag + { + { "padLeft", "One" }, + { "totalLength", 5 }, + { "paddingCharacter", "__" } + }; + Assert.Equal(" One", fn(context, properties)(context)); + + properties = new LanguageExpression.PropertyBag + { + { "padLeft", "One" }, + { "totalLength", 3 }, + { "paddingCharacter", "_" } + }; + Assert.Equal("One", fn(context, properties)(context)); + + properties = new LanguageExpression.PropertyBag + { + { "padLeft", "One" }, + { "totalLength", 1 }, + { "paddingCharacter", "_" } + }; + Assert.Equal("One", fn(context, properties)(context)); + + properties = new LanguageExpression.PropertyBag + { + { "padLeft", "One" }, + { "totalLength", -1 }, + { "paddingCharacter", "_" } + }; + Assert.Equal("One", fn(context, properties)(context)); + + properties = new LanguageExpression.PropertyBag + { + { "padLeft", null }, + { "totalLength", 5 } + }; + Assert.Null(fn(context, properties)(context)); + + properties = new LanguageExpression.PropertyBag + { + { "padLeft", "One" }, + { "totalLength", null } + }; + Assert.Equal("One", fn(context, properties)(context)); + } + + [Fact] + public void PadRight() + { + var context = GetContext(); + var fn = GetFunction("padRight"); + + var properties = new LanguageExpression.PropertyBag + { + { "padRight", "One" }, + { "totalLength", 5 } + }; + Assert.Equal("One ", fn(context, properties)(context)); + + + properties = new LanguageExpression.PropertyBag + { + { "padRight", "One" }, + { "totalLength", 3 } + }; + Assert.Equal("One", fn(context, properties)(context)); + + properties = new LanguageExpression.PropertyBag + { + { "padRight", "One" }, + { "totalLength", 5 }, + { "paddingCharacter", '_' } + }; + Assert.Equal("One__", fn(context, properties)(context)); + + properties = new LanguageExpression.PropertyBag + { + { "padRight", "One" }, + { "totalLength", 5 }, + { "paddingCharacter", "_" } + }; + Assert.Equal("One__", fn(context, properties)(context)); + + properties = new LanguageExpression.PropertyBag + { + { "padRight", "One" }, + { "totalLength", 5 }, + { "paddingCharacter", "__" } + }; + Assert.Equal("One ", fn(context, properties)(context)); + + properties = new LanguageExpression.PropertyBag + { + { "padRight", "One" }, + { "totalLength", 3 }, + { "paddingCharacter", "_" } + }; + Assert.Equal("One", fn(context, properties)(context)); + + properties = new LanguageExpression.PropertyBag + { + { "padRight", "One" }, + { "totalLength", 1 }, + { "paddingCharacter", "_" } + }; + Assert.Equal("One", fn(context, properties)(context)); + + properties = new LanguageExpression.PropertyBag + { + { "padRight", "One" }, + { "totalLength", -1 }, + { "paddingCharacter", "_" } + }; + Assert.Equal("One", fn(context, properties)(context)); + + properties = new LanguageExpression.PropertyBag + { + { "padRight", null }, + { "totalLength", 5 } + }; + Assert.Null(fn(context, properties)(context)); + + properties = new LanguageExpression.PropertyBag + { + { "padRight", "One" }, + { "totalLength", null } + }; + Assert.Equal("One", fn(context, properties)(context)); + } + + #region Helper methods + + private static ExpressionBuilderFn GetFunction(string name) + { + return Functions.Builtin.Single(f => f.Name == name).Fn; + } + + private static PSRuleOption GetOption() + { + var option = new PSRuleOption(); + option.Configuration["config1"] = "123"; + return option; + } + + private static Source[] GetSource() + { + var builder = new SourcePipelineBuilder(null, null); + builder.Directory(GetSourcePath("Selectors.Rule.yaml")); + return builder.Build(); + } + + 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), null), null); + var s = GetSource(); + var result = new ExpressionContext(context, s[0].File[0], Definitions.ResourceKind.Rule, targetObject); + context.Init(s); + context.Begin(); + context.PushScope(Runtime.RunspaceScope.Precondition); + context.EnterTargetObject(new TargetObject(targetObject)); + return result; + } + + private static string GetSourcePath(string fileName) + { + return System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName); + } + + #endregion Helper methods } diff --git a/tests/PSRule.Tests/InputFormatDeserializerTests.cs b/tests/PSRule.Tests/InputFormatDeserializerTests.cs index f10ddf9944..e424b1469f 100644 --- a/tests/PSRule.Tests/InputFormatDeserializerTests.cs +++ b/tests/PSRule.Tests/InputFormatDeserializerTests.cs @@ -7,116 +7,115 @@ using System.Management.Automation; using PSRule.Pipeline; -namespace PSRule +namespace PSRule; + +public sealed class InputFormatDeserializerTests { - public sealed class InputFormatDeserializerTests + [Fact] + public void DeserializeObjectsYaml() + { + var actual = PipelineReceiverActions.ConvertFromYaml(GetYamlContent(), PipelineReceiverActions.PassThru).ToArray(); + + Assert.Equal(2, actual.Length); + Assert.Equal("TestObject1", actual[0].Value.PropertyValue("targetName")); + Assert.Equal("Test", actual[0].Value.PropertyValue("spec").PropertyValue("properties").PropertyValue("kind")); + Assert.Equal(2, actual[1].Value.PropertyValue("spec").PropertyValue("properties").PropertyValue("value2")); + Assert.Equal(2, actual[1].Value.PropertyValue("spec").PropertyValue("properties").PropertyValue("array").Length); + Assert.Equal("TestObject1", PipelineHookActions.BindTargetName(null, false, false, actual[0].Value, out var path)); + Assert.Null(path); + + // Array item + actual = PipelineReceiverActions.ConvertFromYaml(GetYamlContent("3"), PipelineReceiverActions.PassThru).ToArray(); + Assert.Equal(2, actual.Length); + Assert.Equal("item1", actual[0].Value.PropertyValue("name")); + Assert.Equal("value1", actual[0].Value.PropertyValue("value")); + Assert.Equal("item2", actual[1].Value.PropertyValue("name")); + Assert.Equal("value2", actual[1].Value.PropertyValue("value")); + } + + [Fact] + public void DeserializeObjectsJson() + { + var actual = PipelineReceiverActions.ConvertFromJson(GetJsonContent(), PipelineReceiverActions.PassThru).ToArray(); + + Assert.Equal(2, actual.Length); + Assert.Equal("TestObject1", actual[0].Value.PropertyValue("targetName")); + Assert.Equal("Test", actual[0].Value.PropertyValue("spec").PropertyValue("properties").PropertyValue("kind")); + Assert.Equal(2, actual[1].Value.PropertyValue("spec").PropertyValue("properties").PropertyValue("value2")); + Assert.Equal(3, actual[1].Value.PropertyValue("spec").PropertyValue("properties").PropertyValue("array").Length); + Assert.Equal("TestObject1", PipelineHookActions.BindTargetName(null, false, false, actual[0].Value, out var path)); + Assert.Null(path); + actual[0].Value.TryTargetInfo(out var info1); + actual[1].Value.TryTargetInfo(out var info2); + Assert.Equal("some-file.json", info1.Source[0].File); + Assert.Equal("master.items[0]", info1.Path); + Assert.NotNull(info2.Source[0]); + Assert.Equal("[1]", info2.Path); + + // Single item + actual = PipelineReceiverActions.ConvertFromJson(GetJsonContent("Single"), PipelineReceiverActions.PassThru).ToArray(); + Assert.Single(actual); + Assert.Equal("TestObject1", actual[0].Value.PropertyValue("targetName")); + Assert.Equal("Test", actual[0].Value.PropertyValue("spec").PropertyValue("properties").PropertyValue("kind")); + + // Malformed item + Assert.Throws(() => PipelineReceiverActions.ConvertFromJson(new TargetObject(new PSObject("{")), PipelineReceiverActions.PassThru).ToArray()); + Assert.Throws(() => PipelineReceiverActions.ConvertFromJson(new TargetObject(new PSObject("{ \"key\": ")), PipelineReceiverActions.PassThru).ToArray()); + } + + [Fact] + public void DeserializeObjectsMarkdown() + { + var actual = PipelineReceiverActions.ConvertFromMarkdown(GetMarkdownContent(), PipelineReceiverActions.PassThru).ToArray(); + + Assert.Single(actual); + Assert.Equal("TestObject1", actual[0].Value.PropertyValue("targetName")); + Assert.Equal("Test", actual[0].Value.PropertyValue("spec").PropertyValue("properties").PropertyValue("kind")); + Assert.Equal(1, actual[0].Value.PropertyValue("spec").PropertyValue("properties").PropertyValue("value1")); + Assert.Equal(2, actual[0].Value.PropertyValue("spec").PropertyValue("properties").PropertyValue("array").Length); + Assert.Equal("TestObject1", PipelineHookActions.BindTargetName(null, false, false, actual[0].Value, out var path)); + Assert.Null(path); + } + + [Fact] + public void DeserializeObjectsPowerShellData() + { + var actual = PipelineReceiverActions.ConvertFromPowerShellData(GetDataContent(), PipelineReceiverActions.PassThru).ToArray(); + + Assert.Single(actual); + Assert.Equal("TestObject1", actual[0].Value.PropertyValue("targetName")); + Assert.Equal("Test", actual[0].Value.PropertyValue("spec").PropertyValue("properties").PropertyValue("kind")); + Assert.Equal(1, actual[0].Value.PropertyValue("spec").PropertyValue("properties").PropertyValue("value1")); + Assert.Equal(2, actual[0].Value.PropertyValue("spec").PropertyValue("properties").PropertyValue("array").Length); + Assert.Equal("TestObject1", PipelineHookActions.BindTargetName(null, false, false, actual[0].Value, out var path)); + Assert.Null(path); + } + + #region Helper methods + + private static TargetObject GetYamlContent(string suffix = "") + { + var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, $"ObjectFromFile{suffix}.yaml"); + return new TargetObject(new PSObject(File.ReadAllText(path))); + } + + private static TargetObject GetJsonContent(string suffix = "") + { + var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, $"ObjectFromFile{suffix}.json"); + return new TargetObject(new PSObject(File.ReadAllText(path))); + } + + private static TargetObject GetMarkdownContent() + { + var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "ObjectFromFile.md"); + return new TargetObject(new PSObject(File.ReadAllText(path))); + } + + private static TargetObject GetDataContent() { - [Fact] - public void DeserializeObjectsYaml() - { - var actual = PipelineReceiverActions.ConvertFromYaml(GetYamlContent(), PipelineReceiverActions.PassThru).ToArray(); - - Assert.Equal(2, actual.Length); - Assert.Equal("TestObject1", actual[0].Value.PropertyValue("targetName")); - Assert.Equal("Test", actual[0].Value.PropertyValue("spec").PropertyValue("properties").PropertyValue("kind")); - Assert.Equal(2, actual[1].Value.PropertyValue("spec").PropertyValue("properties").PropertyValue("value2")); - Assert.Equal(2, actual[1].Value.PropertyValue("spec").PropertyValue("properties").PropertyValue("array").Length); - Assert.Equal("TestObject1", PipelineHookActions.BindTargetName(null, false, false, actual[0].Value, out var path)); - Assert.Null(path); - - // Array item - actual = PipelineReceiverActions.ConvertFromYaml(GetYamlContent("3"), PipelineReceiverActions.PassThru).ToArray(); - Assert.Equal(2, actual.Length); - Assert.Equal("item1", actual[0].Value.PropertyValue("name")); - Assert.Equal("value1", actual[0].Value.PropertyValue("value")); - Assert.Equal("item2", actual[1].Value.PropertyValue("name")); - Assert.Equal("value2", actual[1].Value.PropertyValue("value")); - } - - [Fact] - public void DeserializeObjectsJson() - { - var actual = PipelineReceiverActions.ConvertFromJson(GetJsonContent(), PipelineReceiverActions.PassThru).ToArray(); - - Assert.Equal(2, actual.Length); - Assert.Equal("TestObject1", actual[0].Value.PropertyValue("targetName")); - Assert.Equal("Test", actual[0].Value.PropertyValue("spec").PropertyValue("properties").PropertyValue("kind")); - Assert.Equal(2, actual[1].Value.PropertyValue("spec").PropertyValue("properties").PropertyValue("value2")); - Assert.Equal(3, actual[1].Value.PropertyValue("spec").PropertyValue("properties").PropertyValue("array").Length); - Assert.Equal("TestObject1", PipelineHookActions.BindTargetName(null, false, false, actual[0].Value, out var path)); - Assert.Null(path); - actual[0].Value.TryTargetInfo(out var info1); - actual[1].Value.TryTargetInfo(out var info2); - Assert.Equal("some-file.json", info1.Source[0].File); - Assert.Equal("master.items[0]", info1.Path); - Assert.NotNull(info2.Source[0]); - Assert.Equal("[1]", info2.Path); - - // Single item - actual = PipelineReceiverActions.ConvertFromJson(GetJsonContent("Single"), PipelineReceiverActions.PassThru).ToArray(); - Assert.Single(actual); - Assert.Equal("TestObject1", actual[0].Value.PropertyValue("targetName")); - Assert.Equal("Test", actual[0].Value.PropertyValue("spec").PropertyValue("properties").PropertyValue("kind")); - - // Malformed item - Assert.Throws(() => PipelineReceiverActions.ConvertFromJson(new TargetObject(new PSObject("{")), PipelineReceiverActions.PassThru).ToArray()); - Assert.Throws(() => PipelineReceiverActions.ConvertFromJson(new TargetObject(new PSObject("{ \"key\": ")), PipelineReceiverActions.PassThru).ToArray()); - } - - [Fact] - public void DeserializeObjectsMarkdown() - { - var actual = PipelineReceiverActions.ConvertFromMarkdown(GetMarkdownContent(), PipelineReceiverActions.PassThru).ToArray(); - - Assert.Single(actual); - Assert.Equal("TestObject1", actual[0].Value.PropertyValue("targetName")); - Assert.Equal("Test", actual[0].Value.PropertyValue("spec").PropertyValue("properties").PropertyValue("kind")); - Assert.Equal(1, actual[0].Value.PropertyValue("spec").PropertyValue("properties").PropertyValue("value1")); - Assert.Equal(2, actual[0].Value.PropertyValue("spec").PropertyValue("properties").PropertyValue("array").Length); - Assert.Equal("TestObject1", PipelineHookActions.BindTargetName(null, false, false, actual[0].Value, out var path)); - Assert.Null(path); - } - - [Fact] - public void DeserializeObjectsPowerShellData() - { - var actual = PipelineReceiverActions.ConvertFromPowerShellData(GetDataContent(), PipelineReceiverActions.PassThru).ToArray(); - - Assert.Single(actual); - Assert.Equal("TestObject1", actual[0].Value.PropertyValue("targetName")); - Assert.Equal("Test", actual[0].Value.PropertyValue("spec").PropertyValue("properties").PropertyValue("kind")); - Assert.Equal(1, actual[0].Value.PropertyValue("spec").PropertyValue("properties").PropertyValue("value1")); - Assert.Equal(2, actual[0].Value.PropertyValue("spec").PropertyValue("properties").PropertyValue("array").Length); - Assert.Equal("TestObject1", PipelineHookActions.BindTargetName(null, false, false, actual[0].Value, out var path)); - Assert.Null(path); - } - - #region Helper methods - - private static TargetObject GetYamlContent(string suffix = "") - { - var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, $"ObjectFromFile{suffix}.yaml"); - return new TargetObject(new PSObject(File.ReadAllText(path))); - } - - private static TargetObject GetJsonContent(string suffix = "") - { - var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, $"ObjectFromFile{suffix}.json"); - return new TargetObject(new PSObject(File.ReadAllText(path))); - } - - private static TargetObject GetMarkdownContent() - { - var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "ObjectFromFile.md"); - return new TargetObject(new PSObject(File.ReadAllText(path))); - } - - private static TargetObject GetDataContent() - { - var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "ObjectFromFile.psd1"); - return new TargetObject(new PSObject(File.ReadAllText(path))); - } - - #endregion Helper methods + var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "ObjectFromFile.psd1"); + return new TargetObject(new PSObject(File.ReadAllText(path))); } + + #endregion Helper methods } diff --git a/tests/PSRule.Tests/InputPathBuilderTests.cs b/tests/PSRule.Tests/InputPathBuilderTests.cs index c65536e115..8d0a663020 100644 --- a/tests/PSRule.Tests/InputPathBuilderTests.cs +++ b/tests/PSRule.Tests/InputPathBuilderTests.cs @@ -6,94 +6,93 @@ using System.Linq; using PSRule.Pipeline; -namespace PSRule +namespace PSRule; + +public sealed class InputPathBuilderTests { - public sealed class InputPathBuilderTests + [Fact] + public void GetPath() { - [Fact] - public void GetPath() - { - var builder = new InputPathBuilder(null, GetWorkingPath(), "*", null, null); - builder.Add("."); - var actual = builder.Build(); - Assert.True(actual.Length > 100); - - builder.Add(GetWorkingPath()); - actual = builder.Build(); - Assert.True(actual.Length > 100); - - builder.Add("./src"); - actual = builder.Build(); - Assert.True(actual.Length == 0); - - builder.Add("./src/"); - actual = builder.Build(); - Assert.True(actual.Length > 100); - - builder.Add("./"); - actual = builder.Build(); - Assert.True(actual.Length > 100); - - builder.Add("./.github/*.yaml"); - actual = builder.Build(); - Assert.Single(actual); - - builder.Add("./.github/**/*.yaml"); - actual = builder.Build(); - Assert.Equal(7, actual.Length); - - builder.Add("./.github/"); - actual = builder.Build(); - Assert.Equal(13, actual.Length); - - builder.Add(".github/"); - actual = builder.Build(); - Assert.Equal(13, actual.Length); - - builder.Add("./*.json"); - actual = builder.Build(); - Assert.True(actual.Length == 3); - - builder.Add("src/"); - actual = builder.Build(); - Assert.True(actual.Length > 100); - - // Check error handling - var writer = new TestWriter(new Configuration.PSRuleOption()); - builder = new InputPathBuilder(writer, GetWorkingPath(), "*", null, null); - builder.Add("ZZ://not/path"); - actual = builder.Build(); - Assert.Empty(actual); - Assert.True(writer.Errors.Count(r => r.FullyQualifiedErrorId == "PSRule.ReadInputFailed") == 1); - } - - [Fact] - public void GetPathRequired() - { - var required = PathFilter.Create(GetWorkingPath(), new string[] { "README.md" }); - var builder = new InputPathBuilder(null, GetWorkingPath(), "*", null, required); - builder.Add("."); - var actual = builder.Build(); - Assert.True(actual.Length == 1); - - builder.Add(GetWorkingPath()); - actual = builder.Build(); - Assert.True(actual.Length == 1); - - required = PathFilter.Create(GetWorkingPath(), new string[] { "**" }); - builder = new InputPathBuilder(null, GetWorkingPath(), "*", null, required); - builder.Add("."); - actual = builder.Build(); - Assert.True(actual.Length > 100); - } - - #region Helper methods - - private static string GetWorkingPath() - { - return Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "../../../../..")); - } - - #endregion Helper methods + var builder = new InputPathBuilder(null, GetWorkingPath(), "*", null, null); + builder.Add("."); + var actual = builder.Build(); + Assert.True(actual.Length > 100); + + builder.Add(GetWorkingPath()); + actual = builder.Build(); + Assert.True(actual.Length > 100); + + builder.Add("./src"); + actual = builder.Build(); + Assert.True(actual.Length == 0); + + builder.Add("./src/"); + actual = builder.Build(); + Assert.True(actual.Length > 100); + + builder.Add("./"); + actual = builder.Build(); + Assert.True(actual.Length > 100); + + builder.Add("./.github/*.yaml"); + actual = builder.Build(); + Assert.Single(actual); + + builder.Add("./.github/**/*.yaml"); + actual = builder.Build(); + Assert.Equal(7, actual.Length); + + builder.Add("./.github/"); + actual = builder.Build(); + Assert.Equal(13, actual.Length); + + builder.Add(".github/"); + actual = builder.Build(); + Assert.Equal(13, actual.Length); + + builder.Add("./*.json"); + actual = builder.Build(); + Assert.True(actual.Length == 3); + + builder.Add("src/"); + actual = builder.Build(); + Assert.True(actual.Length > 100); + + // Check error handling + var writer = new TestWriter(new Configuration.PSRuleOption()); + builder = new InputPathBuilder(writer, GetWorkingPath(), "*", null, null); + builder.Add("ZZ://not/path"); + actual = builder.Build(); + Assert.Empty(actual); + Assert.True(writer.Errors.Count(r => r.FullyQualifiedErrorId == "PSRule.ReadInputFailed") == 1); } + + [Fact] + public void GetPathRequired() + { + var required = PathFilter.Create(GetWorkingPath(), new string[] { "README.md" }); + var builder = new InputPathBuilder(null, GetWorkingPath(), "*", null, required); + builder.Add("."); + var actual = builder.Build(); + Assert.True(actual.Length == 1); + + builder.Add(GetWorkingPath()); + actual = builder.Build(); + Assert.True(actual.Length == 1); + + required = PathFilter.Create(GetWorkingPath(), new string[] { "**" }); + builder = new InputPathBuilder(null, GetWorkingPath(), "*", null, required); + builder.Add("."); + actual = builder.Build(); + Assert.True(actual.Length > 100); + } + + #region Helper methods + + private static string GetWorkingPath() + { + return Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "../../../../..")); + } + + #endregion Helper methods } diff --git a/tests/PSRule.Tests/JsonHelperTests.cs b/tests/PSRule.Tests/JsonHelperTests.cs index d776b80371..59419759e8 100644 --- a/tests/PSRule.Tests/JsonHelperTests.cs +++ b/tests/PSRule.Tests/JsonHelperTests.cs @@ -3,24 +3,23 @@ using Newtonsoft.Json.Linq; -namespace PSRule +namespace PSRule; + +public sealed class JsonHelperTests { - public sealed class JsonHelperTests + [Fact] + public void JTokenToPSObject() { - [Fact] - public void JTokenToPSObject() - { - var actual = JsonHelper.ToPSObject(GetJObject()); - Assert.NotNull(actual); - } - - #region Helper methods + var actual = JsonHelper.ToPSObject(GetJObject()); + Assert.NotNull(actual); + } - private JToken GetJObject() - { - return JObject.Parse("{ \"metadata\": {}, \"parameters\": { \"sku\": { \"type\": \"string\", \"defaultValue\": \"Developer\", \"allowValues\": [ \"Developer\", \"Standard\", \"Premium\" ] } } }"); - } + #region Helper methods - #endregion Helper methods + private JToken GetJObject() + { + return JObject.Parse("{ \"metadata\": {}, \"parameters\": { \"sku\": { \"type\": \"string\", \"defaultValue\": \"Developer\", \"allowValues\": [ \"Developer\", \"Standard\", \"Premium\" ] } } }"); } + + #endregion Helper methods } diff --git a/tests/PSRule.Tests/LanguageVisitorTests.cs b/tests/PSRule.Tests/LanguageVisitorTests.cs index 46ccbe4125..1928a5b3e6 100644 --- a/tests/PSRule.Tests/LanguageVisitorTests.cs +++ b/tests/PSRule.Tests/LanguageVisitorTests.cs @@ -4,26 +4,26 @@ using System.Management.Automation; using PSRule.Host; -namespace PSRule +namespace PSRule; + +public sealed class LanguageVisitorTests { - public sealed class LanguageVisitorTests + [Fact] + public void NestedRule() { - [Fact] - public void NestedRule() - { - var content = @" + var content = @" # Header comment Rule 'Rule1' { } "; - var scriptAst = ScriptBlock.Create(content).Ast; - var visitor = new RuleLanguageAst(); - scriptAst.Visit(visitor); + var scriptAst = ScriptBlock.Create(content).Ast; + var visitor = new RuleLanguageAst(); + scriptAst.Visit(visitor); - Assert.Null(visitor.Errors); + Assert.Null(visitor.Errors); - content = @" + content = @" # Header comment Rule 'Rule1' { Rule 'Rule2' { @@ -31,17 +31,17 @@ public void NestedRule() } } "; - scriptAst = ScriptBlock.Create(content).Ast; - visitor = new RuleLanguageAst(); - scriptAst.Visit(visitor); + scriptAst = ScriptBlock.Create(content).Ast; + visitor = new RuleLanguageAst(); + scriptAst.Visit(visitor); - Assert.Single(visitor.Errors); - } + Assert.Single(visitor.Errors); + } - [Fact] - public void UnvalidRule() - { - var content = @" + [Fact] + public void UnvalidRule() + { + var content = @" Rule '' { } @@ -70,12 +70,11 @@ public void UnvalidRule() "; - var scriptAst = ScriptBlock.Create(content).Ast; - var visitor = new RuleLanguageAst(); - scriptAst.Visit(visitor); + var scriptAst = ScriptBlock.Create(content).Ast; + var visitor = new RuleLanguageAst(); + scriptAst.Visit(visitor); - Assert.NotNull(visitor.Errors); - Assert.Equal(4, visitor.Errors.Count); - } + Assert.NotNull(visitor.Errors); + Assert.Equal(4, visitor.Errors.Count); } } diff --git a/tests/PSRule.Tests/MockLanguageScope.cs b/tests/PSRule.Tests/MockLanguageScope.cs index 21edd88426..aa775a020d 100644 --- a/tests/PSRule.Tests/MockLanguageScope.cs +++ b/tests/PSRule.Tests/MockLanguageScope.cs @@ -8,72 +8,71 @@ using PSRule.Pipeline; using PSRule.Runtime; -namespace PSRule +namespace PSRule; + +internal sealed class MockLanguageScope : ILanguageScope { - internal sealed class MockLanguageScope : ILanguageScope - { - internal RuleFilter RuleFilter; + internal RuleFilter RuleFilter; - public MockLanguageScope(string name) - { - Name = name; - } + public MockLanguageScope(string name) + { + Name = name; + } - public string Name { get; } + public string Name { get; } - public BindingOption Binding => throw new System.NotImplementedException(); + public BindingOption Binding => throw new System.NotImplementedException(); - public string[] Culture => throw new System.NotImplementedException(); + public string[] Culture => throw new System.NotImplementedException(); - public void AddService(string name, object service) - { + public void AddService(string name, object service) + { - } + } - public void Configure(OptionContext context) - { - throw new NotImplementedException(); - } + public void Configure(OptionContext context) + { + throw new NotImplementedException(); + } - public void Dispose() - { + public void Dispose() + { - } + } - public IResourceFilter GetFilter(ResourceKind kind) - { - throw new System.NotImplementedException(); - } + public IResourceFilter GetFilter(ResourceKind kind) + { + throw new System.NotImplementedException(); + } - public object GetService(string name) - { - 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 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 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 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 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; - } + 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 36f19eb73b..3689a04f93 100644 --- a/tests/PSRule.Tests/ModuleConfigTests.cs +++ b/tests/PSRule.Tests/ModuleConfigTests.cs @@ -9,40 +9,39 @@ using PSRule.Pipeline; using PSRule.Runtime; -namespace PSRule +namespace PSRule; + +public sealed class ModuleConfigTests { - public sealed class ModuleConfigTests + [Theory] + [InlineData("ModuleConfig.Rule.yaml")] + [InlineData("ModuleConfig.Rule.jsonc")] + public void ReadModuleConfig(string path) + { + 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); + } + + #region Helper methods + + private static PSRuleOption GetOption() + { + return new PSRuleOption(); + } + + private static Source[] GetSource(string path) + { + var builder = new SourcePipelineBuilder(null, null); + builder.Directory(GetSourcePath(path)); + return builder.Build(); + } + + private static string GetSourcePath(string fileName) { - [Theory] - [InlineData("ModuleConfig.Rule.yaml")] - [InlineData("ModuleConfig.Rule.jsonc")] - public void ReadModuleConfig(string path) - { - 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); - } - - #region Helper methods - - private static PSRuleOption GetOption() - { - return new PSRuleOption(); - } - - private static Source[] GetSource(string path) - { - var builder = new SourcePipelineBuilder(null, null); - builder.Directory(GetSourcePath(path)); - return builder.Build(); - } - - private static string GetSourcePath(string fileName) - { - return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName); - } - - #endregion Helper methods + return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName); } + + #endregion Helper methods } diff --git a/tests/PSRule.Tests/ModulePathComparerTests.cs b/tests/PSRule.Tests/ModulePathComparerTests.cs index c5536c3b57..da5bcf184a 100644 --- a/tests/PSRule.Tests/ModulePathComparerTests.cs +++ b/tests/PSRule.Tests/ModulePathComparerTests.cs @@ -4,35 +4,34 @@ using System; using PSRule.Pipeline; -namespace PSRule +namespace PSRule; + +public sealed class ModulePathComparerTests { - public sealed class ModulePathComparerTests + [Fact] + public void Sort() { - [Fact] - public void Sort() + var paths = new string[] { - var paths = new string[] - { - "C:/modules/PSRule/1.0.0", - "C:/modules/PSRule/1.2.0", - "C:/other/PSRule/1.0.0", - "C:/modules/PSRule/10.0.0", - "C:/other/PSRule/10.0.0", - "C:/other/PSRule/1.2.0", - "C:/other/PSRule/version", - "C:/other/PSRule/0.1.0", - }; - var comparer = new ModulePathComparer(); - Array.Sort(paths, comparer); + "C:/modules/PSRule/1.0.0", + "C:/modules/PSRule/1.2.0", + "C:/other/PSRule/1.0.0", + "C:/modules/PSRule/10.0.0", + "C:/other/PSRule/10.0.0", + "C:/other/PSRule/1.2.0", + "C:/other/PSRule/version", + "C:/other/PSRule/0.1.0", + }; + var comparer = new ModulePathComparer(); + Array.Sort(paths, comparer); - Assert.Equal("C:/modules/PSRule/10.0.0", paths[0]); - Assert.Equal("C:/other/PSRule/10.0.0", paths[1]); - Assert.Equal("C:/modules/PSRule/1.2.0", paths[2]); - Assert.Equal("C:/other/PSRule/1.2.0", paths[3]); - Assert.Equal("C:/modules/PSRule/1.0.0", paths[4]); - Assert.Equal("C:/other/PSRule/1.0.0", paths[5]); - Assert.Equal("C:/other/PSRule/0.1.0", paths[6]); - Assert.Equal("C:/other/PSRule/version", paths[7]); - } + Assert.Equal("C:/modules/PSRule/10.0.0", paths[0]); + Assert.Equal("C:/other/PSRule/10.0.0", paths[1]); + Assert.Equal("C:/modules/PSRule/1.2.0", paths[2]); + Assert.Equal("C:/other/PSRule/1.2.0", paths[3]); + Assert.Equal("C:/modules/PSRule/1.0.0", paths[4]); + Assert.Equal("C:/other/PSRule/1.0.0", paths[5]); + Assert.Equal("C:/other/PSRule/0.1.0", paths[6]); + Assert.Equal("C:/other/PSRule/version", paths[7]); } } diff --git a/tests/PSRule.Tests/ObjectHelperTests.cs b/tests/PSRule.Tests/ObjectHelperTests.cs index 80b7ea87dd..941400b616 100644 --- a/tests/PSRule.Tests/ObjectHelperTests.cs +++ b/tests/PSRule.Tests/ObjectHelperTests.cs @@ -6,119 +6,118 @@ using PSRule.Definitions; using ObjectHelper = PSRule.Runtime.ObjectHelper; -namespace PSRule +namespace PSRule; + +public sealed class ObjectHelperTests { - public sealed class ObjectHelperTests + [Fact] + public void GetFieldPOCO() { - [Fact] - public void GetFieldPOCO() - { - var testObject = GetTestObject(); - - ObjectHelper.GetPath(bindingContext: null, targetObject: testObject, path: "Name", caseSensitive: true, value: out object actual1); - ObjectHelper.GetPath(bindingContext: null, targetObject: testObject, path: "Value.Value1", caseSensitive: false, value: out object actual2); - ObjectHelper.GetPath(bindingContext: null, targetObject: testObject, path: "Metadata.'app.kubernetes.io/name'", caseSensitive: false, value: out object actual3); - ObjectHelper.GetPath(bindingContext: null, targetObject: testObject, path: "Value2[1]", caseSensitive: false, value: out object actual4); - ObjectHelper.GetPath(bindingContext: null, targetObject: testObject, path: ".", caseSensitive: true, value: out object actual5); - ObjectHelper.GetPath(bindingContext: null, targetObject: testObject, path: ".Value2[1]", caseSensitive: false, value: out object actual6); - ObjectHelper.GetPath(bindingContext: null, targetObject: testObject, path: ".Value3[1]", caseSensitive: false, value: out object actual7); - ObjectHelper.GetPath(bindingContext: null, targetObject: testObject, path: ".Value4[0]", caseSensitive: false, value: out object actual8); - ObjectHelper.GetPath(bindingContext: null, targetObject: testObject, path: ".Value5.name", caseSensitive: false, value: out object actual9); - ObjectHelper.GetPath(bindingContext: null, targetObject: testObject, path: ".Value5[2]", caseSensitive: false, value: out object actual10); - - Assert.Equal(expected: testObject.Name, actual: actual1); - Assert.Equal(expected: testObject.Value.Value1, actual: actual2); - Assert.Equal(expected: testObject.Metadata["app.kubernetes.io/name"], actual: actual3); - Assert.Equal(expected: testObject.Value2[1], actual: actual4); - Assert.Equal(expected: testObject, actual: actual5); - Assert.Equal(expected: testObject.Value2[1], actual: actual6); - Assert.Equal(expected: testObject.Value3[1], actual: actual7); - Assert.Equal(expected: "1", actual: actual8); - Assert.Equal(expected: testObject.Value5["name"], actual: actual9); - Assert.Equal(expected: testObject.Value5[2], actual: actual10); - } + var testObject = GetTestObject(); + + ObjectHelper.GetPath(bindingContext: null, targetObject: testObject, path: "Name", caseSensitive: true, value: out object actual1); + ObjectHelper.GetPath(bindingContext: null, targetObject: testObject, path: "Value.Value1", caseSensitive: false, value: out object actual2); + ObjectHelper.GetPath(bindingContext: null, targetObject: testObject, path: "Metadata.'app.kubernetes.io/name'", caseSensitive: false, value: out object actual3); + ObjectHelper.GetPath(bindingContext: null, targetObject: testObject, path: "Value2[1]", caseSensitive: false, value: out object actual4); + ObjectHelper.GetPath(bindingContext: null, targetObject: testObject, path: ".", caseSensitive: true, value: out object actual5); + ObjectHelper.GetPath(bindingContext: null, targetObject: testObject, path: ".Value2[1]", caseSensitive: false, value: out object actual6); + ObjectHelper.GetPath(bindingContext: null, targetObject: testObject, path: ".Value3[1]", caseSensitive: false, value: out object actual7); + ObjectHelper.GetPath(bindingContext: null, targetObject: testObject, path: ".Value4[0]", caseSensitive: false, value: out object actual8); + ObjectHelper.GetPath(bindingContext: null, targetObject: testObject, path: ".Value5.name", caseSensitive: false, value: out object actual9); + ObjectHelper.GetPath(bindingContext: null, targetObject: testObject, path: ".Value5[2]", caseSensitive: false, value: out object actual10); + + Assert.Equal(expected: testObject.Name, actual: actual1); + Assert.Equal(expected: testObject.Value.Value1, actual: actual2); + Assert.Equal(expected: testObject.Metadata["app.kubernetes.io/name"], actual: actual3); + Assert.Equal(expected: testObject.Value2[1], actual: actual4); + Assert.Equal(expected: testObject, actual: actual5); + Assert.Equal(expected: testObject.Value2[1], actual: actual6); + Assert.Equal(expected: testObject.Value3[1], actual: actual7); + Assert.Equal(expected: "1", actual: actual8); + Assert.Equal(expected: testObject.Value5["name"], actual: actual9); + Assert.Equal(expected: testObject.Value5[2], actual: actual10); + } - [Fact] - public void GetFieldDynamic() + [Fact] + public void GetFieldDynamic() + { + var hashtable = new Hashtable { - var hashtable = new Hashtable - { - { "Name", "TestObject1" }, - { "Value", "Value1" } - }; - var testObject = ResourceTags.FromHashtable(hashtable); - - ObjectHelper.GetPath(bindingContext: null, targetObject: testObject, path: "Name", caseSensitive: true, value: out object actual1); - ObjectHelper.GetPath(bindingContext: null, targetObject: testObject, path: "Value", caseSensitive: true, value: out object actual2); - - Assert.Equal(expected: testObject["Name"], actual: actual1); - Assert.Equal(expected: testObject["Value"], actual: actual2); - } + { "Name", "TestObject1" }, + { "Value", "Value1" } + }; + var testObject = ResourceTags.FromHashtable(hashtable); - [Fact] - public void JsonPath() - { - var testObject = GetTestObject(); - Assert.True(ObjectHelper.GetPath(bindingContext: null, targetObject: testObject, "$.Value2[*]", caseSensitive: true, value: out object actual1)); - Assert.NotNull(actual1); - } + ObjectHelper.GetPath(bindingContext: null, targetObject: testObject, path: "Name", caseSensitive: true, value: out object actual1); + ObjectHelper.GetPath(bindingContext: null, targetObject: testObject, path: "Value", caseSensitive: true, value: out object actual2); - private static TestObject1 GetTestObject() - { - var value5 = new TestObject3(); - value5["name"] = "1"; - value5[2] = "2"; - - var result = new TestObject1 - { - Name = "TestObject1", - Value = new TestObject2 { Value1 = "Value1" }, - Value2 = new string[] { "1", "2" }, - Metadata = new Hashtable(), - Value3 = new List(new string[] { "1", "2" }), - Value4 = new List(new string[] { "1" }).AsReadOnly(), - Value5 = value5 - }; - result.Metadata.Add("app.kubernetes.io/name", "KubeName"); - return result; - } + Assert.Equal(expected: testObject["Name"], actual: actual1); + Assert.Equal(expected: testObject["Value"], actual: actual2); + } + + [Fact] + public void JsonPath() + { + var testObject = GetTestObject(); + Assert.True(ObjectHelper.GetPath(bindingContext: null, targetObject: testObject, "$.Value2[*]", caseSensitive: true, value: out object actual1)); + Assert.NotNull(actual1); + } + + private static TestObject1 GetTestObject() + { + var value5 = new TestObject3(); + value5["name"] = "1"; + value5[2] = "2"; - public sealed class TestObject1 + var result = new TestObject1 { - public string Name; + Name = "TestObject1", + Value = new TestObject2 { Value1 = "Value1" }, + Value2 = new string[] { "1", "2" }, + Metadata = new Hashtable(), + Value3 = new List(new string[] { "1", "2" }), + Value4 = new List(new string[] { "1" }).AsReadOnly(), + Value5 = value5 + }; + result.Metadata.Add("app.kubernetes.io/name", "KubeName"); + return result; + } - public TestObject2 Value; + public sealed class TestObject1 + { + public string Name; - public string[] Value2; + public TestObject2 Value; - public Hashtable Metadata; + public string[] Value2; - public IList Value3; + public Hashtable Metadata; - public ICollection Value4; + public IList Value3; - public TestObject3 Value5; - } + public ICollection Value4; + + public TestObject3 Value5; + } + + public sealed class TestObject2 + { + public string Value1; + } + + public sealed class TestObject3 + { + private readonly Dictionary _Internal; - public sealed class TestObject2 + public TestObject3() { - public string Value1; + _Internal = new Dictionary(); } - public sealed class TestObject3 + public object this[object key] { - private readonly Dictionary _Internal; - - public TestObject3() - { - _Internal = new Dictionary(); - } - - public object this[object key] - { - get => _Internal[key]; - set => _Internal[key] = value; - } + get => _Internal[key]; + set => _Internal[key] = value; } } } diff --git a/tests/PSRule.Tests/ObjectPathTests.cs b/tests/PSRule.Tests/ObjectPathTests.cs index 154a2fa1d3..b549c02215 100644 --- a/tests/PSRule.Tests/ObjectPathTests.cs +++ b/tests/PSRule.Tests/ObjectPathTests.cs @@ -7,27 +7,26 @@ using System.Management.Automation; using PSRule.Pipeline; -namespace PSRule +namespace PSRule; + +public sealed class ObjectPathTests { - public sealed class ObjectPathTests + [Fact] + public void UseObjectPath() { - [Fact] - public void UseObjectPath() - { - var actual = PipelineReceiverActions.ConvertFromYaml(GetYamlContent(), - (sourceObject) => PipelineReceiverActions.ReadObjectPath(sourceObject, PipelineReceiverActions.PassThru, "items", true) - ).ToArray(); + var actual = PipelineReceiverActions.ConvertFromYaml(GetYamlContent(), + (sourceObject) => PipelineReceiverActions.ReadObjectPath(sourceObject, PipelineReceiverActions.PassThru, "items", true) + ).ToArray(); - Assert.Equal(2, actual.Length); - Assert.Equal("TestObject1", actual[0].Value.Properties["targetName"].Value); - Assert.Equal("Test", actual[0].Value.PropertyValue("spec").PropertyValue("properties").PropertyValue("kind")); - Assert.Equal(2, actual[1].Value.PropertyValue("spec").PropertyValue("properties").PropertyValue("value2")); - } + Assert.Equal(2, actual.Length); + Assert.Equal("TestObject1", actual[0].Value.Properties["targetName"].Value); + Assert.Equal("Test", actual[0].Value.PropertyValue("spec").PropertyValue("properties").PropertyValue("kind")); + Assert.Equal(2, actual[1].Value.PropertyValue("spec").PropertyValue("properties").PropertyValue("value2")); + } - private TargetObject GetYamlContent() - { - var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "ObjectFromNestedFile.yaml"); - return new TargetObject(new PSObject(File.ReadAllText(path))); - } + private TargetObject GetYamlContent() + { + var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "ObjectFromNestedFile.yaml"); + return new TargetObject(new PSObject(File.ReadAllText(path))); } } diff --git a/tests/PSRule.Tests/OptionContextTests.cs b/tests/PSRule.Tests/OptionContextTests.cs index 07a4b4e91d..7104d4810f 100644 --- a/tests/PSRule.Tests/OptionContextTests.cs +++ b/tests/PSRule.Tests/OptionContextTests.cs @@ -10,157 +10,156 @@ using PSRule.Pipeline; using PSRule.Runtime; -namespace PSRule +namespace PSRule; + +/// +/// Tests for . +/// +public sealed class OptionContextTests { - /// - /// Tests for . - /// - public sealed class OptionContextTests + [Fact] + public void Build() { - [Fact] - public void Build() - { - // Create option context - var builder = new OptionContextBuilder(GetOption()); + // Create option context + var builder = new OptionContextBuilder(GetOption()); - // Check empty scope - var testScope = new LanguageScope(null, "Empty"); - testScope.Configure(builder.Build(testScope.Name)); - Assert.Equal(new string[] { "en-ZZ" }, testScope.Culture); - } + // Check empty scope + var testScope = new LanguageScope(null, "Empty"); + testScope.Configure(builder.Build(testScope.Name)); + Assert.Equal(new string[] { "en-ZZ" }, testScope.Culture); + } - [Fact] - public void Order() - { - // Create option context - var builder = new OptionContextBuilder(GetOption()); - - var localScope = new LanguageScope(null, null); - localScope.Configure(builder.Build(null)); - - var ruleFilter = localScope.GetFilter(ResourceKind.Rule) as RuleFilter; - Assert.NotNull(ruleFilter); - Assert.True(ruleFilter.IncludeLocal); - - // With explict baseline - builder = new OptionContextBuilder(GetOption()); - 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" }); - localScope.Configure(builder.Build(localScope.Name)); - ruleFilter = localScope.GetFilter(ResourceKind.Rule) as RuleFilter; - Assert.NotNull(ruleFilter); - Assert.False(ruleFilter.IncludeLocal); - - 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); - } - - /// - /// Test that options from separate files can be combined. - /// - [Fact] - public void Merge_multiple_options_from_file() - { - var builder = new OptionContextBuilder(); + [Fact] + public void Order() + { + // Create option context + var builder = new OptionContextBuilder(GetOption()); + + var localScope = new LanguageScope(null, null); + localScope.Configure(builder.Build(null)); + + var ruleFilter = localScope.GetFilter(ResourceKind.Rule) as RuleFilter; + Assert.NotNull(ruleFilter); + Assert.True(ruleFilter.IncludeLocal); + + // With explict baseline + builder = new OptionContextBuilder(GetOption()); + 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" }); + localScope.Configure(builder.Build(localScope.Name)); + ruleFilter = localScope.GetFilter(ResourceKind.Rule) as RuleFilter; + Assert.NotNull(ruleFilter); + Assert.False(ruleFilter.IncludeLocal); + + 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); + } + + /// + /// Test that options from separate files can be combined. + /// + [Fact] + public void Merge_multiple_options_from_file() + { + var builder = new OptionContextBuilder(); - builder.Workspace(GetOptionFromFile("PSRule.Tests2.yml")); - builder.Workspace(GetOptionFromFile()); - builder.Workspace(GetOption()); + builder.Workspace(GetOptionFromFile("PSRule.Tests2.yml")); + builder.Workspace(GetOptionFromFile()); + builder.Workspace(GetOption()); - var context = builder.Build(null); + var context = builder.Build(null); - // 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); + // 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); - // With module default baseline - builder.Baseline(ScopeType.Module, "BaselineDefault", "Module1", GetBaseline(targetType: new[] { "defaultType" }, ruleInclude: new[] { "defaultRule" }), false); - context = builder.Build(null); + // With module default baseline + builder.Baseline(ScopeType.Module, "BaselineDefault", "Module1", GetBaseline(targetType: new[] { "defaultType" }, ruleInclude: new[] { "defaultRule" }), false); + context = builder.Build(null); - Assert.Equal(new[] { "ResourceName", "AlternateName" }, context.Binding.TargetName); - Assert.Equal(new[] { "ResourceType", "kind" }, context.Binding.TargetType); - Assert.Equal(new[] { "rule1", "rule2" }, context.Rule.Include); + 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"); + context = builder.Build("Module1"); - Assert.Equal(new[] { "ResourceName", "AlternateName" }, context.Binding.TargetName); - Assert.Equal(new[] { "ResourceType", "kind" }, context.Binding.TargetType); - Assert.Equal(new[] { "rule1", "rule2" }, context.Rule.Include); + Assert.Equal(new[] { "ResourceName", "AlternateName" }, context.Binding.TargetName); + Assert.Equal(new[] { "ResourceType", "kind" }, context.Binding.TargetType); + Assert.Equal(new[] { "rule1", "rule2" }, context.Rule.Include); - // With explict baseline - builder.Baseline(ScopeType.Explicit, "BaselineExplicit", "Module1", GetBaseline(), false); - context = builder.Build(null); + // 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); + Assert.Equal(new[] { "ResourceName", "AlternateName" }, context.Binding.TargetName); + Assert.Equal(new[] { "typeName" }, context.Binding.TargetType); + Assert.Equal(new[] { "rule1" }, context.Rule.Include); - context = builder.Build("Module1"); + context = builder.Build("Module1"); - Assert.Equal(new[] { "ResourceName", "AlternateName" }, context.Binding.TargetName); - Assert.Equal(new[] { "typeName" }, context.Binding.TargetType); - Assert.Equal(new[] { "rule1" }, context.Rule.Include); - } + Assert.Equal(new[] { "ResourceName", "AlternateName" }, context.Binding.TargetName); + Assert.Equal(new[] { "typeName" }, context.Binding.TargetType); + Assert.Equal(new[] { "rule1" }, context.Rule.Include); + } - #region Helper methods + #region Helper methods - private static PSRuleOption GetOption(string[] culture = null, string[] ruleInclude = null) - { - var option = new PSRuleOption(); + private static PSRuleOption GetOption(string[] culture = null, string[] ruleInclude = null) + { + var option = new PSRuleOption(); - // Specify a culture otherwise it varies within CI. - option.Output.Culture = culture ?? new string[] { "en-ZZ" }; + // Specify a culture otherwise it varies within CI. + option.Output.Culture = culture ?? new string[] { "en-ZZ" }; - option.Rule.Include = ruleInclude; + option.Rule.Include = ruleInclude; - // Add a configuration option. - option.Configuration.Add("option6", "value6"); - option.Configuration.Add("option5", "value5"); - return option; - } + // 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)); - } + private static PSRuleOption GetOptionFromFile(string file = "PSRule.Tests.yml") + { + return PSRuleOption.FromFileOrEmpty(GetSourcePath(file)); + } - private static BaselineSpec GetBaseline(string[] targetType = null, string[] ruleInclude = null) + private static BaselineSpec GetBaseline(string[] targetType = null, string[] ruleInclude = null) + { + targetType ??= new[] { "typeName" }; + ruleInclude ??= new[] { "rule1" }; + return new BaselineSpec { - targetType ??= new[] { "typeName" }; - ruleInclude ??= new[] { "rule1" }; - return new BaselineSpec + Binding = new BindingOption { - Binding = new BindingOption - { - TargetType = targetType - }, - Rule = new RuleOption - { - Include = ruleInclude - } - }; - } - - private static string GetSourcePath(string fileName) - { - return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName); - } + TargetType = targetType + }, + Rule = new RuleOption + { + Include = ruleInclude + } + }; + } - #endregion Helper methods + private static string GetSourcePath(string fileName) + { + 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 4b904ca844..7cea4a0ea2 100644 --- a/tests/PSRule.Tests/OutputWriterTests.cs +++ b/tests/PSRule.Tests/OutputWriterTests.cs @@ -15,100 +15,100 @@ using PSRule.Pipeline.Output; using PSRule.Rules; -namespace PSRule +namespace PSRule; + +public sealed class OutputWriterTests { - public sealed class OutputWriterTests + [Fact] + public void Sarif() { - [Fact] - public void Sarif() - { - var option = GetOption(); - option.Output.SarifProblemsOnly = false; - option.Repository.Url = "https://github.com/microsoft/PSRule.UnitTest"; - var output = new TestWriter(option); - var result = new InvokeResult(); - result.Add(GetPass()); - result.Add(GetFail()); - result.Add(GetFail("rid-003", SeverityLevel.Warning)); - result.Add(GetFail("rid-004", SeverityLevel.Information)); - var writer = new SarifOutputWriter(null, output, option, null); - writer.Begin(); - writer.WriteObject(result, false); - writer.End(); + var option = GetOption(); + option.Output.SarifProblemsOnly = false; + option.Repository.Url = "https://github.com/microsoft/PSRule.UnitTest"; + var output = new TestWriter(option); + var result = new InvokeResult(); + result.Add(GetPass()); + result.Add(GetFail()); + result.Add(GetFail("rid-003", SeverityLevel.Warning)); + result.Add(GetFail("rid-004", SeverityLevel.Information)); + var writer = new SarifOutputWriter(null, output, option, null); + writer.Begin(); + writer.WriteObject(result, false); + writer.End(); - var actual = JsonConvert.DeserializeObject(output.Output.OfType().FirstOrDefault()); - Assert.NotNull(actual); - Assert.Equal("PSRule", actual["runs"][0]["tool"]["driver"]["name"]); - Assert.Equal("0.0.1", actual["runs"][0]["tool"]["driver"]["semanticVersion"].Value().Split('+')[0]); - Assert.Equal("https://github.com/microsoft/PSRule.UnitTest", actual["runs"][0]["versionControlProvenance"][0]["repositoryUri"].Value()); + var actual = JsonConvert.DeserializeObject(output.Output.OfType().FirstOrDefault()); + Assert.NotNull(actual); + Assert.Equal("PSRule", actual["runs"][0]["tool"]["driver"]["name"]); + Assert.Equal("0.0.1", actual["runs"][0]["tool"]["driver"]["semanticVersion"].Value().Split('+')[0]); + Assert.Equal("https://github.com/microsoft/PSRule.UnitTest", actual["runs"][0]["versionControlProvenance"][0]["repositoryUri"].Value()); - // Pass - Assert.Equal("TestModule\\rule-001", actual["runs"][0]["results"][0]["ruleId"]); - Assert.Equal("none", actual["runs"][0]["results"][0]["level"]); + // Pass + Assert.Equal("TestModule\\rule-001", actual["runs"][0]["results"][0]["ruleId"]); + Assert.Equal("none", actual["runs"][0]["results"][0]["level"]); - // Fail with error - Assert.Equal("rid-002", actual["runs"][0]["results"][1]["ruleId"]); - Assert.Equal("error", actual["runs"][0]["results"][1]["level"]); + // Fail with error + Assert.Equal("rid-002", actual["runs"][0]["results"][1]["ruleId"]); + Assert.Equal("error", actual["runs"][0]["results"][1]["level"]); - // Fail with warning - Assert.Equal("rid-003", actual["runs"][0]["results"][2]["ruleId"]); - Assert.Null(actual["runs"][0]["results"][2]["level"]); + // Fail with warning + Assert.Equal("rid-003", actual["runs"][0]["results"][2]["ruleId"]); + Assert.Null(actual["runs"][0]["results"][2]["level"]); - // Fail with note - Assert.Equal("rid-004", actual["runs"][0]["results"][3]["ruleId"]); - Assert.Equal("note", actual["runs"][0]["results"][3]["level"]); - } + // Fail with note + Assert.Equal("rid-004", actual["runs"][0]["results"][3]["ruleId"]); + Assert.Equal("note", actual["runs"][0]["results"][3]["level"]); + } - [Fact] - public void SarifProblemsOnly() - { - var option = GetOption(); - var output = new TestWriter(option); - var result = new InvokeResult(); - result.Add(GetPass()); - result.Add(GetFail()); - result.Add(GetFail("rid-003", SeverityLevel.Warning)); - result.Add(GetFail("rid-004", SeverityLevel.Information)); - var writer = new SarifOutputWriter(null, output, option, null); - writer.Begin(); - writer.WriteObject(result, false); - writer.End(); + [Fact] + public void SarifProblemsOnly() + { + var option = GetOption(); + var output = new TestWriter(option); + var result = new InvokeResult(); + result.Add(GetPass()); + result.Add(GetFail()); + result.Add(GetFail("rid-003", SeverityLevel.Warning)); + result.Add(GetFail("rid-004", SeverityLevel.Information)); + var writer = new SarifOutputWriter(null, output, option, null); + writer.Begin(); + writer.WriteObject(result, false); + writer.End(); - var actual = JsonConvert.DeserializeObject(output.Output.OfType().FirstOrDefault()); - Assert.NotNull(actual); - Assert.Equal("PSRule", actual["runs"][0]["tool"]["driver"]["name"]); - Assert.Equal("0.0.1", actual["runs"][0]["tool"]["driver"]["semanticVersion"].Value().Split('+')[0]); + var actual = JsonConvert.DeserializeObject(output.Output.OfType().FirstOrDefault()); + Assert.NotNull(actual); + Assert.Equal("PSRule", actual["runs"][0]["tool"]["driver"]["name"]); + Assert.Equal("0.0.1", actual["runs"][0]["tool"]["driver"]["semanticVersion"].Value().Split('+')[0]); - // Fail with error - Assert.Equal("rid-002", actual["runs"][0]["results"][0]["ruleId"]); - Assert.Equal("error", actual["runs"][0]["results"][0]["level"]); + // Fail with error + Assert.Equal("rid-002", actual["runs"][0]["results"][0]["ruleId"]); + Assert.Equal("error", actual["runs"][0]["results"][0]["level"]); - // Fail with warning - Assert.Equal("rid-003", actual["runs"][0]["results"][1]["ruleId"]); - Assert.Null(actual["runs"][0]["results"][1]["level"]); + // Fail with warning + Assert.Equal("rid-003", actual["runs"][0]["results"][1]["ruleId"]); + Assert.Null(actual["runs"][0]["results"][1]["level"]); - // Fail with note - Assert.Equal("rid-004", actual["runs"][0]["results"][2]["ruleId"]); - Assert.Equal("note", actual["runs"][0]["results"][2]["level"]); - } + // Fail with note + Assert.Equal("rid-004", actual["runs"][0]["results"][2]["ruleId"]); + Assert.Equal("note", actual["runs"][0]["results"][2]["level"]); + } - [Fact] - public void Yaml() - { - var option = GetOption(); - option.Repository.Url = "https://github.com/microsoft/PSRule.UnitTest"; - var output = new TestWriter(option); - var result = new InvokeResult(); - result.Add(GetPass()); - result.Add(GetFail()); - result.Add(GetFail("rid-003", SeverityLevel.Warning)); - result.Add(GetFail("rid-004", SeverityLevel.Information)); - var writer = new YamlOutputWriter(output, option, null); - writer.Begin(); - writer.WriteObject(result, false); - writer.End(); + [Fact] + public void Yaml() + { + var option = GetOption(); + option.Repository.Url = "https://github.com/microsoft/PSRule.UnitTest"; + var output = new TestWriter(option); + var result = new InvokeResult(); + result.Add(GetPass()); + result.Add(GetFail()); + result.Add(GetFail("rid-003", SeverityLevel.Warning)); + result.Add(GetFail("rid-004", SeverityLevel.Information)); + var writer = new YamlOutputWriter(output, option, null); + writer.Begin(); + writer.WriteObject(result, false); + writer.End(); - Assert.Equal(@"- detail: + Assert.Equal(@"- detail: reason: [] info: moduleName: TestModule @@ -172,26 +172,26 @@ public void Yaml() targetType: TestType time: 1000 ", output.Output.OfType().FirstOrDefault()); - } + } - [Fact] - public void Json() - { - var option = GetOption(); - option.Output.JsonIndent = 2; - option.Repository.Url = "https://github.com/microsoft/PSRule.UnitTest"; - var output = new TestWriter(option); - var result = new InvokeResult(); - result.Add(GetPass()); - result.Add(GetFail()); - result.Add(GetFail("rid-003", SeverityLevel.Warning)); - result.Add(GetFail("rid-004", SeverityLevel.Information)); - var writer = new JsonOutputWriter(output, option, null); - writer.Begin(); - writer.WriteObject(result, false); - writer.End(); + [Fact] + public void Json() + { + var option = GetOption(); + option.Output.JsonIndent = 2; + option.Repository.Url = "https://github.com/microsoft/PSRule.UnitTest"; + var output = new TestWriter(option); + var result = new InvokeResult(); + result.Add(GetPass()); + result.Add(GetFail()); + result.Add(GetFail("rid-003", SeverityLevel.Warning)); + result.Add(GetFail("rid-004", SeverityLevel.Information)); + var writer = new JsonOutputWriter(output, option, null); + writer.Begin(); + writer.WriteObject(result, false); + writer.End(); - Assert.Equal(@"[ + Assert.Equal(@"[ { ""detail"": {}, ""info"": { @@ -276,120 +276,119 @@ public void Json() ""time"": 1000 } ]", output.Output.OfType().FirstOrDefault()); - } - - [Fact] - public void NUnit3() - { - var option = GetOption(); - option.Repository.Url = "https://github.com/microsoft/PSRule.UnitTest"; - var output = new TestWriter(option); - var result = new InvokeResult(); - result.Add(GetPass()); - result.Add(GetFail()); - result.Add(GetFail("rid-003", SeverityLevel.Warning)); - result.Add(GetFail("rid-004", SeverityLevel.Information, "Synopsis \"with quotes\".")); - var writer = new NUnit3OutputWriter(output, option, null); - writer.Begin(); - writer.WriteObject(result, false); - writer.End(); + } - var s = output.Output.OfType().FirstOrDefault(); - var doc = new XmlDocument(); - doc.LoadXml(s); + [Fact] + public void NUnit3() + { + var option = GetOption(); + option.Repository.Url = "https://github.com/microsoft/PSRule.UnitTest"; + var output = new TestWriter(option); + var result = new InvokeResult(); + result.Add(GetPass()); + result.Add(GetFail()); + result.Add(GetFail("rid-003", SeverityLevel.Warning)); + result.Add(GetFail("rid-004", SeverityLevel.Information, "Synopsis \"with quotes\".")); + var writer = new NUnit3OutputWriter(output, option, null); + writer.Begin(); + writer.WriteObject(result, false); + writer.End(); - var declaration = doc.ChildNodes.Item(0) as XmlDeclaration; - Assert.Equal("utf-8", declaration.Encoding); - var xml = doc["test-results"]["test-suite"].OuterXml.Replace(System.Environment.NewLine, "\r\n"); - Assert.Equal("", xml); - } + var s = output.Output.OfType().FirstOrDefault(); + var doc = new XmlDocument(); + doc.LoadXml(s); - [Fact] - public void JobSummary() - { - using var stream = new MemoryStream(); - var option = GetOption(); - var output = new TestWriter(option); - var result = new InvokeResult(); - 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")); - var writer = new JobSummaryWriter(output, option, null, outputPath: "reports/summary.md", stream: stream); - writer.Begin(); - writer.WriteObject(result, false); - context.RunTime.Stop(); - writer.End(); + var declaration = doc.ChildNodes.Item(0) as XmlDeclaration; + Assert.Equal("utf-8", declaration.Encoding); + var xml = doc["test-results"]["test-suite"].OuterXml.Replace(System.Environment.NewLine, "\r\n"); + Assert.Equal("", xml); + } - stream.Seek(0, SeekOrigin.Begin); - using var reader = new StreamReader(stream); - var s = reader.ReadToEnd().Replace(System.Environment.NewLine, "\r\n"); - Assert.Equal($"# PSRule result summary\r\n\r\n❌ PSRule completed with an overall result of 'Fail' with 3 rule(s) and 1 target(s) in {context.RunTime.Elapsed}.\r\n\r\n## Analysis\r\n\r\nThe following results were reported with fail or error results.\r\n\r\nName | Target name | Synopsis\r\n---- | ----------- | --------\r\nrule-002 | TestObject1 | This is rule 002.\r\nRule-003 | TestObject1 | This is rule 002.\r\n", s); - } + [Fact] + public void JobSummary() + { + using var stream = new MemoryStream(); + var option = GetOption(); + var output = new TestWriter(option); + var result = new InvokeResult(); + 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")); + var writer = new JobSummaryWriter(output, option, null, outputPath: "reports/summary.md", stream: stream); + writer.Begin(); + writer.WriteObject(result, false); + context.RunTime.Stop(); + writer.End(); - #region Helper methods + stream.Seek(0, SeekOrigin.Begin); + using var reader = new StreamReader(stream); + var s = reader.ReadToEnd().Replace(System.Environment.NewLine, "\r\n"); + Assert.Equal($"# PSRule result summary\r\n\r\n❌ PSRule completed with an overall result of 'Fail' with 3 rule(s) and 1 target(s) in {context.RunTime.Elapsed}.\r\n\r\n## Analysis\r\n\r\nThe following results were reported with fail or error results.\r\n\r\nName | Target name | Synopsis\r\n---- | ----------- | --------\r\nrule-002 | TestObject1 | This is rule 002.\r\nRule-003 | TestObject1 | This is rule 002.\r\n", s); + } - private static RuleRecord GetPass() - { - return new RuleRecord( - runId: "run-001", - ruleId: ResourceId.Parse("TestModule\\rule-001"), - @ref: null, - targetObject: new TargetObject(new PSObject()), - targetName: "TestObject1", - targetType: "TestType", - tag: new ResourceTags(), - info: new RuleHelpInfo( - "rule-001", - "Rule 001", - "TestModule", - synopsis: new InfoString("This is rule 001."), - recommendation: new InfoString("Recommendation for rule 001") - ), - field: new Hashtable(), - level: SeverityLevel.Error, - extent: null, - outcome: RuleOutcome.Pass, - reason: RuleOutcomeReason.Processed - ) - { - Time = 500 - }; - } + #region Helper methods - private static RuleRecord GetFail(string ruleRef = "rid-002", SeverityLevel level = SeverityLevel.Error, string synopsis = "This is rule 002.", string ruleId = "TestModule\\rule-002") + private static RuleRecord GetPass() + { + return new RuleRecord( + runId: "run-001", + ruleId: ResourceId.Parse("TestModule\\rule-001"), + @ref: null, + targetObject: new TargetObject(new PSObject()), + targetName: "TestObject1", + targetType: "TestType", + tag: new ResourceTags(), + info: new RuleHelpInfo( + "rule-001", + "Rule 001", + "TestModule", + synopsis: new InfoString("This is rule 001."), + recommendation: new InfoString("Recommendation for rule 001") + ), + field: new Hashtable(), + level: SeverityLevel.Error, + extent: null, + outcome: RuleOutcome.Pass, + reason: RuleOutcomeReason.Processed + ) { - return new RuleRecord( - runId: "run-001", - ruleId: ResourceId.Parse(ruleId), - @ref: ruleRef, - targetObject: new TargetObject(new PSObject()), - targetName: "TestObject1", - targetType: "TestType", - tag: new ResourceTags(), - info: new RuleHelpInfo( - "rule-002", - "Rule 002", - "TestModule", - synopsis: new InfoString(synopsis), - recommendation: new InfoString("Recommendation for rule 002") - ), - field: new Hashtable(), - level: level, - extent: null, - outcome: RuleOutcome.Fail, - reason: RuleOutcomeReason.Processed - ) - { - Time = 1000 - }; - } + Time = 500 + }; + } - private static PSRuleOption GetOption() + private static RuleRecord GetFail(string ruleRef = "rid-002", SeverityLevel level = SeverityLevel.Error, string synopsis = "This is rule 002.", string ruleId = "TestModule\\rule-002") + { + return new RuleRecord( + runId: "run-001", + ruleId: ResourceId.Parse(ruleId), + @ref: ruleRef, + targetObject: new TargetObject(new PSObject()), + targetName: "TestObject1", + targetType: "TestType", + tag: new ResourceTags(), + info: new RuleHelpInfo( + "rule-002", + "Rule 002", + "TestModule", + synopsis: new InfoString(synopsis), + recommendation: new InfoString("Recommendation for rule 002") + ), + field: new Hashtable(), + level: level, + extent: null, + outcome: RuleOutcome.Fail, + reason: RuleOutcomeReason.Processed + ) { - return new PSRuleOption(); - } + Time = 1000 + }; + } - #endregion Helper methods + private static PSRuleOption GetOption() + { + return new PSRuleOption(); } + + #endregion Helper methods } diff --git a/tests/PSRule.Tests/PSRuleOptionTests.cs b/tests/PSRule.Tests/PSRuleOptionTests.cs index d1eaa662d7..4f17f1de96 100644 --- a/tests/PSRule.Tests/PSRuleOptionTests.cs +++ b/tests/PSRule.Tests/PSRuleOptionTests.cs @@ -7,114 +7,113 @@ using PSRule.Configuration; using PSRule.Pipeline; -namespace PSRule +namespace PSRule; + +/// +/// Tests for . +/// +public sealed class PSRuleOptionTests { - /// - /// Tests for . - /// - public sealed class PSRuleOptionTests + [Fact] + public void GetRootedBasePath() + { + var pwd = Directory.GetCurrentDirectory(); + var basePwd = $"{pwd}{Path.DirectorySeparatorChar}"; + Assert.Equal(basePwd, Environment.GetRootedBasePath(null)); + Assert.Equal(basePwd, Environment.GetRootedBasePath(pwd)); + Assert.Equal(pwd, Environment.GetRootedPath(null)); + Assert.Equal(pwd, Environment.GetRootedPath(pwd)); + } + + [Fact] + public void Configuration() + { + var option = new PSRuleOption(); + option.Configuration.Add("key1", "value1"); + + dynamic configuration = GetConfigurationHelper(option); + Assert.Equal("value1", configuration.key1); + } + + [Fact] + public void GetStringValues() { - [Fact] - public void GetRootedBasePath() - { - var pwd = Directory.GetCurrentDirectory(); - var basePwd = $"{pwd}{Path.DirectorySeparatorChar}"; - Assert.Equal(basePwd, Environment.GetRootedBasePath(null)); - Assert.Equal(basePwd, Environment.GetRootedBasePath(pwd)); - Assert.Equal(pwd, Environment.GetRootedPath(null)); - Assert.Equal(pwd, Environment.GetRootedPath(pwd)); - } - - [Fact] - public void Configuration() - { - var option = new PSRuleOption(); - option.Configuration.Add("key1", "value1"); - - dynamic configuration = GetConfigurationHelper(option); - Assert.Equal("value1", configuration.key1); - } - - [Fact] - public void GetStringValues() - { - var option = new PSRuleOption(); - option.Configuration.Add("key1", "123"); - option.Configuration.Add("key2", new string[] { "123" }); - option.Configuration.Add("key3", new object[] { "123", 456 }); - - var configuration = GetConfigurationHelper(option); - Assert.Equal(new string[] { "123" }, configuration.GetStringValues("key1")); - Assert.Equal(new string[] { "123" }, configuration.GetStringValues("key2")); - Assert.Equal(new string[] { "123", "456" }, configuration.GetStringValues("key3")); - } - - [Fact] - public void GetStringValuesFromYaml() - { - var option = GetOption(); - var actual = option.Configuration["option5"] as Array; - Assert.NotNull(actual); - Assert.Equal(2, actual.Length); - Assert.IsType(actual.GetValue(0)); - var pso = actual.GetValue(0) as PSObject; - Assert.Equal("option5a", pso.BaseObject); - - var configuration = GetConfigurationHelper(option); - Assert.Equal(new string[] { "option5a", "option5b" }, configuration.GetStringValues("option5")); - } - - [Fact] - public void GetObjectArrayFromYaml() - { - var option = GetOption(); - var actual = option.Configuration["option4"] as Array; - Assert.NotNull(actual); - Assert.Equal(2, actual.Length); - Assert.IsType(actual.GetValue(0)); - var pso = actual.GetValue(0) as PSObject; - Assert.Equal("East US", pso.PropertyValue("location")); - } - - [Fact] - public void GetBaselineGroupFromYaml() - { - var option = GetOption(); - var actual = option.Baseline.Group; - Assert.NotNull(actual); - Assert.Single(actual); - Assert.True(actual.TryGetValue("latest", out var latest)); - Assert.Equal(new string[] { ".\\TestBaseline1" }, latest); - } - - #region Helper methods - - 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, null), null); - context.Init(null); - context.Begin(); - return new Runtime.Configuration(context); - } - - private static Source[] GetSource() - { - var builder = new SourcePipelineBuilder(null, null); - builder.Directory(GetSourcePath("FromFile.Rule.ps1")); - return builder.Build(); - } - - private static PSRuleOption GetOption() - { - return PSRuleOption.FromFile(GetSourcePath("PSRule.Tests.yml")); - } - - private static string GetSourcePath(string fileName) - { - return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName); - } - - #endregion Helper methods + var option = new PSRuleOption(); + option.Configuration.Add("key1", "123"); + option.Configuration.Add("key2", new string[] { "123" }); + option.Configuration.Add("key3", new object[] { "123", 456 }); + + var configuration = GetConfigurationHelper(option); + Assert.Equal(new string[] { "123" }, configuration.GetStringValues("key1")); + Assert.Equal(new string[] { "123" }, configuration.GetStringValues("key2")); + Assert.Equal(new string[] { "123", "456" }, configuration.GetStringValues("key3")); } + + [Fact] + public void GetStringValuesFromYaml() + { + var option = GetOption(); + var actual = option.Configuration["option5"] as Array; + Assert.NotNull(actual); + Assert.Equal(2, actual.Length); + Assert.IsType(actual.GetValue(0)); + var pso = actual.GetValue(0) as PSObject; + Assert.Equal("option5a", pso.BaseObject); + + var configuration = GetConfigurationHelper(option); + Assert.Equal(new string[] { "option5a", "option5b" }, configuration.GetStringValues("option5")); + } + + [Fact] + public void GetObjectArrayFromYaml() + { + var option = GetOption(); + var actual = option.Configuration["option4"] as Array; + Assert.NotNull(actual); + Assert.Equal(2, actual.Length); + Assert.IsType(actual.GetValue(0)); + var pso = actual.GetValue(0) as PSObject; + Assert.Equal("East US", pso.PropertyValue("location")); + } + + [Fact] + public void GetBaselineGroupFromYaml() + { + var option = GetOption(); + var actual = option.Baseline.Group; + Assert.NotNull(actual); + Assert.Single(actual); + Assert.True(actual.TryGetValue("latest", out var latest)); + Assert.Equal(new string[] { ".\\TestBaseline1" }, latest); + } + + #region Helper methods + + 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, null), null); + context.Init(null); + context.Begin(); + return new Runtime.Configuration(context); + } + + private static Source[] GetSource() + { + var builder = new SourcePipelineBuilder(null, null); + builder.Directory(GetSourcePath("FromFile.Rule.ps1")); + return builder.Build(); + } + + private static PSRuleOption GetOption() + { + return PSRuleOption.FromFile(GetSourcePath("PSRule.Tests.yml")); + } + + private static string GetSourcePath(string fileName) + { + return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName); + } + + #endregion Helper methods } diff --git a/tests/PSRule.Tests/PathExpressionTests.cs b/tests/PSRule.Tests/PathExpressionTests.cs index 7cab6a5dcb..6b2d810d1f 100644 --- a/tests/PSRule.Tests/PathExpressionTests.cs +++ b/tests/PSRule.Tests/PathExpressionTests.cs @@ -8,353 +8,352 @@ using Newtonsoft.Json.Linq; using PSRule.Runtime.ObjectPath; -namespace PSRule +namespace PSRule; + +/// +/// Tests for a JSONPath expression. +/// +public sealed class PathExpressionTests { - /// - /// Tests for a JSONPath expression. - /// - public sealed class PathExpressionTests + [Fact] + public void Basic() { - [Fact] - public void Basic() - { - var testObject = GetJsonContent(); - - var expression = PathExpression.Create("$[*]"); - Assert.True(expression.IsArray); - Assert.True(expression.TryGet(testObject, false, out object actual1)); - var actualArray = actual1 as object[]; - Assert.NotNull(actualArray); - Assert.Equal(2, actualArray.Length); - - expression = PathExpression.Create("$[-1].TargetName"); - Assert.False(expression.IsArray); - Assert.True(expression.TryGet(testObject, false, out object actual2)); - Assert.False(actual2 is object[]); - Assert.NotNull(actual2); - Assert.Equal("TestObject2", actual2); - - expression = PathExpression.Create("$[-3].TargetName"); - Assert.False(expression.IsArray); - Assert.False(expression.TryGet(testObject, false, out object actual3)); - Assert.Null(actual3); - - expression = PathExpression.Create("$[*].Spec.Properties.array[*].id"); - Assert.True(expression.IsArray); - Assert.True(expression.TryGet(testObject, false, out object[] actual4)); - Assert.NotNull(actual4); - Assert.Equal(4, actual4.Length); - Assert.Equal("1", actual4[0]); - Assert.Equal("2", actual4[1]); - Assert.Equal("1", actual4[2]); - Assert.Equal("2", actual4[3]); - } - - [Fact] - public void WithMemberCase() - { - var testObject = GetJsonContent(); + var testObject = GetJsonContent(); + + var expression = PathExpression.Create("$[*]"); + Assert.True(expression.IsArray); + Assert.True(expression.TryGet(testObject, false, out object actual1)); + var actualArray = actual1 as object[]; + Assert.NotNull(actualArray); + Assert.Equal(2, actualArray.Length); + + expression = PathExpression.Create("$[-1].TargetName"); + Assert.False(expression.IsArray); + Assert.True(expression.TryGet(testObject, false, out object actual2)); + Assert.False(actual2 is object[]); + Assert.NotNull(actual2); + Assert.Equal("TestObject2", actual2); + + expression = PathExpression.Create("$[-3].TargetName"); + Assert.False(expression.IsArray); + Assert.False(expression.TryGet(testObject, false, out object actual3)); + Assert.Null(actual3); + + expression = PathExpression.Create("$[*].Spec.Properties.array[*].id"); + Assert.True(expression.IsArray); + Assert.True(expression.TryGet(testObject, false, out object[] actual4)); + Assert.NotNull(actual4); + Assert.Equal(4, actual4.Length); + Assert.Equal("1", actual4[0]); + Assert.Equal("2", actual4[1]); + Assert.Equal("1", actual4[2]); + Assert.Equal("2", actual4[3]); + } - var expression = PathExpression.Create("$[*].spec.Properties.array[*].id"); - Assert.True(expression.TryGet(testObject, false, out object[] actual)); - Assert.NotNull(actual); - Assert.Equal(4, actual.Length); + [Fact] + public void WithMemberCase() + { + var testObject = GetJsonContent(); - expression = PathExpression.Create("$[*].spec.Properties.array[*].id"); - Assert.False(expression.TryGet(testObject, true, out actual)); - Assert.Null(actual); + var expression = PathExpression.Create("$[*].spec.Properties.array[*].id"); + Assert.True(expression.TryGet(testObject, false, out object[] actual)); + Assert.NotNull(actual); + Assert.Equal(4, actual.Length); - expression = PathExpression.Create("$[0].targetName"); - Assert.True(expression.TryGet(testObject, false, out actual)); - Assert.NotNull(actual); + expression = PathExpression.Create("$[*].spec.Properties.array[*].id"); + Assert.False(expression.TryGet(testObject, true, out actual)); + Assert.Null(actual); - expression = PathExpression.Create("$[0]+targetName"); - Assert.False(expression.TryGet(testObject, false, out actual)); - Assert.Null(actual); + expression = PathExpression.Create("$[0].targetName"); + Assert.True(expression.TryGet(testObject, false, out actual)); + Assert.NotNull(actual); - expression = PathExpression.Create("$[0].targetName"); - Assert.False(expression.TryGet(testObject, true, out actual)); - Assert.Null(actual); + expression = PathExpression.Create("$[0]+targetName"); + Assert.False(expression.TryGet(testObject, false, out actual)); + Assert.Null(actual); - expression = PathExpression.Create("$[0]+targetName"); - Assert.True(expression.TryGet(testObject, true, out actual)); - Assert.NotNull(actual); - } + expression = PathExpression.Create("$[0].targetName"); + Assert.False(expression.TryGet(testObject, true, out actual)); + Assert.Null(actual); - [Fact] - public void WithFilter() - { - var testObject = GetJsonContent(); - - var expression = PathExpression.Create("$[*].Spec.Properties.array[?(@.id=='1')].id"); - Assert.True(expression.IsArray); - Assert.True(expression.TryGet(testObject, false, out object[] actual)); - Assert.NotNull(actual); - Assert.Equal(2, actual.Length); - Assert.Equal("1", actual[0]); - Assert.Equal("1", actual[1]); - - expression = PathExpression.Create("$[*].Spec.Properties.array[?(@.id==1)].id"); - Assert.True(expression.IsArray); - Assert.False(expression.TryGet(testObject, false, out object[] _)); - - expression = PathExpression.Create("$[?@.TargetName == 'TestObject1'].TargetName"); - Assert.True(expression.IsArray); - Assert.True(expression.TryGet(testObject, false, out actual)); - Assert.NotNull(actual); - Assert.Single(actual); - Assert.Equal("TestObject1", actual[0]); - } - - [Fact] - public void WithOrFilter() - { - var testObject = GetJsonContent(); - - var expression = PathExpression.Create("$[*].Spec.Properties.array[?(@.id=='1' || @.id=='2')].id"); - Assert.True(expression.TryGet(testObject, false, out object[] actual)); - Assert.NotNull(actual); - Assert.Equal(4, actual.Length); - Assert.Equal("1", actual[0]); - Assert.Equal("2", actual[1]); - Assert.Equal("1", actual[2]); - Assert.Equal("2", actual[3]); - - expression = PathExpression.Create("$[*].Spec.Properties.array2[?(@ == '1' || @ == '2' || @ == '3')]"); - Assert.True(expression.IsArray); - Assert.True(expression.TryGet(testObject, false, out actual)); - Assert.NotNull(actual); - Assert.Equal(6, actual.Length); - Assert.Equal("1", actual[0]); - Assert.Equal("2", actual[1]); - Assert.Equal("3", actual[2]); - Assert.Equal("1", actual[3]); - Assert.Equal("2", actual[4]); - Assert.Equal("3", actual[5]); - } - - [Fact] - public void WithAndFilter() - { - var testObject = GetJsonContent(); - - var expression = PathExpression.Create("$[*].Spec.Properties.array[?(@.id=='1' && @.id=='2')].id"); - Assert.False(expression.TryGet(testObject, false, out object[] actual)); - Assert.Null(actual); - - expression = PathExpression.Create("$[*].Spec.Properties.array[?(@.id=='1' && @.id=='1')].id"); - Assert.True(expression.TryGet(testObject, false, out actual)); - Assert.NotNull(actual); - Assert.Equal(2, actual.Length); - Assert.Equal("1", actual[0]); - Assert.Equal("1", actual[1]); - - expression = PathExpression.Create("$[*].Spec.Properties.array2[?(@ == '1' && @ == '2')]"); - Assert.False(expression.TryGet(testObject, false, out actual)); - Assert.Null(actual); - - expression = PathExpression.Create("$[*].Spec.Properties.array2[?(@ == '1' && @ == '1')]"); - Assert.True(expression.IsArray); - Assert.True(expression.TryGet(testObject, false, out actual)); - Assert.NotNull(actual); - Assert.Equal(2, actual.Length); - Assert.Equal("1", actual[0]); - Assert.Equal("1", actual[1]); - } - - [Fact] - public void WithCombinedFilter() - { - var testObject = GetJsonContent(); - - var expression = PathExpression.Create("$[?(@Spec.Properties.Kind == 'Test' || @Spec.Properties.Kind == 'Test2') && @Spec.Properties.Value1].TargetName"); - Assert.True(expression.TryGet(testObject, false, out object[] actual)); - Assert.NotNull(actual); - Assert.Single(actual); - Assert.Equal("TestObject1", actual[0]); - - expression = PathExpression.Create("$[?((@Spec.Properties.Kind == 'Test' || @Spec.Properties.Kind == 'Test2') && @Spec.Properties.Value2)].TargetName"); - Assert.True(expression.TryGet(testObject, false, out actual)); - Assert.NotNull(actual); - Assert.Single(actual); - Assert.Equal("TestObject2", actual[0]); - - expression = PathExpression.Create("$[?(@Spec.Properties.Kind == 'Test' || @Spec.Properties.Kind == 'Test2') && (@Spec.Properties.Value1 || @Spec.Properties.Value2)].TargetName"); - Assert.True(expression.TryGet(testObject, false, out actual)); - Assert.NotNull(actual); - Assert.Equal(2, actual.Length); - Assert.Equal("TestObject1", actual[0]); - Assert.Equal("TestObject2", actual[1]); - } - - [Fact] - public void WithNullValue() - { - var testObject = GetJsonContent(); + expression = PathExpression.Create("$[0]+targetName"); + Assert.True(expression.TryGet(testObject, true, out actual)); + Assert.NotNull(actual); + } - var expression = PathExpression.Create("$[?@.spec.properties.from == 'abc'].TargetName"); - Assert.True(expression.TryGet(testObject, false, out object[] actual)); - Assert.NotNull(actual); - Assert.Single(actual); - Assert.Equal("TestObject2", actual[0]); - } + [Fact] + public void WithFilter() + { + var testObject = GetJsonContent(); + + var expression = PathExpression.Create("$[*].Spec.Properties.array[?(@.id=='1')].id"); + Assert.True(expression.IsArray); + Assert.True(expression.TryGet(testObject, false, out object[] actual)); + Assert.NotNull(actual); + Assert.Equal(2, actual.Length); + Assert.Equal("1", actual[0]); + Assert.Equal("1", actual[1]); + + expression = PathExpression.Create("$[*].Spec.Properties.array[?(@.id==1)].id"); + Assert.True(expression.IsArray); + Assert.False(expression.TryGet(testObject, false, out object[] _)); + + expression = PathExpression.Create("$[?@.TargetName == 'TestObject1'].TargetName"); + Assert.True(expression.IsArray); + Assert.True(expression.TryGet(testObject, false, out actual)); + Assert.NotNull(actual); + Assert.Single(actual); + Assert.Equal("TestObject1", actual[0]); + } - [Fact] - public void WithExistsFilter() - { - var testObject = GetJsonContent(); - - var expression = PathExpression.Create("$[?@.Spec.Properties.Kind].TargetName"); - Assert.True(expression.TryGet(testObject, false, out object[] actual)); - Assert.NotNull(actual); - Assert.Equal(2, actual.Length); - Assert.Equal("TestObject1", actual[0]); - Assert.Equal("TestObject2", actual[1]); - - expression = PathExpression.Create("$[?@.Spec.Properties.Value1].TargetName"); - Assert.True(expression.TryGet(testObject, false, out actual)); - Assert.NotNull(actual); - Assert.Single(actual); - Assert.Equal("TestObject1", actual[0]); - - expression = PathExpression.Create("$[?@.Spec.Properties.Value2].TargetName"); - Assert.True(expression.TryGet(testObject, false, out actual)); - Assert.NotNull(actual); - Assert.Single(actual); - Assert.Equal("TestObject2", actual[0]); - } - - [Fact] - public void WithSlice() - { - var testObject = GetJsonContent(); - - var expression = PathExpression.Create("$[0].Spec.Properties.array[:1].id"); - Assert.True(expression.IsArray); - Assert.True(expression.TryGet(testObject, true, out object[] actual)); - Assert.NotNull(actual); - Assert.Single(actual); - Assert.Equal("1", actual[0]); - - expression = PathExpression.Create("$[0].spec.properties.array[:1].id"); - Assert.True(expression.IsArray); - Assert.False(expression.TryGet(testObject, true, out actual)); - Assert.Null(actual); - - expression = PathExpression.Create("$[0].spec.properties.array2[::]"); - Assert.True(expression.IsArray); - Assert.True(expression.TryGet(testObject, false, out actual)); - Assert.NotNull(actual); - Assert.Equal("1", actual[0]); - Assert.Equal("2", actual[1]); - Assert.Equal("3", actual[2]); - - expression = PathExpression.Create("$[0].spec.properties.array2[::-1]"); - Assert.True(expression.IsArray); - Assert.True(expression.TryGet(testObject, false, out actual)); - Assert.NotNull(actual); - Assert.Equal("3", actual[0]); - Assert.Equal("2", actual[1]); - Assert.Equal("1", actual[2]); - - expression = PathExpression.Create("$[0].spec.properties.array2[:1:-1]"); - Assert.True(expression.IsArray); - Assert.True(expression.TryGet(testObject, false, out actual)); - Assert.NotNull(actual); - Assert.Empty(actual); - - expression = PathExpression.Create("$[0].spec.properties.array2[2:1:-1]"); - Assert.True(expression.IsArray); - Assert.True(expression.TryGet(testObject, false, out actual)); - Assert.NotNull(actual); - Assert.Single(actual); - Assert.Equal("3", actual[0]); - } - - [Fact] - public void WithDescendant() - { - var testObject = GetJsonContent(); - - var expression = PathExpression.Create("$[*]..Value2"); - Assert.True(expression.IsArray); - Assert.True(expression.TryGet(testObject, true, out object[] actual)); - Assert.NotNull(actual); - Assert.Single(actual); - Assert.Equal(2L, actual[0]); - - expression = PathExpression.Create("$[*]..id"); - Assert.True(expression.IsArray); - Assert.True(expression.TryGet(testObject, true, out actual)); - Assert.NotNull(actual); - Assert.Equal(4, actual.Length); - Assert.Equal("1", actual[0]); - Assert.Equal("2", actual[1]); - Assert.Equal("1", actual[2]); - Assert.Equal("2", actual[3]); - - expression = PathExpression.Create("$[?@..Value2].TargetName"); - Assert.True(expression.IsArray); - Assert.True(expression.TryGet(testObject, true, out actual)); - Assert.NotNull(actual); - Assert.Single(actual); - Assert.Equal("TestObject2", actual[0]); - - // Handle exception cases - var pso = GetPSObjectContent(); - expression = PathExpression.Create("$..value"); - Assert.False(expression.TryGet(pso, true, out object[] _)); - } - - [Fact] - public void WithArrayExpand() - { - var testObject = GetJsonContent() as JArray; - var expression = PathExpression.Create("Spec.config.[*][*].prop[*].public"); - Assert.True(expression.IsArray); - Assert.True(expression.TryGet(testObject[0], true, out object[] actual)); - Assert.Equal(true, actual[0]); - Assert.True(expression.TryGet(testObject[1], true, out actual)); - Assert.Equal(false, actual[0]); - - expression = PathExpression.Create("Spec.config[*][*].prop[*].public"); - Assert.True(expression.IsArray); - Assert.True(expression.TryGet(testObject[0], true, out actual)); - Assert.Equal(true, actual[0]); - Assert.True(expression.TryGet(testObject[1], true, out actual)); - Assert.Equal(false, actual[0]); - - expression = PathExpression.Create("Spec.config.[*].[*].prop[*].public"); - Assert.True(expression.IsArray); - Assert.True(expression.TryGet(testObject[0], true, out actual)); - Assert.Equal(true, actual[0]); - Assert.True(expression.TryGet(testObject[1], true, out actual)); - Assert.Equal(false, actual[0]); - } - - #region Helper methods - - private static object GetJsonContent() - { - var settings = new JsonLoadSettings - { - CommentHandling = CommentHandling.Ignore - }; - var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "ObjectFromFile.json"); - using var stream = new StreamReader(path); - using var reader = new JsonTextReader(stream); - return JToken.Load(reader, settings); - } - - private static object GetPSObjectContent() + [Fact] + public void WithOrFilter() + { + var testObject = GetJsonContent(); + + var expression = PathExpression.Create("$[*].Spec.Properties.array[?(@.id=='1' || @.id=='2')].id"); + Assert.True(expression.TryGet(testObject, false, out object[] actual)); + Assert.NotNull(actual); + Assert.Equal(4, actual.Length); + Assert.Equal("1", actual[0]); + Assert.Equal("2", actual[1]); + Assert.Equal("1", actual[2]); + Assert.Equal("2", actual[3]); + + expression = PathExpression.Create("$[*].Spec.Properties.array2[?(@ == '1' || @ == '2' || @ == '3')]"); + Assert.True(expression.IsArray); + Assert.True(expression.TryGet(testObject, false, out actual)); + Assert.NotNull(actual); + Assert.Equal(6, actual.Length); + Assert.Equal("1", actual[0]); + Assert.Equal("2", actual[1]); + Assert.Equal("3", actual[2]); + Assert.Equal("1", actual[3]); + Assert.Equal("2", actual[4]); + Assert.Equal("3", actual[5]); + } + + [Fact] + public void WithAndFilter() + { + var testObject = GetJsonContent(); + + var expression = PathExpression.Create("$[*].Spec.Properties.array[?(@.id=='1' && @.id=='2')].id"); + Assert.False(expression.TryGet(testObject, false, out object[] actual)); + Assert.Null(actual); + + expression = PathExpression.Create("$[*].Spec.Properties.array[?(@.id=='1' && @.id=='1')].id"); + Assert.True(expression.TryGet(testObject, false, out actual)); + Assert.NotNull(actual); + Assert.Equal(2, actual.Length); + Assert.Equal("1", actual[0]); + Assert.Equal("1", actual[1]); + + expression = PathExpression.Create("$[*].Spec.Properties.array2[?(@ == '1' && @ == '2')]"); + Assert.False(expression.TryGet(testObject, false, out actual)); + Assert.Null(actual); + + expression = PathExpression.Create("$[*].Spec.Properties.array2[?(@ == '1' && @ == '1')]"); + Assert.True(expression.IsArray); + Assert.True(expression.TryGet(testObject, false, out actual)); + Assert.NotNull(actual); + Assert.Equal(2, actual.Length); + Assert.Equal("1", actual[0]); + Assert.Equal("1", actual[1]); + } + + [Fact] + public void WithCombinedFilter() + { + var testObject = GetJsonContent(); + + var expression = PathExpression.Create("$[?(@Spec.Properties.Kind == 'Test' || @Spec.Properties.Kind == 'Test2') && @Spec.Properties.Value1].TargetName"); + Assert.True(expression.TryGet(testObject, false, out object[] actual)); + Assert.NotNull(actual); + Assert.Single(actual); + Assert.Equal("TestObject1", actual[0]); + + expression = PathExpression.Create("$[?((@Spec.Properties.Kind == 'Test' || @Spec.Properties.Kind == 'Test2') && @Spec.Properties.Value2)].TargetName"); + Assert.True(expression.TryGet(testObject, false, out actual)); + Assert.NotNull(actual); + Assert.Single(actual); + Assert.Equal("TestObject2", actual[0]); + + expression = PathExpression.Create("$[?(@Spec.Properties.Kind == 'Test' || @Spec.Properties.Kind == 'Test2') && (@Spec.Properties.Value1 || @Spec.Properties.Value2)].TargetName"); + Assert.True(expression.TryGet(testObject, false, out actual)); + Assert.NotNull(actual); + Assert.Equal(2, actual.Length); + Assert.Equal("TestObject1", actual[0]); + Assert.Equal("TestObject2", actual[1]); + } + + [Fact] + public void WithNullValue() + { + var testObject = GetJsonContent(); + + var expression = PathExpression.Create("$[?@.spec.properties.from == 'abc'].TargetName"); + Assert.True(expression.TryGet(testObject, false, out object[] actual)); + Assert.NotNull(actual); + Assert.Single(actual); + Assert.Equal("TestObject2", actual[0]); + } + + [Fact] + public void WithExistsFilter() + { + var testObject = GetJsonContent(); + + var expression = PathExpression.Create("$[?@.Spec.Properties.Kind].TargetName"); + Assert.True(expression.TryGet(testObject, false, out object[] actual)); + Assert.NotNull(actual); + Assert.Equal(2, actual.Length); + Assert.Equal("TestObject1", actual[0]); + Assert.Equal("TestObject2", actual[1]); + + expression = PathExpression.Create("$[?@.Spec.Properties.Value1].TargetName"); + Assert.True(expression.TryGet(testObject, false, out actual)); + Assert.NotNull(actual); + Assert.Single(actual); + Assert.Equal("TestObject1", actual[0]); + + expression = PathExpression.Create("$[?@.Spec.Properties.Value2].TargetName"); + Assert.True(expression.TryGet(testObject, false, out actual)); + Assert.NotNull(actual); + Assert.Single(actual); + Assert.Equal("TestObject2", actual[0]); + } + + [Fact] + public void WithSlice() + { + var testObject = GetJsonContent(); + + var expression = PathExpression.Create("$[0].Spec.Properties.array[:1].id"); + Assert.True(expression.IsArray); + Assert.True(expression.TryGet(testObject, true, out object[] actual)); + Assert.NotNull(actual); + Assert.Single(actual); + Assert.Equal("1", actual[0]); + + expression = PathExpression.Create("$[0].spec.properties.array[:1].id"); + Assert.True(expression.IsArray); + Assert.False(expression.TryGet(testObject, true, out actual)); + Assert.Null(actual); + + expression = PathExpression.Create("$[0].spec.properties.array2[::]"); + Assert.True(expression.IsArray); + Assert.True(expression.TryGet(testObject, false, out actual)); + Assert.NotNull(actual); + Assert.Equal("1", actual[0]); + Assert.Equal("2", actual[1]); + Assert.Equal("3", actual[2]); + + expression = PathExpression.Create("$[0].spec.properties.array2[::-1]"); + Assert.True(expression.IsArray); + Assert.True(expression.TryGet(testObject, false, out actual)); + Assert.NotNull(actual); + Assert.Equal("3", actual[0]); + Assert.Equal("2", actual[1]); + Assert.Equal("1", actual[2]); + + expression = PathExpression.Create("$[0].spec.properties.array2[:1:-1]"); + Assert.True(expression.IsArray); + Assert.True(expression.TryGet(testObject, false, out actual)); + Assert.NotNull(actual); + Assert.Empty(actual); + + expression = PathExpression.Create("$[0].spec.properties.array2[2:1:-1]"); + Assert.True(expression.IsArray); + Assert.True(expression.TryGet(testObject, false, out actual)); + Assert.NotNull(actual); + Assert.Single(actual); + Assert.Equal("3", actual[0]); + } + + [Fact] + public void WithDescendant() + { + var testObject = GetJsonContent(); + + var expression = PathExpression.Create("$[*]..Value2"); + Assert.True(expression.IsArray); + Assert.True(expression.TryGet(testObject, true, out object[] actual)); + Assert.NotNull(actual); + Assert.Single(actual); + Assert.Equal(2L, actual[0]); + + expression = PathExpression.Create("$[*]..id"); + Assert.True(expression.IsArray); + Assert.True(expression.TryGet(testObject, true, out actual)); + Assert.NotNull(actual); + Assert.Equal(4, actual.Length); + Assert.Equal("1", actual[0]); + Assert.Equal("2", actual[1]); + Assert.Equal("1", actual[2]); + Assert.Equal("2", actual[3]); + + expression = PathExpression.Create("$[?@..Value2].TargetName"); + Assert.True(expression.IsArray); + Assert.True(expression.TryGet(testObject, true, out actual)); + Assert.NotNull(actual); + Assert.Single(actual); + Assert.Equal("TestObject2", actual[0]); + + // Handle exception cases + var pso = GetPSObjectContent(); + expression = PathExpression.Create("$..value"); + Assert.False(expression.TryGet(pso, true, out object[] _)); + } + + [Fact] + public void WithArrayExpand() + { + var testObject = GetJsonContent() as JArray; + var expression = PathExpression.Create("Spec.config.[*][*].prop[*].public"); + Assert.True(expression.IsArray); + Assert.True(expression.TryGet(testObject[0], true, out object[] actual)); + Assert.Equal(true, actual[0]); + Assert.True(expression.TryGet(testObject[1], true, out actual)); + Assert.Equal(false, actual[0]); + + expression = PathExpression.Create("Spec.config[*][*].prop[*].public"); + Assert.True(expression.IsArray); + Assert.True(expression.TryGet(testObject[0], true, out actual)); + Assert.Equal(true, actual[0]); + Assert.True(expression.TryGet(testObject[1], true, out actual)); + Assert.Equal(false, actual[0]); + + expression = PathExpression.Create("Spec.config.[*].[*].prop[*].public"); + Assert.True(expression.IsArray); + Assert.True(expression.TryGet(testObject[0], true, out actual)); + Assert.Equal(true, actual[0]); + Assert.True(expression.TryGet(testObject[1], true, out actual)); + Assert.Equal(false, actual[0]); + } + + #region Helper methods + + private static object GetJsonContent() + { + var settings = new JsonLoadSettings { - var result = new PSObject(); - result.Properties.Add(new PSNoteProperty("string", "")); - result.Properties.Add(new PSNoteProperty("date", DateTime.Now)); - result.Properties.Add(new PSNoteProperty("int", 0)); - return result; - } - - #endregion Helper methods + CommentHandling = CommentHandling.Ignore + }; + var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "ObjectFromFile.json"); + using var stream = new StreamReader(path); + using var reader = new JsonTextReader(stream); + return JToken.Load(reader, settings); + } + + private static object GetPSObjectContent() + { + var result = new PSObject(); + result.Properties.Add(new PSNoteProperty("string", "")); + result.Properties.Add(new PSNoteProperty("date", DateTime.Now)); + result.Properties.Add(new PSNoteProperty("int", 0)); + return result; } + + #endregion Helper methods } diff --git a/tests/PSRule.Tests/PathFilterTests.cs b/tests/PSRule.Tests/PathFilterTests.cs index 3d22a10f8b..713e5c8fdb 100644 --- a/tests/PSRule.Tests/PathFilterTests.cs +++ b/tests/PSRule.Tests/PathFilterTests.cs @@ -4,99 +4,98 @@ using System; using PSRule.Pipeline; -namespace PSRule +namespace PSRule; + +public sealed class PathFilterTests { - public sealed class PathFilterTests + [Fact] + public void Match() { - [Fact] - public void Match() - { - // Single expression match - var filter = PathFilter.Create(GetWorkingPath(), "**/Resources.Parameters*.json"); - Assert.True(filter.Match("tests/example/Resources.Parameters.json")); - Assert.True(filter.Match("tests/example2/Resources.Parameters2.json")); - Assert.False(filter.Match("tests/example2/Resources.Parameters2.txt")); - filter = PathFilter.Create(GetWorkingPath(), "**/example2/Resources.Parameters*.json"); - Assert.False(filter.Match("tests/example/Resources.Parameters.json")); - Assert.True(filter.Match("tests/example2/Resources.Parameters2.json")); - filter = PathFilter.Create(GetWorkingPath(), "**/example?/Resources.Parameters*.json"); - Assert.True(filter.Match("tests/example/Resources.Parameters.json")); - Assert.True(filter.Match("tests/example2/Resources.Parameters2.json")); - Assert.False(filter.Match("tests/example2/Resources.Parameters2.txt")); - filter = PathFilter.Create(GetWorkingPath(), "tests/example?/Resources.Parameters.json"); - Assert.True(filter.Match("tests/example/Resources.Parameters.json")); - Assert.True(filter.Match("tests/example2/Resources.Parameters.json")); - filter = PathFilter.Create(GetWorkingPath(), "tests/example/Resources.Parameters*.json"); - Assert.True(filter.Match("tests/example/Resources.Parameters.json")); - Assert.True(filter.Match("tests/example/Resources.Parameters2.json")); - - // Multi-expression match - var expressions = new string[] - { - "out/", - "**/bin/", - "**/obj/", - "", - "# Add reports/bin", - "!reports/bin/", - "**/ObjectFromFile*.json", - "!**/ObjectFromFile.json" - }; - filter = PathFilter.Create(GetWorkingPath(), expressions, true); - Assert.True(filter.Match("out/example.parameters.json")); - Assert.False(filter.Match("outer/example.parameters.json")); - Assert.False(filter.Match("reports/out/example.parameters.json")); - Assert.True(filter.Match("src/bin/pwsh.exe")); - Assert.True(filter.Match("src/obj/example.parameters.json")); - Assert.False(filter.Match("reports/bin/other.json")); - Assert.False(filter.Match("ObjectFromFile.json")); - Assert.True(filter.Match("ObjectFromFileSingle.json")); + // Single expression match + var filter = PathFilter.Create(GetWorkingPath(), "**/Resources.Parameters*.json"); + Assert.True(filter.Match("tests/example/Resources.Parameters.json")); + Assert.True(filter.Match("tests/example2/Resources.Parameters2.json")); + Assert.False(filter.Match("tests/example2/Resources.Parameters2.txt")); + filter = PathFilter.Create(GetWorkingPath(), "**/example2/Resources.Parameters*.json"); + Assert.False(filter.Match("tests/example/Resources.Parameters.json")); + Assert.True(filter.Match("tests/example2/Resources.Parameters2.json")); + filter = PathFilter.Create(GetWorkingPath(), "**/example?/Resources.Parameters*.json"); + Assert.True(filter.Match("tests/example/Resources.Parameters.json")); + Assert.True(filter.Match("tests/example2/Resources.Parameters2.json")); + Assert.False(filter.Match("tests/example2/Resources.Parameters2.txt")); + filter = PathFilter.Create(GetWorkingPath(), "tests/example?/Resources.Parameters.json"); + Assert.True(filter.Match("tests/example/Resources.Parameters.json")); + Assert.True(filter.Match("tests/example2/Resources.Parameters.json")); + filter = PathFilter.Create(GetWorkingPath(), "tests/example/Resources.Parameters*.json"); + Assert.True(filter.Match("tests/example/Resources.Parameters.json")); + Assert.True(filter.Match("tests/example/Resources.Parameters2.json")); - // Exclude - filter = PathFilter.Create(GetWorkingPath(), expressions, false); - Assert.False(filter.Match("out/example.parameters.json")); - Assert.True(filter.Match("outer/example.parameters.json")); - Assert.True(filter.Match("reports/out/example.parameters.json")); - Assert.False(filter.Match("src/bin/pwsh.exe")); - Assert.False(filter.Match("src/obj/example.parameters.json")); - Assert.True(filter.Match("reports/bin/other.json")); - } - - [Fact] - public void Builder() + // Multi-expression match + var expressions = new string[] { - var builder = PathFilterBuilder.Create(GetWorkingPath(), new string[] { "out/" }, false, false); - var actual1 = builder.Build(); - Assert.False(actual1.Match("out/not.file")); - Assert.True(actual1.Match(".git/HEAD")); - Assert.True(actual1.Match(".gitignore")); - Assert.True(actual1.Match(".github/CODEOWNERS")); - Assert.True(actual1.Match(".github/dependabot.yml")); + "out/", + "**/bin/", + "**/obj/", + "", + "# Add reports/bin", + "!reports/bin/", + "**/ObjectFromFile*.json", + "!**/ObjectFromFile.json" + }; + filter = PathFilter.Create(GetWorkingPath(), expressions, true); + Assert.True(filter.Match("out/example.parameters.json")); + Assert.False(filter.Match("outer/example.parameters.json")); + Assert.False(filter.Match("reports/out/example.parameters.json")); + Assert.True(filter.Match("src/bin/pwsh.exe")); + Assert.True(filter.Match("src/obj/example.parameters.json")); + Assert.False(filter.Match("reports/bin/other.json")); + Assert.False(filter.Match("ObjectFromFile.json")); + Assert.True(filter.Match("ObjectFromFileSingle.json")); - builder = PathFilterBuilder.Create(GetWorkingPath(), new string[] { "out/" }, true, false); - var actual2 = builder.Build(); - Assert.False(actual2.Match("out/not.file")); - Assert.False(actual2.Match(".git/HEAD")); - Assert.True(actual2.Match(".gitignore")); - Assert.True(actual2.Match(".github/CODEOWNERS")); - Assert.True(actual2.Match(".github/dependabot.yml")); + // Exclude + filter = PathFilter.Create(GetWorkingPath(), expressions, false); + Assert.False(filter.Match("out/example.parameters.json")); + Assert.True(filter.Match("outer/example.parameters.json")); + Assert.True(filter.Match("reports/out/example.parameters.json")); + Assert.False(filter.Match("src/bin/pwsh.exe")); + Assert.False(filter.Match("src/obj/example.parameters.json")); + Assert.True(filter.Match("reports/bin/other.json")); + } - builder = PathFilterBuilder.Create(GetWorkingPath(), new string[] { "out/" }, true, true); - var actual3 = builder.Build(); - Assert.False(actual3.Match("out/not.file")); - Assert.False(actual3.Match(".git/HEAD")); - Assert.False(actual3.Match(".gitignore")); - Assert.False(actual3.Match(".github/CODEOWNERS")); - Assert.True(actual3.Match(".github/dependabot.yml")); - } + [Fact] + public void Builder() + { + var builder = PathFilterBuilder.Create(GetWorkingPath(), new string[] { "out/" }, false, false); + var actual1 = builder.Build(); + Assert.False(actual1.Match("out/not.file")); + Assert.True(actual1.Match(".git/HEAD")); + Assert.True(actual1.Match(".gitignore")); + Assert.True(actual1.Match(".github/CODEOWNERS")); + Assert.True(actual1.Match(".github/dependabot.yml")); - #region Helper methods + builder = PathFilterBuilder.Create(GetWorkingPath(), new string[] { "out/" }, true, false); + var actual2 = builder.Build(); + Assert.False(actual2.Match("out/not.file")); + Assert.False(actual2.Match(".git/HEAD")); + Assert.True(actual2.Match(".gitignore")); + Assert.True(actual2.Match(".github/CODEOWNERS")); + Assert.True(actual2.Match(".github/dependabot.yml")); - private static string GetWorkingPath() - { - return AppDomain.CurrentDomain.BaseDirectory; - } + builder = PathFilterBuilder.Create(GetWorkingPath(), new string[] { "out/" }, true, true); + var actual3 = builder.Build(); + Assert.False(actual3.Match("out/not.file")); + Assert.False(actual3.Match(".git/HEAD")); + Assert.False(actual3.Match(".gitignore")); + Assert.False(actual3.Match(".github/CODEOWNERS")); + Assert.True(actual3.Match(".github/dependabot.yml")); + } + + #region Helper methods - #endregion Helper methods + private static string GetWorkingPath() + { + return AppDomain.CurrentDomain.BaseDirectory; } + + #endregion Helper methods } diff --git a/tests/PSRule.Tests/PathTokenizerTests.cs b/tests/PSRule.Tests/PathTokenizerTests.cs index 33366ebf75..a11dc0a628 100644 --- a/tests/PSRule.Tests/PathTokenizerTests.cs +++ b/tests/PSRule.Tests/PathTokenizerTests.cs @@ -3,802 +3,801 @@ using PSRule.Runtime.ObjectPath; -namespace PSRule +namespace PSRule; + +/// +/// Tests for JSONPath tokenenizer. +/// +public sealed class PathTokenizerTests { + [Fact] + public void Get() + { + var token = PathTokenizer.Get("$[*].Properties.logs[?@.enabled==true].category"); + + Assert.Equal(11, token.Length); + Assert.Equal(PathTokenType.RootRef, token[0].Type); + Assert.Equal(PathTokenType.IndexWildSelector, token[1].Type); + Assert.Equal(PathTokenType.DotSelector, token[2].Type); + Assert.Equal("Properties", token[2].As()); + Assert.Equal(PathTokenType.DotSelector, token[3].Type); + Assert.Equal("logs", token[3].As()); + Assert.Equal(PathTokenType.StartFilter, token[4].Type); + Assert.Equal(PathTokenType.CurrentRef, token[5].Type); + Assert.Equal(PathTokenType.DotSelector, token[6].Type); + Assert.Equal("enabled", token[6].As()); + Assert.Equal(PathTokenType.ComparisonOperator, token[7].Type); + Assert.Equal(FilterOperator.Equal, token[7].As()); + Assert.Equal(PathTokenType.Boolean, token[8].Type); + Assert.True(token[8].As()); + Assert.Equal(PathTokenType.EndFilter, token[9].Type); + Assert.Equal(PathTokenType.DotSelector, token[10].Type); + Assert.Equal("category", token[10].As()); + } + /// - /// Tests for JSONPath tokenenizer. + /// Check tokenizer against simple test cases. /// - public sealed class PathTokenizerTests + [Fact] + public void SimpleTestCases() { - [Fact] - public void Get() + var path = new string[] { - var token = PathTokenizer.Get("$[*].Properties.logs[?@.enabled==true].category"); - - Assert.Equal(11, token.Length); - Assert.Equal(PathTokenType.RootRef, token[0].Type); - Assert.Equal(PathTokenType.IndexWildSelector, token[1].Type); - Assert.Equal(PathTokenType.DotSelector, token[2].Type); - Assert.Equal("Properties", token[2].As()); - Assert.Equal(PathTokenType.DotSelector, token[3].Type); - Assert.Equal("logs", token[3].As()); - Assert.Equal(PathTokenType.StartFilter, token[4].Type); - Assert.Equal(PathTokenType.CurrentRef, token[5].Type); - Assert.Equal(PathTokenType.DotSelector, token[6].Type); - Assert.Equal("enabled", token[6].As()); - Assert.Equal(PathTokenType.ComparisonOperator, token[7].Type); - Assert.Equal(FilterOperator.Equal, token[7].As()); - Assert.Equal(PathTokenType.Boolean, token[8].Type); - Assert.True(token[8].As()); - Assert.Equal(PathTokenType.EndFilter, token[9].Type); - Assert.Equal(PathTokenType.DotSelector, token[10].Type); - Assert.Equal("category", token[10].As()); - } - - /// - /// Check tokenizer against simple test cases. - /// - [Fact] - public void SimpleTestCases() - { - var path = new string[] - { - "store", - ".", - "@", - "$.", - "'store.property'", - "$[10]", - "$[*]", - "$['store.property']", - "$[\"store.property\"]", - "\"store.property\"", - }; - - // store - var token = PathTokenizer.Get(path[0]); - Assert.Single(token); - Assert.Equal(PathTokenType.DotSelector, token[0].Type); - Assert.Equal("store", token[0].As()); - - // . - token = PathTokenizer.Get(path[1]); - Assert.Single(token); - Assert.Equal(PathTokenType.RootRef, token[0].Type); - - // @ - token = PathTokenizer.Get(path[2]); - Assert.Single(token); - Assert.Equal(PathTokenType.RootRef, token[0].Type); - - // $. - token = PathTokenizer.Get(path[3]); - Assert.Single(token); - Assert.Equal(PathTokenType.RootRef, token[0].Type); - - // 'store.property' - token = PathTokenizer.Get(path[4]); - Assert.Single(token); - Assert.Equal(PathTokenType.DotSelector, token[0].Type); - Assert.Equal("store.property", token[0].As()); - - // $[10] - token = PathTokenizer.Get(path[5]); - Assert.Equal(2, token.Length); - Assert.Equal(PathTokenType.RootRef, token[0].Type); - Assert.Equal(PathTokenType.IndexSelector, token[1].Type); - Assert.Equal(10, token[1].As()); - - // $[*] - token = PathTokenizer.Get(path[6]); - Assert.Equal(2, token.Length); - Assert.Equal(PathTokenType.RootRef, token[0].Type); - Assert.Equal(PathTokenType.IndexWildSelector, token[1].Type); - - // $['store.property'] - token = PathTokenizer.Get(path[7]); - Assert.Equal(2, token.Length); - Assert.Equal(PathTokenType.RootRef, token[0].Type); - Assert.Equal(PathTokenType.DotSelector, token[1].Type); - Assert.Equal("store.property", token[1].As()); - - // $["store.property"] - token = PathTokenizer.Get(path[8]); - Assert.Equal(2, token.Length); - Assert.Equal(PathTokenType.RootRef, token[0].Type); - Assert.Equal(PathTokenType.DotSelector, token[1].Type); - Assert.Equal("store.property", token[1].As()); - - // "store.property" - token = PathTokenizer.Get(path[9]); - Assert.Single(token); - Assert.Equal(PathTokenType.DotSelector, token[0].Type); - Assert.Equal("store.property", token[0].As()); - } - - [Fact] - public void PathTests() + "store", + ".", + "@", + "$.", + "'store.property'", + "$[10]", + "$[*]", + "$['store.property']", + "$[\"store.property\"]", + "\"store.property\"", + }; + + // store + var token = PathTokenizer.Get(path[0]); + Assert.Single(token); + Assert.Equal(PathTokenType.DotSelector, token[0].Type); + Assert.Equal("store", token[0].As()); + + // . + token = PathTokenizer.Get(path[1]); + Assert.Single(token); + Assert.Equal(PathTokenType.RootRef, token[0].Type); + + // @ + token = PathTokenizer.Get(path[2]); + Assert.Single(token); + Assert.Equal(PathTokenType.RootRef, token[0].Type); + + // $. + token = PathTokenizer.Get(path[3]); + Assert.Single(token); + Assert.Equal(PathTokenType.RootRef, token[0].Type); + + // 'store.property' + token = PathTokenizer.Get(path[4]); + Assert.Single(token); + Assert.Equal(PathTokenType.DotSelector, token[0].Type); + Assert.Equal("store.property", token[0].As()); + + // $[10] + token = PathTokenizer.Get(path[5]); + Assert.Equal(2, token.Length); + Assert.Equal(PathTokenType.RootRef, token[0].Type); + Assert.Equal(PathTokenType.IndexSelector, token[1].Type); + Assert.Equal(10, token[1].As()); + + // $[*] + token = PathTokenizer.Get(path[6]); + Assert.Equal(2, token.Length); + Assert.Equal(PathTokenType.RootRef, token[0].Type); + Assert.Equal(PathTokenType.IndexWildSelector, token[1].Type); + + // $['store.property'] + token = PathTokenizer.Get(path[7]); + Assert.Equal(2, token.Length); + Assert.Equal(PathTokenType.RootRef, token[0].Type); + Assert.Equal(PathTokenType.DotSelector, token[1].Type); + Assert.Equal("store.property", token[1].As()); + + // $["store.property"] + token = PathTokenizer.Get(path[8]); + Assert.Equal(2, token.Length); + Assert.Equal(PathTokenType.RootRef, token[0].Type); + Assert.Equal(PathTokenType.DotSelector, token[1].Type); + Assert.Equal("store.property", token[1].As()); + + // "store.property" + token = PathTokenizer.Get(path[9]); + Assert.Single(token); + Assert.Equal(PathTokenType.DotSelector, token[0].Type); + Assert.Equal("store.property", token[0].As()); + } + + [Fact] + public void PathTests() + { + var path = new string[] { - var path = new string[] - { - "$['store'].book[0].author", - }; - - // $['store'].book[0].author - var token = PathTokenizer.Get(path[0]); - Assert.Equal(5, token.Length); - Assert.Equal(PathTokenType.RootRef, token[0].Type); - Assert.Equal(PathTokenType.DotSelector, token[1].Type); - Assert.Equal("store", token[1].As()); - Assert.Equal(PathTokenType.DotSelector, token[2].Type); - Assert.Equal("book", token[2].As()); - Assert.Equal(PathTokenType.IndexSelector, token[3].Type); - Assert.Equal(0, token[3].As()); - Assert.Equal(PathTokenType.DotSelector, token[4].Type); - Assert.Equal("author", token[4].As()); - } - - [Fact] - public void MemberNameSchema() + "$['store'].book[0].author", + }; + + // $['store'].book[0].author + var token = PathTokenizer.Get(path[0]); + Assert.Equal(5, token.Length); + Assert.Equal(PathTokenType.RootRef, token[0].Type); + Assert.Equal(PathTokenType.DotSelector, token[1].Type); + Assert.Equal("store", token[1].As()); + Assert.Equal(PathTokenType.DotSelector, token[2].Type); + Assert.Equal("book", token[2].As()); + Assert.Equal(PathTokenType.IndexSelector, token[3].Type); + Assert.Equal(0, token[3].As()); + Assert.Equal(PathTokenType.DotSelector, token[4].Type); + Assert.Equal("author", token[4].As()); + } + + [Fact] + public void MemberNameSchema() + { + var path = new string[] { - var path = new string[] - { - "$schema" - }; - - // $schema - var token = PathTokenizer.Get(path[0]); - Assert.Single(token); - Assert.Equal(PathTokenType.DotSelector, token[0].Type); - Assert.Equal("$schema", token[0].As()); - } - - [Fact] - public void MemberNameWithUnderscore() + "$schema" + }; + + // $schema + var token = PathTokenizer.Get(path[0]); + Assert.Single(token); + Assert.Equal(PathTokenType.DotSelector, token[0].Type); + Assert.Equal("$schema", token[0].As()); + } + + [Fact] + public void MemberNameWithUnderscore() + { + var path = new string[] { - var path = new string[] - { - "member_name", - }; - - // member_name - var token = PathTokenizer.Get(path[0]); - Assert.Single(token); - Assert.Equal(PathTokenType.DotSelector, token[0].Type); - Assert.Equal("member_name", token[0].As()); - } - - [Fact] - public void MemberNameWithDash() + "member_name", + }; + + // member_name + var token = PathTokenizer.Get(path[0]); + Assert.Single(token); + Assert.Equal(PathTokenType.DotSelector, token[0].Type); + Assert.Equal("member_name", token[0].As()); + } + + [Fact] + public void MemberNameWithDash() + { + var path = new string[] { - var path = new string[] - { - "member-name", - "-member-name-", - }; - - // member-name - var token = PathTokenizer.Get(path[0]); - Assert.Single(token); - Assert.Equal(PathTokenType.DotSelector, token[0].Type); - Assert.Equal("member-name", token[0].As()); - - // -member-name- - token = PathTokenizer.Get(path[1]); - Assert.Single(token); - Assert.Equal(PathTokenType.DotSelector, token[0].Type); - Assert.Equal("member-name", token[0].As()); - } - - [Fact] - public void MemberNameWithOption() + "member-name", + "-member-name-", + }; + + // member-name + var token = PathTokenizer.Get(path[0]); + Assert.Single(token); + Assert.Equal(PathTokenType.DotSelector, token[0].Type); + Assert.Equal("member-name", token[0].As()); + + // -member-name- + token = PathTokenizer.Get(path[1]); + Assert.Single(token); + Assert.Equal(PathTokenType.DotSelector, token[0].Type); + Assert.Equal("member-name", token[0].As()); + } + + [Fact] + public void MemberNameWithOption() + { + var path = new string[] { - var path = new string[] - { - "$.name", - "$+name" - }; - - // $.name - var token = PathTokenizer.Get(path[0]); - Assert.Equal(2, token.Length); - Assert.Equal(PathTokenType.RootRef, token[0].Type); - Assert.Equal(PathTokenType.DotSelector, token[1].Type); - Assert.Equal(PathTokenOption.None, token[1].Option); - Assert.Equal("name", token[1].As()); - - // $+name - token = PathTokenizer.Get(path[1]); - Assert.Equal(2, token.Length); - Assert.Equal(PathTokenType.RootRef, token[0].Type); - Assert.Equal(PathTokenType.DotSelector, token[1].Type); - Assert.Equal(PathTokenOption.CaseSensitive, token[1].Option); - Assert.Equal("name", token[1].As()); - } - - [Fact] - public void MemberNameQuoted() + "$.name", + "$+name" + }; + + // $.name + var token = PathTokenizer.Get(path[0]); + Assert.Equal(2, token.Length); + Assert.Equal(PathTokenType.RootRef, token[0].Type); + Assert.Equal(PathTokenType.DotSelector, token[1].Type); + Assert.Equal(PathTokenOption.None, token[1].Option); + Assert.Equal("name", token[1].As()); + + // $+name + token = PathTokenizer.Get(path[1]); + Assert.Equal(2, token.Length); + Assert.Equal(PathTokenType.RootRef, token[0].Type); + Assert.Equal(PathTokenType.DotSelector, token[1].Type); + Assert.Equal(PathTokenOption.CaseSensitive, token[1].Option); + Assert.Equal("name", token[1].As()); + } + + [Fact] + public void MemberNameQuoted() + { + var path = new string[] { - var path = new string[] - { - "'store.property'", - "\"store.property\"", - "['store.property']", - "[\"store.property\"]", - }; - - // 'store.property' - var token = PathTokenizer.Get(path[0]); - Assert.Single(token); - Assert.Equal(PathTokenType.DotSelector, token[0].Type); - Assert.Equal("store.property", token[0].As()); - - // "store.property" - token = PathTokenizer.Get(path[1]); - Assert.Single(token); - Assert.Equal(PathTokenType.DotSelector, token[0].Type); - Assert.Equal("store.property", token[0].As()); - - // ['store.property'] - token = PathTokenizer.Get(path[2]); - Assert.Single(token); - Assert.Equal(PathTokenType.DotSelector, token[0].Type); - Assert.Equal("store.property", token[0].As()); - - // ["store.property"] - token = PathTokenizer.Get(path[3]); - Assert.Single(token); - Assert.Equal(PathTokenType.DotSelector, token[0].Type); - Assert.Equal("store.property", token[0].As()); - } - - [Fact] - public void FilterBoolean() + "'store.property'", + "\"store.property\"", + "['store.property']", + "[\"store.property\"]", + }; + + // 'store.property' + var token = PathTokenizer.Get(path[0]); + Assert.Single(token); + Assert.Equal(PathTokenType.DotSelector, token[0].Type); + Assert.Equal("store.property", token[0].As()); + + // "store.property" + token = PathTokenizer.Get(path[1]); + Assert.Single(token); + Assert.Equal(PathTokenType.DotSelector, token[0].Type); + Assert.Equal("store.property", token[0].As()); + + // ['store.property'] + token = PathTokenizer.Get(path[2]); + Assert.Single(token); + Assert.Equal(PathTokenType.DotSelector, token[0].Type); + Assert.Equal("store.property", token[0].As()); + + // ["store.property"] + token = PathTokenizer.Get(path[3]); + Assert.Single(token); + Assert.Equal(PathTokenType.DotSelector, token[0].Type); + Assert.Equal("store.property", token[0].As()); + } + + [Fact] + public void FilterBoolean() + { + var path = new string[] { - var path = new string[] - { - "$[?(@.enabled==true)]", - "$[?@.enabled==false]", - }; - - // $[?(@.enabled==true)] - var actual = PathTokenizer.Get(path[0]); - Assert.Equal(9, actual.Length); - Assert.Equal(PathTokenType.RootRef, actual[0].Type); - Assert.Equal(PathTokenType.StartFilter, actual[1].Type); - Assert.Equal(PathTokenType.StartGroup, actual[2].Type); - Assert.Equal(PathTokenType.CurrentRef, actual[3].Type); - Assert.Equal(PathTokenType.DotSelector, actual[4].Type); - Assert.Equal("enabled", actual[4].As()); - Assert.Equal(PathTokenType.ComparisonOperator, actual[5].Type); - Assert.Equal(FilterOperator.Equal, actual[5].As()); - Assert.Equal(PathTokenType.Boolean, actual[6].Type); - Assert.True(actual[6].As()); - Assert.Equal(PathTokenType.EndGroup, actual[7].Type); - Assert.Equal(PathTokenType.EndFilter, actual[8].Type); - - // $[?(@.enabled==false)] - actual = PathTokenizer.Get(path[1]); - Assert.Equal(7, actual.Length); - Assert.Equal(PathTokenType.RootRef, actual[0].Type); - Assert.Equal(PathTokenType.StartFilter, actual[1].Type); - Assert.Equal(PathTokenType.CurrentRef, actual[2].Type); - Assert.Equal(PathTokenType.DotSelector, actual[3].Type); - Assert.Equal("enabled", actual[3].As()); - Assert.Equal(PathTokenType.ComparisonOperator, actual[4].Type); - Assert.Equal(FilterOperator.Equal, actual[4].As()); - Assert.Equal(PathTokenType.Boolean, actual[5].Type); - Assert.False(actual[5].As()); - Assert.Equal(PathTokenType.EndFilter, actual[6].Type); - } - - [Fact] - public void FilterInteger() + "$[?(@.enabled==true)]", + "$[?@.enabled==false]", + }; + + // $[?(@.enabled==true)] + var actual = PathTokenizer.Get(path[0]); + Assert.Equal(9, actual.Length); + Assert.Equal(PathTokenType.RootRef, actual[0].Type); + Assert.Equal(PathTokenType.StartFilter, actual[1].Type); + Assert.Equal(PathTokenType.StartGroup, actual[2].Type); + Assert.Equal(PathTokenType.CurrentRef, actual[3].Type); + Assert.Equal(PathTokenType.DotSelector, actual[4].Type); + Assert.Equal("enabled", actual[4].As()); + Assert.Equal(PathTokenType.ComparisonOperator, actual[5].Type); + Assert.Equal(FilterOperator.Equal, actual[5].As()); + Assert.Equal(PathTokenType.Boolean, actual[6].Type); + Assert.True(actual[6].As()); + Assert.Equal(PathTokenType.EndGroup, actual[7].Type); + Assert.Equal(PathTokenType.EndFilter, actual[8].Type); + + // $[?(@.enabled==false)] + actual = PathTokenizer.Get(path[1]); + Assert.Equal(7, actual.Length); + Assert.Equal(PathTokenType.RootRef, actual[0].Type); + Assert.Equal(PathTokenType.StartFilter, actual[1].Type); + Assert.Equal(PathTokenType.CurrentRef, actual[2].Type); + Assert.Equal(PathTokenType.DotSelector, actual[3].Type); + Assert.Equal("enabled", actual[3].As()); + Assert.Equal(PathTokenType.ComparisonOperator, actual[4].Type); + Assert.Equal(FilterOperator.Equal, actual[4].As()); + Assert.Equal(PathTokenType.Boolean, actual[5].Type); + Assert.False(actual[5].As()); + Assert.Equal(PathTokenType.EndFilter, actual[6].Type); + } + + [Fact] + public void FilterInteger() + { + var path = new string[] { - var path = new string[] - { - "$[?@.price<10]", - "$[?@.price < 10]", - }; - - // $[?(@.price<10)] - var actual = PathTokenizer.Get(path[0]); - Assert.Equal(7, actual.Length); - Assert.Equal(PathTokenType.RootRef, actual[0].Type); - Assert.Equal(PathTokenType.StartFilter, actual[1].Type); - Assert.Equal(PathTokenType.CurrentRef, actual[2].Type); - Assert.Equal(PathTokenType.DotSelector, actual[3].Type); - Assert.Equal("price", actual[3].As()); - Assert.Equal(PathTokenType.ComparisonOperator, actual[4].Type); - Assert.Equal(FilterOperator.Less, actual[4].As()); - Assert.Equal(PathTokenType.Integer, actual[5].Type); - Assert.Equal(10, actual[5].As()); - Assert.Equal(PathTokenType.EndFilter, actual[6].Type); - - // $[?(@.price < 10)] - actual = PathTokenizer.Get(path[1]); - Assert.Equal(7, actual.Length); - Assert.Equal(PathTokenType.RootRef, actual[0].Type); - Assert.Equal(PathTokenType.StartFilter, actual[1].Type); - Assert.Equal(PathTokenType.CurrentRef, actual[2].Type); - Assert.Equal(PathTokenType.DotSelector, actual[3].Type); - Assert.Equal("price", actual[3].As()); - Assert.Equal(PathTokenType.ComparisonOperator, actual[4].Type); - Assert.Equal(FilterOperator.Less, actual[4].As()); - Assert.Equal(PathTokenType.Integer, actual[5].Type); - Assert.Equal(10, actual[5].As()); - Assert.Equal(PathTokenType.EndFilter, actual[6].Type); - } - - [Fact] - public void FilterString() + "$[?@.price<10]", + "$[?@.price < 10]", + }; + + // $[?(@.price<10)] + var actual = PathTokenizer.Get(path[0]); + Assert.Equal(7, actual.Length); + Assert.Equal(PathTokenType.RootRef, actual[0].Type); + Assert.Equal(PathTokenType.StartFilter, actual[1].Type); + Assert.Equal(PathTokenType.CurrentRef, actual[2].Type); + Assert.Equal(PathTokenType.DotSelector, actual[3].Type); + Assert.Equal("price", actual[3].As()); + Assert.Equal(PathTokenType.ComparisonOperator, actual[4].Type); + Assert.Equal(FilterOperator.Less, actual[4].As()); + Assert.Equal(PathTokenType.Integer, actual[5].Type); + Assert.Equal(10, actual[5].As()); + Assert.Equal(PathTokenType.EndFilter, actual[6].Type); + + // $[?(@.price < 10)] + actual = PathTokenizer.Get(path[1]); + Assert.Equal(7, actual.Length); + Assert.Equal(PathTokenType.RootRef, actual[0].Type); + Assert.Equal(PathTokenType.StartFilter, actual[1].Type); + Assert.Equal(PathTokenType.CurrentRef, actual[2].Type); + Assert.Equal(PathTokenType.DotSelector, actual[3].Type); + Assert.Equal("price", actual[3].As()); + Assert.Equal(PathTokenType.ComparisonOperator, actual[4].Type); + Assert.Equal(FilterOperator.Less, actual[4].As()); + Assert.Equal(PathTokenType.Integer, actual[5].Type); + Assert.Equal(10, actual[5].As()); + Assert.Equal(PathTokenType.EndFilter, actual[6].Type); + } + + [Fact] + public void FilterString() + { + var path = new string[] { - var path = new string[] - { - "$[?@.id=='1']", - "$[?@.id == \"1\"]", - }; - - // $[?(@.id=='1')] - var actual = PathTokenizer.Get(path[0]); - //Assert.Equal(7, actual.Length); - Assert.Equal(PathTokenType.RootRef, actual[0].Type); - Assert.Equal(PathTokenType.StartFilter, actual[1].Type); - Assert.Equal(PathTokenType.CurrentRef, actual[2].Type); - Assert.Equal(PathTokenType.DotSelector, actual[3].Type); - Assert.Equal("id", actual[3].As()); - Assert.Equal(PathTokenType.ComparisonOperator, actual[4].Type); - Assert.Equal(FilterOperator.Equal, actual[4].As()); - Assert.Equal(PathTokenType.String, actual[5].Type); - Assert.Equal("1", actual[5].As()); - Assert.Equal(PathTokenType.EndFilter, actual[6].Type); - - // $[?(@.id == "1")] - actual = PathTokenizer.Get(path[1]); - Assert.Equal(7, actual.Length); - Assert.Equal(PathTokenType.RootRef, actual[0].Type); - Assert.Equal(PathTokenType.StartFilter, actual[1].Type); - Assert.Equal(PathTokenType.CurrentRef, actual[2].Type); - Assert.Equal(PathTokenType.DotSelector, actual[3].Type); - Assert.Equal("id", actual[3].As()); - Assert.Equal(PathTokenType.ComparisonOperator, actual[4].Type); - Assert.Equal(FilterOperator.Equal, actual[4].As()); - Assert.Equal(PathTokenType.String, actual[5].Type); - Assert.Equal("1", actual[5].As()); - Assert.Equal(PathTokenType.EndFilter, actual[6].Type); - } - - [Fact] - public void FilterExists() + "$[?@.id=='1']", + "$[?@.id == \"1\"]", + }; + + // $[?(@.id=='1')] + var actual = PathTokenizer.Get(path[0]); + //Assert.Equal(7, actual.Length); + Assert.Equal(PathTokenType.RootRef, actual[0].Type); + Assert.Equal(PathTokenType.StartFilter, actual[1].Type); + Assert.Equal(PathTokenType.CurrentRef, actual[2].Type); + Assert.Equal(PathTokenType.DotSelector, actual[3].Type); + Assert.Equal("id", actual[3].As()); + Assert.Equal(PathTokenType.ComparisonOperator, actual[4].Type); + Assert.Equal(FilterOperator.Equal, actual[4].As()); + Assert.Equal(PathTokenType.String, actual[5].Type); + Assert.Equal("1", actual[5].As()); + Assert.Equal(PathTokenType.EndFilter, actual[6].Type); + + // $[?(@.id == "1")] + actual = PathTokenizer.Get(path[1]); + Assert.Equal(7, actual.Length); + Assert.Equal(PathTokenType.RootRef, actual[0].Type); + Assert.Equal(PathTokenType.StartFilter, actual[1].Type); + Assert.Equal(PathTokenType.CurrentRef, actual[2].Type); + Assert.Equal(PathTokenType.DotSelector, actual[3].Type); + Assert.Equal("id", actual[3].As()); + Assert.Equal(PathTokenType.ComparisonOperator, actual[4].Type); + Assert.Equal(FilterOperator.Equal, actual[4].As()); + Assert.Equal(PathTokenType.String, actual[5].Type); + Assert.Equal("1", actual[5].As()); + Assert.Equal(PathTokenType.EndFilter, actual[6].Type); + } + + [Fact] + public void FilterExists() + { + var path = new string[] { - var path = new string[] - { - "$[?@.Spec.Properties.Kind].TargetName", - "$[?@..Value2].TargetName", - }; - - // $[?@.Spec.Properties.Kind].TargetName - var actual = PathTokenizer.Get(path[0]); - Assert.Equal(8, actual.Length); - Assert.Equal(PathTokenType.RootRef, actual[0].Type); - Assert.Equal(PathTokenType.StartFilter, actual[1].Type); - Assert.Equal(PathTokenType.CurrentRef, actual[2].Type); - Assert.Equal(PathTokenType.DotSelector, actual[3].Type); - Assert.Equal("Spec", actual[3].As()); - Assert.Equal(PathTokenType.DotSelector, actual[4].Type); - Assert.Equal("Properties", actual[4].As()); - Assert.Equal(PathTokenType.DotSelector, actual[5].Type); - Assert.Equal("Kind", actual[5].As()); - Assert.Equal(PathTokenType.EndFilter, actual[6].Type); - Assert.Equal(PathTokenType.DotSelector, actual[7].Type); - Assert.Equal("TargetName", actual[7].As()); - - // $[?@.Spec.Properties.Kind].TargetName - actual = PathTokenizer.Get(path[1]); - Assert.Equal(6, actual.Length); - Assert.Equal(PathTokenType.RootRef, actual[0].Type); - Assert.Equal(PathTokenType.StartFilter, actual[1].Type); - Assert.Equal(PathTokenType.CurrentRef, actual[2].Type); - Assert.Equal(PathTokenType.DescendantSelector, actual[3].Type); - Assert.Equal("Value2", actual[3].As()); - Assert.Equal(PathTokenType.EndFilter, actual[4].Type); - Assert.Equal(PathTokenType.DotSelector, actual[5].Type); - Assert.Equal("TargetName", actual[5].As()); - } - - [Fact] - public void FilterNot() + "$[?@.Spec.Properties.Kind].TargetName", + "$[?@..Value2].TargetName", + }; + + // $[?@.Spec.Properties.Kind].TargetName + var actual = PathTokenizer.Get(path[0]); + Assert.Equal(8, actual.Length); + Assert.Equal(PathTokenType.RootRef, actual[0].Type); + Assert.Equal(PathTokenType.StartFilter, actual[1].Type); + Assert.Equal(PathTokenType.CurrentRef, actual[2].Type); + Assert.Equal(PathTokenType.DotSelector, actual[3].Type); + Assert.Equal("Spec", actual[3].As()); + Assert.Equal(PathTokenType.DotSelector, actual[4].Type); + Assert.Equal("Properties", actual[4].As()); + Assert.Equal(PathTokenType.DotSelector, actual[5].Type); + Assert.Equal("Kind", actual[5].As()); + Assert.Equal(PathTokenType.EndFilter, actual[6].Type); + Assert.Equal(PathTokenType.DotSelector, actual[7].Type); + Assert.Equal("TargetName", actual[7].As()); + + // $[?@.Spec.Properties.Kind].TargetName + actual = PathTokenizer.Get(path[1]); + Assert.Equal(6, actual.Length); + Assert.Equal(PathTokenType.RootRef, actual[0].Type); + Assert.Equal(PathTokenType.StartFilter, actual[1].Type); + Assert.Equal(PathTokenType.CurrentRef, actual[2].Type); + Assert.Equal(PathTokenType.DescendantSelector, actual[3].Type); + Assert.Equal("Value2", actual[3].As()); + Assert.Equal(PathTokenType.EndFilter, actual[4].Type); + Assert.Equal(PathTokenType.DotSelector, actual[5].Type); + Assert.Equal("TargetName", actual[5].As()); + } + + [Fact] + public void FilterNot() + { + var path = new string[] { - var path = new string[] - { - "$[?(!@.enabled)]", - "$[?!@.enabled]" - }; - - // $[?(!@.enabled)] - var actual = PathTokenizer.Get(path[0]); - Assert.Equal(8, actual.Length); - Assert.Equal(PathTokenType.RootRef, actual[0].Type); - Assert.Equal(PathTokenType.StartFilter, actual[1].Type); - Assert.Equal(PathTokenType.StartGroup, actual[2].Type); - Assert.Equal(PathTokenType.NotOperator, actual[3].Type); - Assert.Equal(PathTokenType.CurrentRef, actual[4].Type); - Assert.Equal(PathTokenType.DotSelector, actual[5].Type); - Assert.Equal("enabled", actual[5].As()); - Assert.Equal(PathTokenType.EndGroup, actual[6].Type); - Assert.Equal(PathTokenType.EndFilter, actual[7].Type); - - // $[?!@.enabled] - actual = PathTokenizer.Get(path[1]); - Assert.Equal(6, actual.Length); - Assert.Equal(PathTokenType.RootRef, actual[0].Type); - Assert.Equal(PathTokenType.StartFilter, actual[1].Type); - Assert.Equal(PathTokenType.NotOperator, actual[2].Type); - Assert.Equal(PathTokenType.CurrentRef, actual[3].Type); - Assert.Equal(PathTokenType.DotSelector, actual[4].Type); - Assert.Equal("enabled", actual[4].As()); - Assert.Equal(PathTokenType.EndFilter, actual[5].Type); - } - - [Fact] - public void FilterOr() + "$[?(!@.enabled)]", + "$[?!@.enabled]" + }; + + // $[?(!@.enabled)] + var actual = PathTokenizer.Get(path[0]); + Assert.Equal(8, actual.Length); + Assert.Equal(PathTokenType.RootRef, actual[0].Type); + Assert.Equal(PathTokenType.StartFilter, actual[1].Type); + Assert.Equal(PathTokenType.StartGroup, actual[2].Type); + Assert.Equal(PathTokenType.NotOperator, actual[3].Type); + Assert.Equal(PathTokenType.CurrentRef, actual[4].Type); + Assert.Equal(PathTokenType.DotSelector, actual[5].Type); + Assert.Equal("enabled", actual[5].As()); + Assert.Equal(PathTokenType.EndGroup, actual[6].Type); + Assert.Equal(PathTokenType.EndFilter, actual[7].Type); + + // $[?!@.enabled] + actual = PathTokenizer.Get(path[1]); + Assert.Equal(6, actual.Length); + Assert.Equal(PathTokenType.RootRef, actual[0].Type); + Assert.Equal(PathTokenType.StartFilter, actual[1].Type); + Assert.Equal(PathTokenType.NotOperator, actual[2].Type); + Assert.Equal(PathTokenType.CurrentRef, actual[3].Type); + Assert.Equal(PathTokenType.DotSelector, actual[4].Type); + Assert.Equal("enabled", actual[4].As()); + Assert.Equal(PathTokenType.EndFilter, actual[5].Type); + } + + [Fact] + public void FilterOr() + { + var path = new string[] { - var path = new string[] - { - "$[?@.on == true || @.enabled == true]", - "$[?(@.on || @.enabled == true)]", - "$[?(@.disabled == false || @.enabled == true) && @.on]", - }; - - // $[?@.on == true || @.enabled == true] - var actual = PathTokenizer.Get(path[0]); - Assert.Equal(12, actual.Length); - Assert.Equal(PathTokenType.RootRef, actual[0].Type); - Assert.Equal(PathTokenType.StartFilter, actual[1].Type); - - Assert.Equal(PathTokenType.CurrentRef, actual[2].Type); - Assert.Equal(PathTokenType.DotSelector, actual[3].Type); - Assert.Equal("on", actual[3].As()); - Assert.Equal(PathTokenType.ComparisonOperator, actual[4].Type); - Assert.Equal(FilterOperator.Equal, actual[4].As()); - Assert.Equal(PathTokenType.Boolean, actual[5].Type); - Assert.True(actual[5].As()); - - Assert.Equal(PathTokenType.LogicalOperator, actual[6].Type); - Assert.Equal(FilterOperator.Or, actual[6].As()); - - Assert.Equal(PathTokenType.CurrentRef, actual[7].Type); - Assert.Equal(PathTokenType.DotSelector, actual[8].Type); - Assert.Equal("enabled", actual[8].As()); - Assert.Equal(FilterOperator.Equal, actual[9].As()); - Assert.Equal(PathTokenType.Boolean, actual[10].Type); - Assert.True(actual[10].As()); - - //Assert.Equal(PathTokenType.EndFilter, actual[11].Type); - - // $[?(@.on || @.enabled == true)] - actual = PathTokenizer.Get(path[1]); - Assert.Equal(12, actual.Length); - Assert.Equal(PathTokenType.RootRef, actual[0].Type); - Assert.Equal(PathTokenType.StartFilter, actual[1].Type); - Assert.Equal(PathTokenType.StartGroup, actual[2].Type); - - Assert.Equal(PathTokenType.CurrentRef, actual[3].Type); - Assert.Equal(PathTokenType.DotSelector, actual[4].Type); - Assert.Equal("on", actual[4].As()); - - Assert.Equal(PathTokenType.LogicalOperator, actual[5].Type); - Assert.Equal(FilterOperator.Or, actual[5].As()); - - Assert.Equal(PathTokenType.CurrentRef, actual[6].Type); - Assert.Equal(PathTokenType.DotSelector, actual[7].Type); - Assert.Equal("enabled", actual[7].As()); - Assert.Equal(FilterOperator.Equal, actual[8].As()); - Assert.Equal(PathTokenType.Boolean, actual[9].Type); - Assert.True(actual[9].As()); - - Assert.Equal(PathTokenType.EndGroup, actual[10].Type); - Assert.Equal(PathTokenType.EndFilter, actual[11].Type); - - // $[?(@.disabled == false || @.enabled == true) && @.on] - actual = PathTokenizer.Get(path[2]); - Assert.Equal(17, actual.Length); - Assert.Equal(PathTokenType.RootRef, actual[0].Type); - Assert.Equal(PathTokenType.StartFilter, actual[1].Type); - Assert.Equal(PathTokenType.StartGroup, actual[2].Type); - - Assert.Equal(PathTokenType.CurrentRef, actual[3].Type); - Assert.Equal(PathTokenType.DotSelector, actual[4].Type); - Assert.Equal("disabled", actual[4].As()); - Assert.Equal(PathTokenType.ComparisonOperator, actual[5].Type); - Assert.Equal(FilterOperator.Equal, actual[5].As()); - Assert.Equal(PathTokenType.Boolean, actual[6].Type); - Assert.False(actual[6].As()); - - Assert.Equal(PathTokenType.LogicalOperator, actual[7].Type); - Assert.Equal(FilterOperator.Or, actual[7].As()); - - Assert.Equal(PathTokenType.CurrentRef, actual[8].Type); - Assert.Equal(PathTokenType.DotSelector, actual[9].Type); - Assert.Equal("enabled", actual[9].As()); - Assert.Equal(FilterOperator.Equal, actual[10].As()); - Assert.Equal(PathTokenType.Boolean, actual[11].Type); - Assert.True(actual[11].As()); - - Assert.Equal(PathTokenType.EndGroup, actual[12].Type); - - Assert.Equal(PathTokenType.LogicalOperator, actual[13].Type); - Assert.Equal(FilterOperator.And, actual[13].As()); - - Assert.Equal(PathTokenType.CurrentRef, actual[14].Type); - Assert.Equal(PathTokenType.DotSelector, actual[15].Type); - Assert.Equal("on", actual[15].As()); - - Assert.Equal(PathTokenType.EndFilter, actual[16].Type); - } - - [Fact] - public void ArraySlice() + "$[?@.on == true || @.enabled == true]", + "$[?(@.on || @.enabled == true)]", + "$[?(@.disabled == false || @.enabled == true) && @.on]", + }; + + // $[?@.on == true || @.enabled == true] + var actual = PathTokenizer.Get(path[0]); + Assert.Equal(12, actual.Length); + Assert.Equal(PathTokenType.RootRef, actual[0].Type); + Assert.Equal(PathTokenType.StartFilter, actual[1].Type); + + Assert.Equal(PathTokenType.CurrentRef, actual[2].Type); + Assert.Equal(PathTokenType.DotSelector, actual[3].Type); + Assert.Equal("on", actual[3].As()); + Assert.Equal(PathTokenType.ComparisonOperator, actual[4].Type); + Assert.Equal(FilterOperator.Equal, actual[4].As()); + Assert.Equal(PathTokenType.Boolean, actual[5].Type); + Assert.True(actual[5].As()); + + Assert.Equal(PathTokenType.LogicalOperator, actual[6].Type); + Assert.Equal(FilterOperator.Or, actual[6].As()); + + Assert.Equal(PathTokenType.CurrentRef, actual[7].Type); + Assert.Equal(PathTokenType.DotSelector, actual[8].Type); + Assert.Equal("enabled", actual[8].As()); + Assert.Equal(FilterOperator.Equal, actual[9].As()); + Assert.Equal(PathTokenType.Boolean, actual[10].Type); + Assert.True(actual[10].As()); + + //Assert.Equal(PathTokenType.EndFilter, actual[11].Type); + + // $[?(@.on || @.enabled == true)] + actual = PathTokenizer.Get(path[1]); + Assert.Equal(12, actual.Length); + Assert.Equal(PathTokenType.RootRef, actual[0].Type); + Assert.Equal(PathTokenType.StartFilter, actual[1].Type); + Assert.Equal(PathTokenType.StartGroup, actual[2].Type); + + Assert.Equal(PathTokenType.CurrentRef, actual[3].Type); + Assert.Equal(PathTokenType.DotSelector, actual[4].Type); + Assert.Equal("on", actual[4].As()); + + Assert.Equal(PathTokenType.LogicalOperator, actual[5].Type); + Assert.Equal(FilterOperator.Or, actual[5].As()); + + Assert.Equal(PathTokenType.CurrentRef, actual[6].Type); + Assert.Equal(PathTokenType.DotSelector, actual[7].Type); + Assert.Equal("enabled", actual[7].As()); + Assert.Equal(FilterOperator.Equal, actual[8].As()); + Assert.Equal(PathTokenType.Boolean, actual[9].Type); + Assert.True(actual[9].As()); + + Assert.Equal(PathTokenType.EndGroup, actual[10].Type); + Assert.Equal(PathTokenType.EndFilter, actual[11].Type); + + // $[?(@.disabled == false || @.enabled == true) && @.on] + actual = PathTokenizer.Get(path[2]); + Assert.Equal(17, actual.Length); + Assert.Equal(PathTokenType.RootRef, actual[0].Type); + Assert.Equal(PathTokenType.StartFilter, actual[1].Type); + Assert.Equal(PathTokenType.StartGroup, actual[2].Type); + + Assert.Equal(PathTokenType.CurrentRef, actual[3].Type); + Assert.Equal(PathTokenType.DotSelector, actual[4].Type); + Assert.Equal("disabled", actual[4].As()); + Assert.Equal(PathTokenType.ComparisonOperator, actual[5].Type); + Assert.Equal(FilterOperator.Equal, actual[5].As()); + Assert.Equal(PathTokenType.Boolean, actual[6].Type); + Assert.False(actual[6].As()); + + Assert.Equal(PathTokenType.LogicalOperator, actual[7].Type); + Assert.Equal(FilterOperator.Or, actual[7].As()); + + Assert.Equal(PathTokenType.CurrentRef, actual[8].Type); + Assert.Equal(PathTokenType.DotSelector, actual[9].Type); + Assert.Equal("enabled", actual[9].As()); + Assert.Equal(FilterOperator.Equal, actual[10].As()); + Assert.Equal(PathTokenType.Boolean, actual[11].Type); + Assert.True(actual[11].As()); + + Assert.Equal(PathTokenType.EndGroup, actual[12].Type); + + Assert.Equal(PathTokenType.LogicalOperator, actual[13].Type); + Assert.Equal(FilterOperator.And, actual[13].As()); + + Assert.Equal(PathTokenType.CurrentRef, actual[14].Type); + Assert.Equal(PathTokenType.DotSelector, actual[15].Type); + Assert.Equal("on", actual[15].As()); + + Assert.Equal(PathTokenType.EndFilter, actual[16].Type); + } + + [Fact] + public void ArraySlice() + { + var path = new string[] { - var path = new string[] - { - "$.items[-1:]", - "$.items[1:2:-1]", - "$.items[:2]", - "$.items[::2]", - "$.items[::-1].id", - "$.items[:1].id", - }; - - // $.items[-1:] - var token = PathTokenizer.Get(path[0]); - Assert.Equal(3, token.Length); - Assert.Equal(PathTokenType.RootRef, token[0].Type); - Assert.Equal(PathTokenType.DotSelector, token[1].Type); - Assert.Equal("items", token[1].As()); - Assert.Equal(PathTokenType.ArraySliceSelector, token[2].Type); - Assert.Equal(-1, token[2].As()[0]); - Assert.Null(token[2].As()[1]); - Assert.Null(token[2].As()[2]); - - // $.items[1:2:-1] - token = PathTokenizer.Get(path[1]); - Assert.Equal(3, token.Length); - Assert.Equal(PathTokenType.RootRef, token[0].Type); - Assert.Equal(PathTokenType.DotSelector, token[1].Type); - Assert.Equal("items", token[1].As()); - Assert.Equal(PathTokenType.ArraySliceSelector, token[2].Type); - Assert.Equal(1, token[2].As()[0]); - Assert.Equal(2, token[2].As()[1]); - Assert.Equal(-1, token[2].As()[2]); - - // $.items[:2] - token = PathTokenizer.Get(path[2]); - Assert.Equal(3, token.Length); - Assert.Equal(PathTokenType.RootRef, token[0].Type); - Assert.Equal(PathTokenType.DotSelector, token[1].Type); - Assert.Equal("items", token[1].As()); - Assert.Equal(PathTokenType.ArraySliceSelector, token[2].Type); - Assert.Null(token[2].As()[0]); - Assert.Equal(2, token[2].As()[1]); - Assert.Null(token[2].As()[2]); - - // $.items[::2] - token = PathTokenizer.Get(path[3]); - Assert.Equal(3, token.Length); - Assert.Equal(PathTokenType.RootRef, token[0].Type); - Assert.Equal(PathTokenType.DotSelector, token[1].Type); - Assert.Equal("items", token[1].As()); - Assert.Equal(PathTokenType.ArraySliceSelector, token[2].Type); - Assert.Null(token[2].As()[0]); - Assert.Null(token[2].As()[1]); - Assert.Equal(2, token[2].As()[2]); - - // $.items[::-1].id - token = PathTokenizer.Get(path[4]); - Assert.Equal(4, token.Length); - Assert.Equal(PathTokenType.RootRef, token[0].Type); - Assert.Equal(PathTokenType.DotSelector, token[1].Type); - Assert.Equal("items", token[1].As()); - Assert.Equal(PathTokenType.ArraySliceSelector, token[2].Type); - Assert.Null(token[2].As()[0]); - Assert.Null(token[2].As()[1]); - Assert.Equal(-1, token[2].As()[2]); - Assert.Equal(PathTokenType.DotSelector, token[3].Type); - Assert.Equal("id", token[3].As()); - - // $.items[:1].id - token = PathTokenizer.Get(path[5]); - Assert.Equal(4, token.Length); - Assert.Equal(PathTokenType.RootRef, token[0].Type); - Assert.Equal(PathTokenType.DotSelector, token[1].Type); - Assert.Equal("items", token[1].As()); - Assert.Equal(PathTokenType.ArraySliceSelector, token[2].Type); - Assert.Null(token[2].As()[0]); - Assert.Equal(1, token[2].As()[1]); - Assert.Null(token[2].As()[2]); - Assert.Equal(PathTokenType.DotSelector, token[3].Type); - Assert.Equal("id", token[3].As()); - } - - [Fact] - public void Union() + "$.items[-1:]", + "$.items[1:2:-1]", + "$.items[:2]", + "$.items[::2]", + "$.items[::-1].id", + "$.items[:1].id", + }; + + // $.items[-1:] + var token = PathTokenizer.Get(path[0]); + Assert.Equal(3, token.Length); + Assert.Equal(PathTokenType.RootRef, token[0].Type); + Assert.Equal(PathTokenType.DotSelector, token[1].Type); + Assert.Equal("items", token[1].As()); + Assert.Equal(PathTokenType.ArraySliceSelector, token[2].Type); + Assert.Equal(-1, token[2].As()[0]); + Assert.Null(token[2].As()[1]); + Assert.Null(token[2].As()[2]); + + // $.items[1:2:-1] + token = PathTokenizer.Get(path[1]); + Assert.Equal(3, token.Length); + Assert.Equal(PathTokenType.RootRef, token[0].Type); + Assert.Equal(PathTokenType.DotSelector, token[1].Type); + Assert.Equal("items", token[1].As()); + Assert.Equal(PathTokenType.ArraySliceSelector, token[2].Type); + Assert.Equal(1, token[2].As()[0]); + Assert.Equal(2, token[2].As()[1]); + Assert.Equal(-1, token[2].As()[2]); + + // $.items[:2] + token = PathTokenizer.Get(path[2]); + Assert.Equal(3, token.Length); + Assert.Equal(PathTokenType.RootRef, token[0].Type); + Assert.Equal(PathTokenType.DotSelector, token[1].Type); + Assert.Equal("items", token[1].As()); + Assert.Equal(PathTokenType.ArraySliceSelector, token[2].Type); + Assert.Null(token[2].As()[0]); + Assert.Equal(2, token[2].As()[1]); + Assert.Null(token[2].As()[2]); + + // $.items[::2] + token = PathTokenizer.Get(path[3]); + Assert.Equal(3, token.Length); + Assert.Equal(PathTokenType.RootRef, token[0].Type); + Assert.Equal(PathTokenType.DotSelector, token[1].Type); + Assert.Equal("items", token[1].As()); + Assert.Equal(PathTokenType.ArraySliceSelector, token[2].Type); + Assert.Null(token[2].As()[0]); + Assert.Null(token[2].As()[1]); + Assert.Equal(2, token[2].As()[2]); + + // $.items[::-1].id + token = PathTokenizer.Get(path[4]); + Assert.Equal(4, token.Length); + Assert.Equal(PathTokenType.RootRef, token[0].Type); + Assert.Equal(PathTokenType.DotSelector, token[1].Type); + Assert.Equal("items", token[1].As()); + Assert.Equal(PathTokenType.ArraySliceSelector, token[2].Type); + Assert.Null(token[2].As()[0]); + Assert.Null(token[2].As()[1]); + Assert.Equal(-1, token[2].As()[2]); + Assert.Equal(PathTokenType.DotSelector, token[3].Type); + Assert.Equal("id", token[3].As()); + + // $.items[:1].id + token = PathTokenizer.Get(path[5]); + Assert.Equal(4, token.Length); + Assert.Equal(PathTokenType.RootRef, token[0].Type); + Assert.Equal(PathTokenType.DotSelector, token[1].Type); + Assert.Equal("items", token[1].As()); + Assert.Equal(PathTokenType.ArraySliceSelector, token[2].Type); + Assert.Null(token[2].As()[0]); + Assert.Equal(1, token[2].As()[1]); + Assert.Null(token[2].As()[2]); + Assert.Equal(PathTokenType.DotSelector, token[3].Type); + Assert.Equal("id", token[3].As()); + } + + [Fact] + public void Union() + { + var path = new string[] { - var path = new string[] - { - "$.items[1,2]", - "$.items[ 1 , 2 ]", - "$.items['name','value']", - "$.items[ \"name\" , \"value\" ]", - }; - - // $.items[1,2] - var token = PathTokenizer.Get(path[0]); - Assert.Equal(3, token.Length); - Assert.Equal(PathTokenType.RootRef, token[0].Type); - Assert.Equal(PathTokenType.DotSelector, token[1].Type); - Assert.Equal("items", token[1].As()); - Assert.Equal(PathTokenType.UnionIndexSelector, token[2].Type); - Assert.Equal(1, token[2].As()[0]); - Assert.Equal(2, token[2].As()[1]); - - // $.items[ 1 , 2 ] - token = PathTokenizer.Get(path[1]); - Assert.Equal(3, token.Length); - Assert.Equal(PathTokenType.RootRef, token[0].Type); - Assert.Equal(PathTokenType.DotSelector, token[1].Type); - Assert.Equal("items", token[1].As()); - Assert.Equal(PathTokenType.UnionIndexSelector, token[2].Type); - Assert.Equal(1, token[2].As()[0]); - Assert.Equal(2, token[2].As()[1]); - - // $.items['name','value'] - token = PathTokenizer.Get(path[2]); - Assert.Equal(3, token.Length); - Assert.Equal(PathTokenType.RootRef, token[0].Type); - Assert.Equal(PathTokenType.DotSelector, token[1].Type); - Assert.Equal("items", token[1].As()); - Assert.Equal(PathTokenType.UnionQuotedMemberSelector, token[2].Type); - Assert.Equal("name", token[2].As()[0]); - Assert.Equal("value", token[2].As()[1]); - - // $.items[ "name" , "value" ] - token = PathTokenizer.Get(path[3]); - Assert.Equal(3, token.Length); - Assert.Equal(PathTokenType.RootRef, token[0].Type); - Assert.Equal(PathTokenType.DotSelector, token[1].Type); - Assert.Equal("items", token[1].As()); - Assert.Equal(PathTokenType.UnionQuotedMemberSelector, token[2].Type); - Assert.Equal("name", token[2].As()[0]); - Assert.Equal("value", token[2].As()[1]); - } - - /// - /// Check tokenizer against standard test cases. https://goessner.net/articles/JsonPath/index.html - /// - [Fact] - public void StandardTestCases() + "$.items[1,2]", + "$.items[ 1 , 2 ]", + "$.items['name','value']", + "$.items[ \"name\" , \"value\" ]", + }; + + // $.items[1,2] + var token = PathTokenizer.Get(path[0]); + Assert.Equal(3, token.Length); + Assert.Equal(PathTokenType.RootRef, token[0].Type); + Assert.Equal(PathTokenType.DotSelector, token[1].Type); + Assert.Equal("items", token[1].As()); + Assert.Equal(PathTokenType.UnionIndexSelector, token[2].Type); + Assert.Equal(1, token[2].As()[0]); + Assert.Equal(2, token[2].As()[1]); + + // $.items[ 1 , 2 ] + token = PathTokenizer.Get(path[1]); + Assert.Equal(3, token.Length); + Assert.Equal(PathTokenType.RootRef, token[0].Type); + Assert.Equal(PathTokenType.DotSelector, token[1].Type); + Assert.Equal("items", token[1].As()); + Assert.Equal(PathTokenType.UnionIndexSelector, token[2].Type); + Assert.Equal(1, token[2].As()[0]); + Assert.Equal(2, token[2].As()[1]); + + // $.items['name','value'] + token = PathTokenizer.Get(path[2]); + Assert.Equal(3, token.Length); + Assert.Equal(PathTokenType.RootRef, token[0].Type); + Assert.Equal(PathTokenType.DotSelector, token[1].Type); + Assert.Equal("items", token[1].As()); + Assert.Equal(PathTokenType.UnionQuotedMemberSelector, token[2].Type); + Assert.Equal("name", token[2].As()[0]); + Assert.Equal("value", token[2].As()[1]); + + // $.items[ "name" , "value" ] + token = PathTokenizer.Get(path[3]); + Assert.Equal(3, token.Length); + Assert.Equal(PathTokenType.RootRef, token[0].Type); + Assert.Equal(PathTokenType.DotSelector, token[1].Type); + Assert.Equal("items", token[1].As()); + Assert.Equal(PathTokenType.UnionQuotedMemberSelector, token[2].Type); + Assert.Equal("name", token[2].As()[0]); + Assert.Equal("value", token[2].As()[1]); + } + + /// + /// Check tokenizer against standard test cases. https://goessner.net/articles/JsonPath/index.html + /// + [Fact] + public void StandardTestCases() + { + var path = new string[] { - var path = new string[] - { - "$.store.book[*].author", - "$..author", - "$.store.*", - "$.store..price", - "$..book[2]", - "$..book[(@.length-1)]", - "$..book[-1:]", - "$..book[0,1]", - "$..book[:2]", - "$..book[?@.isbn]", - "$..book[?@.price<10]", - "$..*" - }; - - // $.store.book[*].author - var token = PathTokenizer.Get(path[0]); - Assert.Equal(5, token.Length); - Assert.Equal(PathTokenType.RootRef, token[0].Type); - Assert.Equal(PathTokenType.DotSelector, token[1].Type); - Assert.Equal("store", token[1].As()); - Assert.Equal(PathTokenType.DotSelector, token[2].Type); - Assert.Equal("book", token[2].As()); - Assert.Equal(PathTokenType.IndexWildSelector, token[3].Type); - Assert.Equal(PathTokenType.DotSelector, token[4].Type); - Assert.Equal("author", token[4].As()); - - // $..author - token = PathTokenizer.Get(path[1]); - Assert.Equal(2, token.Length); - Assert.Equal(PathTokenType.RootRef, token[0].Type); - Assert.Equal(PathTokenType.DescendantSelector, token[1].Type); - Assert.Equal("author", token[1].As()); - - // $.store.* - token = PathTokenizer.Get(path[2]); - Assert.Equal(3, token.Length); - Assert.Equal(PathTokenType.RootRef, token[0].Type); - Assert.Equal(PathTokenType.DotSelector, token[1].Type); - Assert.Equal("store", token[1].As()); - Assert.Equal(PathTokenType.DotWildSelector, token[2].Type); - - // $.store..price - token = PathTokenizer.Get(path[3]); - Assert.Equal(3, token.Length); - Assert.Equal(PathTokenType.RootRef, token[0].Type); - Assert.Equal(PathTokenType.DotSelector, token[1].Type); - Assert.Equal("store", token[1].As()); - Assert.Equal(PathTokenType.DescendantSelector, token[2].Type); - Assert.Equal("price", token[2].As()); - - // $..book[2] - token = PathTokenizer.Get(path[4]); - Assert.Equal(3, token.Length); - Assert.Equal(PathTokenType.RootRef, token[0].Type); - Assert.Equal(PathTokenType.DescendantSelector, token[1].Type); - Assert.Equal("book", token[1].As()); - Assert.Equal(PathTokenType.IndexSelector, token[2].Type); - Assert.Equal(2, token[2].As()); - - // $..book[(@.length-1)] - token = PathTokenizer.Get(path[5]); - - // $..book[-1:] - token = PathTokenizer.Get(path[6]); - Assert.Equal(3, token.Length); - Assert.Equal(PathTokenType.RootRef, token[0].Type); - Assert.Equal(PathTokenType.DescendantSelector, token[1].Type); - Assert.Equal("book", token[1].As()); - Assert.Equal(PathTokenType.ArraySliceSelector, token[2].Type); - - // $..book[0,1] - token = PathTokenizer.Get(path[7]); - Assert.Equal(3, token.Length); - Assert.Equal(PathTokenType.RootRef, token[0].Type); - Assert.Equal(PathTokenType.DescendantSelector, token[1].Type); - Assert.Equal("book", token[1].As()); - Assert.Equal(PathTokenType.UnionIndexSelector, token[2].Type); - Assert.Equal(0, token[2].As()[0]); - Assert.Equal(1, token[2].As()[1]); - - // $..book[:2] - token = PathTokenizer.Get(path[8]); - Assert.Equal(3, token.Length); - Assert.Equal(PathTokenType.RootRef, token[0].Type); - Assert.Equal(PathTokenType.DescendantSelector, token[1].Type); - Assert.Equal("book", token[1].As()); - Assert.Equal(PathTokenType.ArraySliceSelector, token[2].Type); - - // $..book[?(@.isbn)] - token = PathTokenizer.Get(path[9]); - Assert.Equal(6, token.Length); - Assert.Equal(PathTokenType.RootRef, token[0].Type); - Assert.Equal(PathTokenType.DescendantSelector, token[1].Type); - Assert.Equal("book", token[1].As()); - Assert.Equal(PathTokenType.StartFilter, token[2].Type); - Assert.Equal(PathTokenType.CurrentRef, token[3].Type); - Assert.Equal(PathTokenType.DotSelector, token[4].Type); - Assert.Equal("isbn", token[4].As()); - Assert.Equal(PathTokenType.EndFilter, token[5].Type); - - // $..book[?(@.price<10)] - token = PathTokenizer.Get(path[10]); - Assert.Equal(8, token.Length); - Assert.Equal(PathTokenType.RootRef, token[0].Type); - Assert.Equal(PathTokenType.DescendantSelector, token[1].Type); - Assert.Equal("book", token[1].As()); - Assert.Equal(PathTokenType.StartFilter, token[2].Type); - Assert.Equal(PathTokenType.CurrentRef, token[3].Type); - Assert.Equal(PathTokenType.DotSelector, token[4].Type); - Assert.Equal("price", token[4].As()); - Assert.Equal(PathTokenType.ComparisonOperator, token[5].Type); - Assert.Equal(FilterOperator.Less, token[5].As()); - Assert.Equal(PathTokenType.Integer, token[6].Type); - Assert.Equal(10, token[6].As()); - Assert.Equal(PathTokenType.EndFilter, token[7].Type); - - // $..* - token = PathTokenizer.Get(path[11]); - } + "$.store.book[*].author", + "$..author", + "$.store.*", + "$.store..price", + "$..book[2]", + "$..book[(@.length-1)]", + "$..book[-1:]", + "$..book[0,1]", + "$..book[:2]", + "$..book[?@.isbn]", + "$..book[?@.price<10]", + "$..*" + }; + + // $.store.book[*].author + var token = PathTokenizer.Get(path[0]); + Assert.Equal(5, token.Length); + Assert.Equal(PathTokenType.RootRef, token[0].Type); + Assert.Equal(PathTokenType.DotSelector, token[1].Type); + Assert.Equal("store", token[1].As()); + Assert.Equal(PathTokenType.DotSelector, token[2].Type); + Assert.Equal("book", token[2].As()); + Assert.Equal(PathTokenType.IndexWildSelector, token[3].Type); + Assert.Equal(PathTokenType.DotSelector, token[4].Type); + Assert.Equal("author", token[4].As()); + + // $..author + token = PathTokenizer.Get(path[1]); + Assert.Equal(2, token.Length); + Assert.Equal(PathTokenType.RootRef, token[0].Type); + Assert.Equal(PathTokenType.DescendantSelector, token[1].Type); + Assert.Equal("author", token[1].As()); + + // $.store.* + token = PathTokenizer.Get(path[2]); + Assert.Equal(3, token.Length); + Assert.Equal(PathTokenType.RootRef, token[0].Type); + Assert.Equal(PathTokenType.DotSelector, token[1].Type); + Assert.Equal("store", token[1].As()); + Assert.Equal(PathTokenType.DotWildSelector, token[2].Type); + + // $.store..price + token = PathTokenizer.Get(path[3]); + Assert.Equal(3, token.Length); + Assert.Equal(PathTokenType.RootRef, token[0].Type); + Assert.Equal(PathTokenType.DotSelector, token[1].Type); + Assert.Equal("store", token[1].As()); + Assert.Equal(PathTokenType.DescendantSelector, token[2].Type); + Assert.Equal("price", token[2].As()); + + // $..book[2] + token = PathTokenizer.Get(path[4]); + Assert.Equal(3, token.Length); + Assert.Equal(PathTokenType.RootRef, token[0].Type); + Assert.Equal(PathTokenType.DescendantSelector, token[1].Type); + Assert.Equal("book", token[1].As()); + Assert.Equal(PathTokenType.IndexSelector, token[2].Type); + Assert.Equal(2, token[2].As()); + + // $..book[(@.length-1)] + token = PathTokenizer.Get(path[5]); + + // $..book[-1:] + token = PathTokenizer.Get(path[6]); + Assert.Equal(3, token.Length); + Assert.Equal(PathTokenType.RootRef, token[0].Type); + Assert.Equal(PathTokenType.DescendantSelector, token[1].Type); + Assert.Equal("book", token[1].As()); + Assert.Equal(PathTokenType.ArraySliceSelector, token[2].Type); + + // $..book[0,1] + token = PathTokenizer.Get(path[7]); + Assert.Equal(3, token.Length); + Assert.Equal(PathTokenType.RootRef, token[0].Type); + Assert.Equal(PathTokenType.DescendantSelector, token[1].Type); + Assert.Equal("book", token[1].As()); + Assert.Equal(PathTokenType.UnionIndexSelector, token[2].Type); + Assert.Equal(0, token[2].As()[0]); + Assert.Equal(1, token[2].As()[1]); + + // $..book[:2] + token = PathTokenizer.Get(path[8]); + Assert.Equal(3, token.Length); + Assert.Equal(PathTokenType.RootRef, token[0].Type); + Assert.Equal(PathTokenType.DescendantSelector, token[1].Type); + Assert.Equal("book", token[1].As()); + Assert.Equal(PathTokenType.ArraySliceSelector, token[2].Type); + + // $..book[?(@.isbn)] + token = PathTokenizer.Get(path[9]); + Assert.Equal(6, token.Length); + Assert.Equal(PathTokenType.RootRef, token[0].Type); + Assert.Equal(PathTokenType.DescendantSelector, token[1].Type); + Assert.Equal("book", token[1].As()); + Assert.Equal(PathTokenType.StartFilter, token[2].Type); + Assert.Equal(PathTokenType.CurrentRef, token[3].Type); + Assert.Equal(PathTokenType.DotSelector, token[4].Type); + Assert.Equal("isbn", token[4].As()); + Assert.Equal(PathTokenType.EndFilter, token[5].Type); + + // $..book[?(@.price<10)] + token = PathTokenizer.Get(path[10]); + Assert.Equal(8, token.Length); + Assert.Equal(PathTokenType.RootRef, token[0].Type); + Assert.Equal(PathTokenType.DescendantSelector, token[1].Type); + Assert.Equal("book", token[1].As()); + Assert.Equal(PathTokenType.StartFilter, token[2].Type); + Assert.Equal(PathTokenType.CurrentRef, token[3].Type); + Assert.Equal(PathTokenType.DotSelector, token[4].Type); + Assert.Equal("price", token[4].As()); + Assert.Equal(PathTokenType.ComparisonOperator, token[5].Type); + Assert.Equal(FilterOperator.Less, token[5].As()); + Assert.Equal(PathTokenType.Integer, token[6].Type); + Assert.Equal(10, token[6].As()); + Assert.Equal(PathTokenType.EndFilter, token[7].Type); + + // $..* + token = PathTokenizer.Get(path[11]); } } diff --git a/tests/PSRule.Tests/PipelineTests.cs b/tests/PSRule.Tests/PipelineTests.cs index 05de4826c9..d02c7b67f7 100644 --- a/tests/PSRule.Tests/PipelineTests.cs +++ b/tests/PSRule.Tests/PipelineTests.cs @@ -14,369 +14,368 @@ using PSRule.Resources; using PSRule.Rules; -namespace PSRule +namespace PSRule; + +public sealed class PipelineTests { - public sealed class PipelineTests + internal class TestObject { - internal class TestObject - { - public string Name { get; set; } - } - - [Fact] - public void BuildInvokePipeline() - { - var option = GetOption(); - var builder = PipelineBuilder.Invoke(GetSource(), option, null); - Assert.NotNull(builder.Build()); - } - - [Fact] - public void InvokePipeline() - { - var testObject1 = new TestObject { Name = "TestObject1" }; - var option = GetOption(); - option.Rule.Include = new string[] { "FromFile1" }; - var builder = PipelineBuilder.Invoke(GetSource(), option, null); - var pipeline = builder.Build(); + public string Name { get; set; } + } - Assert.NotNull(pipeline); - pipeline.Begin(); - for (var i = 0; i < 100; i++) - pipeline.Process(PSObject.AsPSObject(testObject1)); + [Fact] + public void BuildInvokePipeline() + { + var option = GetOption(); + var builder = PipelineBuilder.Invoke(GetSource(), option, null); + Assert.NotNull(builder.Build()); + } - pipeline.End(); - } + [Fact] + public void InvokePipeline() + { + var testObject1 = new TestObject { Name = "TestObject1" }; + var option = GetOption(); + option.Rule.Include = new string[] { "FromFile1" }; + var builder = PipelineBuilder.Invoke(GetSource(), option, null); + var pipeline = builder.Build(); + + Assert.NotNull(pipeline); + pipeline.Begin(); + for (var i = 0; i < 100; i++) + pipeline.Process(PSObject.AsPSObject(testObject1)); + + pipeline.End(); + } - [Fact] - public void InvokePipelineWithJObject() + [Fact] + public void InvokePipelineWithJObject() + { + var parent = new JObject { - var parent = new JObject - { - ["resources"] = new JArray(new object[] { - new JObject - { - ["Name"] = "TestValue" - }, - new JObject - { - ["Name"] = "TestValue2" - } - }) - }; - - var option = GetOption(); - option.Rule.Include = new string[] { "ScriptReasonTest" }; - option.Input.Format = InputFormat.File; - var builder = PipelineBuilder.Invoke(GetSource(), option, null); - var writer = new TestWriter(option); - var pipeline = builder.Build(writer); - - Assert.NotNull(pipeline); - pipeline.Begin(); - pipeline.Process(PSObject.AsPSObject(parent["resources"][0])); - pipeline.Process(PSObject.AsPSObject(parent["resources"][1])); - pipeline.End(); - - var actual = (writer.Output[0] as InvokeResult).AsRecord().FirstOrDefault(); - Assert.Equal(RuleOutcome.Pass, actual.Outcome); - - actual = (writer.Output[1] as InvokeResult).AsRecord().FirstOrDefault(); - Assert.Equal(RuleOutcome.Fail, actual.Outcome); - Assert.Equal("Name", actual.Detail.Reason.First().Path); - Assert.Equal("resources[1].Name", actual.Detail.Reason.First().FullPath); - } + ["resources"] = new JArray(new object[] { + new JObject + { + ["Name"] = "TestValue" + }, + new JObject + { + ["Name"] = "TestValue2" + } + }) + }; + + var option = GetOption(); + option.Rule.Include = new string[] { "ScriptReasonTest" }; + option.Input.Format = InputFormat.File; + var builder = PipelineBuilder.Invoke(GetSource(), option, null); + var writer = new TestWriter(option); + var pipeline = builder.Build(writer); + + Assert.NotNull(pipeline); + pipeline.Begin(); + pipeline.Process(PSObject.AsPSObject(parent["resources"][0])); + pipeline.Process(PSObject.AsPSObject(parent["resources"][1])); + pipeline.End(); + + var actual = (writer.Output[0] as InvokeResult).AsRecord().FirstOrDefault(); + Assert.Equal(RuleOutcome.Pass, actual.Outcome); + + actual = (writer.Output[1] as InvokeResult).AsRecord().FirstOrDefault(); + Assert.Equal(RuleOutcome.Fail, actual.Outcome); + Assert.Equal("Name", actual.Detail.Reason.First().Path); + Assert.Equal("resources[1].Name", actual.Detail.Reason.First().FullPath); + } - [Fact] - public void InvokePipelineWithPathPrefix() + [Fact] + public void InvokePipelineWithPathPrefix() + { + var parent = new JObject { - var parent = new JObject - { - ["resources"] = new JArray(new object[] { - new JObject - { - ["Name"] = "TestValue" - }, - new JObject - { - ["Name"] = "TestValue2" - } - }) - }; - - var option = GetOption(); - option.Rule.Include = new string[] { "WithPathPrefix" }; - option.Input.Format = InputFormat.File; - var builder = PipelineBuilder.Invoke(GetSource(), option, null); - var writer = new TestWriter(option); - var pipeline = builder.Build(writer); - - Assert.NotNull(pipeline); - pipeline.Begin(); - pipeline.Process(PSObject.AsPSObject(parent["resources"][0])); - pipeline.Process(PSObject.AsPSObject(parent["resources"][1])); - pipeline.End(); - - var actual = (writer.Output[0] as InvokeResult).AsRecord().FirstOrDefault(); - Assert.Equal(RuleOutcome.Pass, actual.Outcome); + ["resources"] = new JArray(new object[] { + new JObject + { + ["Name"] = "TestValue" + }, + new JObject + { + ["Name"] = "TestValue2" + } + }) + }; + + var option = GetOption(); + option.Rule.Include = new string[] { "WithPathPrefix" }; + option.Input.Format = InputFormat.File; + var builder = PipelineBuilder.Invoke(GetSource(), option, null); + var writer = new TestWriter(option); + var pipeline = builder.Build(writer); + + Assert.NotNull(pipeline); + pipeline.Begin(); + pipeline.Process(PSObject.AsPSObject(parent["resources"][0])); + pipeline.Process(PSObject.AsPSObject(parent["resources"][1])); + pipeline.End(); + + var actual = (writer.Output[0] as InvokeResult).AsRecord().FirstOrDefault(); + Assert.Equal(RuleOutcome.Pass, actual.Outcome); + + actual = (writer.Output[1] as InvokeResult).AsRecord().FirstOrDefault(); + Assert.Equal(RuleOutcome.Fail, actual.Outcome); + Assert.Equal("item.Name", actual.Detail.Reason.First().Path); + Assert.Equal("resources[1].item.Name", actual.Detail.Reason.First().FullPath); + } - actual = (writer.Output[1] as InvokeResult).AsRecord().FirstOrDefault(); - Assert.Equal(RuleOutcome.Fail, actual.Outcome); - Assert.Equal("item.Name", actual.Detail.Reason.First().Path); - Assert.Equal("resources[1].item.Name", actual.Detail.Reason.First().FullPath); - } + [Fact] + public void InvokePipelineWithExclude() + { + var option = GetOption(ruleExcludedAction: ExecutionActionPreference.Warn); + option.Rule.Include = new string[] { "FromFile1" }; + option.Rule.Exclude = new string[] { "FromFile2" }; + var builder = PipelineBuilder.Invoke(GetSource(), option, null); + var writer = new TestWriter(GetOption()); + var pipeline = builder.Build(writer); + + Assert.NotNull(pipeline); + pipeline.Begin(); + pipeline.End(); + + Assert.Contains(writer.Warnings, (string s) => { return s == string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.RuleExcluded, ".\\FromFile2"); }); + } - [Fact] - public void InvokePipelineWithExclude() - { - var option = GetOption(ruleExcludedAction: ExecutionActionPreference.Warn); - option.Rule.Include = new string[] { "FromFile1" }; - option.Rule.Exclude = new string[] { "FromFile2" }; - var builder = PipelineBuilder.Invoke(GetSource(), option, null); - var writer = new TestWriter(GetOption()); - var pipeline = builder.Build(writer); - - Assert.NotNull(pipeline); - pipeline.Begin(); - pipeline.End(); + [Fact] + public void BuildGetPipeline() + { + var builder = PipelineBuilder.Get(GetSource(), GetOption(), null); + Assert.NotNull(builder.Build()); + } - Assert.Contains(writer.Warnings, (string s) => { return s == string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.RuleExcluded, ".\\FromFile2"); }); - } + [Fact] + public void GetRuleWithBaseline() + { + var option = PSRuleOption.FromDefault(); + option.Baseline.Group = new Data.StringArrayMap(); + option.Baseline.Group["test"] = new string[] { ".\\TestBaseline1" }; + option.Execution.InvariantCulture = ExecutionActionPreference.Ignore; + Assert.Single(option.Baseline.Group); - [Fact] - public void BuildGetPipeline() + var builder = PipelineBuilder.Get(GetSource(new string[] { - var builder = PipelineBuilder.Get(GetSource(), GetOption(), null); - Assert.NotNull(builder.Build()); - } + "Baseline.Rule.yaml", + "FromFileBaseline.Rule.ps1" + }), option, null); + builder.Baseline(Configuration.BaselineOption.FromString("@test")); + var writer = new TestWriter(option); + var pipeline = builder.Build(writer); + + pipeline.Begin(); + pipeline.Process(null); + pipeline.End(); + + Assert.Single(writer.Output); + } - [Fact] - public void GetRuleWithBaseline() + [Fact] + public void PipelineWithInvariantCulture() + { + Environment.UseCurrentCulture(CultureInfo.InvariantCulture); + 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 { - var option = PSRuleOption.FromDefault(); - option.Baseline.Group = new Data.StringArrayMap(); - option.Baseline.Group["test"] = new string[] { ".\\TestBaseline1" }; - option.Execution.InvariantCulture = ExecutionActionPreference.Ignore; - Assert.Single(option.Baseline.Group); - - var builder = PipelineBuilder.Get(GetSource(new string[] - { - "Baseline.Rule.yaml", - "FromFileBaseline.Rule.ps1" - }), option, null); - builder.Baseline(Configuration.BaselineOption.FromString("@test")); - var writer = new TestWriter(option); - var pipeline = builder.Build(writer); - pipeline.Begin(); pipeline.Process(null); pipeline.End(); - - Assert.Single(writer.Output); + Assert.Contains(writer.Warnings, (string s) => { return s == PSRuleResources.UsingInvariantCulture; }); } - - [Fact] - public void PipelineWithInvariantCulture() - { - Environment.UseCurrentCulture(CultureInfo.InvariantCulture); - 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 - { - pipeline.Begin(); - pipeline.Process(null); - pipeline.End(); - Assert.Contains(writer.Warnings, (string s) => { return s == PSRuleResources.UsingInvariantCulture; }); - } - finally - { - Environment.UseCurrentCulture(); - } - } - - [Fact] - public void PipelineWithInvariantCultureDisabled() + finally { - Environment.UseCurrentCulture(CultureInfo.InvariantCulture); - var option = new PSRuleOption(); - option.Execution.InvariantCulture = ExecutionActionPreference.Ignore; - 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 - { - pipeline.Begin(); - pipeline.Process(null); - pipeline.End(); - Assert.DoesNotContain(writer.Warnings, (string s) => { return s == PSRuleResources.UsingInvariantCulture; }); - } - finally - { - Environment.UseCurrentCulture(); - } + Environment.UseCurrentCulture(); } + } - [Fact] - public void PipelineWithOptions() + [Fact] + 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 OptionContextBuilder(), null); + var writer = new TestWriter(option); + var pipeline = new GetRulePipeline(context, GetSource(), new PipelineReader(null, null, null), writer, false); + try { - var option = GetOption(GetSourcePath("PSRule.Tests.yml")); - var builder = PipelineBuilder.Get(GetSource(), option, null); - Assert.NotNull(builder.Build()); + pipeline.Begin(); + pipeline.Process(null); + pipeline.End(); + Assert.DoesNotContain(writer.Warnings, (string s) => { return s == PSRuleResources.UsingInvariantCulture; }); } - - [Fact] - public void PipelineWithRequires() + finally { - var option = GetOption(GetSourcePath("PSRule.Tests6.yml")); - var builder = PipelineBuilder.Get(GetSource(), option, null); - Assert.Null(builder.Build()); + Environment.UseCurrentCulture(); } + } - /// - /// An Invoke pipeline reading from an input file. - /// - [Fact] - public void PipelineWithSource() - { - var option = GetOption(); - option.Rule.Include = new string[] { "FromFile1" }; - option.Input.PathIgnore = new string[] - { - "**/ObjectFromFile*.json", - "!**/ObjectFromFile.json" - }; - - // Default - var writer = new TestWriter(option); - var builder = PipelineBuilder.Invoke(GetSource(), option, null); - builder.InputPath(new string[] { "./**/ObjectFromFile*.json" }); - var pipeline = builder.Build(writer); - Assert.NotNull(pipeline); - pipeline.Begin(); - pipeline.Process(GetTestObject()); - pipeline.Process(GetFileObject()); - pipeline.End(); - - var items = writer.Output.OfType().SelectMany(i => i.AsRecord()).ToArray(); - Assert.Equal(4, items.Length); - Assert.True(items[0].HasSource()); - Assert.True(items[1].HasSource()); - Assert.True(items[2].HasSource()); - Assert.True(items[3].HasSource()); - - // With reason full path - option.Rule.Include = new string[] { "ScriptReasonTest" }; - writer = new TestWriter(option); - builder = PipelineBuilder.Invoke(GetSource(), option, null); - PipelineBuilder.Invoke(GetSource(), option, null); - builder.InputPath(new string[] { "./**/ObjectFromFile*.json" }); - pipeline = builder.Build(writer); - Assert.NotNull(pipeline); - pipeline.Begin(); - pipeline.Process(GetTestObject()); - pipeline.Process(GetFileObject()); - pipeline.End(); - - items = writer.Output.OfType().SelectMany(i => i.AsRecord()).ToArray(); - Assert.Equal(4, items.Length); - Assert.Equal("master.items[0].Name", items[0].Detail.Reason.First().FullPath); - Assert.Equal("Name", items[0].Detail.Reason.First().Path); - Assert.Equal("[1].Name", items[1].Detail.Reason.First().FullPath); - Assert.Equal("Name", items[1].Detail.Reason.First().Path); - Assert.Equal("resources[0].Name", items[2].Detail.Reason.First().FullPath); - Assert.Equal("Name", items[2].Detail.Reason.First().Path); - Assert.Equal("Name", items[3].Detail.Reason.First().FullPath); - Assert.Equal("Name", items[3].Detail.Reason.First().Path); - - // With IgnoreObjectSource - option.Rule.Include = new string[] { "FromFile1" }; - option.Input.IgnoreObjectSource = true; - writer = new TestWriter(option); - builder = PipelineBuilder.Invoke(GetSource(), option, null); - PipelineBuilder.Invoke(GetSource(), option, null); - builder.InputPath(new string[] { "./**/ObjectFromFile*.json" }); - pipeline = builder.Build(writer); - Assert.NotNull(pipeline); - pipeline.Begin(); - pipeline.Process(GetTestObject()); - pipeline.Process(GetFileObject()); - pipeline.End(); + [Fact] + public void PipelineWithOptions() + { + var option = GetOption(GetSourcePath("PSRule.Tests.yml")); + var builder = PipelineBuilder.Get(GetSource(), option, null); + Assert.NotNull(builder.Build()); + } - items = writer.Output.OfType().SelectMany(i => i.AsRecord()).ToArray(); - Assert.Equal(2, items.Length); - Assert.True(items[0].HasSource()); - Assert.True(items[1].HasSource()); - } + [Fact] + public void PipelineWithRequires() + { + var option = GetOption(GetSourcePath("PSRule.Tests6.yml")); + var builder = PipelineBuilder.Get(GetSource(), option, null); + Assert.Null(builder.Build()); + } - /// - /// An Invoke pipeline reading from an input file with File format. - /// - [Fact] - public void PipelineWithFileFormat() + /// + /// An Invoke pipeline reading from an input file. + /// + [Fact] + public void PipelineWithSource() + { + var option = GetOption(); + option.Rule.Include = new string[] { "FromFile1" }; + option.Input.PathIgnore = new string[] { - var option = GetOption(); - option.Input.Format = InputFormat.File; - option.Rule.Include = new string[] { "FromFile1" }; - var builder = PipelineBuilder.Invoke(GetSource(), option, null); - builder.InputPath(new string[] { "./**/ObjectFromFile.json" }); - var writer = new TestWriter(option); - var pipeline = builder.Build(writer); - Assert.NotNull(pipeline); - pipeline.Begin(); - pipeline.Process(null); - pipeline.End(); - - var items = writer.Output.OfType().SelectMany(i => i.AsRecord()).ToArray(); - Assert.Single(items); - Assert.True(items[0].HasSource()); - } + "**/ObjectFromFile*.json", + "!**/ObjectFromFile.json" + }; + + // Default + var writer = new TestWriter(option); + var builder = PipelineBuilder.Invoke(GetSource(), option, null); + builder.InputPath(new string[] { "./**/ObjectFromFile*.json" }); + var pipeline = builder.Build(writer); + Assert.NotNull(pipeline); + pipeline.Begin(); + pipeline.Process(GetTestObject()); + pipeline.Process(GetFileObject()); + pipeline.End(); + + var items = writer.Output.OfType().SelectMany(i => i.AsRecord()).ToArray(); + Assert.Equal(4, items.Length); + Assert.True(items[0].HasSource()); + Assert.True(items[1].HasSource()); + Assert.True(items[2].HasSource()); + Assert.True(items[3].HasSource()); + + // With reason full path + option.Rule.Include = new string[] { "ScriptReasonTest" }; + writer = new TestWriter(option); + builder = PipelineBuilder.Invoke(GetSource(), option, null); + PipelineBuilder.Invoke(GetSource(), option, null); + builder.InputPath(new string[] { "./**/ObjectFromFile*.json" }); + pipeline = builder.Build(writer); + Assert.NotNull(pipeline); + pipeline.Begin(); + pipeline.Process(GetTestObject()); + pipeline.Process(GetFileObject()); + pipeline.End(); + + items = writer.Output.OfType().SelectMany(i => i.AsRecord()).ToArray(); + Assert.Equal(4, items.Length); + Assert.Equal("master.items[0].Name", items[0].Detail.Reason.First().FullPath); + Assert.Equal("Name", items[0].Detail.Reason.First().Path); + Assert.Equal("[1].Name", items[1].Detail.Reason.First().FullPath); + Assert.Equal("Name", items[1].Detail.Reason.First().Path); + Assert.Equal("resources[0].Name", items[2].Detail.Reason.First().FullPath); + Assert.Equal("Name", items[2].Detail.Reason.First().Path); + Assert.Equal("Name", items[3].Detail.Reason.First().FullPath); + Assert.Equal("Name", items[3].Detail.Reason.First().Path); + + // With IgnoreObjectSource + option.Rule.Include = new string[] { "FromFile1" }; + option.Input.IgnoreObjectSource = true; + writer = new TestWriter(option); + builder = PipelineBuilder.Invoke(GetSource(), option, null); + PipelineBuilder.Invoke(GetSource(), option, null); + builder.InputPath(new string[] { "./**/ObjectFromFile*.json" }); + pipeline = builder.Build(writer); + Assert.NotNull(pipeline); + pipeline.Begin(); + pipeline.Process(GetTestObject()); + pipeline.Process(GetFileObject()); + pipeline.End(); + + items = writer.Output.OfType().SelectMany(i => i.AsRecord()).ToArray(); + Assert.Equal(2, items.Length); + Assert.True(items[0].HasSource()); + Assert.True(items[1].HasSource()); + } - #region Helper methods + /// + /// An Invoke pipeline reading from an input file with File format. + /// + [Fact] + public void PipelineWithFileFormat() + { + var option = GetOption(); + option.Input.Format = InputFormat.File; + option.Rule.Include = new string[] { "FromFile1" }; + var builder = PipelineBuilder.Invoke(GetSource(), option, null); + builder.InputPath(new string[] { "./**/ObjectFromFile.json" }); + var writer = new TestWriter(option); + var pipeline = builder.Build(writer); + Assert.NotNull(pipeline); + pipeline.Begin(); + pipeline.Process(null); + pipeline.End(); + + var items = writer.Output.OfType().SelectMany(i => i.AsRecord()).ToArray(); + Assert.Single(items); + Assert.True(items[0].HasSource()); + } - private static Source[] GetSource(string[] files = null) - { - var builder = new SourcePipelineBuilder(null, null); - builder.Directory(GetSourcePath("FromFile.Rule.ps1")); - for (var i = 0; files != null && i < files.Length; i++) - builder.Directory(GetSourcePath(files[i])); + #region Helper methods - return builder.Build(); - } + private static Source[] GetSource(string[] files = null) + { + var builder = new SourcePipelineBuilder(null, null); + builder.Directory(GetSourcePath("FromFile.Rule.ps1")); + for (var i = 0; files != null && i < files.Length; i++) + builder.Directory(GetSourcePath(files[i])); - private static PSRuleOption GetOption(string path = null, ExecutionActionPreference ruleExcludedAction = ExecutionActionPreference.None) - { - var option = path == null ? new PSRuleOption() : PSRuleOption.FromFile(path); - option.Rule.IncludeLocal = false; - option.Execution.RuleExcluded = ruleExcludedAction; - return option; - } + return builder.Build(); + } - private static string GetSourcePath(string fileName) - { - return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName); - } + private static PSRuleOption GetOption(string path = null, ExecutionActionPreference ruleExcludedAction = ExecutionActionPreference.None) + { + var option = path == null ? new PSRuleOption() : PSRuleOption.FromFile(path); + option.Rule.IncludeLocal = false; + option.Execution.RuleExcluded = ruleExcludedAction; + return option; + } - private static PSObject GetTestObject() - { - var info = new PSObject(); - info.Properties.Add(new PSNoteProperty("path", "resources[0]")); - var source = new PSObject(); - source.Properties.Add(new PSNoteProperty("file", Environment.GetRootedPath("./ObjectFromFileNotFile.json"))); - source.Properties.Add(new PSNoteProperty("type", "example")); - info.Properties.Add(new PSNoteProperty("source", new PSObject[] { source })); - var o = new PSObject(); - o.Properties.Add(new PSNoteProperty("name", "FromObjectTest")); - o.Properties.Add(new PSNoteProperty("_PSRule", info)); - return o; - } + private static string GetSourcePath(string fileName) + { + return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName); + } - private static PSObject GetFileObject() - { - var info = new FileInfo(Environment.GetRootedPath("./ObjectFromFileSingle.json")); - return new PSObject(info); - } + private static PSObject GetTestObject() + { + var info = new PSObject(); + info.Properties.Add(new PSNoteProperty("path", "resources[0]")); + var source = new PSObject(); + source.Properties.Add(new PSNoteProperty("file", Environment.GetRootedPath("./ObjectFromFileNotFile.json"))); + source.Properties.Add(new PSNoteProperty("type", "example")); + info.Properties.Add(new PSNoteProperty("source", new PSObject[] { source })); + var o = new PSObject(); + o.Properties.Add(new PSNoteProperty("name", "FromObjectTest")); + o.Properties.Add(new PSNoteProperty("_PSRule", info)); + return o; + } - #endregion Helper methods + private static PSObject GetFileObject() + { + var info = new FileInfo(Environment.GetRootedPath("./ObjectFromFileSingle.json")); + return new PSObject(info); } + + #endregion Helper methods } diff --git a/tests/PSRule.Tests/ResourceHelperTests.cs b/tests/PSRule.Tests/ResourceHelperTests.cs index b65711e6ab..750aa1e56c 100644 --- a/tests/PSRule.Tests/ResourceHelperTests.cs +++ b/tests/PSRule.Tests/ResourceHelperTests.cs @@ -3,36 +3,35 @@ using PSRule.Definitions; -namespace PSRule +namespace PSRule; + +public sealed class ResourceHelperTests { - public sealed class ResourceHelperTests + [Fact] + public void ParseIdString() { - [Fact] - public void ParseIdString() - { - ResourceHelper.ParseIdString(null, "Module1\\Resource1", out var moduleName, out var name); - Assert.Equal("Module1", moduleName); - Assert.Equal("Resource1", name); - - ResourceHelper.ParseIdString("Module2", "Module1\\Resource1", out moduleName, out name); - Assert.Equal("Module1", moduleName); - Assert.Equal("Resource1", name); - - ResourceHelper.ParseIdString("Module2", ".\\Resource1", out moduleName, out name); - Assert.Equal(".", moduleName); - Assert.Equal("Resource1", name); - - ResourceHelper.ParseIdString(null, ".\\Resource1", out moduleName, out name); - Assert.Equal(".", moduleName); - Assert.Equal("Resource1", name); - - ResourceHelper.ParseIdString(null, "Resource1", out moduleName, out name); - Assert.Equal(".", moduleName); - Assert.Equal("Resource1", name); - - ResourceHelper.ParseIdString("Module2", "Resource1", out moduleName, out name); - Assert.Equal("Module2", moduleName); - Assert.Equal("Resource1", name); - } + ResourceHelper.ParseIdString(null, "Module1\\Resource1", out var moduleName, out var name); + Assert.Equal("Module1", moduleName); + Assert.Equal("Resource1", name); + + ResourceHelper.ParseIdString("Module2", "Module1\\Resource1", out moduleName, out name); + Assert.Equal("Module1", moduleName); + Assert.Equal("Resource1", name); + + ResourceHelper.ParseIdString("Module2", ".\\Resource1", out moduleName, out name); + Assert.Equal(".", moduleName); + Assert.Equal("Resource1", name); + + ResourceHelper.ParseIdString(null, ".\\Resource1", out moduleName, out name); + Assert.Equal(".", moduleName); + Assert.Equal("Resource1", name); + + ResourceHelper.ParseIdString(null, "Resource1", out moduleName, out name); + Assert.Equal(".", moduleName); + Assert.Equal("Resource1", name); + + ResourceHelper.ParseIdString("Module2", "Resource1", out moduleName, out name); + Assert.Equal("Module2", moduleName); + Assert.Equal("Resource1", name); } } diff --git a/tests/PSRule.Tests/ResourceValidatorTests.cs b/tests/PSRule.Tests/ResourceValidatorTests.cs index 0228f8f1aa..7278b2e006 100644 --- a/tests/PSRule.Tests/ResourceValidatorTests.cs +++ b/tests/PSRule.Tests/ResourceValidatorTests.cs @@ -9,80 +9,79 @@ using PSRule.Pipeline; using PSRule.Runtime; -namespace PSRule +namespace PSRule; + +public sealed class ResourceValidatorTests { - public sealed class ResourceValidatorTests + [Fact] + public void ResourceName() { - [Fact] - public void ResourceName() - { - var writer = new TestWriter(GetOption()); - 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); - Assert.NotNull(rule); - Assert.Empty(writer.Errors); + var writer = new TestWriter(GetOption()); + var context = new RunspaceContext(PipelineContext.New(GetOption(), null, null, null, null, null, new OptionContextBuilder(), null), writer); - // Get invalid rule names YAML - rule = HostHelper.GetRule(GetSource("FromFileName.Rule.yaml"), context, includeDependencies: false); - Assert.NotNull(rule); - Assert.NotEmpty(writer.Errors); - Assert.Equal("PSRule.Parse.InvalidResourceName", writer.Errors[0].FullyQualifiedErrorId); + // Get good rules + var rule = HostHelper.GetRule(GetSource(), context, includeDependencies: false); + Assert.NotNull(rule); + Assert.Empty(writer.Errors); - // Get invalid rule names JSON - writer.Errors.Clear(); - rule = HostHelper.GetRule(GetSource("FromFileName.Rule.jsonc"), context, includeDependencies: false); - Assert.NotNull(rule); - Assert.NotEmpty(writer.Errors); - Assert.Equal("PSRule.Parse.InvalidResourceName", writer.Errors[0].FullyQualifiedErrorId); - } + // Get invalid rule names YAML + rule = HostHelper.GetRule(GetSource("FromFileName.Rule.yaml"), context, includeDependencies: false); + Assert.NotNull(rule); + Assert.NotEmpty(writer.Errors); + Assert.Equal("PSRule.Parse.InvalidResourceName", writer.Errors[0].FullyQualifiedErrorId); - [Fact] - public void IsNameValid() - { - Assert.True(ResourceValidator.IsNameValid("Local.Test.Rule")); - Assert.True(ResourceValidator.IsNameValid("Local_Test_Rule")); - Assert.True(ResourceValidator.IsNameValid("Local-Test-Rule")); - Assert.True(ResourceValidator.IsNameValid("My rule 1")); - Assert.True(ResourceValidator.IsNameValid("Ma règle 1")); - Assert.True(ResourceValidator.IsNameValid("Ο κανόνας μου 1")); - Assert.True(ResourceValidator.IsNameValid("私のルール1")); - Assert.True(ResourceValidator.IsNameValid("我的规则 1")); - Assert.False(ResourceValidator.IsNameValid("My rule '1'")); - Assert.False(ResourceValidator.IsNameValid("My rule \"1\"")); - Assert.False(ResourceValidator.IsNameValid("My rule `1`")); - Assert.False(ResourceValidator.IsNameValid("Test\0Rule")); - Assert.False(ResourceValidator.IsNameValid("Test>Rule")); - Assert.False(ResourceValidator.IsNameValid("Test|Rule")); - Assert.False(ResourceValidator.IsNameValid("Test\nRule")); - Assert.False(ResourceValidator.IsNameValid("Test\tRule")); - Assert.False(ResourceValidator.IsNameValid("Test\\Rule")); - Assert.False(ResourceValidator.IsNameValid("Test/Rule")); - Assert.False(ResourceValidator.IsNameValid("Test Rule.")); - Assert.False(ResourceValidator.IsNameValid("Test Rule-")); - Assert.False(ResourceValidator.IsNameValid("My")); - } + // Get invalid rule names JSON + writer.Errors.Clear(); + rule = HostHelper.GetRule(GetSource("FromFileName.Rule.jsonc"), context, includeDependencies: false); + Assert.NotNull(rule); + Assert.NotEmpty(writer.Errors); + Assert.Equal("PSRule.Parse.InvalidResourceName", writer.Errors[0].FullyQualifiedErrorId); + } - #region Helper methods + [Fact] + public void IsNameValid() + { + Assert.True(ResourceValidator.IsNameValid("Local.Test.Rule")); + Assert.True(ResourceValidator.IsNameValid("Local_Test_Rule")); + Assert.True(ResourceValidator.IsNameValid("Local-Test-Rule")); + Assert.True(ResourceValidator.IsNameValid("My rule 1")); + Assert.True(ResourceValidator.IsNameValid("Ma règle 1")); + Assert.True(ResourceValidator.IsNameValid("Ο κανόνας μου 1")); + Assert.True(ResourceValidator.IsNameValid("私のルール1")); + Assert.True(ResourceValidator.IsNameValid("我的规则 1")); + Assert.False(ResourceValidator.IsNameValid("My rule '1'")); + Assert.False(ResourceValidator.IsNameValid("My rule \"1\"")); + Assert.False(ResourceValidator.IsNameValid("My rule `1`")); + Assert.False(ResourceValidator.IsNameValid("Test\0Rule")); + Assert.False(ResourceValidator.IsNameValid("Test>Rule")); + Assert.False(ResourceValidator.IsNameValid("Test|Rule")); + Assert.False(ResourceValidator.IsNameValid("Test\nRule")); + Assert.False(ResourceValidator.IsNameValid("Test\tRule")); + Assert.False(ResourceValidator.IsNameValid("Test\\Rule")); + Assert.False(ResourceValidator.IsNameValid("Test/Rule")); + Assert.False(ResourceValidator.IsNameValid("Test Rule.")); + Assert.False(ResourceValidator.IsNameValid("Test Rule-")); + Assert.False(ResourceValidator.IsNameValid("My")); + } - private static Source[] GetSource(string path = "FromFile.Rule.yaml") - { - var builder = new SourcePipelineBuilder(null, null); - builder.Directory(GetSourcePath(path)); - return builder.Build(); - } + #region Helper methods - private static PSRuleOption GetOption() - { - return new PSRuleOption(); - } + private static Source[] GetSource(string path = "FromFile.Rule.yaml") + { + var builder = new SourcePipelineBuilder(null, null); + builder.Directory(GetSourcePath(path)); + return builder.Build(); + } - private static string GetSourcePath(string fileName) - { - return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName); - } + private static PSRuleOption GetOption() + { + return new PSRuleOption(); + } - #endregion Helper methods + private static string GetSourcePath(string fileName) + { + return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName); } + + #endregion Helper methods } diff --git a/tests/PSRule.Tests/RuleDocumentTests.cs b/tests/PSRule.Tests/RuleDocumentTests.cs index 17d3bbd02e..0a461eb716 100644 --- a/tests/PSRule.Tests/RuleDocumentTests.cs +++ b/tests/PSRule.Tests/RuleDocumentTests.cs @@ -8,107 +8,107 @@ using PSRule.Definitions; using PSRule.Help; -namespace PSRule +namespace PSRule; + +public sealed class RuleDocumentTests { - public sealed class RuleDocumentTests + /// + /// Try to parse the markdown document using English with Windows line endings. + /// + [Fact] + public void ReadDocument_Windows_en() { - /// - /// Try to parse the markdown document using English with Windows line endings. - /// - [Fact] - public void ReadDocument_Windows_en() - { - var document = GetDocument(GetToken(nx: false, suffix: ""), culture: null); - var expected = GetExpected_en(); - - Assert.Equal(expected.Name, document.Name); - Assert.Equal(expected.Synopsis.Text, document.Synopsis.Text); - Assert.Equal(expected.Description.Text, document.Description.Text); - Assert.Equal(expected.Recommendation.Text, document.Recommendation.Text); - Assert.Equal(expected.Notes.Text, document.Notes.Text); - Assert.Equal(expected.Annotations["severity"], document.Annotations["severity"]); - Assert.Equal(expected.Annotations["category"], document.Annotations["category"]); - Assert.Equal(expected.Links.Length, document.Links.Length); - Assert.Equal(expected.Links[0].Name, document.Links[0].Name); - Assert.Equal(expected.Links[0].Uri, document.Links[0].Uri); - Assert.Equal(expected.Links[1].Name, document.Links[1].Name); - Assert.Equal(expected.Links[1].Uri, document.Links[1].Uri); - } - - /// - /// Try to parse the markdown document using Spanish with Windows line endings. - /// - [Fact] - public void ReadDocument_Windows_es() + var document = GetDocument(GetToken(nx: false, suffix: ""), culture: null); + var expected = GetExpected_en(); + + Assert.Equal(expected.Name, document.Name); + Assert.Equal(expected.Synopsis.Text, document.Synopsis.Text); + Assert.Equal(expected.Description.Text, document.Description.Text); + Assert.Equal(expected.Recommendation.Text, document.Recommendation.Text); + Assert.Equal(expected.Notes.Text, document.Notes.Text); + Assert.Equal(expected.Annotations["severity"], document.Annotations["severity"]); + Assert.Equal(expected.Annotations["category"], document.Annotations["category"]); + Assert.Equal(expected.Links.Length, document.Links.Length); + Assert.Equal(expected.Links[0].Name, document.Links[0].Name); + Assert.Equal(expected.Links[0].Uri, document.Links[0].Uri); + Assert.Equal(expected.Links[1].Name, document.Links[1].Name); + Assert.Equal(expected.Links[1].Uri, document.Links[1].Uri); + } + + /// + /// Try to parse the markdown document using Spanish with Windows line endings. + /// + [Fact] + public void ReadDocument_Windows_es() + { + var document = GetDocument(GetToken(nx: false, suffix: "-es"), culture: "es"); + var expected = GetExpected_es(); + + Assert.Equal(expected.Name, document.Name); + Assert.Equal(expected.Synopsis.Text, document.Synopsis.Text); + Assert.Equal(expected.Description.Text, document.Description.Text); + Assert.Equal(expected.Recommendation.Text, document.Recommendation.Text); + Assert.Equal(expected.Annotations["severity"], document.Annotations["severity"]); + Assert.Equal(expected.Annotations["category"], document.Annotations["category"]); + Assert.Equal(expected.Links.Length, document.Links.Length); + Assert.Equal(expected.Links[0].Name, document.Links[0].Name); + Assert.Equal(expected.Links[0].Uri, document.Links[0].Uri); + Assert.Equal(expected.Links[1].Name, document.Links[1].Name); + Assert.Equal(expected.Links[1].Uri, document.Links[1].Uri); + } + + /// + /// Try to parse the markdown document using English with Linux line endings. + /// + [Fact] + public void ReadDocument_Linux_en() + { + var document = GetDocument(GetToken(nx: true, suffix: ""), culture: null); + var expected = GetExpected_en(); + + Assert.Equal(expected.Name, document.Name); + Assert.Equal(expected.Synopsis.Text, document.Synopsis.Text); + Assert.Equal(expected.Description.Text, document.Description.Text); + Assert.Equal(expected.Recommendation.Text, document.Recommendation.Text); + Assert.Equal(expected.Notes.Text, document.Notes.Text); + Assert.Equal(expected.Annotations["severity"], document.Annotations["severity"]); + Assert.Equal(expected.Annotations["category"], document.Annotations["category"]); + Assert.Equal(expected.Links.Length, document.Links.Length); + Assert.Equal(expected.Links[0].Name, document.Links[0].Name); + Assert.Equal(expected.Links[0].Uri, document.Links[0].Uri); + Assert.Equal(expected.Links[1].Name, document.Links[1].Name); + Assert.Equal(expected.Links[1].Uri, document.Links[1].Uri); + } + + #region Helper methods + + #endregion Helper methods + + private static RuleDocument GetExpected_en() + { + var annotations = new Hashtable { - var document = GetDocument(GetToken(nx: false, suffix: "-es"), culture: "es"); - var expected = GetExpected_es(); - - Assert.Equal(expected.Name, document.Name); - Assert.Equal(expected.Synopsis.Text, document.Synopsis.Text); - Assert.Equal(expected.Description.Text, document.Description.Text); - Assert.Equal(expected.Recommendation.Text, document.Recommendation.Text); - Assert.Equal(expected.Annotations["severity"], document.Annotations["severity"]); - Assert.Equal(expected.Annotations["category"], document.Annotations["category"]); - Assert.Equal(expected.Links.Length, document.Links.Length); - Assert.Equal(expected.Links[0].Name, document.Links[0].Name); - Assert.Equal(expected.Links[0].Uri, document.Links[0].Uri); - Assert.Equal(expected.Links[1].Name, document.Links[1].Name); - Assert.Equal(expected.Links[1].Uri, document.Links[1].Uri); - } - - /// - /// Try to parse the markdown document using English with Linux line endings. - /// - [Fact] - public void ReadDocument_Linux_en() + ["severity"] = "Critical", + ["category"] = "Security" + }; + + var links = new List { - var document = GetDocument(GetToken(nx: true, suffix: ""), culture: null); - var expected = GetExpected_en(); - - Assert.Equal(expected.Name, document.Name); - Assert.Equal(expected.Synopsis.Text, document.Synopsis.Text); - Assert.Equal(expected.Description.Text, document.Description.Text); - Assert.Equal(expected.Recommendation.Text, document.Recommendation.Text); - Assert.Equal(expected.Notes.Text, document.Notes.Text); - Assert.Equal(expected.Annotations["severity"], document.Annotations["severity"]); - Assert.Equal(expected.Annotations["category"], document.Annotations["category"]); - Assert.Equal(expected.Links.Length, document.Links.Length); - Assert.Equal(expected.Links[0].Name, document.Links[0].Name); - Assert.Equal(expected.Links[0].Uri, document.Links[0].Uri); - Assert.Equal(expected.Links[1].Name, document.Links[1].Name); - Assert.Equal(expected.Links[1].Uri, document.Links[1].Uri); - } - - #region Helper methods - - #endregion Helper methods - - private static RuleDocument GetExpected_en() + new() { Name = "PSRule", Uri = "https://github.com/Microsoft/PSRule" }, + new() { Name = "Stable tags", Uri = "https://docs.microsoft.com/en-us/azure/container-registry/container-registry-image-tag-version#stable-tags" } + }; + + var result = new RuleDocument(name: "Use specific tags") { - var annotations = new Hashtable - { - ["severity"] = "Critical", - ["category"] = "Security" - }; - - var links = new List - { - new Link { Name = "PSRule", Uri = "https://github.com/Microsoft/PSRule" }, - new Link { Name = "Stable tags", Uri = "https://docs.microsoft.com/en-us/azure/container-registry/container-registry-image-tag-version#stable-tags" } - }; - - var result = new RuleDocument(name: "Use specific tags") - { - Synopsis = new InfoString("Containers should use specific tags instead of latest."), - Description = new InfoString(@"Containers should use specific tags instead of latest. This is because: + Synopsis = new InfoString("Containers should use specific tags instead of latest."), + Description = new InfoString(@"Containers should use specific tags instead of latest. This is because: - Latest can be updated."), - Annotations = ResourceTags.FromHashtable(annotations), - Recommendation = new InfoString(@"Deployments or pods should identify a specific tag to use for container images instead of latest. When latest is used it may be hard to determine which version of the image is running. + Annotations = ResourceTags.FromHashtable(annotations), + Recommendation = new InfoString(@"Deployments or pods should identify a specific tag to use for container images instead of latest. When latest is used it may be hard to determine which version of the image is running. When using variable tags such as v1.0 (which may refer to v1.0.0 or v1.0.1) consider using imagePullPolicy: Always to ensure that the an out-of-date cached image is not used. The latest tag automatically uses imagePullPolicy: Always instead of the default imagePullPolicy: IfNotPresent."), - Notes = new TextBlock(@"Test that [isIgnored]. + Notes = new TextBlock(@"Test that [isIgnored]. { ""type"": ""Microsoft.Network/virtualNetworks"", @@ -117,55 +117,54 @@ private static RuleDocument GetExpected_en() ""location"": ""[parameters('location')]"", ""properties"": {} }"), - Links = links.ToArray() - }; - return result; - } + Links = links.ToArray() + }; + return result; + } - private static RuleDocument GetExpected_es() - { - var annotations = new Hashtable - { - ["severity"] = "Critico", - ["category"] = "Autenticación" - }; - - var links = new List - { - new Link { Name = "Uso de la autenticación basada en identidad", Uri = "https://docs.microsoft.com/azure/architecture/framework/security/design-identity-authentication#use-identity-based-authentication" }, - new Link { Name = "Autenticación con un registro de contenedor de Azure", Uri = "https://docs.microsoft.com/azure/container-registry/container-registry-authentication?tabs=azure-cli" } - }; - - var result = new RuleDocument(name: "Deshabilitar el usuario adminstrador para ACR") - { - Synopsis = new InfoString("Usar identidades de Azure AD en lugar de usar el usuario administrador del registro."), - Description = new InfoString(@"Azure Container Registry (ACR) incluye una cuenta de usuario administrador incorporada. La cuenta de usuario administrador es una cuenta de usuario única con acceso administrativo al registro. Esta cuenta proporciona acceso de usuario único para pruebas y desarrollo tempranos. La cuenta de usuario administrador no está diseñada para usarse con registros de contenedores de producción. -En su lugar, utilice el control de acceso basado en roles (RBAC). RBAC se puede usar para delegar permisos de registro a una identidad de Azure AD (AAD)."), - Annotations = ResourceTags.FromHashtable(annotations), - Recommendation = new InfoString(@"Considere deshabilitar la cuenta de usuario administrador y solo use la autenticación basada en identidad para las operaciones de registro."), - Links = links.ToArray() - }; - return result; - } - - private static RuleDocument GetDocument(TokenStream stream, string culture) + private static RuleDocument GetExpected_es() + { + var annotations = new Hashtable { - var lexer = new RuleHelpLexer(culture); - return lexer.Process(stream); - } + ["severity"] = "Critico", + ["category"] = "Autenticación" + }; - private static TokenStream GetToken(bool nx, string suffix) + var links = new List { - var reader = new MarkdownReader(yamlHeaderOnly: false); - var content = GetMarkdownContent(suffix); - content = nx ? content.Replace("\r\n", "\n") : content.Replace("\r\n", "\n").Replace("\n", "\r\n"); - return reader.Read(content, Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "RuleDocument.md")); - } + new() { Name = "Uso de la autenticación basada en identidad", Uri = "https://docs.microsoft.com/azure/architecture/framework/security/design-identity-authentication#use-identity-based-authentication" }, + new() { Name = "Autenticación con un registro de contenedor de Azure", Uri = "https://docs.microsoft.com/azure/container-registry/container-registry-authentication?tabs=azure-cli" } + }; - private static string GetMarkdownContent(string suffix) + var result = new RuleDocument(name: "Deshabilitar el usuario adminstrador para ACR") { - var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, $"RuleDocument{suffix}.md"); - return File.ReadAllText(path); - } + Synopsis = new InfoString("Usar identidades de Azure AD en lugar de usar el usuario administrador del registro."), + Description = new InfoString(@"Azure Container Registry (ACR) incluye una cuenta de usuario administrador incorporada. La cuenta de usuario administrador es una cuenta de usuario única con acceso administrativo al registro. Esta cuenta proporciona acceso de usuario único para pruebas y desarrollo tempranos. La cuenta de usuario administrador no está diseñada para usarse con registros de contenedores de producción. +En su lugar, utilice el control de acceso basado en roles (RBAC). RBAC se puede usar para delegar permisos de registro a una identidad de Azure AD (AAD)."), + Annotations = ResourceTags.FromHashtable(annotations), + Recommendation = new InfoString(@"Considere deshabilitar la cuenta de usuario administrador y solo use la autenticación basada en identidad para las operaciones de registro."), + Links = links.ToArray() + }; + return result; + } + + private static RuleDocument GetDocument(TokenStream stream, string culture) + { + var lexer = new RuleHelpLexer(culture); + return lexer.Process(stream); + } + + private static TokenStream GetToken(bool nx, string suffix) + { + var reader = new MarkdownReader(yamlHeaderOnly: false); + var content = GetMarkdownContent(suffix); + content = nx ? content.Replace("\r\n", "\n") : content.Replace("\r\n", "\n").Replace("\n", "\r\n"); + return reader.Read(content, Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "RuleDocument.md")); + } + + private static string GetMarkdownContent(string suffix) + { + var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, $"RuleDocument{suffix}.md"); + return File.ReadAllText(path); } } diff --git a/tests/PSRule.Tests/RuleFilterTests.cs b/tests/PSRule.Tests/RuleFilterTests.cs index a9b17794a1..1f0b8769d3 100644 --- a/tests/PSRule.Tests/RuleFilterTests.cs +++ b/tests/PSRule.Tests/RuleFilterTests.cs @@ -5,77 +5,76 @@ using PSRule.Definitions; using PSRule.Definitions.Rules; -namespace PSRule +namespace PSRule; + +/// +/// Define tests to validate . +/// +public sealed class RuleFilterTests { - /// - /// Define tests to validate . - /// - public sealed class RuleFilterTests + [Fact] + public void MatchInclude() { - [Fact] - public void MatchInclude() - { - var filter = new RuleFilter(new string[] { "rule1", "rule2" }, null, null, null, null); - Assert.True(filter.Match("rule1", null, null)); - Assert.True(filter.Match("Rule2", null, null)); - Assert.False(filter.Match("rule3", null, null)); - } + var filter = new RuleFilter(new string[] { "rule1", "rule2" }, null, null, null, null); + Assert.True(filter.Match("rule1", null, null)); + Assert.True(filter.Match("Rule2", null, null)); + Assert.False(filter.Match("rule3", null, null)); + } - [Fact] - public void MatchExclude() - { - var filter = new RuleFilter(null, null, new string[] { "rule3" }, null, null); - Assert.True(filter.Match("rule1", null, null)); - Assert.True(filter.Match("rule2", null, null)); - Assert.False(filter.Match("Rule3", null, null)); - } + [Fact] + public void MatchExclude() + { + var filter = new RuleFilter(null, null, new string[] { "rule3" }, null, null); + Assert.True(filter.Match("rule1", null, null)); + Assert.True(filter.Match("rule2", null, null)); + Assert.False(filter.Match("Rule3", null, null)); + } - [Fact] - public void MatchTag() + [Fact] + public void MatchTag() + { + // Set resource tags + var resourceTags = new Hashtable(); + + // Create a filter with category equal to group 1 or group 2. + var tag = new Hashtable { - // Set resource tags - var resourceTags = new Hashtable(); + ["category"] = new string[] { "group1", "group2" } + }; + var filter = new RuleFilter(null, tag, null, null, null); - // Create a filter with category equal to group 1 or group 2. - var tag = new Hashtable - { - ["category"] = new string[] { "group1", "group2" } - }; - var filter = new RuleFilter(null, tag, null, null, null); + // Check basic match + resourceTags["category"] = "group2"; + Assert.True(filter.Match("rule", ResourceTags.FromHashtable(resourceTags), null)); + resourceTags["category"] = "group1"; + Assert.True(filter.Match("rule", ResourceTags.FromHashtable(resourceTags), null)); + resourceTags["category"] = "group3"; + Assert.False(filter.Match("rule", ResourceTags.FromHashtable(resourceTags), null)); + Assert.False(filter.Match("rule", null, null)); + } - // Check basic match - resourceTags["category"] = "group2"; - Assert.True(filter.Match("rule", ResourceTags.FromHashtable(resourceTags), null)); - resourceTags["category"] = "group1"; - Assert.True(filter.Match("rule", ResourceTags.FromHashtable(resourceTags), null)); - resourceTags["category"] = "group3"; - Assert.False(filter.Match("rule", ResourceTags.FromHashtable(resourceTags), null)); - Assert.False(filter.Match("rule", null, null)); - } + [Fact] + public void MatchLabels() + { + // Set resource tags + var resourceLabels = new Hashtable(); - [Fact] - public void MatchLabels() + // Create a filter + var labels = new ResourceLabels { - // Set resource tags - var resourceLabels = new Hashtable(); - - // Create a filter - var labels = new ResourceLabels - { - ["framework.v1/control"] = new string[] { "c-1", "c-2" } - }; - var filter = new RuleFilter(null, null, null, null, labels); + ["framework.v1/control"] = new string[] { "c-1", "c-2" } + }; + var filter = new RuleFilter(null, null, null, null, labels); - resourceLabels["framework.v1/control"] = new string[] { "c-2", "c-1" }; - Assert.True(filter.Match("rule", null, ResourceLabels.FromHashtable(resourceLabels))); - resourceLabels["framework.v1/control"] = new string[] { "c-3", "c-1" }; - Assert.True(filter.Match("rule", null, ResourceLabels.FromHashtable(resourceLabels))); - resourceLabels["framework.v1/control"] = new string[] { "c-1", "c-3" }; - Assert.True(filter.Match("rule", null, ResourceLabels.FromHashtable(resourceLabels))); - resourceLabels["framework.v1/control"] = new string[] { "c-3", "c-4" }; - Assert.False(filter.Match("rule", null, ResourceLabels.FromHashtable(resourceLabels))); - resourceLabels["framework.v1/control"] = System.Array.Empty(); - Assert.False(filter.Match("rule", null, ResourceLabels.FromHashtable(resourceLabels))); - } + resourceLabels["framework.v1/control"] = new string[] { "c-2", "c-1" }; + Assert.True(filter.Match("rule", null, ResourceLabels.FromHashtable(resourceLabels))); + resourceLabels["framework.v1/control"] = new string[] { "c-3", "c-1" }; + Assert.True(filter.Match("rule", null, ResourceLabels.FromHashtable(resourceLabels))); + resourceLabels["framework.v1/control"] = new string[] { "c-1", "c-3" }; + Assert.True(filter.Match("rule", null, ResourceLabels.FromHashtable(resourceLabels))); + resourceLabels["framework.v1/control"] = new string[] { "c-3", "c-4" }; + Assert.False(filter.Match("rule", null, ResourceLabels.FromHashtable(resourceLabels))); + resourceLabels["framework.v1/control"] = System.Array.Empty(); + Assert.False(filter.Match("rule", null, ResourceLabels.FromHashtable(resourceLabels))); } } diff --git a/tests/PSRule.Tests/RuleLanguageAstTests.cs b/tests/PSRule.Tests/RuleLanguageAstTests.cs index 0ca2862fd3..d3208cf546 100644 --- a/tests/PSRule.Tests/RuleLanguageAstTests.cs +++ b/tests/PSRule.Tests/RuleLanguageAstTests.cs @@ -5,27 +5,26 @@ using System.IO; using PSRule.Host; -namespace PSRule +namespace PSRule; + +public sealed class RuleLanguageAstTests { - public sealed class RuleLanguageAstTests + [Fact] + public void RuleName() { - [Fact] - public void RuleName() - { - var scriptAst = System.Management.Automation.Language.Parser.ParseFile(GetSourcePath("FromFileName.Rule.ps1"), out _, out _); - var visitor = new RuleLanguageAst(); - scriptAst.Visit(visitor); - - Assert.Equal("PSRule.Parse.InvalidResourceName", visitor.Errors[0].FullyQualifiedErrorId); - } + var scriptAst = System.Management.Automation.Language.Parser.ParseFile(GetSourcePath("FromFileName.Rule.ps1"), out _, out _); + var visitor = new RuleLanguageAst(); + scriptAst.Visit(visitor); - #region Helper methods + Assert.Equal("PSRule.Parse.InvalidResourceName", visitor.Errors[0].FullyQualifiedErrorId); + } - private static string GetSourcePath(string fileName) - { - return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName); - } + #region Helper methods - #endregion Helper methods + private static string GetSourcePath(string fileName) + { + return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName); } + + #endregion Helper methods } diff --git a/tests/PSRule.Tests/RulesTests.cs b/tests/PSRule.Tests/RulesTests.cs index 6fc01ed52f..d8ac191bd8 100644 --- a/tests/PSRule.Tests/RulesTests.cs +++ b/tests/PSRule.Tests/RulesTests.cs @@ -12,379 +12,378 @@ using PSRule.Rules; using PSRule.Runtime; -namespace PSRule +namespace PSRule; + +public sealed class RulesTests { - public sealed class RulesTests + #region Yaml rules + + /// + /// Test that a YAML-based rule can be parsed. + /// + [Fact] + public void ReadYamlRule() + { + var context = new RunspaceContext(PipelineContext.New(GetOption(), null, null, null, null, null, new OptionContextBuilder(), null), new TestWriter(GetOption())); + context.Init(GetSource()); + context.Begin(); + + // From current path + var rule = HostHelper.GetRule(GetSource(), context, includeDependencies: false); + Assert.NotNull(rule); + Assert.Equal("YamlBasicRule", rule[0].Name); + Assert.Equal(Environment.GetRootedPath(""), rule[0].Source.HelpPath); + Assert.Equal(10, rule[0].Extent.Line); + + // From relative path + rule = HostHelper.GetRule(GetSource("../../../FromFile.Rule.yaml"), context, includeDependencies: false); + Assert.NotNull(rule); + Assert.Equal("YamlBasicRule", rule[0].Name); + Assert.Equal(Environment.GetRootedPath("../../.."), rule[0].Source.HelpPath); + + var hashtable = rule[0].Tag.ToHashtable(); + Assert.Equal("tag", hashtable["feature"]); + + var block = HostHelper.GetRuleBlockGraph(GetSource(), context).GetAll(); + var actual = block.FirstOrDefault(b => b.Name == "YamlBasicRule"); + Assert.NotNull(actual.Info.Annotations); + Assert.Equal("test123", actual.Info.Annotations["test_value"]); + Assert.Equal("Basic YAML rule", actual.Info.DisplayName); + Assert.Equal("This is a description of a basic rule.", actual.Info.Description); + Assert.Equal("A YAML rule recommendation for testing.", actual.Info.Recommendation); + Assert.Equal("https://aka.ms/ps-rule", actual.Info.GetOnlineHelpUrl()); + } + + /// + /// Test that a YAML-based rule with sub-selectors can be parsed. + /// + [Fact] + public void ReadYamlSubSelectorRule() + { + 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(); + + // From current path + var rule = HostHelper.GetRule(GetSource("FromFileSubSelector.Rule.yaml"), context, includeDependencies: false); + Assert.NotNull(rule); + Assert.Equal("YamlRuleWithPrecondition", rule[0].Name); + Assert.Equal("YamlRuleWithSubselector", rule[1].Name); + Assert.Equal("YamlRuleWithSubselectorReordered", rule[2].Name); + Assert.Equal("YamlRuleWithQuantifier", rule[3].Name); + + context.Init(GetSource("FromFileSubSelector.Rule.yaml")); + context.Begin(); + var subselector1 = GetRuleVisitor(context, "YamlRuleWithPrecondition", GetSource("FromFileSubSelector.Rule.yaml")); + var subselector2 = GetRuleVisitor(context, "YamlRuleWithSubselector", GetSource("FromFileSubSelector.Rule.yaml")); + var subselector3 = GetRuleVisitor(context, "YamlRuleWithSubselectorReordered", GetSource("FromFileSubSelector.Rule.yaml")); + var subselector4 = GetRuleVisitor(context, "YamlRuleWithQuantifier", GetSource("FromFileSubSelector.Rule.yaml")); + context.EnterLanguageScope(subselector1.Source); + + var actual1 = GetObject((name: "kind", value: "test"), (name: "resources", value: new string[] { "abc", "abc" })); + var actual2 = GetObject((name: "resources", value: new string[] { "abc", "123", "abc" })); + + // YamlRuleWithPrecondition + context.EnterTargetObject(actual1); + context.EnterRuleBlock(subselector1); + Assert.True(subselector1.Condition.If().AllOf()); + + context.EnterTargetObject(actual2); + context.EnterRuleBlock(subselector1); + Assert.True(subselector1.Condition.If().Skipped()); + + // YamlRuleWithSubselector + context.EnterTargetObject(actual1); + context.EnterRuleBlock(subselector2); + Assert.True(subselector2.Condition.If().AllOf()); + + context.EnterTargetObject(actual2); + context.EnterRuleBlock(subselector2); + Assert.False(subselector2.Condition.If().AllOf()); + + // YamlRuleWithSubselectorReordered + context.EnterTargetObject(actual1); + context.EnterRuleBlock(subselector3); + Assert.True(subselector3.Condition.If().AllOf()); + + context.EnterTargetObject(actual2); + context.EnterRuleBlock(subselector3); + Assert.True(subselector3.Condition.If().AllOf()); + + // YamlRuleWithQuantifier + var fromFile = GetObjectAsTarget("ObjectFromFile3.json"); + actual1 = fromFile[0]; + actual2 = fromFile[1]; + var actual3 = fromFile[2]; + + context.EnterTargetObject(actual1); + context.EnterRuleBlock(subselector4); + Assert.True(subselector4.Condition.If().AllOf()); + + context.EnterTargetObject(actual2); + context.EnterRuleBlock(subselector4); + Assert.False(subselector4.Condition.If().AllOf()); + + context.EnterTargetObject(actual3); + context.EnterRuleBlock(subselector4); + Assert.True(subselector4.Condition.If().AllOf()); + } + + [Fact] + public void EvaluateYamlRule() + { + 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); + var yamlTrue = GetRuleVisitor(context, "RuleYamlTrue"); + var yamlFalse = GetRuleVisitor(context, "RuleYamlFalse"); + var customType = GetRuleVisitor(context, "RuleYamlWithCustomType"); + var withSelector = GetRuleVisitor(context, "RuleYamlWithSelector"); + context.EnterLanguageScope(yamlTrue.Source); + + var actual1 = GetObject((name: "value", value: 3)); + var actual2 = GetObject((name: "notValue", value: 3)); + var actual3 = GetObject((name: "value", value: 4)); + actual3.Value.TypeNames.Insert(0, "CustomType"); + + context.EnterTargetObject(actual1); + context.EnterRuleBlock(yamlTrue); + Assert.True(yamlTrue.Condition.If().AllOf()); + context.EnterRuleBlock(yamlFalse); + Assert.False(yamlFalse.Condition.If().AllOf()); + + context.EnterTargetObject(actual2); + context.EnterRuleBlock(yamlTrue); + Assert.False(yamlTrue.Condition.If().AllOf()); + context.EnterRuleBlock(yamlFalse); + Assert.False(yamlFalse.Condition.If().AllOf()); + + context.EnterTargetObject(actual3); + context.EnterRuleBlock(yamlTrue); + Assert.False(yamlTrue.Condition.If().AllOf()); + context.EnterRuleBlock(yamlFalse); + Assert.True(yamlFalse.Condition.If().AllOf()); + + // With type pre-condition + context.EnterTargetObject(actual1); + context.EnterRuleBlock(customType); + Assert.Null(customType.Condition.If()); + + context.EnterTargetObject(actual2); + context.EnterRuleBlock(customType); + Assert.Null(customType.Condition.If()); + + context.EnterTargetObject(actual3); + context.EnterRuleBlock(customType); + Assert.NotNull(customType.Condition.If()); + + // With selector pre-condition + context.EnterTargetObject(actual1); + context.EnterRuleBlock(withSelector); + Assert.Null(withSelector.Condition.If()); + + context.EnterTargetObject(actual2); + context.EnterRuleBlock(withSelector); + Assert.NotNull(withSelector.Condition.If()); + + context.EnterTargetObject(actual3); + context.EnterRuleBlock(withSelector); + Assert.Null(withSelector.Condition.If()); + } + + [Fact] + public void RuleWithObjectPath() + { + 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); + var yamlObjectPath = GetRuleVisitor(context, "YamlObjectPath"); + context.EnterLanguageScope(yamlObjectPath.Source); + + var actual = GetObject(GetSourcePath("ObjectFromFile3.json")); + + context.EnterTargetObject(new TargetObject(new PSObject(actual[0]))); + context.EnterRuleBlock(yamlObjectPath); + Assert.True(yamlObjectPath.Condition.If().AllOf()); + + context.EnterTargetObject(new TargetObject(new PSObject(actual[1]))); + context.EnterRuleBlock(yamlObjectPath); + Assert.False(yamlObjectPath.Condition.If().AllOf()); + + context.EnterTargetObject(new TargetObject(new PSObject(actual[2]))); + context.EnterRuleBlock(yamlObjectPath); + Assert.True(yamlObjectPath.Condition.If().AllOf()); + } + + #endregion Yaml rules + + #region Json rules + + /// + /// Test that a JSON-based rule can be parsed. + /// + [Fact] + public void ReadJsonRule() + { + var context = new RunspaceContext(PipelineContext.New(GetOption(), null, null, null, null, null, new OptionContextBuilder(), null), new TestWriter(GetOption())); + context.Init(GetSource()); + context.Begin(); + + // From current path + var rule = HostHelper.GetRule(GetSource("FromFile.Rule.jsonc"), context, includeDependencies: false); + Assert.NotNull(rule); + Assert.Equal("JsonBasicRule", rule[0].Name); + Assert.Equal(Environment.GetRootedPath(""), rule[0].Source.HelpPath); + Assert.Equal(7, rule[0].Extent.Line); + + // From relative path + rule = HostHelper.GetRule(GetSource("../../../FromFile.Rule.jsonc"), context, includeDependencies: false); + Assert.NotNull(rule); + Assert.Equal("JsonBasicRule", rule[0].Name); + Assert.Equal(Environment.GetRootedPath("../../.."), rule[0].Source.HelpPath); + + var hashtable = rule[0].Tag.ToHashtable(); + Assert.Equal("tag", hashtable["feature"]); + + var block = HostHelper.GetRuleBlockGraph(GetSource("FromFile.Rule.jsonc"), context).GetAll(); + var actual = block.FirstOrDefault(b => b.Name == "JsonBasicRule"); + Assert.NotNull(actual.Info.Annotations); + Assert.Equal("test123", actual.Info.Annotations["test_value"]); + Assert.Equal("Basic JSON rule", actual.Info.DisplayName); + Assert.Equal("This is a description of a basic rule.", actual.Info.Description); + Assert.Equal("A JSON rule recommendation for testing.", actual.Info.Recommendation); + Assert.Equal("https://aka.ms/ps-rule", actual.Info.GetOnlineHelpUrl()); + } + + /// + /// Test that a JSON-based rule with sub-selectors can be parsed. + /// + [Fact] + public void ReadJsonSubSelectorRule() + { + 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(); + + // From current path + var rule = HostHelper.GetRule(GetSource("FromFileSubSelector.Rule.jsonc"), context, includeDependencies: false); + Assert.NotNull(rule); + Assert.Equal("JsonRuleWithPrecondition", rule[0].Name); + Assert.Equal("JsonRuleWithSubselector", rule[1].Name); + Assert.Equal("JsonRuleWithSubselectorReordered", rule[2].Name); + Assert.Equal("JsonRuleWithQuantifier", rule[3].Name); + + context.Init(GetSource("FromFileSubSelector.Rule.yaml")); + context.Begin(); + var subselector1 = GetRuleVisitor(context, "JsonRuleWithPrecondition", GetSource("FromFileSubSelector.Rule.jsonc")); + var subselector2 = GetRuleVisitor(context, "JsonRuleWithSubselector", GetSource("FromFileSubSelector.Rule.jsonc")); + var subselector3 = GetRuleVisitor(context, "JsonRuleWithSubselectorReordered", GetSource("FromFileSubSelector.Rule.jsonc")); + var subselector4 = GetRuleVisitor(context, "JsonRuleWithQuantifier", GetSource("FromFileSubSelector.Rule.jsonc")); + context.EnterLanguageScope(subselector1.Source); + + var actual1 = GetObject((name: "kind", value: "test"), (name: "resources", value: new string[] { "abc", "abc" })); + var actual2 = GetObject((name: "resources", value: new string[] { "abc", "123", "abc" })); + + // JsonRuleWithPrecondition + context.EnterTargetObject(actual1); + context.EnterRuleBlock(subselector1); + Assert.True(subselector1.Condition.If().AllOf()); + + context.EnterTargetObject(actual2); + context.EnterRuleBlock(subselector1); + Assert.True(subselector1.Condition.If().Skipped()); + + // JsonRuleWithSubselector + context.EnterTargetObject(actual1); + context.EnterRuleBlock(subselector2); + Assert.True(subselector2.Condition.If().AllOf()); + + context.EnterTargetObject(actual2); + context.EnterRuleBlock(subselector2); + Assert.False(subselector2.Condition.If().AllOf()); + + // JsonRuleWithSubselectorReordered + context.EnterTargetObject(actual1); + context.EnterRuleBlock(subselector3); + Assert.True(subselector3.Condition.If().AllOf()); + + context.EnterTargetObject(actual2); + context.EnterRuleBlock(subselector3); + Assert.True(subselector3.Condition.If().AllOf()); + + // JsonRuleWithQuantifier + var fromFile = GetObjectAsTarget("ObjectFromFile3.json"); + actual1 = fromFile[0]; + actual2 = fromFile[1]; + var actual3 = fromFile[2]; + + context.EnterTargetObject(actual1); + context.EnterRuleBlock(subselector4); + Assert.True(subselector4.Condition.If().AllOf()); + + context.EnterTargetObject(actual2); + context.EnterRuleBlock(subselector4); + Assert.False(subselector4.Condition.If().AllOf()); + + context.EnterTargetObject(actual3); + context.EnterRuleBlock(subselector4); + Assert.True(subselector4.Condition.If().AllOf()); + } + + #endregion Json rules + + #region Helper methods + + private static PSRuleOption GetOption() { - #region Yaml rules - - /// - /// Test that a YAML-based rule can be parsed. - /// - [Fact] - public void ReadYamlRule() - { - var context = new RunspaceContext(PipelineContext.New(GetOption(), null, null, null, null, null, new OptionContextBuilder(), null), new TestWriter(GetOption())); - context.Init(GetSource()); - context.Begin(); - - // From current path - var rule = HostHelper.GetRule(GetSource(), context, includeDependencies: false); - Assert.NotNull(rule); - Assert.Equal("YamlBasicRule", rule[0].Name); - Assert.Equal(Environment.GetRootedPath(""), rule[0].Source.HelpPath); - Assert.Equal(10, rule[0].Extent.Line); - - // From relative path - rule = HostHelper.GetRule(GetSource("../../../FromFile.Rule.yaml"), context, includeDependencies: false); - Assert.NotNull(rule); - Assert.Equal("YamlBasicRule", rule[0].Name); - Assert.Equal(Environment.GetRootedPath("../../.."), rule[0].Source.HelpPath); - - var hashtable = rule[0].Tag.ToHashtable(); - Assert.Equal("tag", hashtable["feature"]); - - var block = HostHelper.GetRuleBlockGraph(GetSource(), context).GetAll(); - var actual = block.FirstOrDefault(b => b.Name == "YamlBasicRule"); - Assert.NotNull(actual.Info.Annotations); - Assert.Equal("test123", actual.Info.Annotations["test_value"]); - Assert.Equal("Basic YAML rule", actual.Info.DisplayName); - Assert.Equal("This is a description of a basic rule.", actual.Info.Description); - Assert.Equal("A YAML rule recommendation for testing.", actual.Info.Recommendation); - Assert.Equal("https://aka.ms/ps-rule", actual.Info.GetOnlineHelpUrl()); - } - - /// - /// Test that a YAML-based rule with sub-selectors can be parsed. - /// - [Fact] - public void ReadYamlSubSelectorRule() - { - 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(); - - // From current path - var rule = HostHelper.GetRule(GetSource("FromFileSubSelector.Rule.yaml"), context, includeDependencies: false); - Assert.NotNull(rule); - Assert.Equal("YamlRuleWithPrecondition", rule[0].Name); - Assert.Equal("YamlRuleWithSubselector", rule[1].Name); - Assert.Equal("YamlRuleWithSubselectorReordered", rule[2].Name); - Assert.Equal("YamlRuleWithQuantifier", rule[3].Name); - - context.Init(GetSource("FromFileSubSelector.Rule.yaml")); - context.Begin(); - var subselector1 = GetRuleVisitor(context, "YamlRuleWithPrecondition", GetSource("FromFileSubSelector.Rule.yaml")); - var subselector2 = GetRuleVisitor(context, "YamlRuleWithSubselector", GetSource("FromFileSubSelector.Rule.yaml")); - var subselector3 = GetRuleVisitor(context, "YamlRuleWithSubselectorReordered", GetSource("FromFileSubSelector.Rule.yaml")); - var subselector4 = GetRuleVisitor(context, "YamlRuleWithQuantifier", GetSource("FromFileSubSelector.Rule.yaml")); - context.EnterLanguageScope(subselector1.Source); - - var actual1 = GetObject((name: "kind", value: "test"), (name: "resources", value: new string[] { "abc", "abc" })); - var actual2 = GetObject((name: "resources", value: new string[] { "abc", "123", "abc" })); - - // YamlRuleWithPrecondition - context.EnterTargetObject(actual1); - context.EnterRuleBlock(subselector1); - Assert.True(subselector1.Condition.If().AllOf()); - - context.EnterTargetObject(actual2); - context.EnterRuleBlock(subselector1); - Assert.True(subselector1.Condition.If().Skipped()); - - // YamlRuleWithSubselector - context.EnterTargetObject(actual1); - context.EnterRuleBlock(subselector2); - Assert.True(subselector2.Condition.If().AllOf()); - - context.EnterTargetObject(actual2); - context.EnterRuleBlock(subselector2); - Assert.False(subselector2.Condition.If().AllOf()); - - // YamlRuleWithSubselectorReordered - context.EnterTargetObject(actual1); - context.EnterRuleBlock(subselector3); - Assert.True(subselector3.Condition.If().AllOf()); - - context.EnterTargetObject(actual2); - context.EnterRuleBlock(subselector3); - Assert.True(subselector3.Condition.If().AllOf()); - - // YamlRuleWithQuantifier - var fromFile = GetObjectAsTarget("ObjectFromFile3.json"); - actual1 = fromFile[0]; - actual2 = fromFile[1]; - var actual3 = fromFile[2]; - - context.EnterTargetObject(actual1); - context.EnterRuleBlock(subselector4); - Assert.True(subselector4.Condition.If().AllOf()); - - context.EnterTargetObject(actual2); - context.EnterRuleBlock(subselector4); - Assert.False(subselector4.Condition.If().AllOf()); - - context.EnterTargetObject(actual3); - context.EnterRuleBlock(subselector4); - Assert.True(subselector4.Condition.If().AllOf()); - } - - [Fact] - public void EvaluateYamlRule() - { - 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); - var yamlTrue = GetRuleVisitor(context, "RuleYamlTrue"); - var yamlFalse = GetRuleVisitor(context, "RuleYamlFalse"); - var customType = GetRuleVisitor(context, "RuleYamlWithCustomType"); - var withSelector = GetRuleVisitor(context, "RuleYamlWithSelector"); - context.EnterLanguageScope(yamlTrue.Source); - - var actual1 = GetObject((name: "value", value: 3)); - var actual2 = GetObject((name: "notValue", value: 3)); - var actual3 = GetObject((name: "value", value: 4)); - actual3.Value.TypeNames.Insert(0, "CustomType"); - - context.EnterTargetObject(actual1); - context.EnterRuleBlock(yamlTrue); - Assert.True(yamlTrue.Condition.If().AllOf()); - context.EnterRuleBlock(yamlFalse); - Assert.False(yamlFalse.Condition.If().AllOf()); - - context.EnterTargetObject(actual2); - context.EnterRuleBlock(yamlTrue); - Assert.False(yamlTrue.Condition.If().AllOf()); - context.EnterRuleBlock(yamlFalse); - Assert.False(yamlFalse.Condition.If().AllOf()); - - context.EnterTargetObject(actual3); - context.EnterRuleBlock(yamlTrue); - Assert.False(yamlTrue.Condition.If().AllOf()); - context.EnterRuleBlock(yamlFalse); - Assert.True(yamlFalse.Condition.If().AllOf()); - - // With type pre-condition - context.EnterTargetObject(actual1); - context.EnterRuleBlock(customType); - Assert.Null(customType.Condition.If()); - - context.EnterTargetObject(actual2); - context.EnterRuleBlock(customType); - Assert.Null(customType.Condition.If()); - - context.EnterTargetObject(actual3); - context.EnterRuleBlock(customType); - Assert.NotNull(customType.Condition.If()); - - // With selector pre-condition - context.EnterTargetObject(actual1); - context.EnterRuleBlock(withSelector); - Assert.Null(withSelector.Condition.If()); - - context.EnterTargetObject(actual2); - context.EnterRuleBlock(withSelector); - Assert.NotNull(withSelector.Condition.If()); - - context.EnterTargetObject(actual3); - context.EnterRuleBlock(withSelector); - Assert.Null(withSelector.Condition.If()); - } - - [Fact] - public void RuleWithObjectPath() - { - 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); - var yamlObjectPath = GetRuleVisitor(context, "YamlObjectPath"); - context.EnterLanguageScope(yamlObjectPath.Source); - - var actual = GetObject(GetSourcePath("ObjectFromFile3.json")); - - context.EnterTargetObject(new TargetObject(new PSObject(actual[0]))); - context.EnterRuleBlock(yamlObjectPath); - Assert.True(yamlObjectPath.Condition.If().AllOf()); - - context.EnterTargetObject(new TargetObject(new PSObject(actual[1]))); - context.EnterRuleBlock(yamlObjectPath); - Assert.False(yamlObjectPath.Condition.If().AllOf()); - - context.EnterTargetObject(new TargetObject(new PSObject(actual[2]))); - context.EnterRuleBlock(yamlObjectPath); - Assert.True(yamlObjectPath.Condition.If().AllOf()); - } - - #endregion Yaml rules - - #region Json rules - - /// - /// Test that a JSON-based rule can be parsed. - /// - [Fact] - public void ReadJsonRule() - { - var context = new RunspaceContext(PipelineContext.New(GetOption(), null, null, null, null, null, new OptionContextBuilder(), null), new TestWriter(GetOption())); - context.Init(GetSource()); - context.Begin(); - - // From current path - var rule = HostHelper.GetRule(GetSource("FromFile.Rule.jsonc"), context, includeDependencies: false); - Assert.NotNull(rule); - Assert.Equal("JsonBasicRule", rule[0].Name); - Assert.Equal(Environment.GetRootedPath(""), rule[0].Source.HelpPath); - Assert.Equal(7, rule[0].Extent.Line); - - // From relative path - rule = HostHelper.GetRule(GetSource("../../../FromFile.Rule.jsonc"), context, includeDependencies: false); - Assert.NotNull(rule); - Assert.Equal("JsonBasicRule", rule[0].Name); - Assert.Equal(Environment.GetRootedPath("../../.."), rule[0].Source.HelpPath); - - var hashtable = rule[0].Tag.ToHashtable(); - Assert.Equal("tag", hashtable["feature"]); - - var block = HostHelper.GetRuleBlockGraph(GetSource("FromFile.Rule.jsonc"), context).GetAll(); - var actual = block.FirstOrDefault(b => b.Name == "JsonBasicRule"); - Assert.NotNull(actual.Info.Annotations); - Assert.Equal("test123", actual.Info.Annotations["test_value"]); - Assert.Equal("Basic JSON rule", actual.Info.DisplayName); - Assert.Equal("This is a description of a basic rule.", actual.Info.Description); - Assert.Equal("A JSON rule recommendation for testing.", actual.Info.Recommendation); - Assert.Equal("https://aka.ms/ps-rule", actual.Info.GetOnlineHelpUrl()); - } - - /// - /// Test that a JSON-based rule with sub-selectors can be parsed. - /// - [Fact] - public void ReadJsonSubSelectorRule() - { - 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(); - - // From current path - var rule = HostHelper.GetRule(GetSource("FromFileSubSelector.Rule.jsonc"), context, includeDependencies: false); - Assert.NotNull(rule); - Assert.Equal("JsonRuleWithPrecondition", rule[0].Name); - Assert.Equal("JsonRuleWithSubselector", rule[1].Name); - Assert.Equal("JsonRuleWithSubselectorReordered", rule[2].Name); - Assert.Equal("JsonRuleWithQuantifier", rule[3].Name); - - context.Init(GetSource("FromFileSubSelector.Rule.yaml")); - context.Begin(); - var subselector1 = GetRuleVisitor(context, "JsonRuleWithPrecondition", GetSource("FromFileSubSelector.Rule.jsonc")); - var subselector2 = GetRuleVisitor(context, "JsonRuleWithSubselector", GetSource("FromFileSubSelector.Rule.jsonc")); - var subselector3 = GetRuleVisitor(context, "JsonRuleWithSubselectorReordered", GetSource("FromFileSubSelector.Rule.jsonc")); - var subselector4 = GetRuleVisitor(context, "JsonRuleWithQuantifier", GetSource("FromFileSubSelector.Rule.jsonc")); - context.EnterLanguageScope(subselector1.Source); - - var actual1 = GetObject((name: "kind", value: "test"), (name: "resources", value: new string[] { "abc", "abc" })); - var actual2 = GetObject((name: "resources", value: new string[] { "abc", "123", "abc" })); - - // JsonRuleWithPrecondition - context.EnterTargetObject(actual1); - context.EnterRuleBlock(subselector1); - Assert.True(subselector1.Condition.If().AllOf()); - - context.EnterTargetObject(actual2); - context.EnterRuleBlock(subselector1); - Assert.True(subselector1.Condition.If().Skipped()); - - // JsonRuleWithSubselector - context.EnterTargetObject(actual1); - context.EnterRuleBlock(subselector2); - Assert.True(subselector2.Condition.If().AllOf()); - - context.EnterTargetObject(actual2); - context.EnterRuleBlock(subselector2); - Assert.False(subselector2.Condition.If().AllOf()); - - // JsonRuleWithSubselectorReordered - context.EnterTargetObject(actual1); - context.EnterRuleBlock(subselector3); - Assert.True(subselector3.Condition.If().AllOf()); - - context.EnterTargetObject(actual2); - context.EnterRuleBlock(subselector3); - Assert.True(subselector3.Condition.If().AllOf()); - - // JsonRuleWithQuantifier - var fromFile = GetObjectAsTarget("ObjectFromFile3.json"); - actual1 = fromFile[0]; - actual2 = fromFile[1]; - var actual3 = fromFile[2]; - - context.EnterTargetObject(actual1); - context.EnterRuleBlock(subselector4); - Assert.True(subselector4.Condition.If().AllOf()); - - context.EnterTargetObject(actual2); - context.EnterRuleBlock(subselector4); - Assert.False(subselector4.Condition.If().AllOf()); - - context.EnterTargetObject(actual3); - context.EnterRuleBlock(subselector4); - Assert.True(subselector4.Condition.If().AllOf()); - } - - #endregion Json rules - - #region Helper methods - - private static PSRuleOption GetOption() - { - return new PSRuleOption(); - } - - private static Source[] GetSource(string path = "FromFile.Rule.yaml") - { - var builder = new SourcePipelineBuilder(null, null); - builder.Directory(GetSourcePath(path)); - return builder.Build(); - } - - private static TargetObject GetObject(params (string name, object value)[] properties) - { - var result = new PSObject(); - for (var i = 0; properties != null && i < properties.Length; i++) - result.Properties.Add(new PSNoteProperty(properties[i].name, properties[i].value)); - - return new TargetObject(result); - } - - private static object[] GetObject(string path) - { - return JsonConvert.DeserializeObject(File.ReadAllText(path)); - } - - private static TargetObject[] GetObjectAsTarget(string path) - { - return JsonConvert.DeserializeObject(File.ReadAllText(path)).Select(o => new TargetObject(new PSObject(o))).ToArray(); - } - - private static RuleBlock GetRuleVisitor(RunspaceContext context, string name, Source[] source = null) - { - var block = HostHelper.GetRuleBlockGraph(source ?? GetSource(), context).GetAll(); - return block.FirstOrDefault(s => s.Name == name); - } - - private static void ImportSelectors(RunspaceContext context, Source[] source = null) - { - var selectors = HostHelper.GetSelectorForTests(source ?? GetSource(), context).ToArray(); - foreach (var selector in selectors) - context.Pipeline.Import(context, selector); - } - - private static string GetSourcePath(string path) - { - return Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, path)); - } - - #endregion Helper methods + return new PSRuleOption(); } + + private static Source[] GetSource(string path = "FromFile.Rule.yaml") + { + var builder = new SourcePipelineBuilder(null, null); + builder.Directory(GetSourcePath(path)); + return builder.Build(); + } + + private static TargetObject GetObject(params (string name, object value)[] properties) + { + var result = new PSObject(); + for (var i = 0; properties != null && i < properties.Length; i++) + result.Properties.Add(new PSNoteProperty(properties[i].name, properties[i].value)); + + return new TargetObject(result); + } + + private static object[] GetObject(string path) + { + return JsonConvert.DeserializeObject(File.ReadAllText(path)); + } + + private static TargetObject[] GetObjectAsTarget(string path) + { + return JsonConvert.DeserializeObject(File.ReadAllText(path)).Select(o => new TargetObject(new PSObject(o))).ToArray(); + } + + private static RuleBlock GetRuleVisitor(RunspaceContext context, string name, Source[] source = null) + { + var block = HostHelper.GetRuleBlockGraph(source ?? GetSource(), context).GetAll(); + return block.FirstOrDefault(s => s.Name == name); + } + + private static void ImportSelectors(RunspaceContext context, Source[] source = null) + { + var selectors = HostHelper.GetSelectorForTests(source ?? GetSource(), context).ToArray(); + foreach (var selector in selectors) + context.Pipeline.Import(context, selector); + } + + private static string GetSourcePath(string path) + { + return Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, path)); + } + + #endregion Helper methods } diff --git a/tests/PSRule.Tests/SelectorTests.cs b/tests/PSRule.Tests/SelectorTests.cs index 7ca7b9f8f7..2ea819a585 100644 --- a/tests/PSRule.Tests/SelectorTests.cs +++ b/tests/PSRule.Tests/SelectorTests.cs @@ -12,1880 +12,1879 @@ using PSRule.Pipeline; using PSRule.Runtime; -namespace PSRule +namespace PSRule; + +internal enum TestEnumValue { - internal enum TestEnumValue + None = 0, + + All = 1 +} + +public sealed class SelectorTests +{ + private const string SelectorYamlFileName = "Selectors.Rule.yaml"; + private const string SelectorJsonFileName = "Selectors.Rule.jsonc"; + private const string FunctionsYamlFileName = "Functions.Rule.yaml"; + private const string FunctionsJsonFileName = "Functions.Rule.jsonc"; + + [Theory] + [InlineData("Yaml", SelectorYamlFileName)] + [InlineData("Json", SelectorJsonFileName)] + public void ReadSelector(string type, string path) { - None = 0, + var testObject = GetObject((name: "value", value: 3)); + 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(); + Assert.NotNull(selector); + Assert.Equal(102, selector.Length); + + var actual = selector[0]; + var visitor = new SelectorVisitor(context, actual.Id, actual.Source, actual.Spec.If); + Assert.Equal("BasicSelector", actual.Name); + Assert.NotNull(actual.Spec.If); + Assert.False(visitor.Match(testObject)); + + actual = selector[4]; + visitor = new SelectorVisitor(context, actual.Id, actual.Source, actual.Spec.If); + Assert.Equal($"{type}AllOf", actual.Name); + Assert.NotNull(actual.Spec.If); + Assert.False(visitor.Match(testObject)); + } + + #region Conditions - All = 1 + [Theory] + [InlineData("Yaml", SelectorYamlFileName)] + [InlineData("Json", SelectorJsonFileName)] + public void ExistsExpression(string type, string path) + { + var existsTrue = GetSelectorVisitor($"{type}ExistsTrue", GetSource(path), out _); + var existsFalse = GetSelectorVisitor($"{type}ExistsFalse", GetSource(path), out _); + var actual1 = GetObject((name: "value", value: 3)); + var actual2 = GetObject((name: "notValue", value: 3)); + var actual3 = GetObject((name: "value", value: null)); + + Assert.True(existsTrue.Match(actual1)); + Assert.False(existsTrue.Match(actual2)); + Assert.True(existsTrue.Match(actual3)); + + Assert.False(existsFalse.Match(actual1)); + Assert.True(existsFalse.Match(actual2)); + Assert.False(existsFalse.Match(actual3)); } - public sealed class SelectorTests + [Theory] + [InlineData("Yaml", SelectorYamlFileName)] + [InlineData("Json", SelectorJsonFileName)] + public void EqualsExpression(string type, string path) { - private const string SelectorYamlFileName = "Selectors.Rule.yaml"; - private const string SelectorJsonFileName = "Selectors.Rule.jsonc"; - private const string FunctionsYamlFileName = "Functions.Rule.yaml"; - private const string FunctionsJsonFileName = "Functions.Rule.jsonc"; - - [Theory] - [InlineData("Yaml", SelectorYamlFileName)] - [InlineData("Json", SelectorJsonFileName)] - 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 OptionContextBuilder(), null), null); - context.Init(GetSource(path)); - context.Begin(); - var selector = HostHelper.GetSelectorForTests(GetSource(path), context).ToArray(); - Assert.NotNull(selector); - Assert.Equal(102, selector.Length); - - var actual = selector[0]; - var visitor = new SelectorVisitor(context, actual.Id, actual.Source, actual.Spec.If); - Assert.Equal("BasicSelector", actual.Name); - Assert.NotNull(actual.Spec.If); - Assert.False(visitor.Match(testObject)); - - actual = selector[4]; - visitor = new SelectorVisitor(context, actual.Id, actual.Source, actual.Spec.If); - Assert.Equal($"{type}AllOf", actual.Name); - Assert.NotNull(actual.Spec.If); - Assert.False(visitor.Match(testObject)); - } - - #region Conditions - - [Theory] - [InlineData("Yaml", SelectorYamlFileName)] - [InlineData("Json", SelectorJsonFileName)] - public void ExistsExpression(string type, string path) - { - var existsTrue = GetSelectorVisitor($"{type}ExistsTrue", GetSource(path), out _); - var existsFalse = GetSelectorVisitor($"{type}ExistsFalse", GetSource(path), out _); - var actual1 = GetObject((name: "value", value: 3)); - var actual2 = GetObject((name: "notValue", value: 3)); - var actual3 = GetObject((name: "value", value: null)); - - Assert.True(existsTrue.Match(actual1)); - Assert.False(existsTrue.Match(actual2)); - Assert.True(existsTrue.Match(actual3)); - - Assert.False(existsFalse.Match(actual1)); - Assert.True(existsFalse.Match(actual2)); - Assert.False(existsFalse.Match(actual3)); - } - - [Theory] - [InlineData("Yaml", SelectorYamlFileName)] - [InlineData("Json", SelectorJsonFileName)] - public void EqualsExpression(string type, string path) - { - var equals = GetSelectorVisitor($"{type}Equals", GetSource(path), out _); - var actual1 = GetObject( - (name: "ValueString", value: "abc"), - (name: "ValueInt", value: 123), - (name: "ValueBool", value: true), - (name: "ValueEnum", value: TestEnumValue.All) - ); - var actual2 = GetObject( - (name: "ValueString", value: "efg"), - (name: "ValueInt", value: 123), - (name: "ValueBool", value: true) - ); - var actual3 = GetObject( - (name: "ValueString", value: "abc"), - (name: "ValueInt", value: 456), - (name: "ValueBool", value: true) - ); - var actual4 = GetObject( - (name: "ValueString", value: "abc"), - (name: "ValueInt", value: 123), - (name: "ValueBool", value: false) - ); - var actual5 = GetObject( - (name: "ValueString", value: "abc"), - (name: "ValueInt", value: 123), - (name: "ValueBool", value: true), - (name: "ValueEnum", value: TestEnumValue.None) - ); - var actual6 = GetObject( - (name: "ValueString", value: "ABC"), - (name: "ValueInt", value: 123), - (name: "ValueBool", value: true) - ); - - Assert.True(equals.Match(actual1)); - Assert.False(equals.Match(actual2)); - Assert.False(equals.Match(actual3)); - Assert.False(equals.Match(actual4)); - Assert.False(equals.Match(actual5)); - Assert.False(equals.Match(actual6)); - - // With name - var withName = GetSelectorVisitor($"{type}NameEquals", GetSource(path), out var context); - actual1 = GetObject( - (name: "Name", value: "TargetObject1") - ); - actual2 = GetObject( - (name: "Name", value: "TargetObject2") - ); - - context.EnterTargetObject(new TargetObject(actual1)); - Assert.True(withName.Match(actual1)); - - context.EnterTargetObject(new TargetObject(actual2)); - Assert.False(withName.Match(actual2)); - - // With type - var withType = GetSelectorVisitor($"{type}TypeEquals", GetSource(path), out context); - var actual7 = GetObject(); - actual7.TypeNames.Insert(0, "CustomType1"); - var actual8 = GetObject(); - actual8.TypeNames.Insert(0, "CustomType2"); - - context.EnterTargetObject(new TargetObject(actual7)); - Assert.True(withType.Match(actual7)); - - context.EnterTargetObject(new TargetObject(actual8)); - Assert.False(withType.Match(actual8)); - } - - [Theory] - [InlineData("Yaml", SelectorYamlFileName)] - [InlineData("Json", SelectorJsonFileName)] - public void NotEqualsExpression(string type, string path) - { - var notEquals = GetSelectorVisitor($"{type}NotEquals", GetSource(path), out _); - var actual1 = GetObject( - (name: "ValueString", value: "efg"), - (name: "ValueInt", value: 456), - (name: "ValueBool", value: false), - (name: "ValueEnum", value: TestEnumValue.None) - ); - var actual2 = GetObject( - (name: "ValueString", value: "abc"), - (name: "ValueInt", value: 456), - (name: "ValueBool", value: false) - ); - var actual3 = GetObject( - (name: "ValueString", value: "efg"), - (name: "ValueInt", value: 123), - (name: "ValueBool", value: false) - ); - var actual4 = GetObject( - (name: "ValueString", value: "efg"), - (name: "ValueInt", value: 456), - (name: "ValueBool", value: true) - ); - var actual5 = GetObject( - (name: "ValueString", value: "efg"), - (name: "ValueInt", value: 456), - (name: "ValueBool", value: false), - (name: "ValueEnum", value: TestEnumValue.All) - ); - var actual6 = GetObject( - (name: "ValueString", value: "ABC"), - (name: "ValueInt", value: 456), - (name: "ValueBool", value: false) - ); - - Assert.True(notEquals.Match(actual1)); - Assert.False(notEquals.Match(actual2)); - Assert.False(notEquals.Match(actual3)); - Assert.False(notEquals.Match(actual4)); - Assert.False(notEquals.Match(actual5)); - Assert.True(notEquals.Match(actual6)); - - // With name - var withName = GetSelectorVisitor($"{type}NameNotEquals", GetSource(path), out var context); - actual1 = GetObject( - (name: "Name", value: "TargetObject1") - ); - actual2 = GetObject( - (name: "Name", value: "TargetObject2") - ); - - context.EnterTargetObject(new TargetObject(actual1)); - Assert.False(withName.Match(actual1)); - - context.EnterTargetObject(new TargetObject(actual2)); - Assert.True(withName.Match(actual2)); - } - - [Theory] - [InlineData("Yaml", SelectorYamlFileName)] - [InlineData("Json", SelectorJsonFileName)] - public void HasValueExpression(string type, string path) - { - var hasValueTrue = GetSelectorVisitor($"{type}HasValueTrue", GetSource(path), out _); - var hasValueFalse = GetSelectorVisitor($"{type}HasValueFalse", GetSource(path), out _); - var actual1 = GetObject((name: "value", value: 3)); - var actual2 = GetObject((name: "notValue", value: 3)); - var actual3 = GetObject((name: "value", value: null)); - - Assert.True(hasValueTrue.Match(actual1)); - Assert.False(hasValueTrue.Match(actual2)); - Assert.False(hasValueTrue.Match(actual3)); - - Assert.False(hasValueFalse.Match(actual1)); - Assert.True(hasValueFalse.Match(actual2)); - Assert.True(hasValueFalse.Match(actual3)); - - // With name - var withName = GetSelectorVisitor($"{type}NameHasValue", GetSource(path), out var context); - var actual4 = GetObject(); - - context.EnterTargetObject(new TargetObject(actual4)); - Assert.True(withName.Match(actual4)); - } - - [Theory] - [InlineData("Yaml", SelectorYamlFileName)] - [InlineData("Json", SelectorJsonFileName)] - public void MatchExpression(string type, string path) - { - var match = GetSelectorVisitor($"{type}Match", GetSource(path), out _); - var actual1 = GetObject((name: "value", value: "abc")); - var actual2 = GetObject((name: "value", value: "efg")); - var actual3 = GetObject((name: "value", value: "hij")); - var actual4 = GetObject((name: "value", value: 0)); - var actual5 = GetObject(); - - Assert.True(match.Match(actual1)); - Assert.True(match.Match(actual2)); - Assert.False(match.Match(actual3)); - Assert.False(match.Match(actual4)); - Assert.False(match.Match(actual5)); - - // With name - var withName = GetSelectorVisitor($"{type}NameMatch", GetSource(path), out var context); - actual1 = GetObject( - (name: "Name", value: "TargetObject1") - ); - actual2 = GetObject( - (name: "Name", value: "TargetObject2") - ); - - context.EnterTargetObject(new TargetObject(actual1)); - Assert.True(withName.Match(actual1)); - - context.EnterTargetObject(new TargetObject(actual2)); - Assert.False(withName.Match(actual2)); - - // With case-sensitivity - var withCaseSensitivity = GetSelectorVisitor($"{type}MatchCaseSensitive", GetSource(path), out _); - actual1 = GetObject((name: "value", value: "abc")); - actual2 = GetObject((name: "value", value: "aBc")); - - Assert.True(withCaseSensitivity.Match(actual1)); - Assert.False(withCaseSensitivity.Match(actual2)); - } - - [Theory] - [InlineData("Yaml", SelectorYamlFileName)] - [InlineData("Json", SelectorJsonFileName)] - public void NotMatchExpression(string type, string path) - { - var notMatch = GetSelectorVisitor($"{type}NotMatch", GetSource(path), out _); - var actual1 = GetObject((name: "value", value: "abc")); - var actual2 = GetObject((name: "value", value: "efg")); - var actual3 = GetObject((name: "value", value: "hij")); - var actual4 = GetObject((name: "value", value: 0)); - - Assert.False(notMatch.Match(actual1)); - Assert.False(notMatch.Match(actual2)); - Assert.True(notMatch.Match(actual3)); - Assert.True(notMatch.Match(actual4)); - - // With name - var withName = GetSelectorVisitor($"{type}NameNotMatch", GetSource(path), out var context); - actual1 = GetObject( - (name: "Name", value: "TargetObject1") - ); - actual2 = GetObject( - (name: "Name", value: "TargetObject2") - ); - - context.EnterTargetObject(new TargetObject(actual1)); - Assert.False(withName.Match(actual1)); - - context.EnterTargetObject(new TargetObject(actual2)); - Assert.True(withName.Match(actual2)); - - // With case-sensitivity - var withCaseSensitivity = GetSelectorVisitor($"{type}NotMatchCaseSensitive", GetSource(path), out _); - actual1 = GetObject((name: "value", value: "abc")); - actual2 = GetObject((name: "value", value: "aBc")); - - Assert.False(withCaseSensitivity.Match(actual1)); - Assert.True(withCaseSensitivity.Match(actual2)); - } - - [Theory] - [InlineData("Yaml", SelectorYamlFileName)] - [InlineData("Json", SelectorJsonFileName)] - public void WithinPathExpression(string type, string path) - { - // Source Case insensitive - var sourceWithinPath = GetSelectorVisitor($"{type}SourceWithinPath", GetSource(path), out var context); + var equals = GetSelectorVisitor($"{type}Equals", GetSource(path), out _); + var actual1 = GetObject( + (name: "ValueString", value: "abc"), + (name: "ValueInt", value: 123), + (name: "ValueBool", value: true), + (name: "ValueEnum", value: TestEnumValue.All) + ); + var actual2 = GetObject( + (name: "ValueString", value: "efg"), + (name: "ValueInt", value: 123), + (name: "ValueBool", value: true) + ); + var actual3 = GetObject( + (name: "ValueString", value: "abc"), + (name: "ValueInt", value: 456), + (name: "ValueBool", value: true) + ); + var actual4 = GetObject( + (name: "ValueString", value: "abc"), + (name: "ValueInt", value: 123), + (name: "ValueBool", value: false) + ); + var actual5 = GetObject( + (name: "ValueString", value: "abc"), + (name: "ValueInt", value: 123), + (name: "ValueBool", value: true), + (name: "ValueEnum", value: TestEnumValue.None) + ); + var actual6 = GetObject( + (name: "ValueString", value: "ABC"), + (name: "ValueInt", value: 123), + (name: "ValueBool", value: true) + ); + + Assert.True(equals.Match(actual1)); + Assert.False(equals.Match(actual2)); + Assert.False(equals.Match(actual3)); + Assert.False(equals.Match(actual4)); + Assert.False(equals.Match(actual5)); + Assert.False(equals.Match(actual6)); + + // With name + var withName = GetSelectorVisitor($"{type}NameEquals", GetSource(path), out var context); + actual1 = GetObject( + (name: "Name", value: "TargetObject1") + ); + actual2 = GetObject( + (name: "Name", value: "TargetObject2") + ); + + context.EnterTargetObject(new TargetObject(actual1)); + Assert.True(withName.Match(actual1)); + + context.EnterTargetObject(new TargetObject(actual2)); + Assert.False(withName.Match(actual2)); + + // With type + var withType = GetSelectorVisitor($"{type}TypeEquals", GetSource(path), out context); + var actual7 = GetObject(); + actual7.TypeNames.Insert(0, "CustomType1"); + var actual8 = GetObject(); + actual8.TypeNames.Insert(0, "CustomType2"); + + context.EnterTargetObject(new TargetObject(actual7)); + Assert.True(withType.Match(actual7)); + + context.EnterTargetObject(new TargetObject(actual8)); + Assert.False(withType.Match(actual8)); + } - var source = new PSObject(); - source.Properties.Add(new PSNoteProperty("file", "deployments/path/template.json")); - source.Properties.Add(new PSNoteProperty("line", 100)); - source.Properties.Add(new PSNoteProperty("position", 1000)); - source.Properties.Add(new PSNoteProperty("Type", "Template")); - var info = new PSObject(); - info.Properties.Add(new PSNoteProperty("source", new PSObject[] { source })); - var actual1 = new PSObject(); - actual1.Properties.Add(new PSNoteProperty("Name", "TestObject1")); - actual1.Properties.Add(new PSNoteProperty("Value", 1)); - actual1.Properties.Add(new PSNoteProperty("_PSRule", info)); + [Theory] + [InlineData("Yaml", SelectorYamlFileName)] + [InlineData("Json", SelectorJsonFileName)] + public void NotEqualsExpression(string type, string path) + { + var notEquals = GetSelectorVisitor($"{type}NotEquals", GetSource(path), out _); + var actual1 = GetObject( + (name: "ValueString", value: "efg"), + (name: "ValueInt", value: 456), + (name: "ValueBool", value: false), + (name: "ValueEnum", value: TestEnumValue.None) + ); + var actual2 = GetObject( + (name: "ValueString", value: "abc"), + (name: "ValueInt", value: 456), + (name: "ValueBool", value: false) + ); + var actual3 = GetObject( + (name: "ValueString", value: "efg"), + (name: "ValueInt", value: 123), + (name: "ValueBool", value: false) + ); + var actual4 = GetObject( + (name: "ValueString", value: "efg"), + (name: "ValueInt", value: 456), + (name: "ValueBool", value: true) + ); + var actual5 = GetObject( + (name: "ValueString", value: "efg"), + (name: "ValueInt", value: 456), + (name: "ValueBool", value: false), + (name: "ValueEnum", value: TestEnumValue.All) + ); + var actual6 = GetObject( + (name: "ValueString", value: "ABC"), + (name: "ValueInt", value: 456), + (name: "ValueBool", value: false) + ); + + Assert.True(notEquals.Match(actual1)); + Assert.False(notEquals.Match(actual2)); + Assert.False(notEquals.Match(actual3)); + Assert.False(notEquals.Match(actual4)); + Assert.False(notEquals.Match(actual5)); + Assert.True(notEquals.Match(actual6)); + + // With name + var withName = GetSelectorVisitor($"{type}NameNotEquals", GetSource(path), out var context); + actual1 = GetObject( + (name: "Name", value: "TargetObject1") + ); + actual2 = GetObject( + (name: "Name", value: "TargetObject2") + ); + + context.EnterTargetObject(new TargetObject(actual1)); + Assert.False(withName.Match(actual1)); + + context.EnterTargetObject(new TargetObject(actual2)); + Assert.True(withName.Match(actual2)); + } - context.EnterTargetObject(new TargetObject(actual1)); + [Theory] + [InlineData("Yaml", SelectorYamlFileName)] + [InlineData("Json", SelectorJsonFileName)] + public void HasValueExpression(string type, string path) + { + var hasValueTrue = GetSelectorVisitor($"{type}HasValueTrue", GetSource(path), out _); + var hasValueFalse = GetSelectorVisitor($"{type}HasValueFalse", GetSource(path), out _); + var actual1 = GetObject((name: "value", value: 3)); + var actual2 = GetObject((name: "notValue", value: 3)); + var actual3 = GetObject((name: "value", value: null)); + + Assert.True(hasValueTrue.Match(actual1)); + Assert.False(hasValueTrue.Match(actual2)); + Assert.False(hasValueTrue.Match(actual3)); + + Assert.False(hasValueFalse.Match(actual1)); + Assert.True(hasValueFalse.Match(actual2)); + Assert.True(hasValueFalse.Match(actual3)); + + // With name + var withName = GetSelectorVisitor($"{type}NameHasValue", GetSource(path), out var context); + var actual4 = GetObject(); + + context.EnterTargetObject(new TargetObject(actual4)); + Assert.True(withName.Match(actual4)); + } - Assert.True(sourceWithinPath.Match(actual1)); + [Theory] + [InlineData("Yaml", SelectorYamlFileName)] + [InlineData("Json", SelectorJsonFileName)] + public void MatchExpression(string type, string path) + { + var match = GetSelectorVisitor($"{type}Match", GetSource(path), out _); + var actual1 = GetObject((name: "value", value: "abc")); + var actual2 = GetObject((name: "value", value: "efg")); + var actual3 = GetObject((name: "value", value: "hij")); + var actual4 = GetObject((name: "value", value: 0)); + var actual5 = GetObject(); + + Assert.True(match.Match(actual1)); + Assert.True(match.Match(actual2)); + Assert.False(match.Match(actual3)); + Assert.False(match.Match(actual4)); + Assert.False(match.Match(actual5)); + + // With name + var withName = GetSelectorVisitor($"{type}NameMatch", GetSource(path), out var context); + actual1 = GetObject( + (name: "Name", value: "TargetObject1") + ); + actual2 = GetObject( + (name: "Name", value: "TargetObject2") + ); + + context.EnterTargetObject(new TargetObject(actual1)); + Assert.True(withName.Match(actual1)); + + context.EnterTargetObject(new TargetObject(actual2)); + Assert.False(withName.Match(actual2)); + + // With case-sensitivity + var withCaseSensitivity = GetSelectorVisitor($"{type}MatchCaseSensitive", GetSource(path), out _); + actual1 = GetObject((name: "value", value: "abc")); + actual2 = GetObject((name: "value", value: "aBc")); + + Assert.True(withCaseSensitivity.Match(actual1)); + Assert.False(withCaseSensitivity.Match(actual2)); + } - // Source Case sensitive - var sourceWithinPathCaseSensitive = GetSelectorVisitor($"{type}SourceWithinPathCaseSensitive", GetSource(path), out context); + [Theory] + [InlineData("Yaml", SelectorYamlFileName)] + [InlineData("Json", SelectorJsonFileName)] + public void NotMatchExpression(string type, string path) + { + var notMatch = GetSelectorVisitor($"{type}NotMatch", GetSource(path), out _); + var actual1 = GetObject((name: "value", value: "abc")); + var actual2 = GetObject((name: "value", value: "efg")); + var actual3 = GetObject((name: "value", value: "hij")); + var actual4 = GetObject((name: "value", value: 0)); + + Assert.False(notMatch.Match(actual1)); + Assert.False(notMatch.Match(actual2)); + Assert.True(notMatch.Match(actual3)); + Assert.True(notMatch.Match(actual4)); + + // With name + var withName = GetSelectorVisitor($"{type}NameNotMatch", GetSource(path), out var context); + actual1 = GetObject( + (name: "Name", value: "TargetObject1") + ); + actual2 = GetObject( + (name: "Name", value: "TargetObject2") + ); + + context.EnterTargetObject(new TargetObject(actual1)); + Assert.False(withName.Match(actual1)); + + context.EnterTargetObject(new TargetObject(actual2)); + Assert.True(withName.Match(actual2)); + + // With case-sensitivity + var withCaseSensitivity = GetSelectorVisitor($"{type}NotMatchCaseSensitive", GetSource(path), out _); + actual1 = GetObject((name: "value", value: "abc")); + actual2 = GetObject((name: "value", value: "aBc")); + + Assert.False(withCaseSensitivity.Match(actual1)); + Assert.True(withCaseSensitivity.Match(actual2)); + } - context.EnterTargetObject(new TargetObject(actual1)); + [Theory] + [InlineData("Yaml", SelectorYamlFileName)] + [InlineData("Json", SelectorJsonFileName)] + public void WithinPathExpression(string type, string path) + { + // Source Case insensitive + var sourceWithinPath = GetSelectorVisitor($"{type}SourceWithinPath", GetSource(path), out var context); - Assert.False(sourceWithinPathCaseSensitive.Match(actual1)); + var source = new PSObject(); + source.Properties.Add(new PSNoteProperty("file", "deployments/path/template.json")); + source.Properties.Add(new PSNoteProperty("line", 100)); + source.Properties.Add(new PSNoteProperty("position", 1000)); + source.Properties.Add(new PSNoteProperty("Type", "Template")); + var info = new PSObject(); + info.Properties.Add(new PSNoteProperty("source", new PSObject[] { source })); + var actual1 = new PSObject(); + actual1.Properties.Add(new PSNoteProperty("Name", "TestObject1")); + actual1.Properties.Add(new PSNoteProperty("Value", 1)); + actual1.Properties.Add(new PSNoteProperty("_PSRule", info)); - // Field Case insensitive - var fieldWithinPath = GetSelectorVisitor($"{type}FieldWithinPath", GetSource(path), out context); + context.EnterTargetObject(new TargetObject(actual1)); - var actual2 = new PSObject(); - actual2.Properties.Add(new PSNoteProperty("FullName", "policy/policy.json")); + Assert.True(sourceWithinPath.Match(actual1)); - context.EnterTargetObject(new TargetObject(actual2)); + // Source Case sensitive + var sourceWithinPathCaseSensitive = GetSelectorVisitor($"{type}SourceWithinPathCaseSensitive", GetSource(path), out context); - Assert.True(fieldWithinPath.Match(actual2)); + context.EnterTargetObject(new TargetObject(actual1)); - // Field Case sensitive - var fieldWithinPathCaseSensitive = GetSelectorVisitor($"{type}FieldWithinPathCaseSensitive", GetSource(path), out context); + Assert.False(sourceWithinPathCaseSensitive.Match(actual1)); - context.EnterTargetObject(new TargetObject(actual2)); + // Field Case insensitive + var fieldWithinPath = GetSelectorVisitor($"{type}FieldWithinPath", GetSource(path), out context); - Assert.False(fieldWithinPathCaseSensitive.Match(actual2)); - } + var actual2 = new PSObject(); + actual2.Properties.Add(new PSNoteProperty("FullName", "policy/policy.json")); - [Theory] - [InlineData("Yaml", SelectorYamlFileName)] - [InlineData("Json", SelectorJsonFileName)] - public void NotWithinPathExpression(string type, string path) - { - // Source Case insensitive - var notWithinPath = GetSelectorVisitor($"{type}SourceNotWithinPath", GetSource(path), out var context); + context.EnterTargetObject(new TargetObject(actual2)); - var source = new PSObject(); - source.Properties.Add(new PSNoteProperty("file", "deployments/path/template.json")); - source.Properties.Add(new PSNoteProperty("line", 100)); - source.Properties.Add(new PSNoteProperty("position", 1000)); - source.Properties.Add(new PSNoteProperty("Type", "Template")); - var info = new PSObject(); - info.Properties.Add(new PSNoteProperty("source", new PSObject[] { source })); - var actual1 = new PSObject(); - actual1.Properties.Add(new PSNoteProperty("Name", "TestObject1")); - actual1.Properties.Add(new PSNoteProperty("Value", 1)); - actual1.Properties.Add(new PSNoteProperty("_PSRule", info)); + Assert.True(fieldWithinPath.Match(actual2)); - context.EnterTargetObject(new TargetObject(actual1)); + // Field Case sensitive + var fieldWithinPathCaseSensitive = GetSelectorVisitor($"{type}FieldWithinPathCaseSensitive", GetSource(path), out context); - Assert.False(notWithinPath.Match(actual1)); + context.EnterTargetObject(new TargetObject(actual2)); - // Source Case sensitive - var notWithinPathCaseSensitive = GetSelectorVisitor($"{type}SourceNotWithinPathCaseSensitive", GetSource(path), out context); + Assert.False(fieldWithinPathCaseSensitive.Match(actual2)); + } - context.EnterTargetObject(new TargetObject(actual1)); + [Theory] + [InlineData("Yaml", SelectorYamlFileName)] + [InlineData("Json", SelectorJsonFileName)] + public void NotWithinPathExpression(string type, string path) + { + // Source Case insensitive + var notWithinPath = GetSelectorVisitor($"{type}SourceNotWithinPath", GetSource(path), out var context); - Assert.True(notWithinPathCaseSensitive.Match(actual1)); + var source = new PSObject(); + source.Properties.Add(new PSNoteProperty("file", "deployments/path/template.json")); + source.Properties.Add(new PSNoteProperty("line", 100)); + source.Properties.Add(new PSNoteProperty("position", 1000)); + source.Properties.Add(new PSNoteProperty("Type", "Template")); + var info = new PSObject(); + info.Properties.Add(new PSNoteProperty("source", new PSObject[] { source })); + var actual1 = new PSObject(); + actual1.Properties.Add(new PSNoteProperty("Name", "TestObject1")); + actual1.Properties.Add(new PSNoteProperty("Value", 1)); + actual1.Properties.Add(new PSNoteProperty("_PSRule", info)); - // Field Case insensitive - var fieldNotWithinPath = GetSelectorVisitor($"{type}FieldNotWithinPath", GetSource(path), out context); + context.EnterTargetObject(new TargetObject(actual1)); - var actual2 = new PSObject(); - actual2.Properties.Add(new PSNoteProperty("FullName", "policy/policy.json")); + Assert.False(notWithinPath.Match(actual1)); - context.EnterTargetObject(new TargetObject(actual2)); + // Source Case sensitive + var notWithinPathCaseSensitive = GetSelectorVisitor($"{type}SourceNotWithinPathCaseSensitive", GetSource(path), out context); - Assert.False(fieldNotWithinPath.Match(actual2)); + context.EnterTargetObject(new TargetObject(actual1)); - // Field Case sensitive - var fieldNotWithinPathCaseSensitive = GetSelectorVisitor($"{type}FieldNotWithinPathCaseSensitive", GetSource(path), out context); + Assert.True(notWithinPathCaseSensitive.Match(actual1)); - context.EnterTargetObject(new TargetObject(actual2)); + // Field Case insensitive + var fieldNotWithinPath = GetSelectorVisitor($"{type}FieldNotWithinPath", GetSource(path), out context); - Assert.True(fieldNotWithinPathCaseSensitive.Match(actual2)); - } + var actual2 = new PSObject(); + actual2.Properties.Add(new PSNoteProperty("FullName", "policy/policy.json")); - [Theory] - [InlineData("Yaml", SelectorYamlFileName)] - [InlineData("Json", SelectorJsonFileName)] - public void InExpression(string type, string path) - { - var @in = GetSelectorVisitor($"{type}In", GetSource(path), out _); - var actual1 = GetObject((name: "value", value: new string[] { "Value1" })); - var actual2 = GetObject((name: "value", value: new string[] { "Value2" })); - var actual3 = GetObject((name: "value", value: new string[] { "Value3" })); - var actual4 = GetObject((name: "value", value: Array.Empty())); - var actual5 = GetObject((name: "value", value: null)); - var actual6 = GetObject(); - - Assert.True(@in.Match(actual1)); - Assert.True(@in.Match(actual2)); - Assert.False(@in.Match(actual3)); - Assert.False(@in.Match(actual4)); - Assert.False(@in.Match(actual5)); - Assert.False(@in.Match(actual6)); - - // With name - var withName = GetSelectorVisitor($"{type}NameIn", GetSource(path), out var context); - var actual7 = GetObject( - (name: "Name", value: "TargetObject1") - ); - var actual8 = GetObject( - (name: "Name", value: "TargetObject2") - ); - - context.EnterTargetObject(new TargetObject(actual7)); - Assert.True(withName.Match(actual7)); - - context.EnterTargetObject(new TargetObject(actual8)); - Assert.False(withName.Match(actual8)); - } - - [Theory] - [InlineData("Yaml", SelectorYamlFileName)] - [InlineData("Json", SelectorJsonFileName)] - public void NotInExpression(string type, string path) - { - var notIn = GetSelectorVisitor($"{type}NotIn", GetSource(path), out _); - var actual1 = GetObject((name: "value", value: new string[] { "Value1" })); - var actual2 = GetObject((name: "value", value: new string[] { "Value2" })); - var actual3 = GetObject((name: "value", value: new string[] { "Value3" })); - var actual4 = GetObject((name: "value", value: Array.Empty())); - var actual5 = GetObject((name: "value", value: null)); - - Assert.False(notIn.Match(actual1)); - Assert.False(notIn.Match(actual2)); - Assert.True(notIn.Match(actual3)); - Assert.True(notIn.Match(actual4)); - Assert.True(notIn.Match(actual5)); - - // With name - var withName = GetSelectorVisitor($"{type}NameNotIn", GetSource(path), out var context); - var actual6 = GetObject( - (name: "Name", value: "TargetObject1") - ); - var actual7 = GetObject( - (name: "Name", value: "TargetObject2") - ); - - context.EnterTargetObject(new TargetObject(actual6)); - Assert.False(withName.Match(actual6)); - - context.EnterTargetObject(new TargetObject(actual7)); - Assert.True(withName.Match(actual7)); - } - - [Theory] - [InlineData("Yaml", SelectorYamlFileName)] - [InlineData("Json", SelectorJsonFileName)] - public void SetOfExpression(string type, string path) - { - var setOf = GetSelectorVisitor($"{type}SetOf", GetSource(path), out _); - var actual1 = GetObject((name: "value", value: new string[] { "cluster-autoscaler", "kube-apiserver", "kube-scheduler" })); - var actual2 = GetObject((name: "value", value: new string[] { "kube-apiserver", "cluster-autoscaler" })); - var actual3 = GetObject((name: "value", value: new string[] { "cluster-autoscaler" })); - var actual4 = GetObject((name: "value", value: new string[] { "kube-apiserver", "kube-scheduler" })); - var actual5 = GetObject((name: "value", value: new string[] { "kube-scheduler" })); - var actual6 = GetObject((name: "value", value: Array.Empty())); - var actual7 = GetObject((name: "value", value: null)); - var actual8 = GetObject(); - var actual9 = GetObject((name: "value", value: new string[] { "kube-apiserver", "cluster-autoscaler", "kube-apiserver" })); - var actual10 = GetObject((name: "value", value: new string[] { "cluster-autoscaler", "kube-APIserver" })); - - Assert.False(setOf.Match(actual1)); - Assert.True(setOf.Match(actual2)); - Assert.False(setOf.Match(actual3)); - Assert.False(setOf.Match(actual4)); - Assert.False(setOf.Match(actual5)); - Assert.False(setOf.Match(actual6)); - Assert.False(setOf.Match(actual7)); - Assert.False(setOf.Match(actual8)); - Assert.False(setOf.Match(actual9)); - Assert.False(setOf.Match(actual10)); - } - - [Theory] - [InlineData("Yaml", SelectorYamlFileName)] - [InlineData("Json", SelectorJsonFileName)] - public void SubsetExpression(string type, string path) - { - var subset = GetSelectorVisitor($"{type}Subset", GetSource(path), out _); - var actual1 = GetObject((name: "value", value: new string[] { "cluster-autoscaler", "kube-apiserver", "kube-scheduler" })); - var actual2 = GetObject((name: "value", value: new string[] { "kube-apiserver", "cluster-autoscaler" })); - var actual3 = GetObject((name: "value", value: new string[] { "cluster-autoscaler" })); - var actual4 = GetObject((name: "value", value: new string[] { "kube-apiserver", "kube-scheduler" })); - var actual5 = GetObject((name: "value", value: new string[] { "kube-scheduler" })); - var actual6 = GetObject((name: "value", value: Array.Empty())); - var actual7 = GetObject((name: "value", value: null)); - var actual8 = GetObject(); - var actual9 = GetObject((name: "value", value: new string[] { "kube-apiserver", "cluster-autoscaler", "kube-apiserver" })); - var actual10 = GetObject((name: "value", value: new string[] { "cluster-autoscaler", "kube-APIserver" })); - - Assert.True(subset.Match(actual1)); - Assert.True(subset.Match(actual2)); - Assert.False(subset.Match(actual3)); - Assert.False(subset.Match(actual4)); - Assert.False(subset.Match(actual5)); - Assert.False(subset.Match(actual6)); - Assert.False(subset.Match(actual7)); - Assert.False(subset.Match(actual8)); - Assert.False(subset.Match(actual9)); - Assert.False(subset.Match(actual10)); - } - - [Theory] - [InlineData("Yaml", SelectorYamlFileName)] - [InlineData("Json", SelectorJsonFileName)] - public void CountExpression(string type, string path) - { - var count = GetSelectorVisitor($"{type}Count", GetSource(path), out _); - var actual1 = GetObject((name: "value", value: new string[] { "1", "2", "3" })); - var actual2 = GetObject((name: "value", value: new string[] { "2", "1" })); - var actual3 = GetObject((name: "value", value: new string[] { "1" })); - var actual4 = GetObject((name: "value", value: new int[] { 2, 3 })); - var actual5 = GetObject((name: "value", value: new int[] { 3 })); - var actual6 = GetObject((name: "value", value: Array.Empty())); - var actual7 = GetObject((name: "value", value: null)); - var actual8 = GetObject(); - - Assert.False(count.Match(actual1)); - Assert.True(count.Match(actual2)); - Assert.False(count.Match(actual3)); - Assert.True(count.Match(actual4)); - Assert.False(count.Match(actual5)); - Assert.False(count.Match(actual6)); - Assert.False(count.Match(actual7)); - Assert.False(count.Match(actual8)); - } - - [Theory] - [InlineData("Yaml", SelectorYamlFileName)] - [InlineData("Json", SelectorJsonFileName)] - public void NotCountExpression(string type, string path) - { - var count = GetSelectorVisitor($"{type}NotCount", GetSource(path), out _); - var actual1 = GetObject((name: "value", value: new string[] { "1", "2", "3" })); - var actual2 = GetObject((name: "value", value: new string[] { "2", "1" })); - var actual3 = GetObject((name: "value", value: new string[] { "1" })); - var actual4 = GetObject((name: "value", value: new int[] { 2, 3 })); - var actual5 = GetObject((name: "value", value: new int[] { 3 })); - var actual6 = GetObject((name: "value", value: Array.Empty())); - var actual7 = GetObject((name: "value", value: null)); - var actual8 = GetObject(); - - Assert.True(count.Match(actual1)); - Assert.False(count.Match(actual2)); - Assert.True(count.Match(actual3)); - Assert.False(count.Match(actual4)); - Assert.True(count.Match(actual5)); - Assert.True(count.Match(actual6)); - Assert.False(count.Match(actual7)); - Assert.False(count.Match(actual8)); - } - - [Theory] - [InlineData("Yaml", SelectorYamlFileName)] - [InlineData("Json", SelectorJsonFileName)] - public void LessExpression(string type, string path) - { - var less = GetSelectorVisitor($"{type}Less", GetSource(path), out _); - var actual1 = GetObject((name: "value", value: 3)); - var actual2 = GetObject((name: "value", value: 4)); - var actual3 = GetObject((name: "value", value: new string[] { "Value3" })); - var actual4 = GetObject((name: "value", value: Array.Empty())); - var actual5 = GetObject((name: "value", value: null)); - var actual6 = GetObject((name: "value", value: 2)); - var actual7 = GetObject((name: "value", value: -1)); - var actual8 = GetObject((name: "valueStr", value: "0")); - var actual9 = GetObject((name: "valueStr", value: "-1")); - - Assert.False(less.Match(actual1)); - Assert.False(less.Match(actual2)); - Assert.True(less.Match(actual3)); - Assert.True(less.Match(actual4)); - Assert.True(less.Match(actual5)); - Assert.True(less.Match(actual6)); - Assert.True(less.Match(actual7)); - Assert.False(less.Match(actual8)); - Assert.True(less.Match(actual9)); - - // With name - var withName = GetSelectorVisitor($"{type}NameLess", GetSource(path), out var context); - actual1 = GetObject( - (name: "Name", value: "ItemTwo") - ); - actual2 = GetObject( - (name: "Name", value: "ItemThree") - ); - - context.EnterTargetObject(new TargetObject(actual1)); - Assert.True(withName.Match(actual1)); - - context.EnterTargetObject(new TargetObject(actual2)); - Assert.False(withName.Match(actual2)); - } - - [Theory] - [InlineData("Yaml", SelectorYamlFileName)] - [InlineData("Json", SelectorJsonFileName)] - public void LessOrEqualsExpression(string type, string path) - { - var lessOrEquals = GetSelectorVisitor($"{type}LessOrEquals", GetSource(path), out _); - var actual1 = GetObject((name: "value", value: 3)); - var actual2 = GetObject((name: "value", value: 4)); - var actual3 = GetObject((name: "value", value: new string[] { "Value3" })); - var actual4 = GetObject((name: "value", value: Array.Empty())); - var actual5 = GetObject((name: "value", value: null)); - var actual6 = GetObject((name: "value", value: 2)); - var actual7 = GetObject((name: "value", value: -1)); - var actual8 = GetObject((name: "valueStr", value: "0")); - var actual9 = GetObject((name: "valueStr", value: "-1")); - - Assert.True(lessOrEquals.Match(actual1)); - Assert.False(lessOrEquals.Match(actual2)); - Assert.True(lessOrEquals.Match(actual3)); - Assert.True(lessOrEquals.Match(actual4)); - Assert.True(lessOrEquals.Match(actual5)); - Assert.True(lessOrEquals.Match(actual6)); - Assert.True(lessOrEquals.Match(actual7)); - Assert.True(lessOrEquals.Match(actual8)); - Assert.True(lessOrEquals.Match(actual9)); - - // With name - var withName = GetSelectorVisitor($"{type}NameLessOrEquals", GetSource(path), out var context); - actual1 = GetObject( - (name: "Name", value: "ItemTwo") - ); - actual2 = GetObject( - (name: "Name", value: "ItemThree") - ); - - context.EnterTargetObject(new TargetObject(actual1)); - Assert.True(withName.Match(actual1)); - - context.EnterTargetObject(new TargetObject(actual2)); - Assert.False(withName.Match(actual2)); - } - - [Theory] - [InlineData("Yaml", SelectorYamlFileName)] - [InlineData("Json", SelectorJsonFileName)] - public void GreaterExpression(string type, string path) - { - var greater = GetSelectorVisitor($"{type}Greater", GetSource(path), out _); - var actual1 = GetObject((name: "value", value: 3)); - var actual2 = GetObject((name: "value", value: 4)); - var actual3 = GetObject((name: "value", value: new string[] { "Value3" })); - var actual4 = GetObject((name: "value", value: Array.Empty())); - var actual5 = GetObject((name: "value", value: null)); - var actual6 = GetObject((name: "value", value: 2)); - var actual7 = GetObject((name: "value", value: -1)); - var actual8 = GetObject((name: "valueStr", value: "0")); - var actual9 = GetObject((name: "valueStr", value: "-1")); - - Assert.False(greater.Match(actual1)); - Assert.True(greater.Match(actual2)); - Assert.False(greater.Match(actual3)); - Assert.False(greater.Match(actual4)); - Assert.False(greater.Match(actual5)); - Assert.False(greater.Match(actual6)); - Assert.False(greater.Match(actual7)); - Assert.True(greater.Match(actual8)); - Assert.False(greater.Match(actual9)); - - // With name - var withName = GetSelectorVisitor($"{type}NameGreater", GetSource(path), out var context); - actual1 = GetObject( - (name: "Name", value: "ItemTwo") - ); - actual2 = GetObject( - (name: "Name", value: "ItemThree") - ); - - context.EnterTargetObject(new TargetObject(actual1)); - Assert.False(withName.Match(actual1)); - - context.EnterTargetObject(new TargetObject(actual2)); - Assert.True(withName.Match(actual2)); - } - - [Theory] - [InlineData("Yaml", SelectorYamlFileName)] - [InlineData("Json", SelectorJsonFileName)] - public void GreaterOrEqualsExpression(string type, string path) - { - var greaterOrEquals = GetSelectorVisitor($"{type}GreaterOrEquals", GetSource(path), out _); - var actual1 = GetObject((name: "value", value: 3)); - var actual2 = GetObject((name: "value", value: 4)); - var actual3 = GetObject((name: "value", value: new string[] { "Value3" })); - var actual4 = GetObject((name: "value", value: Array.Empty())); - var actual5 = GetObject((name: "value", value: null)); - var actual6 = GetObject((name: "value", value: 2)); - var actual7 = GetObject((name: "value", value: -1)); - var actual8 = GetObject((name: "valueStr", value: "0")); - var actual9 = GetObject((name: "valueStr", value: "-1")); - - Assert.True(greaterOrEquals.Match(actual1)); - Assert.True(greaterOrEquals.Match(actual2)); - Assert.False(greaterOrEquals.Match(actual3)); - Assert.False(greaterOrEquals.Match(actual4)); - Assert.False(greaterOrEquals.Match(actual5)); - Assert.False(greaterOrEquals.Match(actual6)); - Assert.False(greaterOrEquals.Match(actual7)); - Assert.True(greaterOrEquals.Match(actual8)); - Assert.True(greaterOrEquals.Match(actual9)); - - // With name - var withName = GetSelectorVisitor($"{type}NameGreaterOrEquals", GetSource(path), out var context); - actual1 = GetObject( - (name: "Name", value: "ItemTwo") - ); - actual2 = GetObject( - (name: "Name", value: "ItemThree") - ); - - context.EnterTargetObject(new TargetObject(actual1)); - Assert.False(withName.Match(actual1)); - - context.EnterTargetObject(new TargetObject(actual2)); - Assert.True(withName.Match(actual2)); - } - - [Theory] - [InlineData("Yaml", SelectorYamlFileName)] - [InlineData("Json", SelectorJsonFileName)] - public void StartsWithExpression(string type, string path) - { - var startsWith = GetSelectorVisitor($"{type}StartsWith", GetSource(path), out _); - var actual1 = GetObject((name: "value", value: "abc")); - var actual2 = GetObject((name: "value", value: "efg")); - var actual3 = GetObject((name: "value", value: "hij")); - var actual4 = GetObject((name: "value", value: Array.Empty())); - var actual5 = GetObject((name: "value", value: null)); - var actual6 = GetObject(); - var actual7 = GetObject((name: "value", value: "EFG")); - var actual8 = GetObject((name: "value", value: "abc"), (name: "OtherValue", value: TestEnumValue.All)); - var actual9 = GetObject((name: "value", value: "abc"), (name: "OtherValue", value: TestEnumValue.None)); - var actual10 = GetObject((name: "value", value: new string[] { "hij", "abc" })); - - Assert.True(startsWith.Match(actual1)); - Assert.True(startsWith.Match(actual2)); - Assert.False(startsWith.Match(actual3)); - Assert.False(startsWith.Match(actual4)); - Assert.False(startsWith.Match(actual5)); - Assert.False(startsWith.Match(actual6)); - Assert.False(startsWith.Match(actual7)); - Assert.True(startsWith.Match(actual8)); - Assert.False(startsWith.Match(actual9)); - Assert.True(startsWith.Match(actual10)); - - // With name - var withName = GetSelectorVisitor($"{type}NameStartsWith", GetSource(path), out var context); - actual1 = GetObject( - (name: "Name", value: "1TargetObject") - ); - actual2 = GetObject( - (name: "Name", value: "2TargetObject") - ); - - context.EnterTargetObject(new TargetObject(actual1)); - Assert.True(withName.Match(actual1)); - - context.EnterTargetObject(new TargetObject(actual2)); - Assert.False(withName.Match(actual2)); - } - - [Theory] - [InlineData("Yaml", SelectorYamlFileName)] - [InlineData("Json", SelectorJsonFileName)] - public void NotStartsWithExpression(string type, string path) - { - var notStartsWith = GetSelectorVisitor($"{type}NotStartsWith", GetSource(path), out _); - var actual1 = GetObject((name: "value", value: "abc")); - var actual2 = GetObject((name: "value", value: "efg")); - var actual3 = GetObject((name: "value", value: "hij")); - var actual4 = GetObject((name: "value", value: Array.Empty())); - var actual5 = GetObject((name: "value", value: null)); - var actual6 = GetObject(); - var actual7 = GetObject((name: "value", value: "EFG")); - var actual8 = GetObject((name: "value", value: "abc"), (name: "OtherValue", value: TestEnumValue.All)); - var actual9 = GetObject((name: "value", value: "hij"), (name: "OtherValue", value: TestEnumValue.None)); - var actual10 = GetObject((name: "value", value: new string[] { "hij", "abc" })); - - Assert.False(notStartsWith.Match(actual1)); - Assert.False(notStartsWith.Match(actual2)); - Assert.True(notStartsWith.Match(actual3)); - Assert.True(notStartsWith.Match(actual4)); - Assert.True(notStartsWith.Match(actual5)); - Assert.False(notStartsWith.Match(actual6)); - Assert.False(notStartsWith.Match(actual7)); - Assert.False(notStartsWith.Match(actual8)); - Assert.True(notStartsWith.Match(actual9)); - Assert.False(notStartsWith.Match(actual10)); - } - - [Theory] - [InlineData("Yaml", SelectorYamlFileName)] - [InlineData("Json", SelectorJsonFileName)] - public void EndsWithExpression(string type, string path) - { - var endsWith = GetSelectorVisitor($"{type}EndsWith", GetSource(path), out _); - var actual1 = GetObject((name: "value", value: "abc")); - var actual2 = GetObject((name: "value", value: "efg")); - var actual3 = GetObject((name: "value", value: "hij")); - var actual4 = GetObject((name: "value", value: Array.Empty())); - var actual5 = GetObject((name: "value", value: null)); - var actual6 = GetObject(); - var actual7 = GetObject((name: "value", value: "EFG")); - var actual8 = GetObject((name: "value", value: "abc"), (name: "OtherValue", value: TestEnumValue.All)); - var actual9 = GetObject((name: "value", value: "abc"), (name: "OtherValue", value: TestEnumValue.None)); - var actual10 = GetObject((name: "value", value: new string[] { "hij", "abc" })); - - Assert.True(endsWith.Match(actual1)); - Assert.True(endsWith.Match(actual2)); - Assert.False(endsWith.Match(actual3)); - Assert.False(endsWith.Match(actual4)); - Assert.False(endsWith.Match(actual5)); - Assert.False(endsWith.Match(actual6)); - Assert.False(endsWith.Match(actual7)); - Assert.True(endsWith.Match(actual8)); - Assert.False(endsWith.Match(actual9)); - Assert.True(endsWith.Match(actual10)); - - // With name - var withName = GetSelectorVisitor($"{type}NameEndsWith", GetSource(path), out var context); - actual1 = GetObject( - (name: "Name", value: "TargetObject1") - ); - actual2 = GetObject( - (name: "Name", value: "TargetObject2") - ); - - context.EnterTargetObject(new TargetObject(actual1)); - Assert.True(withName.Match(actual1)); - - context.EnterTargetObject(new TargetObject(actual2)); - Assert.False(withName.Match(actual2)); - - // With source - var withSource = GetSelectorVisitor($"{type}EndsWithSource", GetSource(path), out context); - var source = new PSObject(); - source.Properties.Add(new PSNoteProperty("file", "deployments/path/template.json")); - source.Properties.Add(new PSNoteProperty("line", 100)); - source.Properties.Add(new PSNoteProperty("position", 1000)); - source.Properties.Add(new PSNoteProperty("Type", "Template")); - var info = new PSObject(); - info.Properties.Add(new PSNoteProperty("source", new PSObject[] { source })); - actual1 = new PSObject(); - actual1.Properties.Add(new PSNoteProperty("Name", "TestObject1")); - actual1.Properties.Add(new PSNoteProperty("Value", 1)); - actual1.Properties.Add(new PSNoteProperty("_PSRule", info)); - - context.EnterTargetObject(new TargetObject(actual1)); - Assert.True(withSource.Match(actual1)); - } - - [Theory] - [InlineData("Yaml", SelectorYamlFileName)] - [InlineData("Json", SelectorJsonFileName)] - public void NotEndsWithExpression(string type, string path) - { - var notEndsWith = GetSelectorVisitor($"{type}NotEndsWith", GetSource(path), out _); - var actual1 = GetObject((name: "value", value: "abc")); - var actual2 = GetObject((name: "value", value: "efg")); - var actual3 = GetObject((name: "value", value: "hij")); - var actual4 = GetObject((name: "value", value: Array.Empty())); - var actual5 = GetObject((name: "value", value: null)); - var actual6 = GetObject(); - var actual7 = GetObject((name: "value", value: "EFG")); - var actual8 = GetObject((name: "value", value: "abc"), (name: "OtherValue", value: TestEnumValue.All)); - var actual9 = GetObject((name: "value", value: "hij"), (name: "OtherValue", value: TestEnumValue.None)); - var actual10 = GetObject((name: "value", value: new string[] { "hij", "abc" })); - - Assert.False(notEndsWith.Match(actual1)); - Assert.False(notEndsWith.Match(actual2)); - Assert.True(notEndsWith.Match(actual3)); - Assert.True(notEndsWith.Match(actual4)); - Assert.True(notEndsWith.Match(actual5)); - Assert.False(notEndsWith.Match(actual6)); - Assert.False(notEndsWith.Match(actual7)); - Assert.False(notEndsWith.Match(actual8)); - Assert.True(notEndsWith.Match(actual9)); - Assert.False(notEndsWith.Match(actual10)); - } - - [Theory] - [InlineData("Yaml", SelectorYamlFileName)] - [InlineData("Json", SelectorJsonFileName)] - public void ContainsExpression(string type, string path) - { - var contains = GetSelectorVisitor($"{type}Contains", GetSource(path), out _); - var actual1 = GetObject((name: "value", value: "abc")); - var actual2 = GetObject((name: "value", value: "bcd")); - var actual3 = GetObject((name: "value", value: "hij")); - var actual4 = GetObject((name: "value", value: Array.Empty())); - var actual5 = GetObject((name: "value", value: null)); - var actual6 = GetObject(); - var actual7 = GetObject((name: "value", value: "BCD")); - var actual8 = GetObject((name: "value", value: "abc"), (name: "OtherValue", value: TestEnumValue.All)); - var actual9 = GetObject((name: "value", value: "abc"), (name: "OtherValue", value: TestEnumValue.None)); - var actual10 = GetObject((name: "value", value: new string[] { "hij", "abc" })); - - Assert.True(contains.Match(actual1)); - Assert.True(contains.Match(actual2)); - Assert.False(contains.Match(actual3)); - Assert.False(contains.Match(actual4)); - Assert.False(contains.Match(actual5)); - Assert.False(contains.Match(actual6)); - Assert.False(contains.Match(actual7)); - Assert.True(contains.Match(actual8)); - Assert.False(contains.Match(actual9)); - Assert.True(contains.Match(actual10)); - - // With name - var withName = GetSelectorVisitor($"{type}NameContains", GetSource(path), out var context); - actual1 = GetObject( - (name: "Name", value: "Target.1.Object") - ); - actual2 = GetObject( - (name: "Name", value: "Target.2.Object") - ); - - context.EnterTargetObject(new TargetObject(actual1)); - Assert.True(withName.Match(actual1)); - - context.EnterTargetObject(new TargetObject(actual2)); - Assert.False(withName.Match(actual2)); - } - - [Theory] - [InlineData("Yaml", SelectorYamlFileName)] - [InlineData("Json", SelectorJsonFileName)] - public void NotContainsExpression(string type, string path) - { - var notContains = GetSelectorVisitor($"{type}NotContains", GetSource(path), out _); - var actual1 = GetObject((name: "value", value: "abc")); - var actual2 = GetObject((name: "value", value: "bcd")); - var actual3 = GetObject((name: "value", value: "hij")); - var actual4 = GetObject((name: "value", value: Array.Empty())); - var actual5 = GetObject((name: "value", value: null)); - var actual6 = GetObject(); - var actual7 = GetObject((name: "value", value: "BCD")); - var actual8 = GetObject((name: "value", value: "abc"), (name: "OtherValue", value: TestEnumValue.All)); - var actual9 = GetObject((name: "value", value: "hij"), (name: "OtherValue", value: TestEnumValue.None)); - var actual10 = GetObject((name: "value", value: new string[] { "hij", "abc" })); - - Assert.False(notContains.Match(actual1)); - Assert.False(notContains.Match(actual2)); - Assert.True(notContains.Match(actual3)); - Assert.True(notContains.Match(actual4)); - Assert.True(notContains.Match(actual5)); - Assert.False(notContains.Match(actual6)); - Assert.False(notContains.Match(actual7)); - Assert.False(notContains.Match(actual8)); - Assert.True(notContains.Match(actual9)); - Assert.False(notContains.Match(actual10)); - } - - [Theory] - [InlineData("Yaml", SelectorYamlFileName)] - [InlineData("Json", SelectorJsonFileName)] - public void LikeExpression(string type, string path) - { - var like = GetSelectorVisitor($"{type}Like", GetSource(path), out _); - var actual1 = GetObject((name: "value", value: "abc")); - var actual2 = GetObject((name: "value", value: "efg")); - var actual3 = GetObject((name: "value", value: "hij")); - var actual4 = GetObject((name: "value", value: Array.Empty())); - var actual5 = GetObject((name: "value", value: null)); - var actual6 = GetObject(); - var actual7 = GetObject((name: "value", value: "EFG")); - var actual8 = GetObject((name: "value", value: "abc"), (name: "OtherValue", value: "123")); - var actual9 = GetObject((name: "value", value: "abc"), (name: "OtherValue", value: 123)); - - Assert.True(like.Match(actual1)); - Assert.True(like.Match(actual2)); - Assert.False(like.Match(actual3)); - Assert.False(like.Match(actual4)); - Assert.False(like.Match(actual5)); - Assert.False(like.Match(actual6)); - Assert.False(like.Match(actual7)); - Assert.True(like.Match(actual8)); - Assert.True(like.Match(actual9)); - } - - [Theory] - [InlineData("Yaml", SelectorYamlFileName)] - [InlineData("Json", SelectorJsonFileName)] - public void NotLikeExpression(string type, string path) - { - var notLike = GetSelectorVisitor($"{type}NotLike", GetSource(path), out _); - var actual1 = GetObject((name: "value", value: "abc")); - var actual2 = GetObject((name: "value", value: "efg")); - var actual3 = GetObject((name: "value", value: "hij")); - var actual4 = GetObject((name: "value", value: Array.Empty())); - var actual5 = GetObject((name: "value", value: null)); - var actual6 = GetObject(); - var actual7 = GetObject((name: "value", value: "EFG")); - var actual8 = GetObject((name: "value", value: "abc"), (name: "OtherValue", value: "123")); - var actual9 = GetObject((name: "value", value: "hij"), (name: "OtherValue", value: 123)); - - Assert.False(notLike.Match(actual1)); - Assert.False(notLike.Match(actual2)); - Assert.True(notLike.Match(actual3)); - Assert.True(notLike.Match(actual4)); - Assert.True(notLike.Match(actual5)); - Assert.False(notLike.Match(actual6)); - Assert.False(notLike.Match(actual7)); - Assert.False(notLike.Match(actual8)); - Assert.False(notLike.Match(actual9)); - } - - [Theory] - [InlineData("Yaml", SelectorYamlFileName)] - [InlineData("Json", SelectorJsonFileName)] - public void IsStringExpression(string type, string path) - { - var isStringTrue = GetSelectorVisitor($"{type}IsStringTrue", GetSource(path), out _); - var isStringFalse = GetSelectorVisitor($"{type}IsStringFalse", GetSource(path), out _); - var actual1 = GetObject((name: "value", value: "abc")); - var actual2 = GetObject((name: "value", value: 4)); - var actual3 = GetObject((name: "value", value: Array.Empty())); - var actual4 = GetObject((name: "value", value: null)); - var actual5 = GetObject(); - - // isString: true - Assert.True(isStringTrue.Match(actual1)); - Assert.False(isStringTrue.Match(actual2)); - Assert.False(isStringTrue.Match(actual3)); - Assert.False(isStringTrue.Match(actual4)); - Assert.False(isStringTrue.Match(actual5)); - - // isString: false - Assert.False(isStringFalse.Match(actual1)); - Assert.True(isStringFalse.Match(actual2)); - Assert.True(isStringFalse.Match(actual3)); - Assert.True(isStringFalse.Match(actual4)); - Assert.False(isStringFalse.Match(actual5)); - - // With name - var withName = GetSelectorVisitor($"{type}NameIsString", GetSource(path), out var context); - var actual7 = GetObject( - (name: "Name", value: "TargetObject1") - ); - var actual8 = GetObject( - (name: "Name", value: 1) - ); - - context.EnterTargetObject(new TargetObject(actual7)); - Assert.True(withName.Match(actual7)); - - context.EnterTargetObject(new TargetObject(actual8)); - Assert.True(withName.Match(actual8)); - } - - [Theory] - [InlineData("Yaml", SelectorYamlFileName)] - [InlineData("Json", SelectorJsonFileName)] - public void IsArrayExpression(string type, string path) - { - var isArrayTrue = GetSelectorVisitor($"{type}IsArrayTrue", GetSource(path), out _); - var isArrayFalse = GetSelectorVisitor($"{type}IsArrayFalse", GetSource(path), out _); - var actual1 = GetObject((name: "value", value: new string[] { "abc" })); - var actual2 = GetObject((name: "value", value: 4)); - var actual3 = GetObject((name: "value", value: PSObject.AsPSObject(new int[] { 1 }))); - var actual4 = GetObject((name: "value", value: null)); - var actual5 = GetObject((name: "value", value: "abc")); - var actual6 = GetObject((name: "value", value: new int[] { 1 })); - var actual7 = GetObject(); - - // isArray: true - Assert.True(isArrayTrue.Match(actual1)); - Assert.False(isArrayTrue.Match(actual2)); - Assert.True(isArrayTrue.Match(actual3)); - Assert.False(isArrayTrue.Match(actual4)); - Assert.False(isArrayTrue.Match(actual5)); - Assert.True(isArrayTrue.Match(actual6)); - Assert.False(isArrayFalse.Match(actual7)); - - // isArray: false - Assert.False(isArrayFalse.Match(actual1)); - Assert.True(isArrayFalse.Match(actual2)); - Assert.False(isArrayFalse.Match(actual3)); - Assert.True(isArrayFalse.Match(actual4)); - Assert.True(isArrayFalse.Match(actual5)); - Assert.False(isArrayFalse.Match(actual6)); - Assert.False(isArrayFalse.Match(actual7)); - } - - [Theory] - [InlineData("Yaml", SelectorYamlFileName)] - [InlineData("Json", SelectorJsonFileName)] - public void IsBooleanExpression(string type, string path) - { - var actual1 = GetObject((name: "value", value: true)); - var actual2 = GetObject((name: "value", value: false)); - var actual3 = GetObject((name: "value", value: "true")); - var actual4 = GetObject((name: "value", value: "false")); - var actual5 = GetObject((name: "value", value: null)); - var actual6 = GetObject((name: "value", value: PSObject.AsPSObject(true))); - var actual7 = GetObject((name: "value", value: Array.Empty())); - var actual8 = GetObject(); - - // Without conversion - var isBooleanTrue = GetSelectorVisitor($"{type}IsBooleanTrue", GetSource(path), out _); - var isBooleanFalse = GetSelectorVisitor($"{type}IsBooleanFalse", GetSource(path), out _); - - // isBoolean: true - Assert.True(isBooleanTrue.Match(actual1)); - Assert.True(isBooleanTrue.Match(actual2)); - Assert.False(isBooleanTrue.Match(actual3)); - Assert.False(isBooleanTrue.Match(actual4)); - Assert.False(isBooleanTrue.Match(actual5)); - Assert.True(isBooleanTrue.Match(actual6)); - Assert.False(isBooleanTrue.Match(actual7)); - Assert.False(isBooleanTrue.Match(actual8)); - - // isBoolean: false - Assert.False(isBooleanFalse.Match(actual1)); - Assert.False(isBooleanFalse.Match(actual2)); - Assert.True(isBooleanFalse.Match(actual3)); - Assert.True(isBooleanFalse.Match(actual4)); - Assert.True(isBooleanFalse.Match(actual5)); - Assert.False(isBooleanFalse.Match(actual6)); - Assert.True(isBooleanFalse.Match(actual7)); - Assert.False(isBooleanFalse.Match(actual8)); - - // With conversion - var isBooleanConvertTrue = GetSelectorVisitor($"{type}IsBooleanTrueWithConversion", GetSource(path), out _); - var isBooleanConvertFalse = GetSelectorVisitor($"{type}IsBooleanFalseWithConversion", GetSource(path), out _); - - // isBoolean: true - Assert.True(isBooleanConvertTrue.Match(actual1)); - Assert.True(isBooleanConvertTrue.Match(actual2)); - Assert.True(isBooleanConvertTrue.Match(actual3)); - Assert.True(isBooleanConvertTrue.Match(actual4)); - Assert.False(isBooleanConvertTrue.Match(actual5)); - Assert.True(isBooleanConvertTrue.Match(actual6)); - Assert.False(isBooleanConvertTrue.Match(actual7)); - Assert.False(isBooleanConvertTrue.Match(actual8)); - - // isBoolean: false - Assert.False(isBooleanConvertFalse.Match(actual1)); - Assert.False(isBooleanConvertFalse.Match(actual2)); - Assert.False(isBooleanConvertFalse.Match(actual3)); - Assert.False(isBooleanConvertFalse.Match(actual4)); - Assert.True(isBooleanConvertFalse.Match(actual5)); - Assert.False(isBooleanConvertFalse.Match(actual6)); - Assert.True(isBooleanConvertFalse.Match(actual7)); - Assert.False(isBooleanConvertFalse.Match(actual8)); - } - - [Theory] - [InlineData("Yaml", SelectorYamlFileName)] - [InlineData("Json", SelectorJsonFileName)] - public void IsDateTimeExpression(string type, string path) - { - var actual1 = GetObject((name: "value", value: DateTime.Now)); - var actual2 = GetObject((name: "value", value: 1)); - var actual3 = GetObject((name: "value", value: "2021-04-03T15:00:00.00+10:00")); - var actual4 = GetObject((name: "value", value: new JValue(DateTime.Now))); - var actual5 = GetObject((name: "value", value: null)); - var actual6 = GetObject((name: "value", value: PSObject.AsPSObject(DateTime.Now))); - var actual7 = GetObject((name: "value", value: new JValue("2021-04-03T15:00:00.00+10:00"))); - var actual8 = GetObject((name: "value", value: long.MaxValue)); - var actual9 = GetObject(); - - // Without conversion - var isDateTimeTrue = GetSelectorVisitor($"{type}IsDateTimeTrue", GetSource(path), out _); - var isDateTimeFalse = GetSelectorVisitor($"{type}IsDateTimeFalse", GetSource(path), out _); - - // isDateTime: true - Assert.True(isDateTimeTrue.Match(actual1)); - Assert.False(isDateTimeTrue.Match(actual2)); - Assert.False(isDateTimeTrue.Match(actual3)); - Assert.True(isDateTimeTrue.Match(actual4)); - Assert.False(isDateTimeTrue.Match(actual5)); - Assert.True(isDateTimeTrue.Match(actual6)); - Assert.False(isDateTimeTrue.Match(actual7)); - Assert.False(isDateTimeTrue.Match(actual8)); - Assert.False(isDateTimeTrue.Match(actual9)); - - // isDateTime: false - Assert.False(isDateTimeFalse.Match(actual1)); - Assert.True(isDateTimeFalse.Match(actual2)); - Assert.True(isDateTimeFalse.Match(actual3)); - Assert.False(isDateTimeFalse.Match(actual4)); - Assert.True(isDateTimeFalse.Match(actual5)); - Assert.False(isDateTimeFalse.Match(actual6)); - Assert.True(isDateTimeFalse.Match(actual7)); - Assert.True(isDateTimeFalse.Match(actual8)); - Assert.False(isDateTimeFalse.Match(actual9)); - - // With conversion - var isDateTimeConvertTrue = GetSelectorVisitor($"{type}IsDateTimeTrueWithConversion", GetSource(path), out _); - var isDateTimeConvertFalse = GetSelectorVisitor($"{type}IsDateTimeFalseWithConversion", GetSource(path), out _); - - // isDateTime: true - Assert.True(isDateTimeConvertTrue.Match(actual1)); - Assert.True(isDateTimeConvertTrue.Match(actual2)); - Assert.True(isDateTimeConvertTrue.Match(actual3)); - Assert.True(isDateTimeConvertTrue.Match(actual4)); - Assert.False(isDateTimeConvertTrue.Match(actual5)); - Assert.True(isDateTimeConvertTrue.Match(actual6)); - Assert.True(isDateTimeConvertTrue.Match(actual7)); - Assert.False(isDateTimeConvertTrue.Match(actual8)); - Assert.False(isDateTimeConvertTrue.Match(actual9)); - - // isDateTime: false - Assert.False(isDateTimeConvertFalse.Match(actual1)); - Assert.False(isDateTimeConvertFalse.Match(actual2)); - Assert.False(isDateTimeConvertFalse.Match(actual3)); - Assert.False(isDateTimeConvertFalse.Match(actual4)); - Assert.True(isDateTimeConvertFalse.Match(actual5)); - Assert.False(isDateTimeConvertFalse.Match(actual6)); - Assert.False(isDateTimeConvertFalse.Match(actual7)); - Assert.True(isDateTimeConvertFalse.Match(actual8)); - Assert.False(isDateTimeConvertFalse.Match(actual9)); - } - - [Theory] - [InlineData("Yaml", SelectorYamlFileName)] - [InlineData("Json", SelectorJsonFileName)] - public void IsIntegerExpression(string type, string path) - { - var actual1 = GetObject((name: "value", value: 123)); - var actual2 = GetObject((name: "value", value: 1.0f)); - var actual3 = GetObject((name: "value", value: long.MaxValue)); - var actual4 = GetObject((name: "value", value: "123")); - var actual5 = GetObject((name: "value", value: null)); - var actual6 = GetObject((name: "value", value: PSObject.AsPSObject(123))); - var actual7 = GetObject((name: "value", value: byte.MaxValue)); - var actual8 = GetObject(); - - // Without conversion - var isIntegerTrue = GetSelectorVisitor($"{type}IsIntegerTrue", GetSource(path), out _); - var isIntegerFalse = GetSelectorVisitor($"{type}IsIntegerFalse", GetSource(path), out _); - - // isInteger: true - Assert.True(isIntegerTrue.Match(actual1)); - Assert.False(isIntegerTrue.Match(actual2)); - Assert.True(isIntegerTrue.Match(actual3)); - Assert.False(isIntegerTrue.Match(actual4)); - Assert.False(isIntegerTrue.Match(actual5)); - Assert.True(isIntegerTrue.Match(actual6)); - Assert.True(isIntegerTrue.Match(actual7)); - Assert.False(isIntegerTrue.Match(actual8)); - - // isInteger: false - Assert.False(isIntegerFalse.Match(actual1)); - Assert.True(isIntegerFalse.Match(actual2)); - Assert.False(isIntegerFalse.Match(actual3)); - Assert.True(isIntegerFalse.Match(actual4)); - Assert.True(isIntegerFalse.Match(actual5)); - Assert.False(isIntegerFalse.Match(actual6)); - Assert.False(isIntegerFalse.Match(actual7)); - Assert.False(isIntegerFalse.Match(actual8)); - - // With conversion - var isIntegerConvertTrue = GetSelectorVisitor($"{type}IsIntegerTrueWithConversion", GetSource(path), out _); - var isIntegerConvertFalse = GetSelectorVisitor($"{type}IsIntegerFalseWithConversion", GetSource(path), out _); - - // isInteger: true - Assert.True(isIntegerConvertTrue.Match(actual1)); - Assert.False(isIntegerConvertTrue.Match(actual2)); - Assert.True(isIntegerConvertTrue.Match(actual3)); - Assert.True(isIntegerConvertTrue.Match(actual4)); - Assert.False(isIntegerConvertTrue.Match(actual5)); - Assert.True(isIntegerConvertTrue.Match(actual6)); - Assert.True(isIntegerConvertTrue.Match(actual7)); - Assert.False(isIntegerConvertTrue.Match(actual8)); - - // isInteger: false - Assert.False(isIntegerConvertFalse.Match(actual1)); - Assert.True(isIntegerConvertFalse.Match(actual2)); - Assert.False(isIntegerConvertFalse.Match(actual3)); - Assert.False(isIntegerConvertFalse.Match(actual4)); - Assert.True(isIntegerConvertFalse.Match(actual5)); - Assert.False(isIntegerConvertFalse.Match(actual6)); - Assert.False(isIntegerConvertFalse.Match(actual7)); - Assert.False(isIntegerConvertFalse.Match(actual8)); - } - - [Theory] - [InlineData("Yaml", SelectorYamlFileName)] - [InlineData("Json", SelectorJsonFileName)] - public void IsNumericExpression(string type, string path) - { - var actual1 = GetObject((name: "value", value: 123)); - var actual2 = GetObject((name: "value", value: 1.0f)); - var actual3 = GetObject((name: "value", value: long.MaxValue)); - var actual4 = GetObject((name: "value", value: "123")); - var actual5 = GetObject((name: "value", value: null)); - var actual6 = GetObject((name: "value", value: PSObject.AsPSObject(123))); - var actual7 = GetObject((name: "value", value: byte.MaxValue)); - var actual8 = GetObject((name: "value", value: double.MaxValue)); - var actual9 = GetObject(); - - // Without conversion - var isNumericTrue = GetSelectorVisitor($"{type}IsNumericTrue", GetSource(path), out _); - var isNumericFalse = GetSelectorVisitor($"{type}IsNumericFalse", GetSource(path), out _); - - // isNumeric: true - Assert.True(isNumericTrue.Match(actual1)); - Assert.True(isNumericTrue.Match(actual2)); - Assert.True(isNumericTrue.Match(actual3)); - Assert.False(isNumericTrue.Match(actual4)); - Assert.False(isNumericTrue.Match(actual5)); - Assert.True(isNumericTrue.Match(actual6)); - Assert.True(isNumericTrue.Match(actual7)); - Assert.True(isNumericTrue.Match(actual8)); - Assert.False(isNumericTrue.Match(actual9)); - - // isNumeric: false - Assert.False(isNumericFalse.Match(actual1)); - Assert.False(isNumericFalse.Match(actual2)); - Assert.False(isNumericFalse.Match(actual3)); - Assert.True(isNumericFalse.Match(actual4)); - Assert.True(isNumericFalse.Match(actual5)); - Assert.False(isNumericFalse.Match(actual6)); - Assert.False(isNumericFalse.Match(actual7)); - Assert.False(isNumericFalse.Match(actual8)); - Assert.False(isNumericFalse.Match(actual9)); - - // With conversion - var isNumericConvertTrue = GetSelectorVisitor($"{type}IsNumericTrueWithConversion", GetSource(path), out _); - var isNumericConvertFalse = GetSelectorVisitor($"{type}IsNumericFalseWithConversion", GetSource(path), out _); - - // isNumeric: true - Assert.True(isNumericConvertTrue.Match(actual1)); - Assert.True(isNumericConvertTrue.Match(actual2)); - Assert.True(isNumericConvertTrue.Match(actual3)); - Assert.True(isNumericConvertTrue.Match(actual4)); - Assert.False(isNumericConvertTrue.Match(actual5)); - Assert.True(isNumericConvertTrue.Match(actual6)); - Assert.True(isNumericConvertTrue.Match(actual7)); - Assert.True(isNumericConvertTrue.Match(actual8)); - Assert.False(isNumericConvertTrue.Match(actual9)); - - // isNumeric: false - Assert.False(isNumericConvertFalse.Match(actual1)); - Assert.False(isNumericConvertFalse.Match(actual2)); - Assert.False(isNumericConvertFalse.Match(actual3)); - Assert.False(isNumericConvertFalse.Match(actual4)); - Assert.True(isNumericConvertFalse.Match(actual5)); - Assert.False(isNumericConvertFalse.Match(actual6)); - Assert.False(isNumericConvertFalse.Match(actual7)); - Assert.False(isNumericConvertFalse.Match(actual8)); - Assert.False(isNumericConvertFalse.Match(actual9)); - } - - [Theory] - [InlineData("Yaml", SelectorYamlFileName)] - [InlineData("Json", SelectorJsonFileName)] - public void IsLowerExpression(string type, string path) - { - var isLowerTrue = GetSelectorVisitor($"{type}IsLowerTrue", GetSource(path), out _); - var isLowerFalse = GetSelectorVisitor($"{type}IsLowerFalse", GetSource(path), out _); - var actual1 = GetObject((name: "value", value: "abc")); - var actual2 = GetObject((name: "value", value: "aBc")); - var actual3 = GetObject((name: "value", value: "a-b-c")); - var actual4 = GetObject((name: "value", value: 4)); - var actual5 = GetObject((name: "value", value: null)); - var actual6 = GetObject(); - - // isLower: true - Assert.True(isLowerTrue.Match(actual1)); - Assert.False(isLowerTrue.Match(actual2)); - Assert.True(isLowerTrue.Match(actual3)); - Assert.False(isLowerTrue.Match(actual4)); - Assert.False(isLowerTrue.Match(actual5)); - Assert.False(isLowerTrue.Match(actual6)); - - // isLower: false - Assert.False(isLowerFalse.Match(actual1)); - Assert.True(isLowerFalse.Match(actual2)); - Assert.False(isLowerFalse.Match(actual3)); - Assert.True(isLowerFalse.Match(actual4)); - Assert.True(isLowerFalse.Match(actual5)); - Assert.False(isLowerTrue.Match(actual6)); - - // With name - var withName = GetSelectorVisitor($"{type}NameIsLower", GetSource(path), out var context); - var actual7 = GetObject( - (name: "Name", value: "targetobject1") - ); - var actual8 = GetObject( - (name: "Name", value: "TargetObject2") - ); - - context.EnterTargetObject(new TargetObject(actual7)); - Assert.True(withName.Match(actual7)); - - context.EnterTargetObject(new TargetObject(actual8)); - Assert.False(withName.Match(actual8)); - } - - [Theory] - [InlineData("Yaml", SelectorYamlFileName)] - [InlineData("Json", SelectorJsonFileName)] - public void IsUpperExpression(string type, string path) - { - var isUpperTrue = GetSelectorVisitor($"{type}IsUpperTrue", GetSource(path), out _); - var isUpperFalse = GetSelectorVisitor($"{type}IsUpperFalse", GetSource(path), out _); - var actual1 = GetObject((name: "value", value: "ABC")); - var actual2 = GetObject((name: "value", value: "aBc")); - var actual3 = GetObject((name: "value", value: "A-B-C")); - var actual4 = GetObject((name: "value", value: 4)); - var actual5 = GetObject((name: "value", value: null)); - var actual6 = GetObject(); - - // isUpper: true - Assert.True(isUpperTrue.Match(actual1)); - Assert.False(isUpperTrue.Match(actual2)); - Assert.True(isUpperTrue.Match(actual3)); - Assert.False(isUpperTrue.Match(actual4)); - Assert.False(isUpperTrue.Match(actual5)); - Assert.False(isUpperTrue.Match(actual6)); - - // isUpper: false - Assert.False(isUpperFalse.Match(actual1)); - Assert.True(isUpperFalse.Match(actual2)); - Assert.False(isUpperFalse.Match(actual3)); - Assert.True(isUpperFalse.Match(actual4)); - Assert.True(isUpperFalse.Match(actual5)); - Assert.False(isUpperFalse.Match(actual6)); - - // With name - var withName = GetSelectorVisitor($"{type}NameIsUpper", GetSource(path), out var context); - var actual7 = GetObject( - (name: "Name", value: "TARGETOBJECT1") - ); - var actual8 = GetObject( - (name: "Name", value: "TargetObject2") - ); - - context.EnterTargetObject(new TargetObject(actual7)); - Assert.True(withName.Match(actual7)); - - context.EnterTargetObject(new TargetObject(actual8)); - Assert.False(withName.Match(actual8)); - } - - - [Theory] - [InlineData("Yaml", SelectorYamlFileName)] - [InlineData("Json", SelectorJsonFileName)] - public void HasSchemaExpression(string type, string path) - { - var hasSchema = GetSelectorVisitor($"{type}HasSchema", GetSource(path), out _); - var actual1 = GetObject((name: "key", value: "value"), (name: "$schema", value: "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#")); - var actual2 = GetObject((name: "key", value: "value"), (name: "$schema", value: "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json")); - var actual3 = GetObject((name: "key", value: "value"), (name: "$schema", value: "http://schema.management.azure.com/schemas/2019-04-01/DeploymentParameters.json#")); - var actual4 = GetObject((name: "key", value: "value"), (name: "$schema", value: null)); - var actual5 = GetObject((name: "key", value: "value"), (name: "$schema", value: "")); - var actual6 = GetObject(); - - Assert.True(hasSchema.Match(actual1)); - Assert.True(hasSchema.Match(actual2)); - Assert.False(hasSchema.Match(actual3)); - Assert.False(hasSchema.Match(actual4)); - Assert.False(hasSchema.Match(actual5)); - Assert.False(hasSchema.Match(actual6)); - - hasSchema = GetSelectorVisitor($"{type}HasSchemaIgnoreScheme", GetSource(path), out _); - Assert.True(hasSchema.Match(actual1)); - Assert.True(hasSchema.Match(actual2)); - Assert.True(hasSchema.Match(actual3)); - Assert.False(hasSchema.Match(actual4)); - Assert.False(hasSchema.Match(actual5)); - Assert.False(hasSchema.Match(actual6)); - - hasSchema = GetSelectorVisitor($"{type}HasSchemaCaseSensitive", GetSource(path), out _); - Assert.True(hasSchema.Match(actual1)); - Assert.True(hasSchema.Match(actual2)); - Assert.False(hasSchema.Match(actual3)); - Assert.False(hasSchema.Match(actual4)); - Assert.False(hasSchema.Match(actual5)); - Assert.False(hasSchema.Match(actual6)); - - hasSchema = GetSelectorVisitor($"{type}HasAnySchema", GetSource(path), out _); - Assert.True(hasSchema.Match(actual1)); - Assert.True(hasSchema.Match(actual2)); - Assert.True(hasSchema.Match(actual3)); - Assert.False(hasSchema.Match(actual4)); - Assert.False(hasSchema.Match(actual5)); - Assert.False(hasSchema.Match(actual6)); - } - - [Theory] - [InlineData("Yaml", SelectorYamlFileName)] - [InlineData("Json", SelectorJsonFileName)] - public void Version(string type, string path) - { - var actual1 = GetObject((name: "version", value: "1.2.3")); - var actual2 = GetObject((name: "version", value: "0.2.3")); - var actual3 = GetObject((name: "version", value: "2.2.3")); - var actual4 = GetObject((name: "version", value: "1.1.3")); - var actual5 = GetObject((name: "version", value: "1.3.3-preview.1")); - var actual6 = GetObject(); - var actual7 = GetObject((name: "version", value: "a.b.c")); - - var version = GetSelectorVisitor($"{type}Version", GetSource(path), out _); - Assert.True(version.Match(actual1)); - Assert.False(version.Match(actual2)); - Assert.False(version.Match(actual3)); - Assert.False(version.Match(actual4)); - Assert.False(version.Match(actual5)); - Assert.False(version.Match(actual6)); - Assert.False(version.Match(actual7)); - - version = GetSelectorVisitor($"{type}VersionWithPrerelease", GetSource(path), out _); - Assert.True(version.Match(actual1)); - Assert.False(version.Match(actual2)); - Assert.False(version.Match(actual3)); - Assert.False(version.Match(actual4)); - Assert.True(version.Match(actual5)); - Assert.False(version.Match(actual6)); - Assert.False(version.Match(actual7)); - - version = GetSelectorVisitor($"{type}VersionAnyVersion", GetSource(path), out _); - Assert.True(version.Match(actual1)); - Assert.True(version.Match(actual2)); - Assert.True(version.Match(actual3)); - Assert.True(version.Match(actual4)); - Assert.True(version.Match(actual5)); - Assert.False(version.Match(actual6)); - Assert.False(version.Match(actual7)); - } - - [Theory] - [InlineData("Yaml", SelectorYamlFileName)] - [InlineData("Json", SelectorJsonFileName)] - public void APIVersion(string type, string path) - { - var actual1 = GetObject((name: "dateVersion", value: "2015-10-01")); - var actual2 = GetObject((name: "dateVersion", value: "2014-01-01")); - var actual3 = GetObject((name: "dateVersion", value: "2022-01-01")); - var actual4 = GetObject((name: "dateVersion", value: "2015-10-01-preview")); - var actual5 = GetObject((name: "dateVersion", value: "2022-01-01-preview")); - var actual6 = GetObject(); - var actual7 = GetObject((name: "dateVersion", value: "a-b-c")); - - var version = GetSelectorVisitor($"{type}APIVersion", GetSource(path), out _); - Assert.True(version.Match(actual1)); - Assert.False(version.Match(actual2)); - Assert.True(version.Match(actual3)); - Assert.False(version.Match(actual4)); - Assert.False(version.Match(actual5)); - Assert.False(version.Match(actual6)); - Assert.False(version.Match(actual7)); - - version = GetSelectorVisitor($"{type}APIVersionWithPrerelease", GetSource(path), out _); - Assert.True(version.Match(actual1)); - Assert.False(version.Match(actual2)); - Assert.True(version.Match(actual3)); - Assert.False(version.Match(actual4)); - Assert.True(version.Match(actual5)); - Assert.False(version.Match(actual6)); - Assert.False(version.Match(actual7)); - - version = GetSelectorVisitor($"{type}APIVersionAnyVersion", GetSource(path), out _); - Assert.True(version.Match(actual1)); - Assert.True(version.Match(actual2)); - Assert.True(version.Match(actual3)); - Assert.True(version.Match(actual4)); - Assert.True(version.Match(actual5)); - Assert.False(version.Match(actual6)); - Assert.False(version.Match(actual7)); - } - - [Theory] - [InlineData("Yaml", SelectorYamlFileName)] - [InlineData("Json", SelectorJsonFileName)] - public void HasDefault(string type, string path) - { - var actual1 = GetObject((name: "integerValue", value: 100), (name: "boolValue", value: true), (name: "stringValue", value: "testValue")); - var actual2 = GetObject((name: "integerValue", value: 1)); - var actual3 = GetObject((name: "boolValue", value: false)); - var actual4 = GetObject((name: "stringValue", value: "TestValue")); - var actual5 = GetObject(); - var actual6 = GetObject((name: "integerValue", value: new JValue(100))); - var actual7 = GetObject((name: "boolValue", value: new JValue(true))); - var actual8 = GetObject((name: "stringValue", value: new JValue("testValue"))); - - var hasDefault = GetSelectorVisitor($"{type}HasDefault", GetSource(path), out _); - Assert.True(hasDefault.Match(actual1)); - Assert.False(hasDefault.Match(actual2)); - Assert.False(hasDefault.Match(actual3)); - Assert.False(hasDefault.Match(actual4)); - Assert.True(hasDefault.Match(actual5)); - Assert.True(hasDefault.Match(actual6)); - Assert.True(hasDefault.Match(actual7)); - Assert.True(hasDefault.Match(actual8)); - } - - #endregion Conditions - - #region Operators - - [Theory] - [InlineData("Yaml", SelectorYamlFileName)] - [InlineData("Json", SelectorJsonFileName)] - public void AllOf(string type, string path) - { - var allOf = GetSelectorVisitor($"{type}AllOf", GetSource(path), out _); - var actual1 = GetObject((name: "Name", value: "Name1")); - var actual2 = GetObject((name: "AlternateName", value: "Name2")); - var actual3 = GetObject((name: "Name", value: "Name1"), (name: "AlternateName", value: "Name2")); - var actual4 = GetObject((name: "OtherName", value: "Name3")); - - Assert.False(allOf.Match(actual1)); - Assert.False(allOf.Match(actual2)); - Assert.True(allOf.Match(actual3)); - Assert.False(allOf.Match(actual4)); - - // With quantifier - allOf = GetSelectorVisitor($"{type}AllOfWithQuantifier", GetSource(path), out _); - actual1 = GetObject((name: "Name", value: "TargetObject1"), (name: "properties", value: GetObject((name: "logs", value: new object[] - { - GetObject((name: "name", value: "log1")) - })))); - actual2 = GetObject((name: "Name", value: "TargetObject1"), (name: "properties", value: GetObject((name: "logs", value: new object[] - { - GetObject((name: "name", value: "log1")), - GetObject((name: "name", value: "log2")) - })))); - actual3 = GetObject((name: "Name", value: "TargetObject1"), (name: "properties", value: GetObject((name: "logs", value: Array.Empty())))); - - Assert.True(allOf.Match(actual1)); - Assert.True(allOf.Match(actual2)); - Assert.False(allOf.Match(actual3)); - } - - [Theory] - [InlineData("Yaml", SelectorYamlFileName)] - [InlineData("Json", SelectorJsonFileName)] - public void AnyOf(string type, string path) - { - var allOf = GetSelectorVisitor($"{type}AnyOf", GetSource(path), out _); - var actual1 = GetObject((name: "Name", value: "Name1")); - var actual2 = GetObject((name: "AlternateName", value: "Name2")); - var actual3 = GetObject((name: "Name", value: "Name1"), (name: "AlternateName", value: "Name2")); - var actual4 = GetObject((name: "OtherName", value: "Name3")); - - Assert.True(allOf.Match(actual1)); - Assert.True(allOf.Match(actual2)); - Assert.True(allOf.Match(actual3)); - Assert.False(allOf.Match(actual4)); - } - - [Theory] - [InlineData("Yaml", SelectorYamlFileName)] - [InlineData("Json", SelectorJsonFileName)] - public void Not(string type, string path) - { - var allOf = GetSelectorVisitor($"{type}Not", GetSource(path), out _); - var actual1 = GetObject((name: "Name", value: "Name1")); - var actual2 = GetObject((name: "AlternateName", value: "Name2")); - var actual3 = GetObject((name: "Name", value: "Name1"), (name: "AlternateName", value: "Name2")); - var actual4 = GetObject((name: "OtherName", value: "Name3")); - - Assert.False(allOf.Match(actual1)); - Assert.False(allOf.Match(actual2)); - Assert.False(allOf.Match(actual3)); - Assert.True(allOf.Match(actual4)); - } - - #endregion Operators - - #region Properties - - [Theory] - [InlineData("Yaml", SelectorYamlFileName)] - [InlineData("Json", SelectorJsonFileName)] - public void Type(string type, string path) - { - var equals = GetSelectorVisitor($"{type}TypeEquals", GetSource(path), out var context); - var actual1 = GetObject(); - actual1.TypeNames.Insert(0, "CustomType1"); + context.EnterTargetObject(new TargetObject(actual2)); - context.EnterTargetObject(new TargetObject(actual1)); + Assert.False(fieldNotWithinPath.Match(actual2)); - Assert.True(equals.Match(actual1)); - } + // Field Case sensitive + var fieldNotWithinPathCaseSensitive = GetSelectorVisitor($"{type}FieldNotWithinPathCaseSensitive", GetSource(path), out context); - [Theory] - [InlineData("Yaml", SelectorYamlFileName)] - [InlineData("Json", SelectorJsonFileName)] - public void Name(string type, string path) - { - var equals = GetSelectorVisitor($"{type}NameEquals", GetSource(path), out var context); - var actual1 = GetObject( - (name: "Name", value: "TargetObject1") - ); + context.EnterTargetObject(new TargetObject(actual2)); - context.EnterTargetObject(new TargetObject(actual1)); + Assert.True(fieldNotWithinPathCaseSensitive.Match(actual2)); + } - Assert.True(equals.Match(actual1)); - } + [Theory] + [InlineData("Yaml", SelectorYamlFileName)] + [InlineData("Json", SelectorJsonFileName)] + public void InExpression(string type, string path) + { + var @in = GetSelectorVisitor($"{type}In", GetSource(path), out _); + var actual1 = GetObject((name: "value", value: new string[] { "Value1" })); + var actual2 = GetObject((name: "value", value: new string[] { "Value2" })); + var actual3 = GetObject((name: "value", value: new string[] { "Value3" })); + var actual4 = GetObject((name: "value", value: Array.Empty())); + var actual5 = GetObject((name: "value", value: null)); + var actual6 = GetObject(); + + Assert.True(@in.Match(actual1)); + Assert.True(@in.Match(actual2)); + Assert.False(@in.Match(actual3)); + Assert.False(@in.Match(actual4)); + Assert.False(@in.Match(actual5)); + Assert.False(@in.Match(actual6)); + + // With name + var withName = GetSelectorVisitor($"{type}NameIn", GetSource(path), out var context); + var actual7 = GetObject( + (name: "Name", value: "TargetObject1") + ); + var actual8 = GetObject( + (name: "Name", value: "TargetObject2") + ); + + context.EnterTargetObject(new TargetObject(actual7)); + Assert.True(withName.Match(actual7)); + + context.EnterTargetObject(new TargetObject(actual8)); + Assert.False(withName.Match(actual8)); + } - [Theory] - [InlineData("Yaml", SelectorYamlFileName)] - [InlineData("Json", SelectorJsonFileName)] - public void Scope(string type, string path) - { - var testObject = GetObject( - (name: "Name", value: "TargetObject1") - ); + [Theory] + [InlineData("Yaml", SelectorYamlFileName)] + [InlineData("Json", SelectorJsonFileName)] + public void NotInExpression(string type, string path) + { + var notIn = GetSelectorVisitor($"{type}NotIn", GetSource(path), out _); + var actual1 = GetObject((name: "value", value: new string[] { "Value1" })); + var actual2 = GetObject((name: "value", value: new string[] { "Value2" })); + var actual3 = GetObject((name: "value", value: new string[] { "Value3" })); + var actual4 = GetObject((name: "value", value: Array.Empty())); + var actual5 = GetObject((name: "value", value: null)); + + Assert.False(notIn.Match(actual1)); + Assert.False(notIn.Match(actual2)); + Assert.True(notIn.Match(actual3)); + Assert.True(notIn.Match(actual4)); + Assert.True(notIn.Match(actual5)); + + // With name + var withName = GetSelectorVisitor($"{type}NameNotIn", GetSource(path), out var context); + var actual6 = GetObject( + (name: "Name", value: "TargetObject1") + ); + var actual7 = GetObject( + (name: "Name", value: "TargetObject2") + ); + + context.EnterTargetObject(new TargetObject(actual6)); + Assert.False(withName.Match(actual6)); + + context.EnterTargetObject(new TargetObject(actual7)); + Assert.True(withName.Match(actual7)); + } - var equals = GetSelectorVisitor($"{type}ScopeEquals", GetSource(path), out var context); - context.EnterTargetObject(new TargetObject(testObject, scope: new string[] { "/scope1" })); - Assert.True(equals.Match(testObject)); + [Theory] + [InlineData("Yaml", SelectorYamlFileName)] + [InlineData("Json", SelectorJsonFileName)] + public void SetOfExpression(string type, string path) + { + var setOf = GetSelectorVisitor($"{type}SetOf", GetSource(path), out _); + var actual1 = GetObject((name: "value", value: new string[] { "cluster-autoscaler", "kube-apiserver", "kube-scheduler" })); + var actual2 = GetObject((name: "value", value: new string[] { "kube-apiserver", "cluster-autoscaler" })); + var actual3 = GetObject((name: "value", value: new string[] { "cluster-autoscaler" })); + var actual4 = GetObject((name: "value", value: new string[] { "kube-apiserver", "kube-scheduler" })); + var actual5 = GetObject((name: "value", value: new string[] { "kube-scheduler" })); + var actual6 = GetObject((name: "value", value: Array.Empty())); + var actual7 = GetObject((name: "value", value: null)); + var actual8 = GetObject(); + var actual9 = GetObject((name: "value", value: new string[] { "kube-apiserver", "cluster-autoscaler", "kube-apiserver" })); + var actual10 = GetObject((name: "value", value: new string[] { "cluster-autoscaler", "kube-APIserver" })); + + Assert.False(setOf.Match(actual1)); + Assert.True(setOf.Match(actual2)); + Assert.False(setOf.Match(actual3)); + Assert.False(setOf.Match(actual4)); + Assert.False(setOf.Match(actual5)); + Assert.False(setOf.Match(actual6)); + Assert.False(setOf.Match(actual7)); + Assert.False(setOf.Match(actual8)); + Assert.False(setOf.Match(actual9)); + Assert.False(setOf.Match(actual10)); + } - context.EnterTargetObject(new TargetObject(testObject, scope: new string[] { "/scope2" })); - Assert.False(equals.Match(testObject)); + [Theory] + [InlineData("Yaml", SelectorYamlFileName)] + [InlineData("Json", SelectorJsonFileName)] + public void SubsetExpression(string type, string path) + { + var subset = GetSelectorVisitor($"{type}Subset", GetSource(path), out _); + var actual1 = GetObject((name: "value", value: new string[] { "cluster-autoscaler", "kube-apiserver", "kube-scheduler" })); + var actual2 = GetObject((name: "value", value: new string[] { "kube-apiserver", "cluster-autoscaler" })); + var actual3 = GetObject((name: "value", value: new string[] { "cluster-autoscaler" })); + var actual4 = GetObject((name: "value", value: new string[] { "kube-apiserver", "kube-scheduler" })); + var actual5 = GetObject((name: "value", value: new string[] { "kube-scheduler" })); + var actual6 = GetObject((name: "value", value: Array.Empty())); + var actual7 = GetObject((name: "value", value: null)); + var actual8 = GetObject(); + var actual9 = GetObject((name: "value", value: new string[] { "kube-apiserver", "cluster-autoscaler", "kube-apiserver" })); + var actual10 = GetObject((name: "value", value: new string[] { "cluster-autoscaler", "kube-APIserver" })); + + Assert.True(subset.Match(actual1)); + Assert.True(subset.Match(actual2)); + Assert.False(subset.Match(actual3)); + Assert.False(subset.Match(actual4)); + Assert.False(subset.Match(actual5)); + Assert.False(subset.Match(actual6)); + Assert.False(subset.Match(actual7)); + Assert.False(subset.Match(actual8)); + Assert.False(subset.Match(actual9)); + Assert.False(subset.Match(actual10)); + } - context.EnterTargetObject(new TargetObject(testObject)); - Assert.False(equals.Match(testObject)); + [Theory] + [InlineData("Yaml", SelectorYamlFileName)] + [InlineData("Json", SelectorJsonFileName)] + public void CountExpression(string type, string path) + { + var count = GetSelectorVisitor($"{type}Count", GetSource(path), out _); + var actual1 = GetObject((name: "value", value: new string[] { "1", "2", "3" })); + var actual2 = GetObject((name: "value", value: new string[] { "2", "1" })); + var actual3 = GetObject((name: "value", value: new string[] { "1" })); + var actual4 = GetObject((name: "value", value: new int[] { 2, 3 })); + var actual5 = GetObject((name: "value", value: new int[] { 3 })); + var actual6 = GetObject((name: "value", value: Array.Empty())); + var actual7 = GetObject((name: "value", value: null)); + var actual8 = GetObject(); + + Assert.False(count.Match(actual1)); + Assert.True(count.Match(actual2)); + Assert.False(count.Match(actual3)); + Assert.True(count.Match(actual4)); + Assert.False(count.Match(actual5)); + Assert.False(count.Match(actual6)); + Assert.False(count.Match(actual7)); + Assert.False(count.Match(actual8)); + } - var startsWith = GetSelectorVisitor($"{type}ScopeStartsWith", GetSource(path), out context); - context.EnterTargetObject(new TargetObject(testObject, scope: new string[] { "/scope1/" })); - Assert.True(startsWith.Match(testObject)); + [Theory] + [InlineData("Yaml", SelectorYamlFileName)] + [InlineData("Json", SelectorJsonFileName)] + public void NotCountExpression(string type, string path) + { + var count = GetSelectorVisitor($"{type}NotCount", GetSource(path), out _); + var actual1 = GetObject((name: "value", value: new string[] { "1", "2", "3" })); + var actual2 = GetObject((name: "value", value: new string[] { "2", "1" })); + var actual3 = GetObject((name: "value", value: new string[] { "1" })); + var actual4 = GetObject((name: "value", value: new int[] { 2, 3 })); + var actual5 = GetObject((name: "value", value: new int[] { 3 })); + var actual6 = GetObject((name: "value", value: Array.Empty())); + var actual7 = GetObject((name: "value", value: null)); + var actual8 = GetObject(); + + Assert.True(count.Match(actual1)); + Assert.False(count.Match(actual2)); + Assert.True(count.Match(actual3)); + Assert.False(count.Match(actual4)); + Assert.True(count.Match(actual5)); + Assert.True(count.Match(actual6)); + Assert.False(count.Match(actual7)); + Assert.False(count.Match(actual8)); + } - context.EnterTargetObject(new TargetObject(testObject, scope: new string[] { "/scope2/" })); - Assert.True(startsWith.Match(testObject)); + [Theory] + [InlineData("Yaml", SelectorYamlFileName)] + [InlineData("Json", SelectorJsonFileName)] + public void LessExpression(string type, string path) + { + var less = GetSelectorVisitor($"{type}Less", GetSource(path), out _); + var actual1 = GetObject((name: "value", value: 3)); + var actual2 = GetObject((name: "value", value: 4)); + var actual3 = GetObject((name: "value", value: new string[] { "Value3" })); + var actual4 = GetObject((name: "value", value: Array.Empty())); + var actual5 = GetObject((name: "value", value: null)); + var actual6 = GetObject((name: "value", value: 2)); + var actual7 = GetObject((name: "value", value: -1)); + var actual8 = GetObject((name: "valueStr", value: "0")); + var actual9 = GetObject((name: "valueStr", value: "-1")); + + Assert.False(less.Match(actual1)); + Assert.False(less.Match(actual2)); + Assert.True(less.Match(actual3)); + Assert.True(less.Match(actual4)); + Assert.True(less.Match(actual5)); + Assert.True(less.Match(actual6)); + Assert.True(less.Match(actual7)); + Assert.False(less.Match(actual8)); + Assert.True(less.Match(actual9)); + + // With name + var withName = GetSelectorVisitor($"{type}NameLess", GetSource(path), out var context); + actual1 = GetObject( + (name: "Name", value: "ItemTwo") + ); + actual2 = GetObject( + (name: "Name", value: "ItemThree") + ); + + context.EnterTargetObject(new TargetObject(actual1)); + Assert.True(withName.Match(actual1)); + + context.EnterTargetObject(new TargetObject(actual2)); + Assert.False(withName.Match(actual2)); + } - context.EnterTargetObject(new TargetObject(testObject, scope: new string[] { "/scope2" })); - Assert.False(startsWith.Match(testObject)); + [Theory] + [InlineData("Yaml", SelectorYamlFileName)] + [InlineData("Json", SelectorJsonFileName)] + public void LessOrEqualsExpression(string type, string path) + { + var lessOrEquals = GetSelectorVisitor($"{type}LessOrEquals", GetSource(path), out _); + var actual1 = GetObject((name: "value", value: 3)); + var actual2 = GetObject((name: "value", value: 4)); + var actual3 = GetObject((name: "value", value: new string[] { "Value3" })); + var actual4 = GetObject((name: "value", value: Array.Empty())); + var actual5 = GetObject((name: "value", value: null)); + var actual6 = GetObject((name: "value", value: 2)); + var actual7 = GetObject((name: "value", value: -1)); + var actual8 = GetObject((name: "valueStr", value: "0")); + var actual9 = GetObject((name: "valueStr", value: "-1")); + + Assert.True(lessOrEquals.Match(actual1)); + Assert.False(lessOrEquals.Match(actual2)); + Assert.True(lessOrEquals.Match(actual3)); + Assert.True(lessOrEquals.Match(actual4)); + Assert.True(lessOrEquals.Match(actual5)); + Assert.True(lessOrEquals.Match(actual6)); + Assert.True(lessOrEquals.Match(actual7)); + Assert.True(lessOrEquals.Match(actual8)); + Assert.True(lessOrEquals.Match(actual9)); + + // With name + var withName = GetSelectorVisitor($"{type}NameLessOrEquals", GetSource(path), out var context); + actual1 = GetObject( + (name: "Name", value: "ItemTwo") + ); + actual2 = GetObject( + (name: "Name", value: "ItemThree") + ); + + context.EnterTargetObject(new TargetObject(actual1)); + Assert.True(withName.Match(actual1)); + + context.EnterTargetObject(new TargetObject(actual2)); + Assert.False(withName.Match(actual2)); + } - context.EnterTargetObject(new TargetObject(testObject)); - Assert.False(startsWith.Match(testObject)); + [Theory] + [InlineData("Yaml", SelectorYamlFileName)] + [InlineData("Json", SelectorJsonFileName)] + public void GreaterExpression(string type, string path) + { + var greater = GetSelectorVisitor($"{type}Greater", GetSource(path), out _); + var actual1 = GetObject((name: "value", value: 3)); + var actual2 = GetObject((name: "value", value: 4)); + var actual3 = GetObject((name: "value", value: new string[] { "Value3" })); + var actual4 = GetObject((name: "value", value: Array.Empty())); + var actual5 = GetObject((name: "value", value: null)); + var actual6 = GetObject((name: "value", value: 2)); + var actual7 = GetObject((name: "value", value: -1)); + var actual8 = GetObject((name: "valueStr", value: "0")); + var actual9 = GetObject((name: "valueStr", value: "-1")); + + Assert.False(greater.Match(actual1)); + Assert.True(greater.Match(actual2)); + Assert.False(greater.Match(actual3)); + Assert.False(greater.Match(actual4)); + Assert.False(greater.Match(actual5)); + Assert.False(greater.Match(actual6)); + Assert.False(greater.Match(actual7)); + Assert.True(greater.Match(actual8)); + Assert.False(greater.Match(actual9)); + + // With name + var withName = GetSelectorVisitor($"{type}NameGreater", GetSource(path), out var context); + actual1 = GetObject( + (name: "Name", value: "ItemTwo") + ); + actual2 = GetObject( + (name: "Name", value: "ItemThree") + ); + + context.EnterTargetObject(new TargetObject(actual1)); + Assert.False(withName.Match(actual1)); + + context.EnterTargetObject(new TargetObject(actual2)); + Assert.True(withName.Match(actual2)); + } - var hasValueFalse = GetSelectorVisitor($"{type}ScopeHasValueFalse", GetSource(path), out context); - context.EnterTargetObject(new TargetObject(testObject, scope: new string[] { "/scope1" })); - Assert.False(hasValueFalse.Match(testObject)); + [Theory] + [InlineData("Yaml", SelectorYamlFileName)] + [InlineData("Json", SelectorJsonFileName)] + public void GreaterOrEqualsExpression(string type, string path) + { + var greaterOrEquals = GetSelectorVisitor($"{type}GreaterOrEquals", GetSource(path), out _); + var actual1 = GetObject((name: "value", value: 3)); + var actual2 = GetObject((name: "value", value: 4)); + var actual3 = GetObject((name: "value", value: new string[] { "Value3" })); + var actual4 = GetObject((name: "value", value: Array.Empty())); + var actual5 = GetObject((name: "value", value: null)); + var actual6 = GetObject((name: "value", value: 2)); + var actual7 = GetObject((name: "value", value: -1)); + var actual8 = GetObject((name: "valueStr", value: "0")); + var actual9 = GetObject((name: "valueStr", value: "-1")); + + Assert.True(greaterOrEquals.Match(actual1)); + Assert.True(greaterOrEquals.Match(actual2)); + Assert.False(greaterOrEquals.Match(actual3)); + Assert.False(greaterOrEquals.Match(actual4)); + Assert.False(greaterOrEquals.Match(actual5)); + Assert.False(greaterOrEquals.Match(actual6)); + Assert.False(greaterOrEquals.Match(actual7)); + Assert.True(greaterOrEquals.Match(actual8)); + Assert.True(greaterOrEquals.Match(actual9)); + + // With name + var withName = GetSelectorVisitor($"{type}NameGreaterOrEquals", GetSource(path), out var context); + actual1 = GetObject( + (name: "Name", value: "ItemTwo") + ); + actual2 = GetObject( + (name: "Name", value: "ItemThree") + ); + + context.EnterTargetObject(new TargetObject(actual1)); + Assert.False(withName.Match(actual1)); + + context.EnterTargetObject(new TargetObject(actual2)); + Assert.True(withName.Match(actual2)); + } - context.EnterTargetObject(new TargetObject(testObject)); - Assert.True(hasValueFalse.Match(testObject)); + [Theory] + [InlineData("Yaml", SelectorYamlFileName)] + [InlineData("Json", SelectorJsonFileName)] + public void StartsWithExpression(string type, string path) + { + var startsWith = GetSelectorVisitor($"{type}StartsWith", GetSource(path), out _); + var actual1 = GetObject((name: "value", value: "abc")); + var actual2 = GetObject((name: "value", value: "efg")); + var actual3 = GetObject((name: "value", value: "hij")); + var actual4 = GetObject((name: "value", value: Array.Empty())); + var actual5 = GetObject((name: "value", value: null)); + var actual6 = GetObject(); + var actual7 = GetObject((name: "value", value: "EFG")); + var actual8 = GetObject((name: "value", value: "abc"), (name: "OtherValue", value: TestEnumValue.All)); + var actual9 = GetObject((name: "value", value: "abc"), (name: "OtherValue", value: TestEnumValue.None)); + var actual10 = GetObject((name: "value", value: new string[] { "hij", "abc" })); + + Assert.True(startsWith.Match(actual1)); + Assert.True(startsWith.Match(actual2)); + Assert.False(startsWith.Match(actual3)); + Assert.False(startsWith.Match(actual4)); + Assert.False(startsWith.Match(actual5)); + Assert.False(startsWith.Match(actual6)); + Assert.False(startsWith.Match(actual7)); + Assert.True(startsWith.Match(actual8)); + Assert.False(startsWith.Match(actual9)); + Assert.True(startsWith.Match(actual10)); + + // With name + var withName = GetSelectorVisitor($"{type}NameStartsWith", GetSource(path), out var context); + actual1 = GetObject( + (name: "Name", value: "1TargetObject") + ); + actual2 = GetObject( + (name: "Name", value: "2TargetObject") + ); + + context.EnterTargetObject(new TargetObject(actual1)); + Assert.True(withName.Match(actual1)); + + context.EnterTargetObject(new TargetObject(actual2)); + Assert.False(withName.Match(actual2)); + } - var hasValueTrue = GetSelectorVisitor($"{type}ScopeHasValueTrue", GetSource(path), out context); - context.EnterTargetObject(new TargetObject(testObject, scope: new string[] { "/scope1" })); - Assert.True(hasValueTrue.Match(testObject)); + [Theory] + [InlineData("Yaml", SelectorYamlFileName)] + [InlineData("Json", SelectorJsonFileName)] + public void NotStartsWithExpression(string type, string path) + { + var notStartsWith = GetSelectorVisitor($"{type}NotStartsWith", GetSource(path), out _); + var actual1 = GetObject((name: "value", value: "abc")); + var actual2 = GetObject((name: "value", value: "efg")); + var actual3 = GetObject((name: "value", value: "hij")); + var actual4 = GetObject((name: "value", value: Array.Empty())); + var actual5 = GetObject((name: "value", value: null)); + var actual6 = GetObject(); + var actual7 = GetObject((name: "value", value: "EFG")); + var actual8 = GetObject((name: "value", value: "abc"), (name: "OtherValue", value: TestEnumValue.All)); + var actual9 = GetObject((name: "value", value: "hij"), (name: "OtherValue", value: TestEnumValue.None)); + var actual10 = GetObject((name: "value", value: new string[] { "hij", "abc" })); + + Assert.False(notStartsWith.Match(actual1)); + Assert.False(notStartsWith.Match(actual2)); + Assert.True(notStartsWith.Match(actual3)); + Assert.True(notStartsWith.Match(actual4)); + Assert.True(notStartsWith.Match(actual5)); + Assert.False(notStartsWith.Match(actual6)); + Assert.False(notStartsWith.Match(actual7)); + Assert.False(notStartsWith.Match(actual8)); + Assert.True(notStartsWith.Match(actual9)); + Assert.False(notStartsWith.Match(actual10)); + } - context.EnterTargetObject(new TargetObject(testObject)); - Assert.False(hasValueTrue.Match(testObject)); - } + [Theory] + [InlineData("Yaml", SelectorYamlFileName)] + [InlineData("Json", SelectorJsonFileName)] + public void EndsWithExpression(string type, string path) + { + var endsWith = GetSelectorVisitor($"{type}EndsWith", GetSource(path), out _); + var actual1 = GetObject((name: "value", value: "abc")); + var actual2 = GetObject((name: "value", value: "efg")); + var actual3 = GetObject((name: "value", value: "hij")); + var actual4 = GetObject((name: "value", value: Array.Empty())); + var actual5 = GetObject((name: "value", value: null)); + var actual6 = GetObject(); + var actual7 = GetObject((name: "value", value: "EFG")); + var actual8 = GetObject((name: "value", value: "abc"), (name: "OtherValue", value: TestEnumValue.All)); + var actual9 = GetObject((name: "value", value: "abc"), (name: "OtherValue", value: TestEnumValue.None)); + var actual10 = GetObject((name: "value", value: new string[] { "hij", "abc" })); + + Assert.True(endsWith.Match(actual1)); + Assert.True(endsWith.Match(actual2)); + Assert.False(endsWith.Match(actual3)); + Assert.False(endsWith.Match(actual4)); + Assert.False(endsWith.Match(actual5)); + Assert.False(endsWith.Match(actual6)); + Assert.False(endsWith.Match(actual7)); + Assert.True(endsWith.Match(actual8)); + Assert.False(endsWith.Match(actual9)); + Assert.True(endsWith.Match(actual10)); + + // With name + var withName = GetSelectorVisitor($"{type}NameEndsWith", GetSource(path), out var context); + actual1 = GetObject( + (name: "Name", value: "TargetObject1") + ); + actual2 = GetObject( + (name: "Name", value: "TargetObject2") + ); + + context.EnterTargetObject(new TargetObject(actual1)); + Assert.True(withName.Match(actual1)); + + context.EnterTargetObject(new TargetObject(actual2)); + Assert.False(withName.Match(actual2)); + + // With source + var withSource = GetSelectorVisitor($"{type}EndsWithSource", GetSource(path), out context); + var source = new PSObject(); + source.Properties.Add(new PSNoteProperty("file", "deployments/path/template.json")); + source.Properties.Add(new PSNoteProperty("line", 100)); + source.Properties.Add(new PSNoteProperty("position", 1000)); + source.Properties.Add(new PSNoteProperty("Type", "Template")); + var info = new PSObject(); + info.Properties.Add(new PSNoteProperty("source", new PSObject[] { source })); + actual1 = new PSObject(); + actual1.Properties.Add(new PSNoteProperty("Name", "TestObject1")); + actual1.Properties.Add(new PSNoteProperty("Value", 1)); + actual1.Properties.Add(new PSNoteProperty("_PSRule", info)); + + context.EnterTargetObject(new TargetObject(actual1)); + Assert.True(withSource.Match(actual1)); + } - #endregion Properties + [Theory] + [InlineData("Yaml", SelectorYamlFileName)] + [InlineData("Json", SelectorJsonFileName)] + public void NotEndsWithExpression(string type, string path) + { + var notEndsWith = GetSelectorVisitor($"{type}NotEndsWith", GetSource(path), out _); + var actual1 = GetObject((name: "value", value: "abc")); + var actual2 = GetObject((name: "value", value: "efg")); + var actual3 = GetObject((name: "value", value: "hij")); + var actual4 = GetObject((name: "value", value: Array.Empty())); + var actual5 = GetObject((name: "value", value: null)); + var actual6 = GetObject(); + var actual7 = GetObject((name: "value", value: "EFG")); + var actual8 = GetObject((name: "value", value: "abc"), (name: "OtherValue", value: TestEnumValue.All)); + var actual9 = GetObject((name: "value", value: "hij"), (name: "OtherValue", value: TestEnumValue.None)); + var actual10 = GetObject((name: "value", value: new string[] { "hij", "abc" })); + + Assert.False(notEndsWith.Match(actual1)); + Assert.False(notEndsWith.Match(actual2)); + Assert.True(notEndsWith.Match(actual3)); + Assert.True(notEndsWith.Match(actual4)); + Assert.True(notEndsWith.Match(actual5)); + Assert.False(notEndsWith.Match(actual6)); + Assert.False(notEndsWith.Match(actual7)); + Assert.False(notEndsWith.Match(actual8)); + Assert.True(notEndsWith.Match(actual9)); + Assert.False(notEndsWith.Match(actual10)); + } - #region Functions + [Theory] + [InlineData("Yaml", SelectorYamlFileName)] + [InlineData("Json", SelectorJsonFileName)] + public void ContainsExpression(string type, string path) + { + var contains = GetSelectorVisitor($"{type}Contains", GetSource(path), out _); + var actual1 = GetObject((name: "value", value: "abc")); + var actual2 = GetObject((name: "value", value: "bcd")); + var actual3 = GetObject((name: "value", value: "hij")); + var actual4 = GetObject((name: "value", value: Array.Empty())); + var actual5 = GetObject((name: "value", value: null)); + var actual6 = GetObject(); + var actual7 = GetObject((name: "value", value: "BCD")); + var actual8 = GetObject((name: "value", value: "abc"), (name: "OtherValue", value: TestEnumValue.All)); + var actual9 = GetObject((name: "value", value: "abc"), (name: "OtherValue", value: TestEnumValue.None)); + var actual10 = GetObject((name: "value", value: new string[] { "hij", "abc" })); + + Assert.True(contains.Match(actual1)); + Assert.True(contains.Match(actual2)); + Assert.False(contains.Match(actual3)); + Assert.False(contains.Match(actual4)); + Assert.False(contains.Match(actual5)); + Assert.False(contains.Match(actual6)); + Assert.False(contains.Match(actual7)); + Assert.True(contains.Match(actual8)); + Assert.False(contains.Match(actual9)); + Assert.True(contains.Match(actual10)); + + // With name + var withName = GetSelectorVisitor($"{type}NameContains", GetSource(path), out var context); + actual1 = GetObject( + (name: "Name", value: "Target.1.Object") + ); + actual2 = GetObject( + (name: "Name", value: "Target.2.Object") + ); + + context.EnterTargetObject(new TargetObject(actual1)); + Assert.True(withName.Match(actual1)); + + context.EnterTargetObject(new TargetObject(actual2)); + Assert.False(withName.Match(actual2)); + } - [Theory] - [InlineData("Yaml", FunctionsYamlFileName)] - [InlineData("Json", FunctionsJsonFileName)] - public void WithFunction(string type, string path) - { - var example1 = GetSelectorVisitor($"{type}.Fn.Example1", GetSource(path), out _); - var example2 = GetSelectorVisitor($"{type}.Fn.Example2", GetSource(path), out _); - var example3 = GetSelectorVisitor($"{type}.Fn.Example3", GetSource(path), out _); - var example4 = GetSelectorVisitor($"{type}.Fn.Example4", GetSource(path), out _); - var example5 = GetSelectorVisitor($"{type}.Fn.Example5", GetSource(path), out _); - var example6 = GetSelectorVisitor($"{type}.Fn.Example6", GetSource(path), out _); - var actual1 = GetObject( - (name: "Name", value: "TestObject1") - ); - - Assert.True(example1.Match(actual1)); - Assert.True(example2.Match(actual1)); - Assert.True(example3.Match(actual1)); - Assert.True(example4.Match(actual1)); - Assert.True(example5.Match(actual1)); - Assert.True(example6.Match(actual1)); - } - - [Theory] - [InlineData("Yaml", FunctionsYamlFileName)] - [InlineData("Json", FunctionsJsonFileName)] - public void WithFunctionSpecific(string type, string path) - { - var example1 = GetSelectorVisitor($"{type}.Fn.Replace", GetSource(path), out _); - var example2 = GetSelectorVisitor($"{type}.Fn.Trim", GetSource(path), out _); - var example3 = GetSelectorVisitor($"{type}.Fn.First", GetSource(path), out _); - var example4 = GetSelectorVisitor($"{type}.Fn.Last", GetSource(path), out _); - var example5 = GetSelectorVisitor($"{type}.Fn.Split", GetSource(path), out _); - var example6 = GetSelectorVisitor($"{type}.Fn.PadLeft", GetSource(path), out _); - var example7 = GetSelectorVisitor($"{type}.Fn.PadRight", GetSource(path), out _); - var actual1 = GetObject( - (name: "Name", value: "TestObject1") - ); - - Assert.True(example1.Match(actual1)); - Assert.True(example2.Match(actual1)); - Assert.True(example3.Match(actual1)); - Assert.True(example4.Match(actual1)); - Assert.True(example5.Match(actual1)); - Assert.True(example6.Match(actual1)); - Assert.True(example7.Match(actual1)); - } - - #endregion Functions - - #region Helper methods - - private static PSRuleOption GetOption() - { - var option = new PSRuleOption(); - option.Configuration["ConfigArray"] = new string[] { "1", "2", "3", "4", "5" }; - return option; - } + [Theory] + [InlineData("Yaml", SelectorYamlFileName)] + [InlineData("Json", SelectorJsonFileName)] + public void NotContainsExpression(string type, string path) + { + var notContains = GetSelectorVisitor($"{type}NotContains", GetSource(path), out _); + var actual1 = GetObject((name: "value", value: "abc")); + var actual2 = GetObject((name: "value", value: "bcd")); + var actual3 = GetObject((name: "value", value: "hij")); + var actual4 = GetObject((name: "value", value: Array.Empty())); + var actual5 = GetObject((name: "value", value: null)); + var actual6 = GetObject(); + var actual7 = GetObject((name: "value", value: "BCD")); + var actual8 = GetObject((name: "value", value: "abc"), (name: "OtherValue", value: TestEnumValue.All)); + var actual9 = GetObject((name: "value", value: "hij"), (name: "OtherValue", value: TestEnumValue.None)); + var actual10 = GetObject((name: "value", value: new string[] { "hij", "abc" })); + + Assert.False(notContains.Match(actual1)); + Assert.False(notContains.Match(actual2)); + Assert.True(notContains.Match(actual3)); + Assert.True(notContains.Match(actual4)); + Assert.True(notContains.Match(actual5)); + Assert.False(notContains.Match(actual6)); + Assert.False(notContains.Match(actual7)); + Assert.False(notContains.Match(actual8)); + Assert.True(notContains.Match(actual9)); + Assert.False(notContains.Match(actual10)); + } - private static Source[] GetSource(string path) - { - var builder = new SourcePipelineBuilder(null, null); - builder.Directory(GetSourcePath(path)); - return builder.Build(); - } + [Theory] + [InlineData("Yaml", SelectorYamlFileName)] + [InlineData("Json", SelectorJsonFileName)] + public void LikeExpression(string type, string path) + { + var like = GetSelectorVisitor($"{type}Like", GetSource(path), out _); + var actual1 = GetObject((name: "value", value: "abc")); + var actual2 = GetObject((name: "value", value: "efg")); + var actual3 = GetObject((name: "value", value: "hij")); + var actual4 = GetObject((name: "value", value: Array.Empty())); + var actual5 = GetObject((name: "value", value: null)); + var actual6 = GetObject(); + var actual7 = GetObject((name: "value", value: "EFG")); + var actual8 = GetObject((name: "value", value: "abc"), (name: "OtherValue", value: "123")); + var actual9 = GetObject((name: "value", value: "abc"), (name: "OtherValue", value: 123)); + + Assert.True(like.Match(actual1)); + Assert.True(like.Match(actual2)); + Assert.False(like.Match(actual3)); + Assert.False(like.Match(actual4)); + Assert.False(like.Match(actual5)); + Assert.False(like.Match(actual6)); + Assert.False(like.Match(actual7)); + Assert.True(like.Match(actual8)); + Assert.True(like.Match(actual9)); + } - private static PSObject GetObject(params (string name, object value)[] properties) - { - var result = new PSObject(); - for (var i = 0; properties != null && i < properties.Length; i++) - result.Properties.Add(new PSNoteProperty(properties[i].name, properties[i].value)); + [Theory] + [InlineData("Yaml", SelectorYamlFileName)] + [InlineData("Json", SelectorJsonFileName)] + public void NotLikeExpression(string type, string path) + { + var notLike = GetSelectorVisitor($"{type}NotLike", GetSource(path), out _); + var actual1 = GetObject((name: "value", value: "abc")); + var actual2 = GetObject((name: "value", value: "efg")); + var actual3 = GetObject((name: "value", value: "hij")); + var actual4 = GetObject((name: "value", value: Array.Empty())); + var actual5 = GetObject((name: "value", value: null)); + var actual6 = GetObject(); + var actual7 = GetObject((name: "value", value: "EFG")); + var actual8 = GetObject((name: "value", value: "abc"), (name: "OtherValue", value: "123")); + var actual9 = GetObject((name: "value", value: "hij"), (name: "OtherValue", value: 123)); + + Assert.False(notLike.Match(actual1)); + Assert.False(notLike.Match(actual2)); + Assert.True(notLike.Match(actual3)); + Assert.True(notLike.Match(actual4)); + Assert.True(notLike.Match(actual5)); + Assert.False(notLike.Match(actual6)); + Assert.False(notLike.Match(actual7)); + Assert.False(notLike.Match(actual8)); + Assert.False(notLike.Match(actual9)); + } + + [Theory] + [InlineData("Yaml", SelectorYamlFileName)] + [InlineData("Json", SelectorJsonFileName)] + public void IsStringExpression(string type, string path) + { + var isStringTrue = GetSelectorVisitor($"{type}IsStringTrue", GetSource(path), out _); + var isStringFalse = GetSelectorVisitor($"{type}IsStringFalse", GetSource(path), out _); + var actual1 = GetObject((name: "value", value: "abc")); + var actual2 = GetObject((name: "value", value: 4)); + var actual3 = GetObject((name: "value", value: Array.Empty())); + var actual4 = GetObject((name: "value", value: null)); + var actual5 = GetObject(); + + // isString: true + Assert.True(isStringTrue.Match(actual1)); + Assert.False(isStringTrue.Match(actual2)); + Assert.False(isStringTrue.Match(actual3)); + Assert.False(isStringTrue.Match(actual4)); + Assert.False(isStringTrue.Match(actual5)); + + // isString: false + Assert.False(isStringFalse.Match(actual1)); + Assert.True(isStringFalse.Match(actual2)); + Assert.True(isStringFalse.Match(actual3)); + Assert.True(isStringFalse.Match(actual4)); + Assert.False(isStringFalse.Match(actual5)); + + // With name + var withName = GetSelectorVisitor($"{type}NameIsString", GetSource(path), out var context); + var actual7 = GetObject( + (name: "Name", value: "TargetObject1") + ); + var actual8 = GetObject( + (name: "Name", value: 1) + ); + + context.EnterTargetObject(new TargetObject(actual7)); + Assert.True(withName.Match(actual7)); + + context.EnterTargetObject(new TargetObject(actual8)); + Assert.True(withName.Match(actual8)); + } + + [Theory] + [InlineData("Yaml", SelectorYamlFileName)] + [InlineData("Json", SelectorJsonFileName)] + public void IsArrayExpression(string type, string path) + { + var isArrayTrue = GetSelectorVisitor($"{type}IsArrayTrue", GetSource(path), out _); + var isArrayFalse = GetSelectorVisitor($"{type}IsArrayFalse", GetSource(path), out _); + var actual1 = GetObject((name: "value", value: new string[] { "abc" })); + var actual2 = GetObject((name: "value", value: 4)); + var actual3 = GetObject((name: "value", value: PSObject.AsPSObject(new int[] { 1 }))); + var actual4 = GetObject((name: "value", value: null)); + var actual5 = GetObject((name: "value", value: "abc")); + var actual6 = GetObject((name: "value", value: new int[] { 1 })); + var actual7 = GetObject(); + + // isArray: true + Assert.True(isArrayTrue.Match(actual1)); + Assert.False(isArrayTrue.Match(actual2)); + Assert.True(isArrayTrue.Match(actual3)); + Assert.False(isArrayTrue.Match(actual4)); + Assert.False(isArrayTrue.Match(actual5)); + Assert.True(isArrayTrue.Match(actual6)); + Assert.False(isArrayFalse.Match(actual7)); + + // isArray: false + Assert.False(isArrayFalse.Match(actual1)); + Assert.True(isArrayFalse.Match(actual2)); + Assert.False(isArrayFalse.Match(actual3)); + Assert.True(isArrayFalse.Match(actual4)); + Assert.True(isArrayFalse.Match(actual5)); + Assert.False(isArrayFalse.Match(actual6)); + Assert.False(isArrayFalse.Match(actual7)); + } + + [Theory] + [InlineData("Yaml", SelectorYamlFileName)] + [InlineData("Json", SelectorJsonFileName)] + public void IsBooleanExpression(string type, string path) + { + var actual1 = GetObject((name: "value", value: true)); + var actual2 = GetObject((name: "value", value: false)); + var actual3 = GetObject((name: "value", value: "true")); + var actual4 = GetObject((name: "value", value: "false")); + var actual5 = GetObject((name: "value", value: null)); + var actual6 = GetObject((name: "value", value: PSObject.AsPSObject(true))); + var actual7 = GetObject((name: "value", value: Array.Empty())); + var actual8 = GetObject(); + + // Without conversion + var isBooleanTrue = GetSelectorVisitor($"{type}IsBooleanTrue", GetSource(path), out _); + var isBooleanFalse = GetSelectorVisitor($"{type}IsBooleanFalse", GetSource(path), out _); + + // isBoolean: true + Assert.True(isBooleanTrue.Match(actual1)); + Assert.True(isBooleanTrue.Match(actual2)); + Assert.False(isBooleanTrue.Match(actual3)); + Assert.False(isBooleanTrue.Match(actual4)); + Assert.False(isBooleanTrue.Match(actual5)); + Assert.True(isBooleanTrue.Match(actual6)); + Assert.False(isBooleanTrue.Match(actual7)); + Assert.False(isBooleanTrue.Match(actual8)); + + // isBoolean: false + Assert.False(isBooleanFalse.Match(actual1)); + Assert.False(isBooleanFalse.Match(actual2)); + Assert.True(isBooleanFalse.Match(actual3)); + Assert.True(isBooleanFalse.Match(actual4)); + Assert.True(isBooleanFalse.Match(actual5)); + Assert.False(isBooleanFalse.Match(actual6)); + Assert.True(isBooleanFalse.Match(actual7)); + Assert.False(isBooleanFalse.Match(actual8)); + + // With conversion + var isBooleanConvertTrue = GetSelectorVisitor($"{type}IsBooleanTrueWithConversion", GetSource(path), out _); + var isBooleanConvertFalse = GetSelectorVisitor($"{type}IsBooleanFalseWithConversion", GetSource(path), out _); + + // isBoolean: true + Assert.True(isBooleanConvertTrue.Match(actual1)); + Assert.True(isBooleanConvertTrue.Match(actual2)); + Assert.True(isBooleanConvertTrue.Match(actual3)); + Assert.True(isBooleanConvertTrue.Match(actual4)); + Assert.False(isBooleanConvertTrue.Match(actual5)); + Assert.True(isBooleanConvertTrue.Match(actual6)); + Assert.False(isBooleanConvertTrue.Match(actual7)); + Assert.False(isBooleanConvertTrue.Match(actual8)); + + // isBoolean: false + Assert.False(isBooleanConvertFalse.Match(actual1)); + Assert.False(isBooleanConvertFalse.Match(actual2)); + Assert.False(isBooleanConvertFalse.Match(actual3)); + Assert.False(isBooleanConvertFalse.Match(actual4)); + Assert.True(isBooleanConvertFalse.Match(actual5)); + Assert.False(isBooleanConvertFalse.Match(actual6)); + Assert.True(isBooleanConvertFalse.Match(actual7)); + Assert.False(isBooleanConvertFalse.Match(actual8)); + } - return result; - } + [Theory] + [InlineData("Yaml", SelectorYamlFileName)] + [InlineData("Json", SelectorJsonFileName)] + public void IsDateTimeExpression(string type, string path) + { + var actual1 = GetObject((name: "value", value: DateTime.Now)); + var actual2 = GetObject((name: "value", value: 1)); + var actual3 = GetObject((name: "value", value: "2021-04-03T15:00:00.00+10:00")); + var actual4 = GetObject((name: "value", value: new JValue(DateTime.Now))); + var actual5 = GetObject((name: "value", value: null)); + var actual6 = GetObject((name: "value", value: PSObject.AsPSObject(DateTime.Now))); + var actual7 = GetObject((name: "value", value: new JValue("2021-04-03T15:00:00.00+10:00"))); + var actual8 = GetObject((name: "value", value: long.MaxValue)); + var actual9 = GetObject(); + + // Without conversion + var isDateTimeTrue = GetSelectorVisitor($"{type}IsDateTimeTrue", GetSource(path), out _); + var isDateTimeFalse = GetSelectorVisitor($"{type}IsDateTimeFalse", GetSource(path), out _); + + // isDateTime: true + Assert.True(isDateTimeTrue.Match(actual1)); + Assert.False(isDateTimeTrue.Match(actual2)); + Assert.False(isDateTimeTrue.Match(actual3)); + Assert.True(isDateTimeTrue.Match(actual4)); + Assert.False(isDateTimeTrue.Match(actual5)); + Assert.True(isDateTimeTrue.Match(actual6)); + Assert.False(isDateTimeTrue.Match(actual7)); + Assert.False(isDateTimeTrue.Match(actual8)); + Assert.False(isDateTimeTrue.Match(actual9)); + + // isDateTime: false + Assert.False(isDateTimeFalse.Match(actual1)); + Assert.True(isDateTimeFalse.Match(actual2)); + Assert.True(isDateTimeFalse.Match(actual3)); + Assert.False(isDateTimeFalse.Match(actual4)); + Assert.True(isDateTimeFalse.Match(actual5)); + Assert.False(isDateTimeFalse.Match(actual6)); + Assert.True(isDateTimeFalse.Match(actual7)); + Assert.True(isDateTimeFalse.Match(actual8)); + Assert.False(isDateTimeFalse.Match(actual9)); + + // With conversion + var isDateTimeConvertTrue = GetSelectorVisitor($"{type}IsDateTimeTrueWithConversion", GetSource(path), out _); + var isDateTimeConvertFalse = GetSelectorVisitor($"{type}IsDateTimeFalseWithConversion", GetSource(path), out _); + + // isDateTime: true + Assert.True(isDateTimeConvertTrue.Match(actual1)); + Assert.True(isDateTimeConvertTrue.Match(actual2)); + Assert.True(isDateTimeConvertTrue.Match(actual3)); + Assert.True(isDateTimeConvertTrue.Match(actual4)); + Assert.False(isDateTimeConvertTrue.Match(actual5)); + Assert.True(isDateTimeConvertTrue.Match(actual6)); + Assert.True(isDateTimeConvertTrue.Match(actual7)); + Assert.False(isDateTimeConvertTrue.Match(actual8)); + Assert.False(isDateTimeConvertTrue.Match(actual9)); + + // isDateTime: false + Assert.False(isDateTimeConvertFalse.Match(actual1)); + Assert.False(isDateTimeConvertFalse.Match(actual2)); + Assert.False(isDateTimeConvertFalse.Match(actual3)); + Assert.False(isDateTimeConvertFalse.Match(actual4)); + Assert.True(isDateTimeConvertFalse.Match(actual5)); + Assert.False(isDateTimeConvertFalse.Match(actual6)); + Assert.False(isDateTimeConvertFalse.Match(actual7)); + Assert.True(isDateTimeConvertFalse.Match(actual8)); + Assert.False(isDateTimeConvertFalse.Match(actual9)); + } - private static SelectorVisitor GetSelectorVisitor(string name, Source[] source, out RunspaceContext context) + [Theory] + [InlineData("Yaml", SelectorYamlFileName)] + [InlineData("Json", SelectorJsonFileName)] + public void IsIntegerExpression(string type, string path) + { + var actual1 = GetObject((name: "value", value: 123)); + var actual2 = GetObject((name: "value", value: 1.0f)); + var actual3 = GetObject((name: "value", value: long.MaxValue)); + var actual4 = GetObject((name: "value", value: "123")); + var actual5 = GetObject((name: "value", value: null)); + var actual6 = GetObject((name: "value", value: PSObject.AsPSObject(123))); + var actual7 = GetObject((name: "value", value: byte.MaxValue)); + var actual8 = GetObject(); + + // Without conversion + var isIntegerTrue = GetSelectorVisitor($"{type}IsIntegerTrue", GetSource(path), out _); + var isIntegerFalse = GetSelectorVisitor($"{type}IsIntegerFalse", GetSource(path), out _); + + // isInteger: true + Assert.True(isIntegerTrue.Match(actual1)); + Assert.False(isIntegerTrue.Match(actual2)); + Assert.True(isIntegerTrue.Match(actual3)); + Assert.False(isIntegerTrue.Match(actual4)); + Assert.False(isIntegerTrue.Match(actual5)); + Assert.True(isIntegerTrue.Match(actual6)); + Assert.True(isIntegerTrue.Match(actual7)); + Assert.False(isIntegerTrue.Match(actual8)); + + // isInteger: false + Assert.False(isIntegerFalse.Match(actual1)); + Assert.True(isIntegerFalse.Match(actual2)); + Assert.False(isIntegerFalse.Match(actual3)); + Assert.True(isIntegerFalse.Match(actual4)); + Assert.True(isIntegerFalse.Match(actual5)); + Assert.False(isIntegerFalse.Match(actual6)); + Assert.False(isIntegerFalse.Match(actual7)); + Assert.False(isIntegerFalse.Match(actual8)); + + // With conversion + var isIntegerConvertTrue = GetSelectorVisitor($"{type}IsIntegerTrueWithConversion", GetSource(path), out _); + var isIntegerConvertFalse = GetSelectorVisitor($"{type}IsIntegerFalseWithConversion", GetSource(path), out _); + + // isInteger: true + Assert.True(isIntegerConvertTrue.Match(actual1)); + Assert.False(isIntegerConvertTrue.Match(actual2)); + Assert.True(isIntegerConvertTrue.Match(actual3)); + Assert.True(isIntegerConvertTrue.Match(actual4)); + Assert.False(isIntegerConvertTrue.Match(actual5)); + Assert.True(isIntegerConvertTrue.Match(actual6)); + Assert.True(isIntegerConvertTrue.Match(actual7)); + Assert.False(isIntegerConvertTrue.Match(actual8)); + + // isInteger: false + Assert.False(isIntegerConvertFalse.Match(actual1)); + Assert.True(isIntegerConvertFalse.Match(actual2)); + Assert.False(isIntegerConvertFalse.Match(actual3)); + Assert.False(isIntegerConvertFalse.Match(actual4)); + Assert.True(isIntegerConvertFalse.Match(actual5)); + Assert.False(isIntegerConvertFalse.Match(actual6)); + Assert.False(isIntegerConvertFalse.Match(actual7)); + Assert.False(isIntegerConvertFalse.Match(actual8)); + } + + [Theory] + [InlineData("Yaml", SelectorYamlFileName)] + [InlineData("Json", SelectorJsonFileName)] + public void IsNumericExpression(string type, string path) + { + var actual1 = GetObject((name: "value", value: 123)); + var actual2 = GetObject((name: "value", value: 1.0f)); + var actual3 = GetObject((name: "value", value: long.MaxValue)); + var actual4 = GetObject((name: "value", value: "123")); + var actual5 = GetObject((name: "value", value: null)); + var actual6 = GetObject((name: "value", value: PSObject.AsPSObject(123))); + var actual7 = GetObject((name: "value", value: byte.MaxValue)); + var actual8 = GetObject((name: "value", value: double.MaxValue)); + var actual9 = GetObject(); + + // Without conversion + var isNumericTrue = GetSelectorVisitor($"{type}IsNumericTrue", GetSource(path), out _); + var isNumericFalse = GetSelectorVisitor($"{type}IsNumericFalse", GetSource(path), out _); + + // isNumeric: true + Assert.True(isNumericTrue.Match(actual1)); + Assert.True(isNumericTrue.Match(actual2)); + Assert.True(isNumericTrue.Match(actual3)); + Assert.False(isNumericTrue.Match(actual4)); + Assert.False(isNumericTrue.Match(actual5)); + Assert.True(isNumericTrue.Match(actual6)); + Assert.True(isNumericTrue.Match(actual7)); + Assert.True(isNumericTrue.Match(actual8)); + Assert.False(isNumericTrue.Match(actual9)); + + // isNumeric: false + Assert.False(isNumericFalse.Match(actual1)); + Assert.False(isNumericFalse.Match(actual2)); + Assert.False(isNumericFalse.Match(actual3)); + Assert.True(isNumericFalse.Match(actual4)); + Assert.True(isNumericFalse.Match(actual5)); + Assert.False(isNumericFalse.Match(actual6)); + Assert.False(isNumericFalse.Match(actual7)); + Assert.False(isNumericFalse.Match(actual8)); + Assert.False(isNumericFalse.Match(actual9)); + + // With conversion + var isNumericConvertTrue = GetSelectorVisitor($"{type}IsNumericTrueWithConversion", GetSource(path), out _); + var isNumericConvertFalse = GetSelectorVisitor($"{type}IsNumericFalseWithConversion", GetSource(path), out _); + + // isNumeric: true + Assert.True(isNumericConvertTrue.Match(actual1)); + Assert.True(isNumericConvertTrue.Match(actual2)); + Assert.True(isNumericConvertTrue.Match(actual3)); + Assert.True(isNumericConvertTrue.Match(actual4)); + Assert.False(isNumericConvertTrue.Match(actual5)); + Assert.True(isNumericConvertTrue.Match(actual6)); + Assert.True(isNumericConvertTrue.Match(actual7)); + Assert.True(isNumericConvertTrue.Match(actual8)); + Assert.False(isNumericConvertTrue.Match(actual9)); + + // isNumeric: false + Assert.False(isNumericConvertFalse.Match(actual1)); + Assert.False(isNumericConvertFalse.Match(actual2)); + Assert.False(isNumericConvertFalse.Match(actual3)); + Assert.False(isNumericConvertFalse.Match(actual4)); + Assert.True(isNumericConvertFalse.Match(actual5)); + Assert.False(isNumericConvertFalse.Match(actual6)); + Assert.False(isNumericConvertFalse.Match(actual7)); + Assert.False(isNumericConvertFalse.Match(actual8)); + Assert.False(isNumericConvertFalse.Match(actual9)); + } + + [Theory] + [InlineData("Yaml", SelectorYamlFileName)] + [InlineData("Json", SelectorJsonFileName)] + public void IsLowerExpression(string type, string path) + { + var isLowerTrue = GetSelectorVisitor($"{type}IsLowerTrue", GetSource(path), out _); + var isLowerFalse = GetSelectorVisitor($"{type}IsLowerFalse", GetSource(path), out _); + var actual1 = GetObject((name: "value", value: "abc")); + var actual2 = GetObject((name: "value", value: "aBc")); + var actual3 = GetObject((name: "value", value: "a-b-c")); + var actual4 = GetObject((name: "value", value: 4)); + var actual5 = GetObject((name: "value", value: null)); + var actual6 = GetObject(); + + // isLower: true + Assert.True(isLowerTrue.Match(actual1)); + Assert.False(isLowerTrue.Match(actual2)); + Assert.True(isLowerTrue.Match(actual3)); + Assert.False(isLowerTrue.Match(actual4)); + Assert.False(isLowerTrue.Match(actual5)); + Assert.False(isLowerTrue.Match(actual6)); + + // isLower: false + Assert.False(isLowerFalse.Match(actual1)); + Assert.True(isLowerFalse.Match(actual2)); + Assert.False(isLowerFalse.Match(actual3)); + Assert.True(isLowerFalse.Match(actual4)); + Assert.True(isLowerFalse.Match(actual5)); + Assert.False(isLowerTrue.Match(actual6)); + + // With name + var withName = GetSelectorVisitor($"{type}NameIsLower", GetSource(path), out var context); + var actual7 = GetObject( + (name: "Name", value: "targetobject1") + ); + var actual8 = GetObject( + (name: "Name", value: "TargetObject2") + ); + + context.EnterTargetObject(new TargetObject(actual7)); + Assert.True(withName.Match(actual7)); + + context.EnterTargetObject(new TargetObject(actual8)); + Assert.False(withName.Match(actual8)); + } + + [Theory] + [InlineData("Yaml", SelectorYamlFileName)] + [InlineData("Json", SelectorJsonFileName)] + public void IsUpperExpression(string type, string path) + { + var isUpperTrue = GetSelectorVisitor($"{type}IsUpperTrue", GetSource(path), out _); + var isUpperFalse = GetSelectorVisitor($"{type}IsUpperFalse", GetSource(path), out _); + var actual1 = GetObject((name: "value", value: "ABC")); + var actual2 = GetObject((name: "value", value: "aBc")); + var actual3 = GetObject((name: "value", value: "A-B-C")); + var actual4 = GetObject((name: "value", value: 4)); + var actual5 = GetObject((name: "value", value: null)); + var actual6 = GetObject(); + + // isUpper: true + Assert.True(isUpperTrue.Match(actual1)); + Assert.False(isUpperTrue.Match(actual2)); + Assert.True(isUpperTrue.Match(actual3)); + Assert.False(isUpperTrue.Match(actual4)); + Assert.False(isUpperTrue.Match(actual5)); + Assert.False(isUpperTrue.Match(actual6)); + + // isUpper: false + Assert.False(isUpperFalse.Match(actual1)); + Assert.True(isUpperFalse.Match(actual2)); + Assert.False(isUpperFalse.Match(actual3)); + Assert.True(isUpperFalse.Match(actual4)); + Assert.True(isUpperFalse.Match(actual5)); + Assert.False(isUpperFalse.Match(actual6)); + + // With name + var withName = GetSelectorVisitor($"{type}NameIsUpper", GetSource(path), out var context); + var actual7 = GetObject( + (name: "Name", value: "TARGETOBJECT1") + ); + var actual8 = GetObject( + (name: "Name", value: "TargetObject2") + ); + + context.EnterTargetObject(new TargetObject(actual7)); + Assert.True(withName.Match(actual7)); + + context.EnterTargetObject(new TargetObject(actual8)); + Assert.False(withName.Match(actual8)); + } + + + [Theory] + [InlineData("Yaml", SelectorYamlFileName)] + [InlineData("Json", SelectorJsonFileName)] + public void HasSchemaExpression(string type, string path) + { + var hasSchema = GetSelectorVisitor($"{type}HasSchema", GetSource(path), out _); + var actual1 = GetObject((name: "key", value: "value"), (name: "$schema", value: "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#")); + var actual2 = GetObject((name: "key", value: "value"), (name: "$schema", value: "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json")); + var actual3 = GetObject((name: "key", value: "value"), (name: "$schema", value: "http://schema.management.azure.com/schemas/2019-04-01/DeploymentParameters.json#")); + var actual4 = GetObject((name: "key", value: "value"), (name: "$schema", value: null)); + var actual5 = GetObject((name: "key", value: "value"), (name: "$schema", value: "")); + var actual6 = GetObject(); + + Assert.True(hasSchema.Match(actual1)); + Assert.True(hasSchema.Match(actual2)); + Assert.False(hasSchema.Match(actual3)); + Assert.False(hasSchema.Match(actual4)); + Assert.False(hasSchema.Match(actual5)); + Assert.False(hasSchema.Match(actual6)); + + hasSchema = GetSelectorVisitor($"{type}HasSchemaIgnoreScheme", GetSource(path), out _); + Assert.True(hasSchema.Match(actual1)); + Assert.True(hasSchema.Match(actual2)); + Assert.True(hasSchema.Match(actual3)); + Assert.False(hasSchema.Match(actual4)); + Assert.False(hasSchema.Match(actual5)); + Assert.False(hasSchema.Match(actual6)); + + hasSchema = GetSelectorVisitor($"{type}HasSchemaCaseSensitive", GetSource(path), out _); + Assert.True(hasSchema.Match(actual1)); + Assert.True(hasSchema.Match(actual2)); + Assert.False(hasSchema.Match(actual3)); + Assert.False(hasSchema.Match(actual4)); + Assert.False(hasSchema.Match(actual5)); + Assert.False(hasSchema.Match(actual6)); + + hasSchema = GetSelectorVisitor($"{type}HasAnySchema", GetSource(path), out _); + Assert.True(hasSchema.Match(actual1)); + Assert.True(hasSchema.Match(actual2)); + Assert.True(hasSchema.Match(actual3)); + Assert.False(hasSchema.Match(actual4)); + Assert.False(hasSchema.Match(actual5)); + Assert.False(hasSchema.Match(actual6)); + } + + [Theory] + [InlineData("Yaml", SelectorYamlFileName)] + [InlineData("Json", SelectorJsonFileName)] + public void Version(string type, string path) + { + var actual1 = GetObject((name: "version", value: "1.2.3")); + var actual2 = GetObject((name: "version", value: "0.2.3")); + var actual3 = GetObject((name: "version", value: "2.2.3")); + var actual4 = GetObject((name: "version", value: "1.1.3")); + var actual5 = GetObject((name: "version", value: "1.3.3-preview.1")); + var actual6 = GetObject(); + var actual7 = GetObject((name: "version", value: "a.b.c")); + + var version = GetSelectorVisitor($"{type}Version", GetSource(path), out _); + Assert.True(version.Match(actual1)); + Assert.False(version.Match(actual2)); + Assert.False(version.Match(actual3)); + Assert.False(version.Match(actual4)); + Assert.False(version.Match(actual5)); + Assert.False(version.Match(actual6)); + Assert.False(version.Match(actual7)); + + version = GetSelectorVisitor($"{type}VersionWithPrerelease", GetSource(path), out _); + Assert.True(version.Match(actual1)); + Assert.False(version.Match(actual2)); + Assert.False(version.Match(actual3)); + Assert.False(version.Match(actual4)); + Assert.True(version.Match(actual5)); + Assert.False(version.Match(actual6)); + Assert.False(version.Match(actual7)); + + version = GetSelectorVisitor($"{type}VersionAnyVersion", GetSource(path), out _); + Assert.True(version.Match(actual1)); + Assert.True(version.Match(actual2)); + Assert.True(version.Match(actual3)); + Assert.True(version.Match(actual4)); + Assert.True(version.Match(actual5)); + Assert.False(version.Match(actual6)); + Assert.False(version.Match(actual7)); + } + + [Theory] + [InlineData("Yaml", SelectorYamlFileName)] + [InlineData("Json", SelectorJsonFileName)] + public void APIVersion(string type, string path) + { + var actual1 = GetObject((name: "dateVersion", value: "2015-10-01")); + var actual2 = GetObject((name: "dateVersion", value: "2014-01-01")); + var actual3 = GetObject((name: "dateVersion", value: "2022-01-01")); + var actual4 = GetObject((name: "dateVersion", value: "2015-10-01-preview")); + var actual5 = GetObject((name: "dateVersion", value: "2022-01-01-preview")); + var actual6 = GetObject(); + var actual7 = GetObject((name: "dateVersion", value: "a-b-c")); + + var version = GetSelectorVisitor($"{type}APIVersion", GetSource(path), out _); + Assert.True(version.Match(actual1)); + Assert.False(version.Match(actual2)); + Assert.True(version.Match(actual3)); + Assert.False(version.Match(actual4)); + Assert.False(version.Match(actual5)); + Assert.False(version.Match(actual6)); + Assert.False(version.Match(actual7)); + + version = GetSelectorVisitor($"{type}APIVersionWithPrerelease", GetSource(path), out _); + Assert.True(version.Match(actual1)); + Assert.False(version.Match(actual2)); + Assert.True(version.Match(actual3)); + Assert.False(version.Match(actual4)); + Assert.True(version.Match(actual5)); + Assert.False(version.Match(actual6)); + Assert.False(version.Match(actual7)); + + version = GetSelectorVisitor($"{type}APIVersionAnyVersion", GetSource(path), out _); + Assert.True(version.Match(actual1)); + Assert.True(version.Match(actual2)); + Assert.True(version.Match(actual3)); + Assert.True(version.Match(actual4)); + Assert.True(version.Match(actual5)); + Assert.False(version.Match(actual6)); + Assert.False(version.Match(actual7)); + } + + [Theory] + [InlineData("Yaml", SelectorYamlFileName)] + [InlineData("Json", SelectorJsonFileName)] + public void HasDefault(string type, string path) + { + var actual1 = GetObject((name: "integerValue", value: 100), (name: "boolValue", value: true), (name: "stringValue", value: "testValue")); + var actual2 = GetObject((name: "integerValue", value: 1)); + var actual3 = GetObject((name: "boolValue", value: false)); + var actual4 = GetObject((name: "stringValue", value: "TestValue")); + var actual5 = GetObject(); + var actual6 = GetObject((name: "integerValue", value: new JValue(100))); + var actual7 = GetObject((name: "boolValue", value: new JValue(true))); + var actual8 = GetObject((name: "stringValue", value: new JValue("testValue"))); + + var hasDefault = GetSelectorVisitor($"{type}HasDefault", GetSource(path), out _); + Assert.True(hasDefault.Match(actual1)); + Assert.False(hasDefault.Match(actual2)); + Assert.False(hasDefault.Match(actual3)); + Assert.False(hasDefault.Match(actual4)); + Assert.True(hasDefault.Match(actual5)); + Assert.True(hasDefault.Match(actual6)); + Assert.True(hasDefault.Match(actual7)); + Assert.True(hasDefault.Match(actual8)); + } + + #endregion Conditions + + #region Operators + + [Theory] + [InlineData("Yaml", SelectorYamlFileName)] + [InlineData("Json", SelectorJsonFileName)] + public void AllOf(string type, string path) + { + var allOf = GetSelectorVisitor($"{type}AllOf", GetSource(path), out _); + var actual1 = GetObject((name: "Name", value: "Name1")); + var actual2 = GetObject((name: "AlternateName", value: "Name2")); + var actual3 = GetObject((name: "Name", value: "Name1"), (name: "AlternateName", value: "Name2")); + var actual4 = GetObject((name: "OtherName", value: "Name3")); + + Assert.False(allOf.Match(actual1)); + Assert.False(allOf.Match(actual2)); + Assert.True(allOf.Match(actual3)); + Assert.False(allOf.Match(actual4)); + + // With quantifier + allOf = GetSelectorVisitor($"{type}AllOfWithQuantifier", GetSource(path), out _); + actual1 = GetObject((name: "Name", value: "TargetObject1"), (name: "properties", value: GetObject((name: "logs", value: new object[] { - var builder = new OptionContextBuilder(GetOption()); - 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); - return new SelectorVisitor(context, selector.Id, selector.Source, selector.Spec.If); - } - - private static string GetSourcePath(string fileName) + GetObject((name: "name", value: "log1")) + })))); + actual2 = GetObject((name: "Name", value: "TargetObject1"), (name: "properties", value: GetObject((name: "logs", value: new object[] { - return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName); - } + GetObject((name: "name", value: "log1")), + GetObject((name: "name", value: "log2")) + })))); + actual3 = GetObject((name: "Name", value: "TargetObject1"), (name: "properties", value: GetObject((name: "logs", value: Array.Empty())))); + + Assert.True(allOf.Match(actual1)); + Assert.True(allOf.Match(actual2)); + Assert.False(allOf.Match(actual3)); + } + + [Theory] + [InlineData("Yaml", SelectorYamlFileName)] + [InlineData("Json", SelectorJsonFileName)] + public void AnyOf(string type, string path) + { + var allOf = GetSelectorVisitor($"{type}AnyOf", GetSource(path), out _); + var actual1 = GetObject((name: "Name", value: "Name1")); + var actual2 = GetObject((name: "AlternateName", value: "Name2")); + var actual3 = GetObject((name: "Name", value: "Name1"), (name: "AlternateName", value: "Name2")); + var actual4 = GetObject((name: "OtherName", value: "Name3")); + + Assert.True(allOf.Match(actual1)); + Assert.True(allOf.Match(actual2)); + Assert.True(allOf.Match(actual3)); + Assert.False(allOf.Match(actual4)); + } + + [Theory] + [InlineData("Yaml", SelectorYamlFileName)] + [InlineData("Json", SelectorJsonFileName)] + public void Not(string type, string path) + { + var allOf = GetSelectorVisitor($"{type}Not", GetSource(path), out _); + var actual1 = GetObject((name: "Name", value: "Name1")); + var actual2 = GetObject((name: "AlternateName", value: "Name2")); + var actual3 = GetObject((name: "Name", value: "Name1"), (name: "AlternateName", value: "Name2")); + var actual4 = GetObject((name: "OtherName", value: "Name3")); + + Assert.False(allOf.Match(actual1)); + Assert.False(allOf.Match(actual2)); + Assert.False(allOf.Match(actual3)); + Assert.True(allOf.Match(actual4)); + } - #endregion Helper methods + #endregion Operators + + #region Properties + + [Theory] + [InlineData("Yaml", SelectorYamlFileName)] + [InlineData("Json", SelectorJsonFileName)] + public void Type(string type, string path) + { + var equals = GetSelectorVisitor($"{type}TypeEquals", GetSource(path), out var context); + var actual1 = GetObject(); + actual1.TypeNames.Insert(0, "CustomType1"); + + context.EnterTargetObject(new TargetObject(actual1)); + + Assert.True(equals.Match(actual1)); + } + + [Theory] + [InlineData("Yaml", SelectorYamlFileName)] + [InlineData("Json", SelectorJsonFileName)] + public void Name(string type, string path) + { + var equals = GetSelectorVisitor($"{type}NameEquals", GetSource(path), out var context); + var actual1 = GetObject( + (name: "Name", value: "TargetObject1") + ); + + context.EnterTargetObject(new TargetObject(actual1)); + + Assert.True(equals.Match(actual1)); + } + + [Theory] + [InlineData("Yaml", SelectorYamlFileName)] + [InlineData("Json", SelectorJsonFileName)] + public void Scope(string type, string path) + { + var testObject = GetObject( + (name: "Name", value: "TargetObject1") + ); + + var equals = GetSelectorVisitor($"{type}ScopeEquals", GetSource(path), out var context); + context.EnterTargetObject(new TargetObject(testObject, scope: new string[] { "/scope1" })); + Assert.True(equals.Match(testObject)); + + context.EnterTargetObject(new TargetObject(testObject, scope: new string[] { "/scope2" })); + Assert.False(equals.Match(testObject)); + + context.EnterTargetObject(new TargetObject(testObject)); + Assert.False(equals.Match(testObject)); + + var startsWith = GetSelectorVisitor($"{type}ScopeStartsWith", GetSource(path), out context); + context.EnterTargetObject(new TargetObject(testObject, scope: new string[] { "/scope1/" })); + Assert.True(startsWith.Match(testObject)); + + context.EnterTargetObject(new TargetObject(testObject, scope: new string[] { "/scope2/" })); + Assert.True(startsWith.Match(testObject)); + + context.EnterTargetObject(new TargetObject(testObject, scope: new string[] { "/scope2" })); + Assert.False(startsWith.Match(testObject)); + + context.EnterTargetObject(new TargetObject(testObject)); + Assert.False(startsWith.Match(testObject)); + + var hasValueFalse = GetSelectorVisitor($"{type}ScopeHasValueFalse", GetSource(path), out context); + context.EnterTargetObject(new TargetObject(testObject, scope: new string[] { "/scope1" })); + Assert.False(hasValueFalse.Match(testObject)); + + context.EnterTargetObject(new TargetObject(testObject)); + Assert.True(hasValueFalse.Match(testObject)); + + var hasValueTrue = GetSelectorVisitor($"{type}ScopeHasValueTrue", GetSource(path), out context); + context.EnterTargetObject(new TargetObject(testObject, scope: new string[] { "/scope1" })); + Assert.True(hasValueTrue.Match(testObject)); + + context.EnterTargetObject(new TargetObject(testObject)); + Assert.False(hasValueTrue.Match(testObject)); } + + #endregion Properties + + #region Functions + + [Theory] + [InlineData("Yaml", FunctionsYamlFileName)] + [InlineData("Json", FunctionsJsonFileName)] + public void WithFunction(string type, string path) + { + var example1 = GetSelectorVisitor($"{type}.Fn.Example1", GetSource(path), out _); + var example2 = GetSelectorVisitor($"{type}.Fn.Example2", GetSource(path), out _); + var example3 = GetSelectorVisitor($"{type}.Fn.Example3", GetSource(path), out _); + var example4 = GetSelectorVisitor($"{type}.Fn.Example4", GetSource(path), out _); + var example5 = GetSelectorVisitor($"{type}.Fn.Example5", GetSource(path), out _); + var example6 = GetSelectorVisitor($"{type}.Fn.Example6", GetSource(path), out _); + var actual1 = GetObject( + (name: "Name", value: "TestObject1") + ); + + Assert.True(example1.Match(actual1)); + Assert.True(example2.Match(actual1)); + Assert.True(example3.Match(actual1)); + Assert.True(example4.Match(actual1)); + Assert.True(example5.Match(actual1)); + Assert.True(example6.Match(actual1)); + } + + [Theory] + [InlineData("Yaml", FunctionsYamlFileName)] + [InlineData("Json", FunctionsJsonFileName)] + public void WithFunctionSpecific(string type, string path) + { + var example1 = GetSelectorVisitor($"{type}.Fn.Replace", GetSource(path), out _); + var example2 = GetSelectorVisitor($"{type}.Fn.Trim", GetSource(path), out _); + var example3 = GetSelectorVisitor($"{type}.Fn.First", GetSource(path), out _); + var example4 = GetSelectorVisitor($"{type}.Fn.Last", GetSource(path), out _); + var example5 = GetSelectorVisitor($"{type}.Fn.Split", GetSource(path), out _); + var example6 = GetSelectorVisitor($"{type}.Fn.PadLeft", GetSource(path), out _); + var example7 = GetSelectorVisitor($"{type}.Fn.PadRight", GetSource(path), out _); + var actual1 = GetObject( + (name: "Name", value: "TestObject1") + ); + + Assert.True(example1.Match(actual1)); + Assert.True(example2.Match(actual1)); + Assert.True(example3.Match(actual1)); + Assert.True(example4.Match(actual1)); + Assert.True(example5.Match(actual1)); + Assert.True(example6.Match(actual1)); + Assert.True(example7.Match(actual1)); + } + + #endregion Functions + + #region Helper methods + + private static PSRuleOption GetOption() + { + var option = new PSRuleOption(); + option.Configuration["ConfigArray"] = new string[] { "1", "2", "3", "4", "5" }; + return option; + } + + private static Source[] GetSource(string path) + { + var builder = new SourcePipelineBuilder(null, null); + builder.Directory(GetSourcePath(path)); + return builder.Build(); + } + + private static PSObject GetObject(params (string name, object value)[] properties) + { + var result = new PSObject(); + for (var i = 0; properties != null && i < properties.Length; i++) + result.Properties.Add(new PSNoteProperty(properties[i].name, properties[i].value)); + + return result; + } + + 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, null), null); + context.Init(source); + context.Begin(); + var selector = HostHelper.GetSelectorForTests(source, context).ToArray().FirstOrDefault(s => s.Name == name); + return new SelectorVisitor(context, selector.Id, selector.Source, selector.Spec.If); + } + + private static string GetSourcePath(string fileName) + { + return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName); + } + + #endregion Helper methods } diff --git a/tests/PSRule.Tests/SemanticBreakTests.cs b/tests/PSRule.Tests/SemanticBreakTests.cs index b3154b21b4..f600ba272e 100644 --- a/tests/PSRule.Tests/SemanticBreakTests.cs +++ b/tests/PSRule.Tests/SemanticBreakTests.cs @@ -1,53 +1,52 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -namespace PSRule +namespace PSRule; + +public sealed class SemanticBreakTests { - public sealed class SemanticBreakTests + [Fact] + public void BreakShort() { - [Fact] - public void BreakShort() - { - var original = "Consider using Hybrid Use Benefit for eligible workloads."; - var actual = original.SplitSemantic(); - Assert.Single(actual); - Assert.Equal(original, actual[0]); - } + var original = "Consider using Hybrid Use Benefit for eligible workloads."; + var actual = original.SplitSemantic(); + Assert.Single(actual); + Assert.Equal(original, actual[0]); + } - [Fact] - public void BreakLong() - { - var original = "Use a minimum of Standard for production container registries. Basic container registries are only recommended for non-production deployments. Consider upgrading ACR to Premium and enabling geo-replication between Azure regions to provide an in region registry to complement high availability or disaster recovery for container environments."; - var actual = original.SplitSemantic(); - Assert.Equal("Use a minimum of Standard for production container registries. Basic container", actual[0]); - Assert.Equal("registries are only recommended for non-production deployments. Consider", actual[1]); - Assert.Equal("upgrading ACR to Premium and enabling geo-replication between Azure regions to", actual[2]); - Assert.Equal("provide an in region registry to complement high availability or disaster", actual[3]); - Assert.Equal("recovery for container environments.", actual[4]); - Assert.Equal(5, actual.Length); - } + [Fact] + public void BreakLong() + { + var original = "Use a minimum of Standard for production container registries. Basic container registries are only recommended for non-production deployments. Consider upgrading ACR to Premium and enabling geo-replication between Azure regions to provide an in region registry to complement high availability or disaster recovery for container environments."; + var actual = original.SplitSemantic(); + Assert.Equal("Use a minimum of Standard for production container registries. Basic container", actual[0]); + Assert.Equal("registries are only recommended for non-production deployments. Consider", actual[1]); + Assert.Equal("upgrading ACR to Premium and enabling geo-replication between Azure regions to", actual[2]); + Assert.Equal("provide an in region registry to complement high availability or disaster", actual[3]); + Assert.Equal("recovery for container environments.", actual[4]); + Assert.Equal(5, actual.Length); + } - [Fact] - public void BreakDash() - { - var original = "Consider configuring NSGs rules to block outbound management traffic from non-management hosts."; - var actual = original.SplitSemantic(); - Assert.Equal("Consider configuring NSGs rules to block outbound management traffic from", actual[0]); - Assert.Equal("non-management hosts.", actual[1]); - Assert.Equal(2, actual.Length); - } + [Fact] + public void BreakDash() + { + var original = "Consider configuring NSGs rules to block outbound management traffic from non-management hosts."; + var actual = original.SplitSemantic(); + Assert.Equal("Consider configuring NSGs rules to block outbound management traffic from", actual[0]); + Assert.Equal("non-management hosts.", actual[1]); + Assert.Equal(2, actual.Length); + } - [Fact] - public void BreakNewLine() - { - var original = "Use a minimum of Standard for production container registries. Basic container registries are only recommended for non-production deployments.\r\nConsider upgrading ACR to Premium and enabling geo-replication between Azure regions to provide an in region registry to complement high availability or disaster recovery for container environments."; - var actual = original.SplitSemantic(); - Assert.Equal("Use a minimum of Standard for production container registries. Basic container", actual[0]); - Assert.Equal("registries are only recommended for non-production deployments.", actual[1]); - Assert.Equal("Consider upgrading ACR to Premium and enabling geo-replication between Azure", actual[2]); - Assert.Equal("regions to provide an in region registry to complement high availability or", actual[3]); - Assert.Equal("disaster recovery for container environments.", actual[4]); - Assert.Equal(5, actual.Length); - } + [Fact] + public void BreakNewLine() + { + var original = "Use a minimum of Standard for production container registries. Basic container registries are only recommended for non-production deployments.\r\nConsider upgrading ACR to Premium and enabling geo-replication between Azure regions to provide an in region registry to complement high availability or disaster recovery for container environments."; + var actual = original.SplitSemantic(); + Assert.Equal("Use a minimum of Standard for production container registries. Basic container", actual[0]); + Assert.Equal("registries are only recommended for non-production deployments.", actual[1]); + Assert.Equal("Consider upgrading ACR to Premium and enabling geo-replication between Azure", actual[2]); + Assert.Equal("regions to provide an in region registry to complement high availability or", actual[3]); + Assert.Equal("disaster recovery for container environments.", actual[4]); + Assert.Equal(5, actual.Length); } } diff --git a/tests/PSRule.Tests/SemanticVersionTests.cs b/tests/PSRule.Tests/SemanticVersionTests.cs index 50d5a138c5..3c6b65889e 100644 --- a/tests/PSRule.Tests/SemanticVersionTests.cs +++ b/tests/PSRule.Tests/SemanticVersionTests.cs @@ -3,218 +3,217 @@ using PSRule.Data; -namespace PSRule +namespace PSRule; + +/// +/// Tests for semantic version comparison. +/// +public sealed class SemanticVersionTests { /// - /// Tests for semantic version comparison. + /// Test parsing of versions. /// - public sealed class SemanticVersionTests + [Fact] + public void Version() { - /// - /// Test parsing of versions. - /// - [Fact] - public void Version() - { - Assert.True(SemanticVersion.TryParseVersion("1.2.3-alpha.3+7223b39", out var actual1)); - Assert.Equal(1, actual1.Major); - Assert.Equal(2, actual1.Minor); - Assert.Equal(3, actual1.Patch); - Assert.Equal("alpha.3", actual1.Prerelease.Value); - Assert.Equal("7223b39", actual1.Build); + Assert.True(SemanticVersion.TryParseVersion("1.2.3-alpha.3+7223b39", out var actual1)); + Assert.Equal(1, actual1.Major); + Assert.Equal(2, actual1.Minor); + Assert.Equal(3, actual1.Patch); + Assert.Equal("alpha.3", actual1.Prerelease.Value); + Assert.Equal("7223b39", actual1.Build); - Assert.True(SemanticVersion.TryParseVersion("v1.2.3-alpha.3", out var actual2)); - Assert.Equal(1, actual2.Major); - Assert.Equal(2, actual2.Minor); - Assert.Equal(3, actual2.Patch); - Assert.Equal("alpha.3", actual2.Prerelease.Value); + Assert.True(SemanticVersion.TryParseVersion("v1.2.3-alpha.3", out var actual2)); + Assert.Equal(1, actual2.Major); + Assert.Equal(2, actual2.Minor); + Assert.Equal(3, actual2.Patch); + Assert.Equal("alpha.3", actual2.Prerelease.Value); - Assert.True(SemanticVersion.TryParseVersion("v1.2.3+7223b39", out var actual3)); - Assert.Equal(1, actual3.Major); - Assert.Equal(2, actual3.Minor); - Assert.Equal(3, actual3.Patch); - Assert.Equal("7223b39", actual3.Build); - } + Assert.True(SemanticVersion.TryParseVersion("v1.2.3+7223b39", out var actual3)); + Assert.Equal(1, actual3.Major); + Assert.Equal(2, actual3.Minor); + Assert.Equal(3, actual3.Patch); + Assert.Equal("7223b39", actual3.Build); + } - /// - /// Test ordering of versions by comparison. - /// - [Fact] - public void VersionOrder() - { - Assert.True(SemanticVersion.TryParseVersion("1.0.0", out var actual1)); - Assert.True(SemanticVersion.TryParseVersion("1.2.0", out var actual2)); - Assert.True(SemanticVersion.TryParseVersion("10.0.0", out var actual3)); - Assert.True(SemanticVersion.TryParseVersion("1.0.2", out var actual4)); + /// + /// Test ordering of versions by comparison. + /// + [Fact] + public void VersionOrder() + { + Assert.True(SemanticVersion.TryParseVersion("1.0.0", out var actual1)); + Assert.True(SemanticVersion.TryParseVersion("1.2.0", out var actual2)); + Assert.True(SemanticVersion.TryParseVersion("10.0.0", out var actual3)); + Assert.True(SemanticVersion.TryParseVersion("1.0.2", out var actual4)); - Assert.True(actual1.CompareTo(actual1) == 0); - Assert.True(actual1.CompareTo(actual2) < 0); - Assert.True(actual1.CompareTo(actual3) < 0); - Assert.True(actual1.CompareTo(actual4) < 0); - Assert.True(actual2.CompareTo(actual2) == 0); - Assert.True(actual2.CompareTo(actual1) > 0); - Assert.True(actual2.CompareTo(actual3) < 0); - Assert.True(actual2.CompareTo(actual4) > 0); - } + Assert.True(actual1.CompareTo(actual1) == 0); + Assert.True(actual1.CompareTo(actual2) < 0); + Assert.True(actual1.CompareTo(actual3) < 0); + Assert.True(actual1.CompareTo(actual4) < 0); + Assert.True(actual2.CompareTo(actual2) == 0); + Assert.True(actual2.CompareTo(actual1) > 0); + Assert.True(actual2.CompareTo(actual3) < 0); + Assert.True(actual2.CompareTo(actual4) > 0); + } - /// - /// Test parsing of constraints. - /// - [Fact] - public void Constraint() - { - // Versions - Assert.True(SemanticVersion.TryParseVersion("1.2.3", out var version1)); - Assert.True(SemanticVersion.TryParseVersion("1.2.3-alpha.3+7223b39", out var version2)); - Assert.True(SemanticVersion.TryParseVersion("3.4.5-alpha.9", out var version3)); - Assert.True(SemanticVersion.TryParseVersion("3.4.5", out var version4)); - Assert.False(SemanticVersion.TryParseVersion("1.2.3-", out var _)); - Assert.True(SemanticVersion.TryParseVersion("1.2.3-0", out var _)); - Assert.False(SemanticVersion.TryParseVersion("1.2.3-0123", out var _)); - Assert.True(SemanticVersion.TryParseVersion("1.2.3-0A", out var _)); + /// + /// Test parsing of constraints. + /// + [Fact] + public void Constraint() + { + // Versions + Assert.True(SemanticVersion.TryParseVersion("1.2.3", out var version1)); + Assert.True(SemanticVersion.TryParseVersion("1.2.3-alpha.3+7223b39", out var version2)); + Assert.True(SemanticVersion.TryParseVersion("3.4.5-alpha.9", out var version3)); + Assert.True(SemanticVersion.TryParseVersion("3.4.5", out var version4)); + Assert.False(SemanticVersion.TryParseVersion("1.2.3-", out var _)); + Assert.True(SemanticVersion.TryParseVersion("1.2.3-0", out var _)); + Assert.False(SemanticVersion.TryParseVersion("1.2.3-0123", out var _)); + Assert.True(SemanticVersion.TryParseVersion("1.2.3-0A", out var _)); - // Constraints - Assert.True(SemanticVersion.TryParseConstraint("1.2.3", out var actual1)); - Assert.True(SemanticVersion.TryParseConstraint("1.2.3-alpha.3", out var actual2)); - Assert.True(SemanticVersion.TryParseConstraint(">1.2.3-alpha.3", out var actual3)); - Assert.True(SemanticVersion.TryParseConstraint(">1.2.3-alpha.1", out var actual4)); - Assert.True(SemanticVersion.TryParseConstraint("<1.2.3-beta", out var actual5)); - Assert.True(SemanticVersion.TryParseConstraint("^1.2.3-alpha", out var actual6)); - Assert.True(SemanticVersion.TryParseConstraint("<3.4.6", out var actual7)); - Assert.True(SemanticVersion.TryParseConstraint("=v1.2.3", out var actual8)); - Assert.True(SemanticVersion.TryParseConstraint(">=v1.2.3", out var actual9)); - Assert.True(SemanticVersion.TryParseConstraint(">=v1.2.3-0", out var actual10)); - Assert.True(SemanticVersion.TryParseConstraint("<3.4.5", out var actual11)); - Assert.True(SemanticVersion.TryParseConstraint("<3.4.5-9999999999", out var actual12)); - Assert.True(SemanticVersion.TryParseConstraint("^1.0.0", out var actual13)); - Assert.True(SemanticVersion.TryParseConstraint("<1.2.3-0", out var actual14)); - Assert.True(SemanticVersion.TryParseConstraint("1.2.3|| >=3.4.5-0 3.4.5", out var actual15)); - Assert.True(SemanticVersion.TryParseConstraint("1.2.3 ||>=3.4.5-0 || 3.4.5", out var actual16)); - Assert.True(SemanticVersion.TryParseConstraint("1.2.3||3.4.5", out var actual17)); - Assert.True(SemanticVersion.TryParseConstraint(">=1.2.3", out var actual18, includePrerelease: true)); - Assert.True(SemanticVersion.TryParseConstraint("<=3.4.5-0", out var actual19, includePrerelease: true)); - Assert.True(SemanticVersion.TryParseConstraint("@pre >=1.2.3", out var actual20)); - Assert.True(SemanticVersion.TryParseConstraint("@prerelease <=3.4.5-0", out var actual21)); + // Constraints + Assert.True(SemanticVersion.TryParseConstraint("1.2.3", out var actual1)); + Assert.True(SemanticVersion.TryParseConstraint("1.2.3-alpha.3", out var actual2)); + Assert.True(SemanticVersion.TryParseConstraint(">1.2.3-alpha.3", out var actual3)); + Assert.True(SemanticVersion.TryParseConstraint(">1.2.3-alpha.1", out var actual4)); + Assert.True(SemanticVersion.TryParseConstraint("<1.2.3-beta", out var actual5)); + Assert.True(SemanticVersion.TryParseConstraint("^1.2.3-alpha", out var actual6)); + Assert.True(SemanticVersion.TryParseConstraint("<3.4.6", out var actual7)); + Assert.True(SemanticVersion.TryParseConstraint("=v1.2.3", out var actual8)); + Assert.True(SemanticVersion.TryParseConstraint(">=v1.2.3", out var actual9)); + Assert.True(SemanticVersion.TryParseConstraint(">=v1.2.3-0", out var actual10)); + Assert.True(SemanticVersion.TryParseConstraint("<3.4.5", out var actual11)); + Assert.True(SemanticVersion.TryParseConstraint("<3.4.5-9999999999", out var actual12)); + Assert.True(SemanticVersion.TryParseConstraint("^1.0.0", out var actual13)); + Assert.True(SemanticVersion.TryParseConstraint("<1.2.3-0", out var actual14)); + Assert.True(SemanticVersion.TryParseConstraint("1.2.3|| >=3.4.5-0 3.4.5", out var actual15)); + Assert.True(SemanticVersion.TryParseConstraint("1.2.3 ||>=3.4.5-0 || 3.4.5", out var actual16)); + Assert.True(SemanticVersion.TryParseConstraint("1.2.3||3.4.5", out var actual17)); + Assert.True(SemanticVersion.TryParseConstraint(">=1.2.3", out var actual18, includePrerelease: true)); + Assert.True(SemanticVersion.TryParseConstraint("<=3.4.5-0", out var actual19, includePrerelease: true)); + Assert.True(SemanticVersion.TryParseConstraint("@pre >=1.2.3", out var actual20)); + Assert.True(SemanticVersion.TryParseConstraint("@prerelease <=3.4.5-0", out var actual21)); - // Version1 - 1.2.3 - Assert.True(actual1.Equals(version1)); - Assert.False(actual2.Equals(version1)); - Assert.True(actual3.Equals(version1)); - Assert.True(actual4.Equals(version1)); - Assert.False(actual5.Equals(version1)); - Assert.True(actual6.Equals(version1)); - Assert.True(actual7.Equals(version1)); - Assert.True(actual8.Equals(version1)); - Assert.True(actual9.Equals(version1)); - Assert.True(actual10.Equals(version1)); - Assert.True(actual11.Equals(version1)); - Assert.True(actual12.Equals(version1)); - Assert.True(actual13.Equals(version1)); - Assert.False(actual14.Equals(version1)); - Assert.True(actual15.Equals(version1)); - Assert.True(actual16.Equals(version1)); - Assert.True(actual17.Equals(version1)); - Assert.True(actual18.Equals(version1)); - Assert.True(actual19.Equals(version1)); - Assert.True(actual20.Equals(version1)); - Assert.True(actual21.Equals(version1)); + // Version1 - 1.2.3 + Assert.True(actual1.Equals(version1)); + Assert.False(actual2.Equals(version1)); + Assert.True(actual3.Equals(version1)); + Assert.True(actual4.Equals(version1)); + Assert.False(actual5.Equals(version1)); + Assert.True(actual6.Equals(version1)); + Assert.True(actual7.Equals(version1)); + Assert.True(actual8.Equals(version1)); + Assert.True(actual9.Equals(version1)); + Assert.True(actual10.Equals(version1)); + Assert.True(actual11.Equals(version1)); + Assert.True(actual12.Equals(version1)); + Assert.True(actual13.Equals(version1)); + Assert.False(actual14.Equals(version1)); + Assert.True(actual15.Equals(version1)); + Assert.True(actual16.Equals(version1)); + Assert.True(actual17.Equals(version1)); + Assert.True(actual18.Equals(version1)); + Assert.True(actual19.Equals(version1)); + Assert.True(actual20.Equals(version1)); + Assert.True(actual21.Equals(version1)); - // Version2 - 1.2.3-alpha.3+7223b39 - Assert.False(actual1.Equals(version2)); - Assert.True(actual2.Equals(version2)); - Assert.False(actual3.Equals(version2)); - Assert.True(actual4.Equals(version2)); - Assert.True(actual5.Equals(version2)); - Assert.True(actual6.Equals(version2)); - Assert.False(actual7.Equals(version2)); - Assert.False(actual8.Equals(version2)); - Assert.False(actual9.Equals(version2)); - Assert.True(actual10.Equals(version2)); - Assert.False(actual11.Equals(version2)); - Assert.False(actual12.Equals(version2)); - Assert.False(actual13.Equals(version2)); - Assert.False(actual14.Equals(version2)); - Assert.False(actual15.Equals(version2)); - Assert.False(actual16.Equals(version2)); - Assert.False(actual17.Equals(version2)); - Assert.False(actual18.Equals(version2)); - Assert.True(actual19.Equals(version2)); - Assert.False(actual20.Equals(version2)); - Assert.True(actual21.Equals(version2)); + // Version2 - 1.2.3-alpha.3+7223b39 + Assert.False(actual1.Equals(version2)); + Assert.True(actual2.Equals(version2)); + Assert.False(actual3.Equals(version2)); + Assert.True(actual4.Equals(version2)); + Assert.True(actual5.Equals(version2)); + Assert.True(actual6.Equals(version2)); + Assert.False(actual7.Equals(version2)); + Assert.False(actual8.Equals(version2)); + Assert.False(actual9.Equals(version2)); + Assert.True(actual10.Equals(version2)); + Assert.False(actual11.Equals(version2)); + Assert.False(actual12.Equals(version2)); + Assert.False(actual13.Equals(version2)); + Assert.False(actual14.Equals(version2)); + Assert.False(actual15.Equals(version2)); + Assert.False(actual16.Equals(version2)); + Assert.False(actual17.Equals(version2)); + Assert.False(actual18.Equals(version2)); + Assert.True(actual19.Equals(version2)); + Assert.False(actual20.Equals(version2)); + Assert.True(actual21.Equals(version2)); - // Version3 - 3.4.5-alpha.9 - Assert.False(actual1.Equals(version3)); - Assert.False(actual2.Equals(version3)); - Assert.False(actual3.Equals(version3)); - Assert.False(actual4.Equals(version3)); - Assert.False(actual5.Equals(version3)); - Assert.False(actual6.Equals(version3)); - Assert.False(actual7.Equals(version3)); - Assert.False(actual8.Equals(version3)); - Assert.False(actual9.Equals(version3)); - Assert.False(actual10.Equals(version3)); - Assert.False(actual11.Equals(version3)); - Assert.False(actual12.Equals(version3)); - Assert.False(actual13.Equals(version3)); - Assert.False(actual14.Equals(version3)); - Assert.False(actual15.Equals(version3)); - Assert.True(actual16.Equals(version3)); - Assert.False(actual17.Equals(version3)); - Assert.True(actual18.Equals(version3)); - Assert.False(actual19.Equals(version3)); - Assert.True(actual20.Equals(version3)); - Assert.False(actual21.Equals(version3)); + // Version3 - 3.4.5-alpha.9 + Assert.False(actual1.Equals(version3)); + Assert.False(actual2.Equals(version3)); + Assert.False(actual3.Equals(version3)); + Assert.False(actual4.Equals(version3)); + Assert.False(actual5.Equals(version3)); + Assert.False(actual6.Equals(version3)); + Assert.False(actual7.Equals(version3)); + Assert.False(actual8.Equals(version3)); + Assert.False(actual9.Equals(version3)); + Assert.False(actual10.Equals(version3)); + Assert.False(actual11.Equals(version3)); + Assert.False(actual12.Equals(version3)); + Assert.False(actual13.Equals(version3)); + Assert.False(actual14.Equals(version3)); + Assert.False(actual15.Equals(version3)); + Assert.True(actual16.Equals(version3)); + Assert.False(actual17.Equals(version3)); + Assert.True(actual18.Equals(version3)); + Assert.False(actual19.Equals(version3)); + Assert.True(actual20.Equals(version3)); + Assert.False(actual21.Equals(version3)); - // Version4 - 3.4.5 - Assert.False(actual1.Equals(version4)); - Assert.False(actual2.Equals(version4)); - Assert.True(actual3.Equals(version4)); - Assert.True(actual4.Equals(version4)); - Assert.False(actual5.Equals(version4)); - Assert.False(actual6.Equals(version4)); - Assert.True(actual7.Equals(version4)); - Assert.False(actual8.Equals(version4)); - Assert.True(actual9.Equals(version4)); - Assert.True(actual10.Equals(version4)); - Assert.False(actual11.Equals(version4)); - Assert.False(actual12.Equals(version4)); - Assert.False(actual13.Equals(version4)); - Assert.False(actual14.Equals(version4)); - Assert.True(actual15.Equals(version4)); - Assert.True(actual16.Equals(version4)); - Assert.True(actual17.Equals(version4)); - Assert.True(actual18.Equals(version4)); - Assert.False(actual19.Equals(version4)); - Assert.True(actual20.Equals(version4)); - Assert.False(actual21.Equals(version4)); - } + // Version4 - 3.4.5 + Assert.False(actual1.Equals(version4)); + Assert.False(actual2.Equals(version4)); + Assert.True(actual3.Equals(version4)); + Assert.True(actual4.Equals(version4)); + Assert.False(actual5.Equals(version4)); + Assert.False(actual6.Equals(version4)); + Assert.True(actual7.Equals(version4)); + Assert.False(actual8.Equals(version4)); + Assert.True(actual9.Equals(version4)); + Assert.True(actual10.Equals(version4)); + Assert.False(actual11.Equals(version4)); + Assert.False(actual12.Equals(version4)); + Assert.False(actual13.Equals(version4)); + Assert.False(actual14.Equals(version4)); + Assert.True(actual15.Equals(version4)); + Assert.True(actual16.Equals(version4)); + Assert.True(actual17.Equals(version4)); + Assert.True(actual18.Equals(version4)); + Assert.False(actual19.Equals(version4)); + Assert.True(actual20.Equals(version4)); + Assert.False(actual21.Equals(version4)); + } - /// - /// Test parsing and order of pre-releases. - /// - [Fact] - public void Prerelease() - { - var actual1 = new SemanticVersion.PR(null); - var actual2 = new SemanticVersion.PR("alpha"); - var actual3 = new SemanticVersion.PR("alpha.1"); - var actual4 = new SemanticVersion.PR("alpha.beta"); - var actual5 = new SemanticVersion.PR("beta"); - var actual6 = new SemanticVersion.PR("beta.2"); - var actual7 = new SemanticVersion.PR("beta.11"); - var actual8 = new SemanticVersion.PR("rc.1"); + /// + /// Test parsing and order of pre-releases. + /// + [Fact] + public void Prerelease() + { + var actual1 = new SemanticVersion.PR(null); + var actual2 = new SemanticVersion.PR("alpha"); + var actual3 = new SemanticVersion.PR("alpha.1"); + var actual4 = new SemanticVersion.PR("alpha.beta"); + var actual5 = new SemanticVersion.PR("beta"); + var actual6 = new SemanticVersion.PR("beta.2"); + var actual7 = new SemanticVersion.PR("beta.11"); + var actual8 = new SemanticVersion.PR("rc.1"); - Assert.True(actual1.CompareTo(actual1) == 0); - Assert.True(actual1.CompareTo(actual2) > 0); - Assert.True(actual1.CompareTo(actual6) > 0); - Assert.True(actual2.CompareTo(actual3) < 0); - Assert.True(actual3.CompareTo(actual4) < 0); - Assert.True(actual4.CompareTo(actual5) < 0); - Assert.True(actual5.CompareTo(actual6) < 0); - Assert.True(actual6.CompareTo(actual7) < 0); - Assert.True(actual7.CompareTo(actual8) < 0); - Assert.True(actual8.CompareTo(actual1) < 0); - Assert.True(actual8.CompareTo(actual2) > 0); - } + Assert.True(actual1.CompareTo(actual1) == 0); + Assert.True(actual1.CompareTo(actual2) > 0); + Assert.True(actual1.CompareTo(actual6) > 0); + Assert.True(actual2.CompareTo(actual3) < 0); + Assert.True(actual3.CompareTo(actual4) < 0); + Assert.True(actual4.CompareTo(actual5) < 0); + Assert.True(actual5.CompareTo(actual6) < 0); + Assert.True(actual6.CompareTo(actual7) < 0); + Assert.True(actual7.CompareTo(actual8) < 0); + Assert.True(actual8.CompareTo(actual1) < 0); + Assert.True(actual8.CompareTo(actual2) > 0); } } diff --git a/tests/PSRule.Tests/StringExtensionsTests.cs b/tests/PSRule.Tests/StringExtensionsTests.cs index 5bf37c32e6..6f6598538e 100644 --- a/tests/PSRule.Tests/StringExtensionsTests.cs +++ b/tests/PSRule.Tests/StringExtensionsTests.cs @@ -1,28 +1,27 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -namespace PSRule +namespace PSRule; + +public sealed class StringExtensionsTests { - public sealed class StringExtensionsTests + [Fact] + public void Replace() { - [Fact] - public void Replace() - { - var actual = "One Two Three"; - Assert.Equal("OneTwoThree", actual.Replace(" ", "", caseSensitive: true)); - Assert.Equal("OneTwoThree", actual.Replace(" ", "", caseSensitive: false)); - Assert.Equal("One Two ", actual.Replace("Three", "", caseSensitive: true)); - Assert.Equal("One Two ", actual.Replace("Three", "", caseSensitive: false)); - Assert.Equal(" Two Three", actual.Replace("One", "", caseSensitive: true)); - Assert.Equal(" Two Three", actual.Replace("One", "", caseSensitive: false)); - Assert.Equal("One 2 Three", actual.Replace("Two", "2", caseSensitive: true)); - Assert.Equal("One 2 Three", actual.Replace("Two", "2", caseSensitive: false)); - Assert.Equal("One Two Three", actual.Replace("two", "2", caseSensitive: true)); - Assert.Equal("One 2 Three", actual.Replace("two", "2", caseSensitive: false)); - Assert.Equal("One Two Three", actual.Replace("three", "3", caseSensitive: true)); - Assert.Equal("One Two 3", actual.Replace("three", "3", caseSensitive: false)); - Assert.Equal("One Two Three", actual.Replace("one", "1", caseSensitive: true)); - Assert.Equal("1 Two Three", actual.Replace("one", "1", caseSensitive: false)); - } + var actual = "One Two Three"; + Assert.Equal("OneTwoThree", actual.Replace(" ", "", caseSensitive: true)); + Assert.Equal("OneTwoThree", actual.Replace(" ", "", caseSensitive: false)); + Assert.Equal("One Two ", actual.Replace("Three", "", caseSensitive: true)); + Assert.Equal("One Two ", actual.Replace("Three", "", caseSensitive: false)); + Assert.Equal(" Two Three", actual.Replace("One", "", caseSensitive: true)); + Assert.Equal(" Two Three", actual.Replace("One", "", caseSensitive: false)); + Assert.Equal("One 2 Three", actual.Replace("Two", "2", caseSensitive: true)); + Assert.Equal("One 2 Three", actual.Replace("Two", "2", caseSensitive: false)); + Assert.Equal("One Two Three", actual.Replace("two", "2", caseSensitive: true)); + Assert.Equal("One 2 Three", actual.Replace("two", "2", caseSensitive: false)); + Assert.Equal("One Two Three", actual.Replace("three", "3", caseSensitive: true)); + Assert.Equal("One Two 3", actual.Replace("three", "3", caseSensitive: false)); + Assert.Equal("One Two Three", actual.Replace("one", "1", caseSensitive: true)); + Assert.Equal("1 Two Three", actual.Replace("one", "1", caseSensitive: false)); } } diff --git a/tests/PSRule.Tests/SuppressionFilterTests.cs b/tests/PSRule.Tests/SuppressionFilterTests.cs index ec77d9694a..e8eee3f079 100644 --- a/tests/PSRule.Tests/SuppressionFilterTests.cs +++ b/tests/PSRule.Tests/SuppressionFilterTests.cs @@ -10,55 +10,54 @@ using PSRule.Rules; using PSRule.Runtime; -namespace PSRule +namespace PSRule; + +public sealed class SuppressionFilterTests { - public sealed class SuppressionFilterTests + [Fact] + public void Match() { - [Fact] - public void Match() - { - var option = GetOption(); - 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); - var resourceIndex = new ResourceIndex(rules); - var filter = new SuppressionFilter(context, option.Suppression, resourceIndex); - - Assert.True(filter.Match(new ResourceId(".", "YAML.RuleWithAlias1", ResourceIdKind.Unknown), "TestObject1")); - Assert.False(filter.Match(new ResourceId(".", "JSON.RuleWithAlias1", ResourceIdKind.Unknown), "TestObject1")); - Assert.True(filter.Match(new ResourceId(".", "PS.RuleWithAlias1", ResourceIdKind.Unknown), "TestObject1")); - - Assert.True(filter.Match(new ResourceId(".", "YAML.RuleWithAlias1", ResourceIdKind.Unknown), "TestObject2")); - Assert.True(filter.Match(new ResourceId(".", "JSON.RuleWithAlias1", ResourceIdKind.Unknown), "TestObject2")); - Assert.False(filter.Match(new ResourceId(".", "PS.RuleWithAlias1", ResourceIdKind.Unknown), "TestObject2")); - - Assert.False(filter.Match(new ResourceId(".", "YAML.RuleWithAlias1", ResourceIdKind.Unknown), "TestObject3")); - Assert.True(filter.Match(new ResourceId(".", "JSON.RuleWithAlias1", ResourceIdKind.Unknown), "TestObject3")); - Assert.True(filter.Match(new ResourceId(".", "PS.RuleWithAlias1", ResourceIdKind.Unknown), "TestObject3")); - } - - #region Helper methods + var option = GetOption(); + 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); + var resourceIndex = new ResourceIndex(rules); + var filter = new SuppressionFilter(context, option.Suppression, resourceIndex); + + Assert.True(filter.Match(new ResourceId(".", "YAML.RuleWithAlias1", ResourceIdKind.Unknown), "TestObject1")); + Assert.False(filter.Match(new ResourceId(".", "JSON.RuleWithAlias1", ResourceIdKind.Unknown), "TestObject1")); + Assert.True(filter.Match(new ResourceId(".", "PS.RuleWithAlias1", ResourceIdKind.Unknown), "TestObject1")); + + Assert.True(filter.Match(new ResourceId(".", "YAML.RuleWithAlias1", ResourceIdKind.Unknown), "TestObject2")); + Assert.True(filter.Match(new ResourceId(".", "JSON.RuleWithAlias1", ResourceIdKind.Unknown), "TestObject2")); + Assert.False(filter.Match(new ResourceId(".", "PS.RuleWithAlias1", ResourceIdKind.Unknown), "TestObject2")); + + Assert.False(filter.Match(new ResourceId(".", "YAML.RuleWithAlias1", ResourceIdKind.Unknown), "TestObject3")); + Assert.True(filter.Match(new ResourceId(".", "JSON.RuleWithAlias1", ResourceIdKind.Unknown), "TestObject3")); + Assert.True(filter.Match(new ResourceId(".", "PS.RuleWithAlias1", ResourceIdKind.Unknown), "TestObject3")); + } - private static Source[] GetSource() - { - var builder = new SourcePipelineBuilder(null, null); - builder.Directory(GetSourcePath("FromFileAlias.Rule.yaml")); - builder.Directory(GetSourcePath("FromFileAlias.Rule.jsonc")); - builder.Directory(GetSourcePath("FromFileAlias.Rule.ps1")); - return builder.Build(); - } + #region Helper methods - private static PSRuleOption GetOption(string path = "PSRule.Tests14.yml") - { - return PSRuleOption.FromFileOrEmpty(path); - } + private static Source[] GetSource() + { + var builder = new SourcePipelineBuilder(null, null); + builder.Directory(GetSourcePath("FromFileAlias.Rule.yaml")); + builder.Directory(GetSourcePath("FromFileAlias.Rule.jsonc")); + builder.Directory(GetSourcePath("FromFileAlias.Rule.ps1")); + return builder.Build(); + } - private static string GetSourcePath(string fileName) - { - return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName); - } + private static PSRuleOption GetOption(string path = "PSRule.Tests14.yml") + { + return PSRuleOption.FromFileOrEmpty(path); + } - #endregion Helper methods + private static string GetSourcePath(string fileName) + { + return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName); } + + #endregion Helper methods } diff --git a/tests/PSRule.Tests/SuppressionGroupTests.cs b/tests/PSRule.Tests/SuppressionGroupTests.cs index 66f5325340..5280dbc5c0 100644 --- a/tests/PSRule.Tests/SuppressionGroupTests.cs +++ b/tests/PSRule.Tests/SuppressionGroupTests.cs @@ -10,109 +10,108 @@ using PSRule.Pipeline; using PSRule.Runtime; -namespace PSRule +namespace PSRule; + +public sealed class SuppressionGroupTests { - public sealed class SuppressionGroupTests + [Theory] + [InlineData("SuppressionGroups.Rule.yaml")] + [InlineData("SuppressionGroups.Rule.jsonc")] + public void ReadSuppressionGroup(string path) + { + var context = new RunspaceContext(PipelineContext.New(GetOption(), null, null, null, null, null, GetOptionContext(), null), null); + context.Init(GetSource(path)); + context.Begin(); + var suppressionGroup = HostHelper.GetSuppressionGroupForTests(GetSource(path), context).ToArray(); + Assert.NotNull(suppressionGroup); + Assert.Equal(5, suppressionGroup.Length); + + var actual = suppressionGroup[0]; + Assert.Equal("SuppressWithTargetName", actual.Name); + Assert.Equal("Ignore test objects by name.", actual.Info.Synopsis.Text); + Assert.Null(actual.Spec.ExpiresOn); + Assert.Contains(context.Pipeline.SuppressionGroup, g => g.Id.Equals(".\\SuppressWithTargetName")); + + actual = suppressionGroup[1]; + Assert.Equal("SuppressWithTestType", actual.Name); + Assert.Equal("Ignore test objects by type.", actual.Info.Synopsis.Text); + Assert.Null(actual.Spec.ExpiresOn); + Assert.Contains(context.Pipeline.SuppressionGroup, g => g.Id.Equals(".\\SuppressWithTestType")); + + actual = suppressionGroup[2]; + Assert.Equal("SuppressWithNonProdTag", actual.Name); + Assert.Equal("Ignore objects with non-production tag.", actual.Info.Synopsis.Text); + Assert.Null(actual.Spec.ExpiresOn); + Assert.Contains(context.Pipeline.SuppressionGroup, g => g.Id.Equals(".\\SuppressWithNonProdTag")); + + actual = suppressionGroup[3]; + Assert.Equal("SuppressWithExpiry", actual.Name); + Assert.Equal("Suppress with expiry.", actual.Info.Synopsis.Text); + Assert.Equal(DateTime.Parse("2022-01-01T00:00:00Z").ToUniversalTime(), actual.Spec.ExpiresOn); + Assert.DoesNotContain(context.Pipeline.SuppressionGroup, g => g.Id.Equals(".\\SuppressWithExpiry")); + + actual = suppressionGroup[4]; + Assert.Equal("SuppressByScope", actual.Name); + Assert.Equal("Suppress by scope.", actual.Info.Synopsis.Text); + } + + //[Theory] + //[InlineData("SuppressionGroups.Rule.yaml")] + //[InlineData("SuppressionGroups.Rule.jsonc")] + //public void EvaluateSuppressionGroup(string path) + //{ + // var context = new RunspaceContext(PipelineContext.New(GetOption(), null, null, PipelineHookActions.BindTargetName, PipelineHookActions.BindTargetType, PipelineHookActions.BindField, GetOptionContext(), null), null); + // context.Init(GetSource(path)); + // context.Begin(); + // var suppressionGroup = HostHelper.GetSuppressionGroup(GetSource(path), context).ToArray(); + // Assert.NotNull(suppressionGroup); + + // var testObject = GetObject((name: "name", value: "TestObject1")); + // context.EnterTargetObject(new TargetObject(testObject, targetName: "TestObject1", scope: "/scope1")); + + // var actual = suppressionGroup[0]; + // var visitor = new SuppressionGroupVisitor(context, actual.Id, actual.Source, actual.Spec, actual.Info); + // Assert.True(visitor.TryMatch(testObject, out _)); + + // actual = suppressionGroup[4]; + // visitor = new SuppressionGroupVisitor(context, actual.Id, actual.Source, actual.Spec, actual.Info); + // //Assert.True(visitor.TryMatch()); + //} + + #region Helper methods + + private static PSRuleOption GetOption() + { + var option = new PSRuleOption(); + option.Output.Culture = new string[] { "en-US", "en" }; + return option; + } + + private static OptionContextBuilder GetOptionContext() { - [Theory] - [InlineData("SuppressionGroups.Rule.yaml")] - [InlineData("SuppressionGroups.Rule.jsonc")] - public void ReadSuppressionGroup(string path) - { - var context = new RunspaceContext(PipelineContext.New(GetOption(), null, null, null, null, null, GetOptionContext(), null), null); - context.Init(GetSource(path)); - context.Begin(); - var suppressionGroup = HostHelper.GetSuppressionGroupForTests(GetSource(path), context).ToArray(); - Assert.NotNull(suppressionGroup); - Assert.Equal(5, suppressionGroup.Length); - - var actual = suppressionGroup[0]; - Assert.Equal("SuppressWithTargetName", actual.Name); - Assert.Equal("Ignore test objects by name.", actual.Info.Synopsis.Text); - Assert.Null(actual.Spec.ExpiresOn); - Assert.Contains(context.Pipeline.SuppressionGroup, g => g.Id.Equals(".\\SuppressWithTargetName")); - - actual = suppressionGroup[1]; - Assert.Equal("SuppressWithTestType", actual.Name); - Assert.Equal("Ignore test objects by type.", actual.Info.Synopsis.Text); - Assert.Null(actual.Spec.ExpiresOn); - Assert.Contains(context.Pipeline.SuppressionGroup, g => g.Id.Equals(".\\SuppressWithTestType")); - - actual = suppressionGroup[2]; - Assert.Equal("SuppressWithNonProdTag", actual.Name); - Assert.Equal("Ignore objects with non-production tag.", actual.Info.Synopsis.Text); - Assert.Null(actual.Spec.ExpiresOn); - Assert.Contains(context.Pipeline.SuppressionGroup, g => g.Id.Equals(".\\SuppressWithNonProdTag")); - - actual = suppressionGroup[3]; - Assert.Equal("SuppressWithExpiry", actual.Name); - Assert.Equal("Suppress with expiry.", actual.Info.Synopsis.Text); - Assert.Equal(DateTime.Parse("2022-01-01T00:00:00Z").ToUniversalTime(), actual.Spec.ExpiresOn); - Assert.DoesNotContain(context.Pipeline.SuppressionGroup, g => g.Id.Equals(".\\SuppressWithExpiry")); - - actual = suppressionGroup[4]; - Assert.Equal("SuppressByScope", actual.Name); - Assert.Equal("Suppress by scope.", actual.Info.Synopsis.Text); - } - - //[Theory] - //[InlineData("SuppressionGroups.Rule.yaml")] - //[InlineData("SuppressionGroups.Rule.jsonc")] - //public void EvaluateSuppressionGroup(string path) - //{ - // var context = new RunspaceContext(PipelineContext.New(GetOption(), null, null, PipelineHookActions.BindTargetName, PipelineHookActions.BindTargetType, PipelineHookActions.BindField, GetOptionContext(), null), null); - // context.Init(GetSource(path)); - // context.Begin(); - // var suppressionGroup = HostHelper.GetSuppressionGroup(GetSource(path), context).ToArray(); - // Assert.NotNull(suppressionGroup); - - // var testObject = GetObject((name: "name", value: "TestObject1")); - // context.EnterTargetObject(new TargetObject(testObject, targetName: "TestObject1", scope: "/scope1")); - - // var actual = suppressionGroup[0]; - // var visitor = new SuppressionGroupVisitor(context, actual.Id, actual.Source, actual.Spec, actual.Info); - // Assert.True(visitor.TryMatch(testObject, out _)); - - // actual = suppressionGroup[4]; - // visitor = new SuppressionGroupVisitor(context, actual.Id, actual.Source, actual.Spec, actual.Info); - // //Assert.True(visitor.TryMatch()); - //} - - #region Helper methods - - private static PSRuleOption GetOption() - { - var option = new PSRuleOption(); - option.Output.Culture = new string[] { "en-US", "en" }; - return option; - } - - private static OptionContextBuilder GetOptionContext() - { - return new OptionContextBuilder(GetOption()); - } - - private static Source[] GetSource(string path) - { - var builder = new SourcePipelineBuilder(null, null); - builder.Directory(GetSourcePath(path)); - return builder.Build(); - } - - private static string GetSourcePath(string fileName) - { - return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName); - } - - private static PSObject GetObject(params (string name, object value)[] properties) - { - var result = new PSObject(); - for (var i = 0; properties != null && i < properties.Length; i++) - result.Properties.Add(new PSNoteProperty(properties[i].name, properties[i].value)); - - return result; - } - - #endregion Helper methods + return new OptionContextBuilder(GetOption()); } + + private static Source[] GetSource(string path) + { + var builder = new SourcePipelineBuilder(null, null); + builder.Directory(GetSourcePath(path)); + return builder.Build(); + } + + private static string GetSourcePath(string fileName) + { + return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName); + } + + private static PSObject GetObject(params (string name, object value)[] properties) + { + var result = new PSObject(); + for (var i = 0; properties != null && i < properties.Length; i++) + result.Properties.Add(new PSNoteProperty(properties[i].name, properties[i].value)); + + return result; + } + + #endregion Helper methods } diff --git a/tests/PSRule.Tests/TargetBinderTests.cs b/tests/PSRule.Tests/TargetBinderTests.cs index 51d788dc2e..4391329647 100644 --- a/tests/PSRule.Tests/TargetBinderTests.cs +++ b/tests/PSRule.Tests/TargetBinderTests.cs @@ -8,136 +8,135 @@ using PSRule.Definitions.ModuleConfigs; using PSRule.Pipeline; -namespace PSRule +namespace PSRule; + +public sealed class TargetBinderTests { - public sealed class TargetBinderTests + [Fact] + public void BindTargetObject() { - [Fact] - public void BindTargetObject() - { - var binder = GetBinder(); - var targetObject = GetTargetObject(); - binder.Bind(targetObject); + var binder = GetBinder(); + var targetObject = GetTargetObject(); + binder.Bind(targetObject); - var m1 = binder.Result("Module1"); - Assert.Equal("Name1", m1.TargetName); - Assert.Equal("Type1", m1.TargetType); + var m1 = binder.Result("Module1"); + Assert.Equal("Name1", m1.TargetName); + Assert.Equal("Type1", m1.TargetType); - var m2 = binder.Result("Module2"); - Assert.Equal("Name2", m2.TargetName); - Assert.Equal("Type1", m2.TargetType); + var m2 = binder.Result("Module2"); + Assert.Equal("Name2", m2.TargetName); + Assert.Equal("Type1", m2.TargetType); - var m0 = binder.Result("."); - Assert.Equal("Name1", m0.TargetName); - Assert.Equal("System.Management.Automation.PSCustomObject", m0.TargetType); + var m0 = binder.Result("."); + Assert.Equal("Name1", m0.TargetName); + Assert.Equal("System.Management.Automation.PSCustomObject", m0.TargetType); - // With specified type - targetObject = GetTargetObject(targetType: "ManualType"); - binder.Bind(targetObject); + // With specified type + targetObject = GetTargetObject(targetType: "ManualType"); + binder.Bind(targetObject); - m1 = binder.Result("Module1"); - Assert.Equal("Name1", m1.TargetName); - Assert.Equal("Type1", m1.TargetType); + m1 = binder.Result("Module1"); + Assert.Equal("Name1", m1.TargetName); + Assert.Equal("Type1", m1.TargetType); - var m3 = binder.Result("Module3"); - Assert.Equal("Name1", m3.TargetName); - Assert.Equal("ManualType", m3.TargetType); - } + var m3 = binder.Result("Module3"); + Assert.Equal("Name1", m3.TargetName); + Assert.Equal("ManualType", m3.TargetType); + } - [Fact] - public void BindJObject() - { - var binder = GetBinder(); - var targetObject = new TargetObject(PSObject.AsPSObject(JToken.Parse("{ \"name\": \"Name1\", \"type\": \"Type1\", \"AlternativeName\": \"Name2\", \"AlternativeType\": \"Type2\" }"))); - binder.Bind(targetObject); + [Fact] + public void BindJObject() + { + var binder = GetBinder(); + var targetObject = new TargetObject(PSObject.AsPSObject(JToken.Parse("{ \"name\": \"Name1\", \"type\": \"Type1\", \"AlternativeName\": \"Name2\", \"AlternativeType\": \"Type2\" }"))); + binder.Bind(targetObject); - var m1 = binder.Result("Module1"); - Assert.Equal("Name1", m1.TargetName); - Assert.Equal("Type1", m1.TargetType); + var m1 = binder.Result("Module1"); + Assert.Equal("Name1", m1.TargetName); + Assert.Equal("Type1", m1.TargetType); - var m2 = binder.Result("Module2"); - Assert.Equal("Name2", m2.TargetName); - Assert.Equal("Type1", m2.TargetType); + var m2 = binder.Result("Module2"); + Assert.Equal("Name2", m2.TargetName); + Assert.Equal("Type1", m2.TargetType); - var m0 = binder.Result("."); - Assert.Equal("Name1", m0.TargetName); - Assert.Equal("System.Management.Automation.PSCustomObject", m0.TargetType); - } + var m0 = binder.Result("."); + Assert.Equal("Name1", m0.TargetName); + Assert.Equal("System.Management.Automation.PSCustomObject", m0.TargetType); + } - #region Helper methods + #region Helper methods - private static TargetObject GetTargetObject(string targetType = null) - { - var pso = new PSObject(); - pso.Properties.Add(new PSNoteProperty("name", "Name1")); - pso.Properties.Add(new PSNoteProperty("type", "Type1")); - pso.Properties.Add(new PSNoteProperty("AlternativeName", "Name2")); - pso.Properties.Add(new PSNoteProperty("AlternativeType", "Type2")); - return new TargetObject(pso, targetType: targetType); - } - - private static ITargetBinder GetBinder() - { - var builder = new TargetBinderBuilder(PipelineHookActions.BindTargetName, PipelineHookActions.BindTargetType, PipelineHookActions.BindField, null); - var option = new OptionContextBuilder(); + private static TargetObject GetTargetObject(string targetType = null) + { + var pso = new PSObject(); + pso.Properties.Add(new PSNoteProperty("name", "Name1")); + pso.Properties.Add(new PSNoteProperty("type", "Type1")); + pso.Properties.Add(new PSNoteProperty("AlternativeName", "Name2")); + pso.Properties.Add(new PSNoteProperty("AlternativeType", "Type2")); + return new TargetObject(pso, targetType: targetType); + } - option.ModuleConfig("Module1", new ModuleConfigV1Spec - { - Binding = new BindingOption - { - TargetName = new[] { "name" }, - TargetType = new[] { "type" } - } - }); - - option.ModuleConfig("Module2", new ModuleConfigV1Spec + private static ITargetBinder GetBinder() + { + var builder = new TargetBinderBuilder(PipelineHookActions.BindTargetName, PipelineHookActions.BindTargetType, PipelineHookActions.BindField, null); + var option = new OptionContextBuilder(); + + option.ModuleConfig("Module1", new ModuleConfigV1Spec + { + Binding = new BindingOption { - Binding = new BindingOption - { - TargetName = new[] { "AlternativeName" }, - TargetType = new[] { "type" } - } - }); - - option.ModuleConfig("Module3", new ModuleConfigV1Spec + TargetName = new[] { "name" }, + TargetType = new[] { "type" } + } + }); + + option.ModuleConfig("Module2", new ModuleConfigV1Spec + { + Binding = new BindingOption { - Binding = new BindingOption - { - TargetName = new[] { "name" }, - TargetType = new[] { "type" }, - PreferTargetInfo = true - } - }); - - var scopes = new Runtime.LanguageScopeSet(null); - - scopes.Import("Module1", out var module1); - module1.Configure(option.Build(module1.Name)); - builder.With(module1); - - scopes.Import("Module2", out var module2); - module2.Configure(option.Build(module2.Name)); - builder.With(module2); - - scopes.Import("Module3", out var module3); - module3.Configure(option.Build(module3.Name)); - builder.With(module3); - - scopes.Import(".", out var local); - local.Configure(option.Build(local.Name)); - builder.With(local); - return builder.Build(); - } - - private static IBaselineV1Spec GetOption(string[] targetName, string[] targetType, bool preferTargetInfo = false) + TargetName = new[] { "AlternativeName" }, + TargetType = new[] { "type" } + } + }); + + option.ModuleConfig("Module3", new ModuleConfigV1Spec { - var result = new BaselineOption.BaselineInline(); - result.Binding.TargetName = targetName; - result.Binding.TargetType = targetType; - result.Binding.PreferTargetInfo = preferTargetInfo; - return result; - } - - #endregion Helper methods + Binding = new BindingOption + { + TargetName = new[] { "name" }, + TargetType = new[] { "type" }, + PreferTargetInfo = true + } + }); + + var scopes = new Runtime.LanguageScopeSet(null); + + scopes.Import("Module1", out var module1); + module1.Configure(option.Build(module1.Name)); + builder.With(module1); + + scopes.Import("Module2", out var module2); + module2.Configure(option.Build(module2.Name)); + builder.With(module2); + + scopes.Import("Module3", out var module3); + module3.Configure(option.Build(module3.Name)); + builder.With(module3); + + scopes.Import(".", out var local); + local.Configure(option.Build(local.Name)); + builder.With(local); + return builder.Build(); } + + private static IBaselineV1Spec GetOption(string[] targetName, string[] targetType, bool preferTargetInfo = false) + { + var result = new BaselineOption.BaselineInline(); + result.Binding.TargetName = targetName; + result.Binding.TargetType = targetType; + result.Binding.PreferTargetInfo = preferTargetInfo; + return result; + } + + #endregion Helper methods } diff --git a/tests/PSRule.Tests/TargetInfoTests.cs b/tests/PSRule.Tests/TargetInfoTests.cs index 8ca4bdab97..b5b6c9bcb4 100644 --- a/tests/PSRule.Tests/TargetInfoTests.cs +++ b/tests/PSRule.Tests/TargetInfoTests.cs @@ -3,63 +3,62 @@ using System.Management.Automation; -namespace PSRule +namespace PSRule; + +public sealed class TargetInfoTests { - public sealed class TargetInfoTests + [Fact] + public void TargetSourceInfo() { - [Fact] - public void TargetSourceInfo() - { - var source = new PSObject(); - source.Properties.Add(new PSNoteProperty("file", "file.json")); - source.Properties.Add(new PSNoteProperty("line", 100)); - source.Properties.Add(new PSNoteProperty("position", 1000)); - source.Properties.Add(new PSNoteProperty("Type", "Origin")); - var info = new PSObject(); - info.Properties.Add(new PSNoteProperty("source", new PSObject[] { source })); - var o = new PSObject(); - o.Properties.Add(new PSNoteProperty("_PSRule", info)); - o.ConvertTargetInfoProperty(); + var source = new PSObject(); + source.Properties.Add(new PSNoteProperty("file", "file.json")); + source.Properties.Add(new PSNoteProperty("line", 100)); + source.Properties.Add(new PSNoteProperty("position", 1000)); + source.Properties.Add(new PSNoteProperty("Type", "Origin")); + var info = new PSObject(); + info.Properties.Add(new PSNoteProperty("source", new PSObject[] { source })); + var o = new PSObject(); + o.Properties.Add(new PSNoteProperty("_PSRule", info)); + o.ConvertTargetInfoProperty(); - var actual = o.GetSourceInfo(); - Assert.NotNull(actual); - Assert.Equal("file.json", actual[0].File); - Assert.Equal(100, actual[0].Line); - Assert.Equal(1000, actual[0].Position); - Assert.Equal("Origin", actual[0].Type); - } + var actual = o.GetSourceInfo(); + Assert.NotNull(actual); + Assert.Equal("file.json", actual[0].File); + Assert.Equal(100, actual[0].Line); + Assert.Equal(1000, actual[0].Position); + Assert.Equal("Origin", actual[0].Type); + } - [Fact] - public void TargetIssueInfo() - { - var issue = new PSObject(); - issue.Properties.Add(new PSNoteProperty("Type", "CustomIssue")); - issue.Properties.Add(new PSNoteProperty("name", "Issue.1")); - issue.Properties.Add(new PSNoteProperty("message", "Some issue")); - var info = new PSObject(); - info.Properties.Add(new PSNoteProperty("issue", new PSObject[] { issue })); - var o = new PSObject(); - o.Properties.Add(new PSNoteProperty("_PSRule", info)); - o.ConvertTargetInfoProperty(); + [Fact] + public void TargetIssueInfo() + { + var issue = new PSObject(); + issue.Properties.Add(new PSNoteProperty("Type", "CustomIssue")); + issue.Properties.Add(new PSNoteProperty("name", "Issue.1")); + issue.Properties.Add(new PSNoteProperty("message", "Some issue")); + var info = new PSObject(); + info.Properties.Add(new PSNoteProperty("issue", new PSObject[] { issue })); + var o = new PSObject(); + o.Properties.Add(new PSNoteProperty("_PSRule", info)); + o.ConvertTargetInfoProperty(); - var actual = o.GetIssueInfo(); - Assert.NotNull(actual); - Assert.Equal("CustomIssue", actual[0].Type); - Assert.Equal("Issue.1", actual[0].Name); - Assert.Equal("Some issue", actual[0].Message); - } + var actual = o.GetIssueInfo(); + Assert.NotNull(actual); + Assert.Equal("CustomIssue", actual[0].Type); + Assert.Equal("Issue.1", actual[0].Name); + Assert.Equal("Some issue", actual[0].Message); + } - [Fact] - public void TargetPath() - { - var info = new PSObject(); - info.Properties.Add(new PSNoteProperty("path", "resources[0]")); - var o = new PSObject(); - o.Properties.Add(new PSNoteProperty("_PSRule", info)); - o.ConvertTargetInfoProperty(); + [Fact] + public void TargetPath() + { + var info = new PSObject(); + info.Properties.Add(new PSNoteProperty("path", "resources[0]")); + var o = new PSObject(); + o.Properties.Add(new PSNoteProperty("_PSRule", info)); + o.ConvertTargetInfoProperty(); - var actual = o.GetTargetPath(); - Assert.Equal("resources[0]", actual); - } + var actual = o.GetTargetPath(); + Assert.Equal("resources[0]", actual); } } diff --git a/tests/PSRule.Tests/TargetNameBindingTests.cs b/tests/PSRule.Tests/TargetNameBindingTests.cs index 7ced67040b..c6cc71bb4e 100644 --- a/tests/PSRule.Tests/TargetNameBindingTests.cs +++ b/tests/PSRule.Tests/TargetNameBindingTests.cs @@ -7,80 +7,79 @@ using PSRule.Options; using PSRule.Pipeline; -namespace PSRule +namespace PSRule; + +public sealed class TargetNameBindingTests { - public sealed class TargetNameBindingTests + internal class TestModel1 { - internal class TestModel1 - { - public string NotName { get; set; } - } + public string NotName { get; set; } + } - internal class TestModel2 - { - public string NotName { get; set; } - } + internal class TestModel2 + { + public string NotName { get; set; } + } - internal class TestModel3 : ITargetInfo - { - public string Name { get; set; } + internal class TestModel3 : ITargetInfo + { + public string Name { get; set; } - string ITargetInfo.TargetName => "TestModel3"; + string ITargetInfo.TargetName => "TestModel3"; - string ITargetInfo.TargetType => "TestModel3"; + string ITargetInfo.TargetType => "TestModel3"; - TargetSourceInfo ITargetInfo.Source => null; - } + TargetSourceInfo ITargetInfo.Source => null; + } - [Fact] - public void UnboundObjectTargetName() - { - var testObject1 = new TestModel1 { NotName = "TestObject1" }; - var testObject2 = new TestModel2 { NotName = "TestObject1" }; - var pso1 = PSObject.AsPSObject(testObject1); - var pso2 = PSObject.AsPSObject(testObject2); + [Fact] + public void UnboundObjectTargetName() + { + var testObject1 = new TestModel1 { NotName = "TestObject1" }; + var testObject2 = new TestModel2 { NotName = "TestObject1" }; + var pso1 = PSObject.AsPSObject(testObject1); + var pso2 = PSObject.AsPSObject(testObject2); - // SHA512 - PipelineContext.CurrentThread = PipelineContext.New(GetOption(), null, null, null, null, null, null, null); + // SHA512 + PipelineContext.CurrentThread = PipelineContext.New(GetOption(), null, null, null, null, null, null, null); - Assert.Equal("f3d2f8ce966af96a8d320e8f5c088604324885a0d02f44b174", PipelineHookActions.BindTargetName(null, false, false, pso1, out _)); - Assert.Equal("f3d2f8ce966af96a8d320e8f5c088604324885a0d02f44b174", PipelineHookActions.BindTargetName(null, false, false, pso2, out _)); + Assert.Equal("f3d2f8ce966af96a8d320e8f5c088604324885a0d02f44b174", PipelineHookActions.BindTargetName(null, false, false, pso1, out _)); + Assert.Equal("f3d2f8ce966af96a8d320e8f5c088604324885a0d02f44b174", PipelineHookActions.BindTargetName(null, false, false, pso2, out _)); - // SHA256 - PipelineContext.CurrentThread = PipelineContext.New(GetOption(HashAlgorithm.SHA256), null, null, null, null, null, null, null); + // SHA256 + PipelineContext.CurrentThread = PipelineContext.New(GetOption(HashAlgorithm.SHA256), null, null, null, null, null, null, null); - Assert.Equal("67327c8cd8622d17cf1702a76cbbb685e9ef260ce39c9f6779", PipelineHookActions.BindTargetName(null, false, false, pso1, out _)); - Assert.Equal("67327c8cd8622d17cf1702a76cbbb685e9ef260ce39c9f6779", PipelineHookActions.BindTargetName(null, false, false, pso2, out _)); - } + Assert.Equal("67327c8cd8622d17cf1702a76cbbb685e9ef260ce39c9f6779", PipelineHookActions.BindTargetName(null, false, false, pso1, out _)); + Assert.Equal("67327c8cd8622d17cf1702a76cbbb685e9ef260ce39c9f6779", PipelineHookActions.BindTargetName(null, false, false, pso2, out _)); + } - [Fact] - public void PreferTargetInfo() - { - var testObject1 = new TestModel3 { Name = "OtherName" }; - var pso1 = PSObject.AsPSObject(testObject1); + [Fact] + public void PreferTargetInfo() + { + var testObject1 = new TestModel3 { Name = "OtherName" }; + var pso1 = PSObject.AsPSObject(testObject1); - PipelineContext.CurrentThread = PipelineContext.New(GetOption(), null, null, null, null, null, null, null); + PipelineContext.CurrentThread = PipelineContext.New(GetOption(), null, null, null, null, null, null, null); - var actual = PipelineHookActions.BindTargetName(new string[] { "Name" }, false, false, pso1, out var path); - Assert.Equal("OtherName", actual); - Assert.Equal("Name", path); + var actual = PipelineHookActions.BindTargetName(new string[] { "Name" }, false, false, pso1, out var path); + Assert.Equal("OtherName", actual); + Assert.Equal("Name", path); - actual = PipelineHookActions.BindTargetName(new string[] { "NotName" }, false, false, pso1, out path); - Assert.Equal("TestModel3", actual); - Assert.Null(path); + actual = PipelineHookActions.BindTargetName(new string[] { "NotName" }, false, false, pso1, out path); + Assert.Equal("TestModel3", actual); + Assert.Null(path); - actual = PipelineHookActions.BindTargetName(new string[] { "Name" }, false, true, pso1, out path); - Assert.Equal("TestModel3", actual); - Assert.Null(path); - } + actual = PipelineHookActions.BindTargetName(new string[] { "Name" }, false, true, pso1, out path); + Assert.Equal("TestModel3", actual); + Assert.Null(path); + } - private static PSRuleOption GetOption(HashAlgorithm? hashAlgorithm = null) - { - var option = new PSRuleOption(); - if (hashAlgorithm != null) - option.Execution.HashAlgorithm = hashAlgorithm; + private static PSRuleOption GetOption(HashAlgorithm? hashAlgorithm = null) + { + var option = new PSRuleOption(); + if (hashAlgorithm != null) + option.Execution.HashAlgorithm = hashAlgorithm; - return option; - } + return option; } } diff --git a/tests/PSRule.Tests/TestAssertWriter.cs b/tests/PSRule.Tests/TestAssertWriter.cs index 7c342a2a88..f00d1dbfef 100644 --- a/tests/PSRule.Tests/TestAssertWriter.cs +++ b/tests/PSRule.Tests/TestAssertWriter.cs @@ -6,31 +6,30 @@ using PSRule.Configuration; using PSRule.Pipeline; -namespace PSRule +namespace PSRule; + +internal sealed class TestAssertWriter : PipelineWriter { - internal sealed class TestAssertWriter : PipelineWriter - { - private readonly StringBuilder _Output; + private readonly StringBuilder _Output; - public TestAssertWriter(PSRuleOption option) - : base(null, option, null) - { - _Output = new StringBuilder(); - } + public TestAssertWriter(PSRuleOption option) + : base(null, option, null) + { + _Output = new StringBuilder(); + } - public string Output => _Output.ToString(); + public string Output => _Output.ToString(); - public void Clear() - { - _Output.Clear(); - } + public void Clear() + { + _Output.Clear(); + } - public override void WriteHost(HostInformationMessage info) - { - if (info.NoNewLine.GetValueOrDefault(false)) - _Output.Append(info.Message); - else - _Output.AppendLine(info.Message); - } + public override void WriteHost(HostInformationMessage info) + { + if (info.NoNewLine.GetValueOrDefault(false)) + _Output.Append(info.Message); + else + _Output.AppendLine(info.Message); } } diff --git a/tests/PSRule.Tests/TestWriter.cs b/tests/PSRule.Tests/TestWriter.cs index b8b4f6bb54..32ea5489a7 100644 --- a/tests/PSRule.Tests/TestWriter.cs +++ b/tests/PSRule.Tests/TestWriter.cs @@ -6,61 +6,60 @@ using PSRule.Configuration; using PSRule.Pipeline; -namespace PSRule +namespace PSRule; + +internal sealed class TestWriter : PipelineWriter { - internal sealed class TestWriter : PipelineWriter + internal List Errors = new(); + internal List Warnings = new(); + internal List Information = new(); + internal List Output = new(); + + public TestWriter(PSRuleOption option) + : base(null, option, null) { } + + public override void WriteError(ErrorRecord errorRecord) { - internal List Errors = new(); - internal List Warnings = new(); - internal List Information = new(); - internal List Output = new(); + Errors.Add(errorRecord); + } - public TestWriter(PSRuleOption option) - : base(null, option, null) { } + public override void WriteWarning(string message) + { + Warnings.Add(message); + } - public override void WriteError(ErrorRecord errorRecord) - { - Errors.Add(errorRecord); - } + public override bool ShouldWriteError() + { + return true; + } - public override void WriteWarning(string message) - { - Warnings.Add(message); - } + public override bool ShouldWriteWarning() + { + return true; + } - public override bool ShouldWriteError() - { - return true; - } + public override void WriteObject(object sendToPipeline, bool enumerateCollection) + { + if (sendToPipeline == null) + return; - public override bool ShouldWriteWarning() + if (enumerateCollection && sendToPipeline is IEnumerable enumerable) { - return true; + Output.AddRange(enumerable); } - - public override void WriteObject(object sendToPipeline, bool enumerateCollection) + else { - if (sendToPipeline == null) - return; - - if (enumerateCollection && sendToPipeline is IEnumerable enumerable) - { - Output.AddRange(enumerable); - } - else - { - Output.Add(sendToPipeline); - } + Output.Add(sendToPipeline); } + } - public override bool ShouldWriteInformation() - { - return true; - } + public override bool ShouldWriteInformation() + { + return true; + } - public override void WriteInformation(InformationRecord informationRecord) - { - Information.Add(informationRecord.MessageData); - } + public override void WriteInformation(InformationRecord informationRecord) + { + Information.Add(informationRecord.MessageData); } } From b65a56a2b5f4055f1a7a0ab981ffb240973b1aed Mon Sep 17 00:00:00 2001 From: Bernie White Date: Thu, 21 Dec 2023 16:14:37 +1000 Subject: [PATCH 150/177] Code quality updates (#1711) * Code quality updates * Fix --- src/PSRule/Host/HostHelper.cs | 4 ++-- src/PSRule/Pipeline/Output/NUnit3OutputWriter.cs | 2 ++ tests/PSRule.Tests/AssertTests.cs | 11 +++++++---- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/PSRule/Host/HostHelper.cs b/src/PSRule/Host/HostHelper.cs index c42d309d0b..4b859770f0 100644 --- a/src/PSRule/Host/HostHelper.cs +++ b/src/PSRule/Host/HostHelper.cs @@ -560,8 +560,8 @@ private static DependencyTargetCollection ToRuleBlockV1(ILanguageBloc condition: new RuleVisitor(context, block.Id, block.Source, block.Spec), alias: block.Alias, tag: block.Metadata.Tags, - dependsOn: null, // TODO: No support for DependsOn yet - configuration: null, // TODO: No support for rule configuration use module or workspace config + dependsOn: null, // No support for DependsOn yet + configuration: null, // No support for rule configuration use module or workspace config extent: null, flags: block.Flags, labels: block.Metadata.Labels diff --git a/src/PSRule/Pipeline/Output/NUnit3OutputWriter.cs b/src/PSRule/Pipeline/Output/NUnit3OutputWriter.cs index b47452c0ab..b4cc964983 100644 --- a/src/PSRule/Pipeline/Output/NUnit3OutputWriter.cs +++ b/src/PSRule/Pipeline/Output/NUnit3OutputWriter.cs @@ -39,6 +39,8 @@ protected override string Serialize(InvokeResult[] o) var fail = o.Sum(r => r.Fail); xml.WriteStartElement("test-results"); + + // Schema requires http scheme. xml.WriteAttributeString("xsi", "noNamespaceSchemaLocation", "http://www.w3.org/2001/XMLSchema-instance", "nunit_schema_2.5.xsd"); xml.WriteAttributeString("name", "PSRule"); xml.WriteAttributeString("total", total.ToString()); diff --git a/tests/PSRule.Tests/AssertTests.cs b/tests/PSRule.Tests/AssertTests.cs index 89f52e2d11..16b9abd9a2 100644 --- a/tests/PSRule.Tests/AssertTests.cs +++ b/tests/PSRule.Tests/AssertTests.cs @@ -211,9 +211,12 @@ public void HasJsonSchema() SetContext(); var assert = GetAssertionHelper(); + const string DRAFT7_HTTP = "http://json-schema.org/draft-07/schema#"; + const string DRAFT7_HTTPS = "https://json-schema.org/draft-07/schema#"; + var actual1 = GetObject((name: "$schema", value: "abc")); var actual2 = GetObject((name: "schema", value: "abc")); - var actual3 = GetObject((name: "$schema", value: "http://json-schema.org/draft-07/schema#")); + var actual3 = GetObject((name: "$schema", value: DRAFT7_HTTP)); var actual4 = GetObject((name: "$schema", value: "http://json-schema.org/draft-07/schema#definition")); var actual5 = GetObject((name: "$schema", value: "")); @@ -222,9 +225,9 @@ public void HasJsonSchema() Assert.False(assert.HasJsonSchema(actual2, new string[] { "abc" }).Result); Assert.True(assert.HasJsonSchema(actual1, new string[] { "efg", "abc" }).Result); Assert.False(assert.HasJsonSchema(actual3, new string[] { "https://json-schema.org/draft-07/schema" }).Result); - Assert.True(assert.HasJsonSchema(actual3, new string[] { "https://json-schema.org/draft-07/schema#" }, true).Result); - Assert.True(assert.HasJsonSchema(actual3, new string[] { "https://json-schema.org/draft-07/schema#", "http://json-schema.org/draft-07/schema#" }).Result); - Assert.False(assert.HasJsonSchema(actual4, new string[] { "https://json-schema.org/draft-07/schema#" }, true).Result); + Assert.True(assert.HasJsonSchema(actual3, new string[] { DRAFT7_HTTPS }, true).Result); + Assert.True(assert.HasJsonSchema(actual3, new string[] { DRAFT7_HTTPS, DRAFT7_HTTP }).Result); + Assert.False(assert.HasJsonSchema(actual4, new string[] { DRAFT7_HTTPS }, true).Result); Assert.False(assert.HasJsonSchema(actual5, null).Result); Assert.Equal("$schema", assert.HasJsonSchema(actual2, new string[] { "abc" }).ToResultReason().FirstOrDefault().Path); From 107eb694945965b5c4d57b75b182ef8ef3ae9d8d Mon Sep 17 00:00:00 2001 From: Bernie White Date: Thu, 21 Dec 2023 22:23:53 +1000 Subject: [PATCH 151/177] Code quality updates (#1712) * Code quality updates * Refactoring --- src/PSRule.Badges/BadgeException.cs | 29 +++++ src/PSRule.Badges/BadgeResources.cs | 14 +- src/PSRule.Badges/PSRule.Badges.csproj | 16 +++ .../Resources/Messages.Designer.cs | 72 ++++++++++ src/PSRule.Badges/Resources/Messages.resx | 123 ++++++++++++++++++ src/PSRule/Pipeline/GetBaselinePipeline.cs | 2 +- src/PSRule/Pipeline/GetRuleHelpPipeline.cs | 2 +- src/PSRule/Pipeline/GetRulePipeline.cs | 2 +- src/PSRule/Pipeline/GetTargetPipeline.cs | 6 +- src/PSRule/Pipeline/InvokePipelineBuilder.cs | 4 +- src/PSRule/Pipeline/PipelineBuilder.cs | 4 +- src/PSRule/Pipeline/PipelineContext.cs | 6 +- ...pelineReader.cs => PipelineInputStream.cs} | 16 ++- src/PSRule/Pipeline/RulePipeline.cs | 4 +- tests/PSRule.Tests/PipelineTests.cs | 4 +- 15 files changed, 281 insertions(+), 23 deletions(-) create mode 100644 src/PSRule.Badges/BadgeException.cs create mode 100644 src/PSRule.Badges/Resources/Messages.Designer.cs create mode 100644 src/PSRule.Badges/Resources/Messages.resx rename src/PSRule/Pipeline/{PipelineReader.cs => PipelineInputStream.cs} (81%) diff --git a/src/PSRule.Badges/BadgeException.cs b/src/PSRule.Badges/BadgeException.cs new file mode 100644 index 0000000000..5b9ccc7441 --- /dev/null +++ b/src/PSRule.Badges/BadgeException.cs @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Runtime.Serialization; + +namespace PSRule.Badges; + +/// +/// A generic exception for handling of badges. +/// +[Serializable] +internal class BadgeException : Exception +{ + public BadgeException() + { + } + + public BadgeException(string message) : base(message) + { + } + + public BadgeException(string message, Exception innerException) : base(message, innerException) + { + } + + protected BadgeException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } +} diff --git a/src/PSRule.Badges/BadgeResources.cs b/src/PSRule.Badges/BadgeResources.cs index fe87f9e6e1..55dbbb5eb6 100644 --- a/src/PSRule.Badges/BadgeResources.cs +++ b/src/PSRule.Badges/BadgeResources.cs @@ -1,8 +1,9 @@ -// Copyright (c) Microsoft Corporation. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using System.Reflection; using Newtonsoft.Json; +using PSRule.Badges.Resources; namespace PSRule.Badges; @@ -13,8 +14,8 @@ internal static class BadgeResources { private const string DEFAULT_CULTURE_RESOURCE = "PSRule.Badges.Resources.en.json"; - private static char[] _Char; - private static double[] _Width; + private static char[]? _Char; + private static double[]? _Width; /// /// Load pre-calculated widths for characters. @@ -29,6 +30,9 @@ private static void Load() using var reader = new StreamReader(stream); var json = reader.ReadToEnd(); var d = JsonConvert.DeserializeObject(json); + if (d == null) + throw new BadgeException(Messages.LoadException); + _Char = new char[d.Length]; _Width = new double[d.Length]; for (var i = 0; i < d.Length; i++) @@ -52,6 +56,8 @@ private static double GetWidth(char c) /// private static double Find(char c) { + if (_Char == null || _Width == null) return 0d; + var index = Array.BinarySearch(_Char, c); return index >= 0 ? _Width[index] : 0d; } @@ -59,7 +65,7 @@ private static double Find(char c) public static double Measure(string s) { var length = 0d; - for (var i = 0; i < s.Length; i++) + for (var i = 0; s != null && i < s.Length; i++) length += GetWidth(s[i]); return length; diff --git a/src/PSRule.Badges/PSRule.Badges.csproj b/src/PSRule.Badges/PSRule.Badges.csproj index 4dc36456d2..caba5918ac 100644 --- a/src/PSRule.Badges/PSRule.Badges.csproj +++ b/src/PSRule.Badges/PSRule.Badges.csproj @@ -6,6 +6,7 @@ Microsoft.PSRule.Badges Library {309bed8b-4e60-4c42-a2b4-37a2e7ebef3f} + enable README.md True @@ -22,4 +23,19 @@ + + + Messages.resx + True + True + + + + + + Messages.Designer.cs + ResXFileCodeGenerator + + + diff --git a/src/PSRule.Badges/Resources/Messages.Designer.cs b/src/PSRule.Badges/Resources/Messages.Designer.cs new file mode 100644 index 0000000000..d15641d323 --- /dev/null +++ b/src/PSRule.Badges/Resources/Messages.Designer.cs @@ -0,0 +1,72 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace PSRule.Badges.Resources { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Messages { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Messages() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("PSRule.Badges.Resources.Messages", typeof(Messages).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Failed to load badge resources.. + /// + internal static string LoadException { + get { + return ResourceManager.GetString("LoadException", resourceCulture); + } + } + } +} diff --git a/src/PSRule.Badges/Resources/Messages.resx b/src/PSRule.Badges/Resources/Messages.resx new file mode 100644 index 0000000000..f3f958d755 --- /dev/null +++ b/src/PSRule.Badges/Resources/Messages.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Failed to load badge resources. + + \ No newline at end of file diff --git a/src/PSRule/Pipeline/GetBaselinePipeline.cs b/src/PSRule/Pipeline/GetBaselinePipeline.cs index 1e7a664c86..27cb9a24c7 100644 --- a/src/PSRule/Pipeline/GetBaselinePipeline.cs +++ b/src/PSRule/Pipeline/GetBaselinePipeline.cs @@ -14,7 +14,7 @@ internal sealed class GetBaselinePipeline : RulePipeline internal GetBaselinePipeline( PipelineContext pipeline, Source[] source, - PipelineReader reader, + PipelineInputStream reader, IPipelineWriter writer, IResourceFilter filter ) diff --git a/src/PSRule/Pipeline/GetRuleHelpPipeline.cs b/src/PSRule/Pipeline/GetRuleHelpPipeline.cs index 4d5bc3f268..3dfe489fcb 100644 --- a/src/PSRule/Pipeline/GetRuleHelpPipeline.cs +++ b/src/PSRule/Pipeline/GetRuleHelpPipeline.cs @@ -176,7 +176,7 @@ protected override PipelineWriter PrepareWriter() internal sealed class GetRuleHelpPipeline : RulePipeline, IPipeline { - internal GetRuleHelpPipeline(PipelineContext pipeline, Source[] source, PipelineReader reader, IPipelineWriter writer) + internal GetRuleHelpPipeline(PipelineContext pipeline, Source[] source, PipelineInputStream reader, IPipelineWriter writer) : base(pipeline, source, reader, writer) { // Do nothing diff --git a/src/PSRule/Pipeline/GetRulePipeline.cs b/src/PSRule/Pipeline/GetRulePipeline.cs index b045c2622a..cc9bdb32df 100644 --- a/src/PSRule/Pipeline/GetRulePipeline.cs +++ b/src/PSRule/Pipeline/GetRulePipeline.cs @@ -12,7 +12,7 @@ internal sealed class GetRulePipeline : RulePipeline, IPipeline internal GetRulePipeline( PipelineContext pipeline, Source[] source, - PipelineReader reader, + PipelineInputStream reader, IPipelineWriter writer, bool includeDependencies ) diff --git a/src/PSRule/Pipeline/GetTargetPipeline.cs b/src/PSRule/Pipeline/GetTargetPipeline.cs index 34723d4ea1..1683ae32a6 100644 --- a/src/PSRule/Pipeline/GetTargetPipeline.cs +++ b/src/PSRule/Pipeline/GetTargetPipeline.cs @@ -72,7 +72,7 @@ public override IPipeline Build(IPipelineWriter writer = null) } /// - protected override PipelineReader PrepareReader() + protected override PipelineInputStream PrepareReader() { if (!string.IsNullOrEmpty(Option.Input.ObjectPath)) { @@ -124,7 +124,7 @@ protected override PipelineReader PrepareReader() return PipelineReceiverActions.DetectInputFormat(sourceObject, next); }); } - return new PipelineReader(VisitTargetObject, _InputPath, GetInputObjectSourceFilter()); + return new PipelineInputStream(VisitTargetObject, _InputPath, GetInputObjectSourceFilter()); } } @@ -133,7 +133,7 @@ protected override PipelineReader PrepareReader() /// internal sealed class GetTargetPipeline : RulePipeline { - internal GetTargetPipeline(PipelineContext context, PipelineReader reader, IPipelineWriter writer) + internal GetTargetPipeline(PipelineContext context, PipelineInputStream reader, IPipelineWriter writer) : base(context, null, reader, writer) { } public override void Process(PSObject sourceObject) diff --git a/src/PSRule/Pipeline/InvokePipelineBuilder.cs b/src/PSRule/Pipeline/InvokePipelineBuilder.cs index 6554ec9c69..3e07256b31 100644 --- a/src/PSRule/Pipeline/InvokePipelineBuilder.cs +++ b/src/PSRule/Pipeline/InvokePipelineBuilder.cs @@ -149,7 +149,7 @@ private static bool IsBlocked(string path) } } - protected override PipelineReader PrepareReader() + protected override PipelineInputStream PrepareReader() { if (!string.IsNullOrEmpty(Option.Input.ObjectPath)) { @@ -201,7 +201,7 @@ protected override PipelineReader PrepareReader() return PipelineReceiverActions.DetectInputFormat(sourceObject, next); }); } - return new PipelineReader(VisitTargetObject, _InputPath, GetInputObjectSourceFilter()); + return new PipelineInputStream(VisitTargetObject, _InputPath, GetInputObjectSourceFilter()); } } diff --git a/src/PSRule/Pipeline/PipelineBuilder.cs b/src/PSRule/Pipeline/PipelineBuilder.cs index 31ab502335..e4ee3d3ae2 100644 --- a/src/PSRule/Pipeline/PipelineBuilder.cs +++ b/src/PSRule/Pipeline/PipelineBuilder.cs @@ -468,9 +468,9 @@ protected string ResolveBaselineGroup(string name) return baselines[0]; } - protected virtual PipelineReader PrepareReader() + protected virtual PipelineInputStream PrepareReader() { - return new PipelineReader(null, null, GetInputObjectSourceFilter()); + return new PipelineInputStream(null, null, GetInputObjectSourceFilter()); } protected virtual PipelineWriter PrepareWriter() diff --git a/src/PSRule/Pipeline/PipelineContext.cs b/src/PSRule/Pipeline/PipelineContext.cs index 1e20e2418f..c601b58552 100644 --- a/src/PSRule/Pipeline/PipelineContext.cs +++ b/src/PSRule/Pipeline/PipelineContext.cs @@ -53,7 +53,7 @@ internal sealed class PipelineContext : IDisposable, IBindingContext internal readonly Dictionary Selector; internal readonly List SuppressionGroup; internal readonly IHostContext HostContext; - internal readonly PipelineReader Reader; + internal readonly PipelineInputStream Reader; internal readonly BindTargetMethod BindTargetName; internal readonly BindTargetMethod BindTargetType; internal readonly BindTargetMethod BindField; @@ -65,7 +65,7 @@ internal sealed class PipelineContext : IDisposable, IBindingContext public System.Security.Cryptography.HashAlgorithm ObjectHashAlgorithm { get; } - private PipelineContext(PSRuleOption option, IHostContext hostContext, PipelineReader reader, BindTargetMethod bindTargetName, BindTargetMethod bindTargetType, BindTargetMethod bindField, OptionContextBuilder optionBuilder, IList unresolved) + private PipelineContext(PSRuleOption option, IHostContext hostContext, PipelineInputStream reader, BindTargetMethod bindTargetName, BindTargetMethod bindTargetType, BindTargetMethod bindField, OptionContextBuilder optionBuilder, IList unresolved) { _OptionBuilder = optionBuilder; Option = option; @@ -90,7 +90,7 @@ private PipelineContext(PSRuleOption option, IHostContext hostContext, PipelineR _DefaultOptionContext = _OptionBuilder?.Build(null); } - public static PipelineContext New(PSRuleOption option, IHostContext hostContext, PipelineReader reader, BindTargetMethod bindTargetName, BindTargetMethod bindTargetType, BindTargetMethod bindField, OptionContextBuilder optionBuilder, IList unresolved) + public static PipelineContext New(PSRuleOption option, IHostContext hostContext, PipelineInputStream reader, BindTargetMethod bindTargetName, BindTargetMethod bindTargetType, BindTargetMethod bindField, OptionContextBuilder optionBuilder, IList unresolved) { var context = new PipelineContext(option, hostContext, reader, bindTargetName, bindTargetType, bindField, optionBuilder, unresolved); CurrentThread = context; diff --git a/src/PSRule/Pipeline/PipelineReader.cs b/src/PSRule/Pipeline/PipelineInputStream.cs similarity index 81% rename from src/PSRule/Pipeline/PipelineReader.cs rename to src/PSRule/Pipeline/PipelineInputStream.cs index 0c66466aa3..64497dca61 100644 --- a/src/PSRule/Pipeline/PipelineReader.cs +++ b/src/PSRule/Pipeline/PipelineInputStream.cs @@ -6,14 +6,17 @@ namespace PSRule.Pipeline; -internal sealed class PipelineReader +/// +/// A stream of input objects that will be evaluated. +/// +internal sealed class PipelineInputStream { private readonly VisitTargetObject _Input; private readonly InputPathBuilder _InputPath; private readonly PathFilter _InputFilter; private readonly ConcurrentQueue _Queue; - public PipelineReader(VisitTargetObject input, InputPathBuilder inputPath, PathFilter inputFilter) + public PipelineInputStream(VisitTargetObject input, InputPathBuilder inputPath, PathFilter inputFilter) { _Input = input; _InputPath = inputPath; @@ -25,6 +28,12 @@ public PipelineReader(VisitTargetObject input, InputPathBuilder inputPath, PathF public bool IsEmpty => _Queue.IsEmpty; + /// + /// Add a new object into the stream. + /// + /// An object to process. + /// A pre-bound type. + /// Determines if expansion is skipped. public void Enqueue(PSObject sourceObject, string targetType = null, bool skipExpansion = false) { if (sourceObject == null) @@ -46,6 +55,9 @@ public void Enqueue(PSObject sourceObject, string targetType = null, bool skipEx EnqueueInternal(item); } + /// + /// Get the next object in the stream. + /// public bool TryDequeue(out TargetObject sourceObject) { return _Queue.TryDequeue(out sourceObject); diff --git a/src/PSRule/Pipeline/RulePipeline.cs b/src/PSRule/Pipeline/RulePipeline.cs index 207ff27ed0..653c61ea5c 100644 --- a/src/PSRule/Pipeline/RulePipeline.cs +++ b/src/PSRule/Pipeline/RulePipeline.cs @@ -11,13 +11,13 @@ internal abstract class RulePipeline : IPipeline protected readonly PipelineContext Pipeline; protected readonly RunspaceContext Context; protected readonly Source[] Source; - protected readonly PipelineReader Reader; + protected readonly PipelineInputStream Reader; protected readonly IPipelineWriter Writer; // Track whether Dispose has been called. private bool _Disposed; - protected RulePipeline(PipelineContext context, Source[] source, PipelineReader reader, IPipelineWriter writer) + protected RulePipeline(PipelineContext context, Source[] source, PipelineInputStream reader, IPipelineWriter writer) { Result = new DefaultPipelineResult(writer); Pipeline = context; diff --git a/tests/PSRule.Tests/PipelineTests.cs b/tests/PSRule.Tests/PipelineTests.cs index d02c7b67f7..23a7395ef0 100644 --- a/tests/PSRule.Tests/PipelineTests.cs +++ b/tests/PSRule.Tests/PipelineTests.cs @@ -181,7 +181,7 @@ public void PipelineWithInvariantCulture() Environment.UseCurrentCulture(CultureInfo.InvariantCulture); 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); + var pipeline = new GetRulePipeline(context, GetSource(), new PipelineInputStream(null, null, null), writer, false); try { pipeline.Begin(); @@ -203,7 +203,7 @@ public void PipelineWithInvariantCultureDisabled() option.Execution.InvariantCulture = ExecutionActionPreference.Ignore; 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); + var pipeline = new GetRulePipeline(context, GetSource(), new PipelineInputStream(null, null, null), writer, false); try { pipeline.Begin(); From e6a0e446d29b2865feecc811bbfa316c486e9b9a Mon Sep 17 00:00:00 2001 From: Bernie White Date: Thu, 21 Dec 2023 23:49:49 +1000 Subject: [PATCH 152/177] Rename analyze CLI command to run #1713 (#1714) * Rename analyze CLI command to run #1713 * Documentation fixes --- docs/CHANGELOG-v3.md | 2 + docs/concepts/cli.md | 41 -------------- docs/concepts/cli/index.md | 13 +++++ docs/concepts/cli/module.md | 14 +++++ docs/concepts/cli/restore.md | 3 ++ docs/concepts/cli/run.md | 25 +++++++++ docs/faq.md | 2 +- docs/upgrade-notes.md | 10 ++++ mkdocs.yml | 20 +++++-- requirements-docs.txt | 3 +- src/PSRule.Tool/ClientBuilder.cs | 16 +++--- src/PSRule.Tool/ClientHelper.cs | 27 ++++++---- src/PSRule.Tool/ModuleOptions.cs | 8 +-- src/PSRule.Tool/PSRule.Tool.csproj | 3 ++ .../Resources/CmdStrings.Designer.cs | 54 +++++++++---------- src/PSRule.Tool/Resources/CmdStrings.resx | 18 +++---- src/PSRule.Tool/RestoreOptions.cs | 4 +- .../{AnalyzerOptions.cs => RunOptions.cs} | 12 ++--- src/PSRule.Types/Data/SemanticVersion.cs | 2 +- tests/PSRule.Tool.Tests/CommandTests.cs | 6 +-- 20 files changed, 166 insertions(+), 117 deletions(-) delete mode 100644 docs/concepts/cli.md create mode 100644 docs/concepts/cli/index.md create mode 100644 docs/concepts/cli/module.md create mode 100644 docs/concepts/cli/restore.md create mode 100644 docs/concepts/cli/run.md rename src/PSRule.Tool/{AnalyzerOptions.cs => RunOptions.cs} (51%) diff --git a/docs/CHANGELOG-v3.md b/docs/CHANGELOG-v3.md index 0fe39a107b..edcd727f28 100644 --- a/docs/CHANGELOG-v3.md +++ b/docs/CHANGELOG-v3.md @@ -30,6 +30,8 @@ See [upgrade notes][1] for helpful information when upgrading from previous vers What's changed since pre-release v3.0.0-B0093: - General improvements: + - **Breaking change:** Renamed `analyze` CLI command to `run` by @BernieWhite. + [#1713](https://github.com/microsoft/PSRule/issues/1713) - Added `--outcome` argument for CLI to support filtering output by @bernieWhite. [#1706](https://github.com/microsoft/PSRule/issues/1706) - Engineering: diff --git a/docs/concepts/cli.md b/docs/concepts/cli.md deleted file mode 100644 index 8fbc31d2e1..0000000000 --- a/docs/concepts/cli.md +++ /dev/null @@ -1,41 +0,0 @@ -# PSRule CLI - -!!! Abstract - PSRule provides a command-line interface (CLI) to run rules and analyze results. - This article describes the commands available in the CLI. - -## `analyze` - -Run rule analysis. - -### `--outcome` - -Allows filtering of results by outcome. -The supported values are: - -- `Pass` - Results that passed. -- `Fail` - Results that did not pass. -- `Error` - Results that failed to be evaluted correctly due to an error. -- `Processed` - All results that were processed. - This aggregated outcome includes `Pass`, `Fail`, or `Error` results. -- `Problem` - Processed results that did not pass. - This aggregated outcome includes `Fail`, or `Error` results. - -To specify multiple values, specify the parameter multiple times. -For example: `--outcome Pass --Outcome Fail`. - -## `module add` - -Add one or more modules to the lock file. - -## `module remove` - -Remove one or more modules from the lock file. - -## `module upgrade` - -Upgrade to the latest versions any modules within the lock file. - -## `restore` - -Restore modules defined in configuration locally. diff --git a/docs/concepts/cli/index.md b/docs/concepts/cli/index.md new file mode 100644 index 0000000000..95b94e54cc --- /dev/null +++ b/docs/concepts/cli/index.md @@ -0,0 +1,13 @@ +# PSRule CLI + +!!! Abstract + PSRule provides a command-line interface (CLI) to run rules and analyze results. + This article describes the commands available in the CLI. + +## Commands + +The following commands are available in the PSRule CLI: + +- [run](./run.md) +- [module](./module.md) +- [restore](./restore.md) diff --git a/docs/concepts/cli/module.md b/docs/concepts/cli/module.md new file mode 100644 index 0000000000..a493606b81 --- /dev/null +++ b/docs/concepts/cli/module.md @@ -0,0 +1,14 @@ + +# ps-rule module + +## `module add` + +Add one or more modules to the lock file. + +## `module remove` + +Remove one or more modules from the lock file. + +## `module upgrade` + +Upgrade to the latest versions any modules within the lock file. diff --git a/docs/concepts/cli/restore.md b/docs/concepts/cli/restore.md new file mode 100644 index 0000000000..08eb57732c --- /dev/null +++ b/docs/concepts/cli/restore.md @@ -0,0 +1,3 @@ +# ps-rule restore + +Restore modules defined in configuration locally. diff --git a/docs/concepts/cli/run.md b/docs/concepts/cli/run.md new file mode 100644 index 0000000000..b21ff13cdf --- /dev/null +++ b/docs/concepts/cli/run.md @@ -0,0 +1,25 @@ +# ps-rule run + +Run rule analysis. + +## Optional parameters + +### `--baseline` + +The name of a specific baseline to use. + +### `--outcome` + +Allows filtering of results by outcome. +The supported values are: + +- `Pass` - Results for rules that passed. +- `Fail` - Results for rules that did not pass. +- `Error` - Results for rules that raised an error are returned. +- `Processed` - All results that were processed. + This aggregated outcome includes `Pass`, `Fail`, or `Error` results. +- `Problem` - Processed results that did not pass. + This aggregated outcome includes `Fail`, or `Error` results. + +To specify multiple values, specify the parameter multiple times. +For example: `--outcome Pass --Outcome Fail`. diff --git a/docs/faq.md b/docs/faq.md index 7ff52ccb03..5f72d83f66 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -186,7 +186,7 @@ To do this you have two options: 1. Exclude files from analysis — Configure the [Input.PathIgnore][9] option. 2. Disable the warning entirely — Set the [Execution.UnprocessedObject][10] option to `Ignore`. - [10]: concepts/PSRule/en-US/about_PSRule_Options/#executionunprocessedobject + [10]: concepts/PSRule/en-US/about_PSRule_Options.md#executionunprocessedobject ## How do I layer on custom rules on top of an existing module? diff --git a/docs/upgrade-notes.md b/docs/upgrade-notes.md index 53ea43ee89..ea225bc85f 100644 --- a/docs/upgrade-notes.md +++ b/docs/upgrade-notes.md @@ -42,6 +42,16 @@ We recommend upgrading to PowerShell 7.3 or later. At the time of writing, PowerShell 7.3 is the most recent version of PowerShell. PowerShell 7.4 is expected, and PSRule v4 will aim to support the latest version of PowerShell as the minimum requirement. +### Rename of analyze CLI command to run + +Previously in PSRule _v2.8.0_ to _v2.9.0, the CLI command used to run rules was `analyze`. +From _v3.0.0_, the CLI command has been renamed to `run`. +This change was made to simplify the CLI usage and reduce confusion that might occur between locales. + +For example: `ps-rule analyze` is now `ps-rule run`. + +The `run` command provides similar output to the `Assert-PSRule` cmdlet in PowerShell. + ## Upgrading to v2.0.0 ### Resources naming restrictions diff --git a/mkdocs.yml b/mkdocs.yml index 35dc5bb6d9..887e56219f 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -31,9 +31,12 @@ theme: - navigation.sections: level: 1 - navigation.tabs + - navigation.footer - content.code.annotate - content.tabs.link - content.code.copy + - content.action.edit + - content.action.view nav: - Home: index.md @@ -97,7 +100,11 @@ nav: - New-PSRuleOption: commands/PSRule/en-US/New-PSRuleOption.md - Set-PSRuleOption: commands/PSRule/en-US/Set-PSRuleOption.md - Test-PSRuleTarget: commands/PSRule/en-US/Test-PSRuleTarget.md - - CLI commands: concepts/cli.md + - CLI commands: + - Index: concepts/cli/index.md + - run: concepts/cli/run.md + - module: concepts/cli/module.md + - restore: concepts/cli/restore.md - Assertion methods: concepts/PSRule/en-US/about_PSRule_Assert.md - Baselines: concepts/PSRule/en-US/about_PSRule_Baseline.md - Badges: concepts/PSRule/en-US/about_PSRule_Badges.md @@ -133,18 +140,23 @@ markdown_extensions: - mdx_truly_sane_lists - pymdownx.tasklist - pymdownx.emoji: - emoji_index: !!python/name:materialx.emoji.twemoji - emoji_generator: !!python/name:materialx.emoji.to_svg + emoji_index: !!python/name:material.extensions.emoji.twemoji + emoji_generator: !!python/name:material.extensions.emoji.to_svg - toc: permalink: '#' separator: '-' plugins: + - git-revision-date-localized: + enable_creation_date: true + fallback_to_build_date: true + - git-committers: + repository: microsoft/PSRule + branch: main - mkdocs-simple-hooks: hooks: on_page_markdown: 'docs.hooks:replace_maml' - search - - git-revision-date - redirects: redirect_maps: 'authoring/writing-rules.md': 'authoring/testing-infrastructure.md' diff --git a/requirements-docs.txt b/requirements-docs.txt index 22c48353b6..4737650545 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -3,6 +3,7 @@ mkdocs-material==9.5.2 pymdown-extensions==10.5 mike==2.0.0 mkdocs-simple-hooks==0.1.5 -mkdocs-git-revision-date-plugin==0.3.2 +mkdocs-git-revision-date-localized-plugin==1.2.1 +mkdocs-git-committers-plugin-2==2.2.2 mdx-truly-sane-lists==1.3 mkdocs-redirects==1.2.1 diff --git a/src/PSRule.Tool/ClientBuilder.cs b/src/PSRule.Tool/ClientBuilder.cs index c97fcc8a31..01a60de625 100644 --- a/src/PSRule.Tool/ClientBuilder.cs +++ b/src/PSRule.Tool/ClientBuilder.cs @@ -10,7 +10,7 @@ namespace PSRule.Tool; internal sealed class ClientBuilder { - private static readonly string _Version = (Assembly.GetEntryAssembly() ?? Assembly.GetExecutingAssembly()).GetCustomAttribute().InformationalVersion; + private static readonly string? _Version = (Assembly.GetEntryAssembly() ?? Assembly.GetExecutingAssembly()).GetCustomAttribute()?.InformationalVersion; private readonly Option _Option; private readonly Option _Verbose; @@ -62,11 +62,11 @@ private ClientBuilder(RootCommand cmd) ); _Baseline = new Option( new string[] { "--baseline" }, - CmdStrings.Analyze_Baseline_Description + CmdStrings.Run_Baseline_Description ); _Outcome = new Option( new string[] { "--outcome" }, - description: CmdStrings.Analyze_Outcome_Description + description: CmdStrings.Run_Outcome_Description ).FromAmong("Pass", "Fail", "Error", "Processed", "Problem"); _Outcome.Arity = ArgumentArity.ZeroOrMore; @@ -102,15 +102,15 @@ public static Command New() Name = "ps-rule" }; var builder = new ClientBuilder(cmd); - builder.AddAnalyze(); + builder.AddRun(); builder.AddModule(); builder.AddRestore(); return builder.Command; } - private void AddAnalyze() + private void AddRun() { - var cmd = new Command("analyze", CmdStrings.Analyze_Description); + var cmd = new Command("run", CmdStrings.Run_Description); cmd.AddOption(_Path); cmd.AddOption(_OutputPath); cmd.AddOption(_OutputFormat); @@ -120,7 +120,7 @@ private void AddAnalyze() cmd.AddOption(_Outcome); cmd.SetHandler((invocation) => { - var option = new AnalyzerOptions + var option = new RunOptions { Path = invocation.ParseResult.GetValueForOption(_Path), InputPath = invocation.ParseResult.GetValueForOption(_InputPath), @@ -247,7 +247,7 @@ private void AddModule() /// /// Convert string arguments to flags of . /// - private static RuleOutcome? ParseOutcome(string[] s) + private static RuleOutcome? ParseOutcome(string[]? s) { var result = RuleOutcome.None; for (var i = 0; s != null && i < s.Length; i++) diff --git a/src/PSRule.Tool/ClientHelper.cs b/src/PSRule.Tool/ClientHelper.cs index 3b99d53648..77f3e51265 100644 --- a/src/PSRule.Tool/ClientHelper.cs +++ b/src/PSRule.Tool/ClientHelper.cs @@ -44,7 +44,7 @@ internal sealed class ClientHelper private const string FIELD_PSDATA = "PSData"; private const string PRERELEASE_SEPARATOR = "-"; - public static int RunAnalyze(AnalyzerOptions operationOptions, ClientContext clientContext, InvocationContext invocation) + public static int RunAnalyze(RunOptions operationOptions, ClientContext clientContext, InvocationContext invocation) { var exitCode = 0; var host = new ClientHost(invocation, operationOptions.Verbose, operationOptions.Debug); @@ -60,7 +60,7 @@ public static int RunAnalyze(AnalyzerOptions operationOptions, ClientContext cli option.Output.Outcome = operationOptions.Outcome; // Build command - var builder = CommandLineBuilder.Assert(operationOptions.Module, option, host, file); + var builder = CommandLineBuilder.Assert(operationOptions.Module ?? [], option, host, file); builder.Baseline(BaselineOption.FromString(operationOptions.Baseline)); builder.InputPath(inputPath); builder.UnblockPublisher(PUBLISHER); @@ -123,6 +123,8 @@ public static int RunRestore(RestoreOptions operationOptions, ClientContext clie public static int AddModule(ModuleOptions operationOptions, ClientContext clientContext, InvocationContext invocation) { var exitCode = 0; + if (operationOptions.Module == null || operationOptions.Module.Length == 0) return exitCode; + var host = new ClientHost(invocation, operationOptions.Verbose, operationOptions.Debug); var option = GetOption(host); var requires = option.Requires.ToDictionary(); @@ -142,7 +144,7 @@ public static int AddModule(ModuleOptions operationOptions, ClientContext client // Check if the target version is valid with the constraint if set. if (targetVersion != null && moduleConstraint != null && !moduleConstraint.Constraint.Equals(targetVersion)) { - invocation.LogError(Messages.Error_503, operationOptions.Version); + invocation.LogError(Messages.Error_503, operationOptions.Version!); return ERROR_MODULE_ADD_VIOLATES_CONSTRAINT; } @@ -180,6 +182,8 @@ public static int AddModule(ModuleOptions operationOptions, ClientContext client public static int RemoveModule(ModuleOptions operationOptions, ClientContext clientContext, InvocationContext invocation) { var exitCode = 0; + if (operationOptions.Module == null || operationOptions.Module.Length == 0) return exitCode; + var host = new ClientHost(invocation, operationOptions.Verbose, operationOptions.Debug); var file = LockFile.Read(null); @@ -237,7 +241,7 @@ public static int UpgradeModule(ModuleOptions operationOptions, ClientContext cl return exitCode; } - private static bool IsInstalled(PowerShell pwsh, string module, SemanticVersion.Version targetVersion, out SemanticVersion.Version installedVersion) + private static bool IsInstalled(PowerShell pwsh, string module, SemanticVersion.Version targetVersion, out SemanticVersion.Version? installedVersion) { pwsh.Commands.Clear(); pwsh.Streams.ClearStreams(); @@ -250,7 +254,9 @@ private static bool IsInstalled(PowerShell pwsh, string module, SemanticVersion. foreach (var version in versions) { if (TryModuleInfo(version, out var versionString) && + versionString != null && SemanticVersion.TryParseVersion(versionString, out var v) && + v != null && (targetVersion == null || targetVersion.Equals(v)) && v.CompareTo(installedVersion) > 0) installedVersion = v; @@ -258,20 +264,20 @@ private static bool IsInstalled(PowerShell pwsh, string module, SemanticVersion. return installedVersion != null; } - private static bool TryModuleInfo(PSObject value, out string version) + private static bool TryModuleInfo(PSObject value, out string? version) { version = null; if (value?.BaseObject is not PSModuleInfo info) return false; version = info.Version?.ToString(); - if (TryPrivateData(info, FIELD_PSDATA, out var psData) && psData.ContainsKey(FIELD_PRERELEASE)) - version = string.Concat(version, PRERELEASE_SEPARATOR, psData[FIELD_PRERELEASE].ToString()); + if (TryPrivateData(info, FIELD_PSDATA, out var psData) && psData != null && psData.ContainsKey(FIELD_PRERELEASE)) + version = string.Concat(version, PRERELEASE_SEPARATOR, psData[FIELD_PRERELEASE]?.ToString()); return version != null; } - private static bool TryPrivateData(PSModuleInfo info, string propertyName, out Hashtable value) + private static bool TryPrivateData(PSModuleInfo info, string propertyName, out Hashtable? value) { value = null; if (info.PrivateData is Hashtable privateData && privateData.ContainsKey(propertyName) && privateData[propertyName] is Hashtable data) @@ -282,7 +288,7 @@ private static bool TryPrivateData(PSModuleInfo info, string propertyName, out H return false; } - private static SemanticVersion.Version FindVersion(PowerShell pwsh, string module, ModuleConstraint constraint, SemanticVersion.Version targetVersion, SemanticVersion.Version installedVersion) + private static SemanticVersion.Version? FindVersion(PowerShell pwsh, string module, ModuleConstraint? constraint, SemanticVersion.Version? targetVersion, SemanticVersion.Version? installedVersion) { pwsh.Commands.Clear(); pwsh.Streams.ClearStreams(); @@ -291,11 +297,12 @@ private static SemanticVersion.Version FindVersion(PowerShell pwsh, string modul .AddParameter("AllVersions"); var versions = pwsh.Invoke(); - SemanticVersion.Version result = null; + SemanticVersion.Version? result = null; foreach (var version in versions) { if (version.Properties[PARAM_VERSION].Value is string versionString && SemanticVersion.TryParseVersion(versionString, out var v) && + v != null && (constraint == null || constraint.Constraint.Equals(v)) && (targetVersion == null || targetVersion.Equals(v)) && v.CompareTo(result) > 0 && diff --git a/src/PSRule.Tool/ModuleOptions.cs b/src/PSRule.Tool/ModuleOptions.cs index 4c823c7f7f..acc92bb290 100644 --- a/src/PSRule.Tool/ModuleOptions.cs +++ b/src/PSRule.Tool/ModuleOptions.cs @@ -5,19 +5,19 @@ namespace PSRule.Tool; internal sealed class ModuleOptions { - public string[] Path { get; set; } + public string[]? Path { get; set; } - public string Option { get; set; } + public string? Option { get; set; } public bool Verbose { get; set; } public bool Debug { get; set; } - public string[] Module { get; set; } + public string[]? Module { get; set; } public bool Force { get; set; } - public string Version { get; set; } + public string? Version { get; set; } public bool SkipVerification { get; set; } } diff --git a/src/PSRule.Tool/PSRule.Tool.csproj b/src/PSRule.Tool/PSRule.Tool.csproj index 38308aa267..4fd2e4b842 100644 --- a/src/PSRule.Tool/PSRule.Tool.csproj +++ b/src/PSRule.Tool/PSRule.Tool.csproj @@ -5,6 +5,9 @@ Microsoft.PSRule.Tool PSRule.Tool Exe + {bddbfdb8-614f-4b8a-930c-dcb60144598c} + 12.0 + enable net7.0 PSRule.Tool.Program true diff --git a/src/PSRule.Tool/Resources/CmdStrings.Designer.cs b/src/PSRule.Tool/Resources/CmdStrings.Designer.cs index 8638b85fda..73776aeabc 100644 --- a/src/PSRule.Tool/Resources/CmdStrings.Designer.cs +++ b/src/PSRule.Tool/Resources/CmdStrings.Designer.cs @@ -60,33 +60,6 @@ internal CmdStrings() { } } - /// - /// Looks up a localized string similar to The specific baseline to use.. - /// - internal static string Analyze_Baseline_Description { - get { - return ResourceManager.GetString("Analyze_Baseline_Description", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Run rule analysis.. - /// - internal static string Analyze_Description { - get { - return ResourceManager.GetString("Analyze_Description", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Specifies the rule results to show in output. By default, Pass/ Fail/ Error results are shown.. - /// - internal static string Analyze_Outcome_Description { - get { - return ResourceManager.GetString("Analyze_Outcome_Description", resourceCulture); - } - } - /// /// Looks up a localized string similar to PSRule CLI. /// @@ -230,5 +203,32 @@ internal static string Restore_Force_Description { return ResourceManager.GetString("Restore_Force_Description", resourceCulture); } } + + /// + /// Looks up a localized string similar to The name of a specific baseline to use.. + /// + internal static string Run_Baseline_Description { + get { + return ResourceManager.GetString("Run_Baseline_Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Run rule analysis.. + /// + internal static string Run_Description { + get { + return ResourceManager.GetString("Run_Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Specifies the rule results to show in output. By default, Pass/ Fail/ Error results are shown.. + /// + internal static string Run_Outcome_Description { + get { + return ResourceManager.GetString("Run_Outcome_Description", resourceCulture); + } + } } } diff --git a/src/PSRule.Tool/Resources/CmdStrings.resx b/src/PSRule.Tool/Resources/CmdStrings.resx index 479984214d..21c6ae6eb8 100644 --- a/src/PSRule.Tool/Resources/CmdStrings.resx +++ b/src/PSRule.Tool/Resources/CmdStrings.resx @@ -117,15 +117,6 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - The specific baseline to use. - - - Run rule analysis. - - - Specifies the rule results to show in output. By default, Pass/ Fail/ Error results are shown. - PSRule CLI @@ -174,4 +165,13 @@ Restore modules even when an existing version that meets constaints is already installed locally. + + The name of a specific baseline to use. + + + Run rule analysis. + + + Specifies the rule results to show in output. By default, Pass/ Fail/ Error results are shown. + \ No newline at end of file diff --git a/src/PSRule.Tool/RestoreOptions.cs b/src/PSRule.Tool/RestoreOptions.cs index f38d2c87ff..d06205849d 100644 --- a/src/PSRule.Tool/RestoreOptions.cs +++ b/src/PSRule.Tool/RestoreOptions.cs @@ -5,9 +5,9 @@ namespace PSRule.Tool; internal sealed class RestoreOptions { - public string[] Path { get; set; } + public string[]? Path { get; set; } - public string Option { get; set; } + public string? Option { get; set; } public bool Verbose { get; set; } diff --git a/src/PSRule.Tool/AnalyzerOptions.cs b/src/PSRule.Tool/RunOptions.cs similarity index 51% rename from src/PSRule.Tool/AnalyzerOptions.cs rename to src/PSRule.Tool/RunOptions.cs index ac361ba237..d4e84bab53 100644 --- a/src/PSRule.Tool/AnalyzerOptions.cs +++ b/src/PSRule.Tool/RunOptions.cs @@ -5,19 +5,19 @@ namespace PSRule.Tool; -internal sealed class AnalyzerOptions +internal sealed class RunOptions { - public string[] Path { get; set; } + public string[]? Path { get; set; } - public string[] Module { get; set; } + public string[]? Module { get; set; } - public string Option { get; set; } + public string? Option { get; set; } - public string Baseline { get; set; } + public string? Baseline { get; set; } public RuleOutcome? Outcome { get; set; } - public string[] InputPath { get; set; } + public string[]? InputPath { get; set; } public bool Verbose { get; set; } diff --git a/src/PSRule.Types/Data/SemanticVersion.cs b/src/PSRule.Types/Data/SemanticVersion.cs index ca8e71475a..3d4d921754 100644 --- a/src/PSRule.Types/Data/SemanticVersion.cs +++ b/src/PSRule.Types/Data/SemanticVersion.cs @@ -450,7 +450,7 @@ public bool Equals(int major, int minor, int patch, string? prerelease = null) /// /// Compare the version against another version. /// - public int CompareTo(Version other) + public int CompareTo(Version? other) { if (other == null) return 1; diff --git a/tests/PSRule.Tool.Tests/CommandTests.cs b/tests/PSRule.Tool.Tests/CommandTests.cs index aaffd77d51..a7f8003467 100644 --- a/tests/PSRule.Tool.Tests/CommandTests.cs +++ b/tests/PSRule.Tool.Tests/CommandTests.cs @@ -11,13 +11,13 @@ namespace PSRule.Tool; public sealed class CommandTests { [Fact] - public async Task Analyze() + public async Task Run() { var console = new TestConsole(); var builder = ClientBuilder.New(); - Assert.NotNull(builder.Subcommands.FirstOrDefault(c => c.Name == "analyze")); + Assert.NotNull(builder.Subcommands.FirstOrDefault(c => c.Name == "run")); - await builder.InvokeAsync("analyze", console); + await builder.InvokeAsync("run", console); var output = console.Out.ToString(); Assert.NotNull(output); From 796af43ceb29dc0411104aef7fe59e2aed6e0c81 Mon Sep 17 00:00:00 2001 From: Bernie White Date: Fri, 22 Dec 2023 00:22:38 +1000 Subject: [PATCH 153/177] Pre-release v3.0.0-B0122 (#1715) --- docs/CHANGELOG-v3.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/CHANGELOG-v3.md b/docs/CHANGELOG-v3.md index edcd727f28..bb63dd0f7d 100644 --- a/docs/CHANGELOG-v3.md +++ b/docs/CHANGELOG-v3.md @@ -27,6 +27,8 @@ See [upgrade notes][1] for helpful information when upgrading from previous vers ## Unreleased +## v3.0.0-B0122 (pre-release) + What's changed since pre-release v3.0.0-B0093: - General improvements: From b919090b3692daf8a8b8f3c14c85924d076907a7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 22 Dec 2023 18:41:31 +1000 Subject: [PATCH 154/177] Bump mkdocs-git-revision-date-localized-plugin from 1.2.1 to 1.2.2 (#1716) Bumps [mkdocs-git-revision-date-localized-plugin](https://github.com/timvink/mkdocs-git-revision-date-localized-plugin) from 1.2.1 to 1.2.2. - [Release notes](https://github.com/timvink/mkdocs-git-revision-date-localized-plugin/releases) - [Commits](https://github.com/timvink/mkdocs-git-revision-date-localized-plugin/compare/v1.2.1...v1.2.2) --- updated-dependencies: - dependency-name: mkdocs-git-revision-date-localized-plugin dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements-docs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-docs.txt b/requirements-docs.txt index 4737650545..953edb4290 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -3,7 +3,7 @@ mkdocs-material==9.5.2 pymdown-extensions==10.5 mike==2.0.0 mkdocs-simple-hooks==0.1.5 -mkdocs-git-revision-date-localized-plugin==1.2.1 +mkdocs-git-revision-date-localized-plugin==1.2.2 mkdocs-git-committers-plugin-2==2.2.2 mdx-truly-sane-lists==1.3 mkdocs-redirects==1.2.1 From c8d81fe679a52867f72eaed15b7502605ca7c7b8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 27 Dec 2023 20:07:37 +1000 Subject: [PATCH 155/177] Bump mkdocs-material from 9.5.2 to 9.5.3 (#1719) Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.5.2 to 9.5.3. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.5.2...9.5.3) --- updated-dependencies: - dependency-name: mkdocs-material dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements-docs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-docs.txt b/requirements-docs.txt index 953edb4290..149aafe554 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -1,5 +1,5 @@ mkdocs==1.5.3 -mkdocs-material==9.5.2 +mkdocs-material==9.5.3 pymdown-extensions==10.5 mike==2.0.0 mkdocs-simple-hooks==0.1.5 From 0a2a54542e343a7f9514f0f3a7f5566243bc3cf6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 30 Dec 2023 09:38:11 +1000 Subject: [PATCH 156/177] Bump pymdown-extensions from 10.5 to 10.6 (#1720) Bumps [pymdown-extensions](https://github.com/facelessuser/pymdown-extensions) from 10.5 to 10.6. - [Release notes](https://github.com/facelessuser/pymdown-extensions/releases) - [Commits](https://github.com/facelessuser/pymdown-extensions/compare/10.5...10.6) --- updated-dependencies: - dependency-name: pymdown-extensions dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements-docs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-docs.txt b/requirements-docs.txt index 149aafe554..90198b7c72 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -1,6 +1,6 @@ mkdocs==1.5.3 mkdocs-material==9.5.3 -pymdown-extensions==10.5 +pymdown-extensions==10.6 mike==2.0.0 mkdocs-simple-hooks==0.1.5 mkdocs-git-revision-date-localized-plugin==1.2.2 From 6ed349a5eccb434882ee3488c101fc3bb4ce691d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Jan 2024 09:53:26 +1000 Subject: [PATCH 157/177] Bump pymdown-extensions from 10.6 to 10.7 (#1721) Bumps [pymdown-extensions](https://github.com/facelessuser/pymdown-extensions) from 10.6 to 10.7. - [Release notes](https://github.com/facelessuser/pymdown-extensions/releases) - [Commits](https://github.com/facelessuser/pymdown-extensions/compare/10.6...10.7) --- updated-dependencies: - dependency-name: pymdown-extensions dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements-docs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-docs.txt b/requirements-docs.txt index 90198b7c72..d147878a14 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -1,6 +1,6 @@ mkdocs==1.5.3 mkdocs-material==9.5.3 -pymdown-extensions==10.6 +pymdown-extensions==10.7 mike==2.0.0 mkdocs-simple-hooks==0.1.5 mkdocs-git-revision-date-localized-plugin==1.2.2 From 65353378b9631b4424592c246056b5a2ac09938e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Jan 2024 13:11:03 +1000 Subject: [PATCH 158/177] Bump xunit from 2.6.3 to 2.6.4 (#1718) * Bump xunit from 2.6.3 to 2.6.4 Bumps [xunit](https://github.com/xunit/xunit) from 2.6.3 to 2.6.4. - [Commits](https://github.com/xunit/xunit/compare/2.6.3...2.6.4) --- updated-dependencies: - dependency-name: xunit dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * Bump change log --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Bernie White --- docs/CHANGELOG-v3.md | 6 ++++++ tests/PSRule.Tests/PSRule.Tests.csproj | 2 +- tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/CHANGELOG-v3.md b/docs/CHANGELOG-v3.md index bb63dd0f7d..8f0fea9bcb 100644 --- a/docs/CHANGELOG-v3.md +++ b/docs/CHANGELOG-v3.md @@ -27,6 +27,12 @@ See [upgrade notes][1] for helpful information when upgrading from previous vers ## Unreleased +What's changed since pre-release v3.0.0-B0122: + +- Engineering: + - Bump xunit to v2.6.4. + [#1718](https://github.com/microsoft/PSRule/pull/1718) + ## v3.0.0-B0122 (pre-release) What's changed since pre-release v3.0.0-B0093: diff --git a/tests/PSRule.Tests/PSRule.Tests.csproj b/tests/PSRule.Tests/PSRule.Tests.csproj index 8c8a8327b4..52206a8563 100644 --- a/tests/PSRule.Tests/PSRule.Tests.csproj +++ b/tests/PSRule.Tests/PSRule.Tests.csproj @@ -13,7 +13,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj b/tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj index 6b6f0714b3..cb10eb14cf 100644 --- a/tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj +++ b/tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj @@ -11,7 +11,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive From e4f8bbe9eb4eb17a1002bb107091ea3f33ed2939 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Jan 2024 13:30:12 +1000 Subject: [PATCH 159/177] Bump xunit.runner.visualstudio from 2.5.5 to 2.5.6 (#1717) * Bump xunit.runner.visualstudio from 2.5.5 to 2.5.6 Bumps [xunit.runner.visualstudio](https://github.com/xunit/visualstudio.xunit) from 2.5.5 to 2.5.6. - [Release notes](https://github.com/xunit/visualstudio.xunit/releases) - [Commits](https://github.com/xunit/visualstudio.xunit/compare/2.5.5...2.5.6) --- updated-dependencies: - dependency-name: xunit.runner.visualstudio dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * Bump change log --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Bernie White --- docs/CHANGELOG-v3.md | 2 ++ tests/PSRule.Tests/PSRule.Tests.csproj | 2 +- tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/CHANGELOG-v3.md b/docs/CHANGELOG-v3.md index 8f0fea9bcb..3ec4cdb147 100644 --- a/docs/CHANGELOG-v3.md +++ b/docs/CHANGELOG-v3.md @@ -32,6 +32,8 @@ What's changed since pre-release v3.0.0-B0122: - Engineering: - Bump xunit to v2.6.4. [#1718](https://github.com/microsoft/PSRule/pull/1718) + - Bump xunit.runner.visualstudio to v2.5.6. + [#1717](https://github.com/microsoft/PSRule/pull/1717) ## v3.0.0-B0122 (pre-release) diff --git a/tests/PSRule.Tests/PSRule.Tests.csproj b/tests/PSRule.Tests/PSRule.Tests.csproj index 52206a8563..0b5eaf82d5 100644 --- a/tests/PSRule.Tests/PSRule.Tests.csproj +++ b/tests/PSRule.Tests/PSRule.Tests.csproj @@ -14,7 +14,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj b/tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj index cb10eb14cf..bce617495c 100644 --- a/tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj +++ b/tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj @@ -12,7 +12,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive From ca2dc7b1c6916818f20c96eb0e504c5c50f1f1f7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Jan 2024 08:53:33 +1000 Subject: [PATCH 160/177] Bump mkdocs-git-committers-plugin-2 from 2.2.2 to 2.2.3 (#1726) Bumps [mkdocs-git-committers-plugin-2](https://github.com/ojacques/mkdocs-git-committers-plugin-2) from 2.2.2 to 2.2.3. - [Release notes](https://github.com/ojacques/mkdocs-git-committers-plugin-2/releases) - [Commits](https://github.com/ojacques/mkdocs-git-committers-plugin-2/compare/2.2.2...2.2.3) --- updated-dependencies: - dependency-name: mkdocs-git-committers-plugin-2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements-docs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-docs.txt b/requirements-docs.txt index d147878a14..2ae749840f 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -4,6 +4,6 @@ pymdown-extensions==10.7 mike==2.0.0 mkdocs-simple-hooks==0.1.5 mkdocs-git-revision-date-localized-plugin==1.2.2 -mkdocs-git-committers-plugin-2==2.2.2 +mkdocs-git-committers-plugin-2==2.2.3 mdx-truly-sane-lists==1.3 mkdocs-redirects==1.2.1 From c841736164769786d0ab1131aef9ccde77dbc9aa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 Jan 2024 12:28:51 +1000 Subject: [PATCH 161/177] Bump xunit from 2.6.4 to 2.6.5 (#1724) * Bump xunit from 2.6.4 to 2.6.5 Bumps [xunit](https://github.com/xunit/xunit) from 2.6.4 to 2.6.5. - [Commits](https://github.com/xunit/xunit/compare/2.6.4...2.6.5) --- updated-dependencies: - dependency-name: xunit dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * Bump change log --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Bernie White --- docs/CHANGELOG-v3.md | 4 ++-- tests/PSRule.Tests/PSRule.Tests.csproj | 2 +- tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/CHANGELOG-v3.md b/docs/CHANGELOG-v3.md index 3ec4cdb147..d6bad6b823 100644 --- a/docs/CHANGELOG-v3.md +++ b/docs/CHANGELOG-v3.md @@ -30,8 +30,8 @@ See [upgrade notes][1] for helpful information when upgrading from previous vers What's changed since pre-release v3.0.0-B0122: - Engineering: - - Bump xunit to v2.6.4. - [#1718](https://github.com/microsoft/PSRule/pull/1718) + - Bump xunit to v2.6.5. + [#1724](https://github.com/microsoft/PSRule/pull/1724) - Bump xunit.runner.visualstudio to v2.5.6. [#1717](https://github.com/microsoft/PSRule/pull/1717) diff --git a/tests/PSRule.Tests/PSRule.Tests.csproj b/tests/PSRule.Tests/PSRule.Tests.csproj index 0b5eaf82d5..e1efa943ec 100644 --- a/tests/PSRule.Tests/PSRule.Tests.csproj +++ b/tests/PSRule.Tests/PSRule.Tests.csproj @@ -13,7 +13,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj b/tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj index bce617495c..1180d45325 100644 --- a/tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj +++ b/tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj @@ -11,7 +11,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive From 479a365a88e6acb4c8e0e129f5b10a7f96c1b7b4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 Jan 2024 17:13:30 +1000 Subject: [PATCH 162/177] Bump BenchmarkDotNet from 0.13.11 to 0.13.12 (#1725) * Bump BenchmarkDotNet from 0.13.11 to 0.13.12 Bumps [BenchmarkDotNet](https://github.com/dotnet/BenchmarkDotNet) from 0.13.11 to 0.13.12. - [Release notes](https://github.com/dotnet/BenchmarkDotNet/releases) - [Commits](https://github.com/dotnet/BenchmarkDotNet/compare/v0.13.11...v0.13.12) --- updated-dependencies: - dependency-name: BenchmarkDotNet dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * Bump change log --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Bernie White --- docs/CHANGELOG-v3.md | 2 ++ src/PSRule.Benchmark/PSRule.Benchmark.csproj | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/CHANGELOG-v3.md b/docs/CHANGELOG-v3.md index d6bad6b823..1e52d230ab 100644 --- a/docs/CHANGELOG-v3.md +++ b/docs/CHANGELOG-v3.md @@ -30,6 +30,8 @@ See [upgrade notes][1] for helpful information when upgrading from previous vers What's changed since pre-release v3.0.0-B0122: - Engineering: + - Bump BenchmarkDotNet to v0.13.12. + [#1725](https://github.com/microsoft/PSRule/pull/1725) - Bump xunit to v2.6.5. [#1724](https://github.com/microsoft/PSRule/pull/1724) - Bump xunit.runner.visualstudio to v2.5.6. diff --git a/src/PSRule.Benchmark/PSRule.Benchmark.csproj b/src/PSRule.Benchmark/PSRule.Benchmark.csproj index 12f44176fc..2b99a651c3 100644 --- a/src/PSRule.Benchmark/PSRule.Benchmark.csproj +++ b/src/PSRule.Benchmark/PSRule.Benchmark.csproj @@ -15,7 +15,7 @@ - + From dcb5664be9be4f4636991fd37e4b7175156fea86 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 13 Jan 2024 13:26:44 +1000 Subject: [PATCH 163/177] Bump BenchmarkDotNet.Diagnostics.Windows from 0.13.11 to 0.13.12 (#1728) * Bump BenchmarkDotNet.Diagnostics.Windows from 0.13.11 to 0.13.12 Bumps [BenchmarkDotNet.Diagnostics.Windows](https://github.com/dotnet/BenchmarkDotNet) from 0.13.11 to 0.13.12. - [Release notes](https://github.com/dotnet/BenchmarkDotNet/releases) - [Commits](https://github.com/dotnet/BenchmarkDotNet/compare/v0.13.11...v0.13.12) --- updated-dependencies: - dependency-name: BenchmarkDotNet.Diagnostics.Windows dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * Bump change log --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Bernie White --- docs/CHANGELOG-v3.md | 2 ++ src/PSRule.Benchmark/PSRule.Benchmark.csproj | 2 +- src/PSRule.Benchmark/packages.lock.json | 18 +++++++++--------- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/docs/CHANGELOG-v3.md b/docs/CHANGELOG-v3.md index 1e52d230ab..fa3594f3d6 100644 --- a/docs/CHANGELOG-v3.md +++ b/docs/CHANGELOG-v3.md @@ -32,6 +32,8 @@ What's changed since pre-release v3.0.0-B0122: - Engineering: - Bump BenchmarkDotNet to v0.13.12. [#1725](https://github.com/microsoft/PSRule/pull/1725) + - Bump BenchmarkDotNet.Diagnostics.Windows to v0.13.12. + [#1728](https://github.com/microsoft/PSRule/pull/1728) - Bump xunit to v2.6.5. [#1724](https://github.com/microsoft/PSRule/pull/1724) - Bump xunit.runner.visualstudio to v2.5.6. diff --git a/src/PSRule.Benchmark/PSRule.Benchmark.csproj b/src/PSRule.Benchmark/PSRule.Benchmark.csproj index 2b99a651c3..4b45078bea 100644 --- a/src/PSRule.Benchmark/PSRule.Benchmark.csproj +++ b/src/PSRule.Benchmark/PSRule.Benchmark.csproj @@ -19,7 +19,7 @@ - + diff --git a/src/PSRule.Benchmark/packages.lock.json b/src/PSRule.Benchmark/packages.lock.json index bab1b611f3..625bf5ce0c 100644 --- a/src/PSRule.Benchmark/packages.lock.json +++ b/src/PSRule.Benchmark/packages.lock.json @@ -4,11 +4,11 @@ "net7.0": { "BenchmarkDotNet.Diagnostics.Windows": { "type": "Direct", - "requested": "[0.13.11, )", - "resolved": "0.13.11", - "contentHash": "L/x9mk8ERv0IC39xolg9NW4mY23AMqPlMClFYBuCW7lTNZxw62ASBuE/NxB6R0DfpGtICAQERdw2xqsa/rqg2g==", + "requested": "[0.13.12, )", + "resolved": "0.13.12", + "contentHash": "HdARbwcGFymgrSFoLVtuJaacyNW9WLuGljX0hsTarSO+80nI2OmbDS09R+I9kIeJ8Gn/1zJFjKgefmAlADhlww==", "dependencies": { - "BenchmarkDotNet": "0.13.11", + "BenchmarkDotNet": "0.13.12", "Microsoft.Diagnostics.Tracing.TraceEvent": "3.0.2" } }, @@ -91,10 +91,10 @@ }, "BenchmarkDotNet": { "type": "Transitive", - "resolved": "0.13.11", - "contentHash": "BSsrfesUFgrjy/MtlupiUrZKgcBCCKmFXmNaVQLrBCs0/2iE/qH1puN7lskTE9zM0+MdiCr09zKk6MXKmwmwxw==", + "resolved": "0.13.12", + "contentHash": "aKnzpUZJJfLBHG7zcfQZhCexZQKcJgElC8qcFUTXPMYFlVauJBobuOmtRnmrapqC2j7EjjZCsPxa3yLvFLx5/Q==", "dependencies": { - "BenchmarkDotNet.Annotations": "0.13.11", + "BenchmarkDotNet.Annotations": "0.13.12", "CommandLineParser": "2.9.1", "Gee.External.Capstone": "2.3.0", "Iced": "1.17.0", @@ -108,8 +108,8 @@ }, "BenchmarkDotNet.Annotations": { "type": "Transitive", - "resolved": "0.13.11", - "contentHash": "JOX+Bhp+PNnAtu/er9iGiN8QRIrYzilxUcJbwrF8VYyTNE8r4jlewa17/FbW1G7Jp2JUZwVS1A8a0bGILKhikw==" + "resolved": "0.13.12", + "contentHash": "4zmFOOJqW1GrEP/t5XKgh97LH9r6zixGy2IA0JAaoTNNnZ8kPBt9u/XagsGNyV0e7rglOpFcWc6wI5EjefKpKA==" }, "CommandLineParser": { "type": "Transitive", From 8d85b1f166fc70baf47ca99e40a8aab2ee04f3f5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 13 Jan 2024 15:41:30 +1000 Subject: [PATCH 164/177] Bump System.Drawing.Common from 8.0.0 to 8.0.1 (#1727) * Bump System.Drawing.Common from 8.0.0 to 8.0.1 Bumps [System.Drawing.Common](https://github.com/dotnet/winforms) from 8.0.0 to 8.0.1. - [Release notes](https://github.com/dotnet/winforms/releases) - [Changelog](https://github.com/dotnet/winforms/blob/main/docs/release-activity.md) - [Commits](https://github.com/dotnet/winforms/compare/v8.0.0...v8.0.1) --- updated-dependencies: - dependency-name: System.Drawing.Common dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * Bump change log --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Bernie White --- docs/CHANGELOG-v3.md | 2 ++ src/PSRule.BuildTool/PSRule.BuildTool.csproj | 2 +- src/PSRule.BuildTool/packages.lock.json | 6 +++--- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/CHANGELOG-v3.md b/docs/CHANGELOG-v3.md index fa3594f3d6..3721abb943 100644 --- a/docs/CHANGELOG-v3.md +++ b/docs/CHANGELOG-v3.md @@ -38,6 +38,8 @@ What's changed since pre-release v3.0.0-B0122: [#1724](https://github.com/microsoft/PSRule/pull/1724) - Bump xunit.runner.visualstudio to v2.5.6. [#1717](https://github.com/microsoft/PSRule/pull/1717) + - Bump System.Drawing.Common to v8.0.1. + [#1727](https://github.com/microsoft/PSRule/pull/1727) ## v3.0.0-B0122 (pre-release) diff --git a/src/PSRule.BuildTool/PSRule.BuildTool.csproj b/src/PSRule.BuildTool/PSRule.BuildTool.csproj index da2ed23880..c2c10f0dcd 100644 --- a/src/PSRule.BuildTool/PSRule.BuildTool.csproj +++ b/src/PSRule.BuildTool/PSRule.BuildTool.csproj @@ -11,7 +11,7 @@ - + diff --git a/src/PSRule.BuildTool/packages.lock.json b/src/PSRule.BuildTool/packages.lock.json index 7e9468afbf..c692a3946f 100644 --- a/src/PSRule.BuildTool/packages.lock.json +++ b/src/PSRule.BuildTool/packages.lock.json @@ -30,9 +30,9 @@ }, "System.Drawing.Common": { "type": "Direct", - "requested": "[8.0.0, )", - "resolved": "8.0.0", - "contentHash": "JkbHJjtI/dWc5dfmEdJlbe3VwgZqCkZRtfuWFh5GOv0f+gGCfBtzMpIVkmdkj2AObO9y+oiOi81UGwH3aBYuqA==", + "requested": "[8.0.1, )", + "resolved": "8.0.1", + "contentHash": "x0rAZECxIGx/YVjN28YRdpqka0+H7YMN9741FUDzipXPDzesd60gef/LI0ZCOcYSDsacTLTHvMAvxHG+TjbNNQ==", "dependencies": { "Microsoft.Win32.SystemEvents": "8.0.0" } From 1ccaa73a51e76d3442a925f767fb50b392fd9315 Mon Sep 17 00:00:00 2001 From: Bernie White Date: Mon, 15 Jan 2024 09:33:57 +1000 Subject: [PATCH 165/177] Added CLI commands to list and report status of locked modules #1729 (#1731) * Added CLI commands to list and report status of locked modules #1729 * Update tests --- .markdownlint.json | 132 ++--- .vscode/settings.json | 2 + docs/CHANGELOG-v3.md | 9 + docs/concepts/cli/index.md | 39 +- docs/concepts/cli/module.md | 59 ++- docs/concepts/cli/restore.md | 3 - docs/concepts/lockfile.md | 2 - docs/{install-instructions.md => install.md} | 3 +- mkdocs.yml | 2 +- ps-rule.lock.json | 1 + schemas/PSRule-lock.schema.json | 13 +- src/PSRule.Tool/ClientBuilder.cs | 165 +++++-- src/PSRule.Tool/ClientContext.cs | 30 +- src/PSRule.Tool/ClientContextExtensions.cs | 31 ++ src/PSRule.Tool/ClientHelper.cs | 340 ------------- src/PSRule.Tool/ClientHost.cs | 19 +- src/PSRule.Tool/Commands/ModuleCommand.cs | 460 ++++++++++++++++++ src/PSRule.Tool/Commands/RunCommand.cs | 58 +++ src/PSRule.Tool/InvocationExtensions.cs | 28 -- src/PSRule.Tool/{ => Models}/ModuleOptions.cs | 8 +- src/PSRule.Tool/Models/ModuleRecord.cs | 13 + .../{ => Models}/RestoreOptions.cs | 8 +- src/PSRule.Tool/{ => Models}/RunOptions.cs | 8 +- .../Resources/CmdStrings.Designer.cs | 65 ++- src/PSRule.Tool/Resources/CmdStrings.resx | 19 +- .../Resources/Messages.Designer.cs | 8 +- src/PSRule.Tool/Resources/Messages.resx | 6 +- src/PSRule/Pipeline/Dependencies/LockFile.cs | 9 +- tests/PSRule.Tool.Tests/CommandTests.cs | 9 +- 29 files changed, 985 insertions(+), 564 deletions(-) delete mode 100644 docs/concepts/cli/restore.md rename docs/{install-instructions.md => install.md} (99%) create mode 100644 src/PSRule.Tool/ClientContextExtensions.cs delete mode 100644 src/PSRule.Tool/ClientHelper.cs create mode 100644 src/PSRule.Tool/Commands/ModuleCommand.cs create mode 100644 src/PSRule.Tool/Commands/RunCommand.cs delete mode 100644 src/PSRule.Tool/InvocationExtensions.cs rename src/PSRule.Tool/{ => Models}/ModuleOptions.cs (70%) create mode 100644 src/PSRule.Tool/Models/ModuleRecord.cs rename src/PSRule.Tool/{ => Models}/RestoreOptions.cs (58%) rename src/PSRule.Tool/{ => Models}/RunOptions.cs (71%) diff --git a/.markdownlint.json b/.markdownlint.json index e38419f104..070b1116de 100644 --- a/.markdownlint.json +++ b/.markdownlint.json @@ -1,68 +1,68 @@ { - "default": true, - "header-increment": true, - "first-header-h1": { - "level": 1 - }, - "header-style": { - "style": "atx" - }, - "ul-style": { - "style": "dash" - }, - "list-indent": true, - "ul-start-left": true, - "ul-indent": { - "indent": 2 - }, - "no-trailing-spaces": true, - "no-hard-tabs": true, - "no-reversed-links": true, - "no-multiple-blanks": true, - "line-length": { - "line_length": 120, - "code_blocks": false, - "tables": false, - "headers": true - }, - "commands-show-output": true, - "no-missing-space-atx": true, - "no-multiple-space-atx": true, - "no-missing-space-closed-atx": true, - "no-multiple-space-closed-atx": true, - "blanks-around-headers": true, - "header-start-left": true, - "no-duplicate-header": true, - "single-h1": true, - "no-trailing-punctuation": { - "punctuation": ".,;:!" - }, - "no-multiple-space-blockquote": true, - "no-blanks-blockquote": true, - "ol-prefix": { - "style": "one_or_ordered" - }, - "list-marker-space": true, - "blanks-around-fences": true, - "blanks-around-lists": true, - "no-bare-urls": true, - "hr-style": { - "style": "---" - }, - "no-emphasis-as-header": true, - "no-space-in-emphasis": true, - "no-space-in-code": true, - "no-space-in-links": true, - "fenced-code-language": true, - "first-line-h1": false, - "no-empty-links": true, - "proper-names": { - "names": [ - "PowerShell", - "JavaScript" - ], - "code_blocks": false - }, - "no-alt-text": true, - "code-block-style": false + "default": true, + "header-increment": true, + "first-header-h1": { + "level": 1 + }, + "header-style": { + "style": "atx" + }, + "ul-style": { + "style": "dash" + }, + "list-indent": true, + "ul-start-left": true, + "ul-indent": { + "indent": 2 + }, + "no-trailing-spaces": true, + "no-hard-tabs": true, + "no-reversed-links": true, + "no-multiple-blanks": true, + "line-length": { + "line_length": 120, + "code_blocks": false, + "tables": false, + "headers": true + }, + "commands-show-output": true, + "no-missing-space-atx": true, + "no-multiple-space-atx": true, + "no-missing-space-closed-atx": true, + "no-multiple-space-closed-atx": true, + "blanks-around-headers": true, + "header-start-left": true, + "no-duplicate-header": true, + "single-h1": true, + "no-trailing-punctuation": { + "punctuation": ".,;:!" + }, + "no-multiple-space-blockquote": true, + "no-blanks-blockquote": true, + "ol-prefix": { + "style": "one_or_ordered" + }, + "list-marker-space": true, + "blanks-around-fences": true, + "blanks-around-lists": true, + "no-bare-urls": true, + "hr-style": { + "style": "---" + }, + "no-emphasis-as-header": true, + "no-space-in-emphasis": true, + "no-space-in-code": true, + "no-space-in-links": true, + "fenced-code-language": true, + "first-line-h1": false, + "no-empty-links": true, + "proper-names": { + "names": [ + "PowerShell", + "JavaScript" + ], + "code_blocks": false + }, + "no-alt-text": true, + "code-block-style": false } diff --git a/.vscode/settings.json b/.vscode/settings.json index a2be4ed9ff..954682c10d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -83,6 +83,7 @@ }, "cSpell.words": [ "APPSERVICEMININSTANCECOUNT", + "Arity", "CLIXML", "cmdlet", "cmdlets", @@ -98,6 +99,7 @@ "Newtonsoft", "NOTCOUNT", "proxied", + "PSDATA", "PSRULE", "pwsh", "quickstart", diff --git a/docs/CHANGELOG-v3.md b/docs/CHANGELOG-v3.md index 3721abb943..ca55e9f6bf 100644 --- a/docs/CHANGELOG-v3.md +++ b/docs/CHANGELOG-v3.md @@ -29,6 +29,15 @@ See [upgrade notes][1] for helpful information when upgrading from previous vers What's changed since pre-release v3.0.0-B0122: +- General improvements: + - **Breaking change:** Moved the `restore` command to a sub-command of `module` by @BernieWhite. + [#1730](https://github.com/microsoft/PSRule/issues/1730) + - The functionality of the `restore` command is now available as `module restore`. + - Added CLI commands to list and report status of locked modules by @BernieWhite. + [#1729](https://github.com/microsoft/PSRule/issues/1729) + - Added `module init` sub-command to initialize the lock file from configured options. + - Added `module list` sub-command to list locked and unlocked modules associated with the workspace. + - Added `version` property to the lock file schema to support versioning of the lock file. - Engineering: - Bump BenchmarkDotNet to v0.13.12. [#1725](https://github.com/microsoft/PSRule/pull/1725) diff --git a/docs/concepts/cli/index.md b/docs/concepts/cli/index.md index 95b94e54cc..f0ff5093c2 100644 --- a/docs/concepts/cli/index.md +++ b/docs/concepts/cli/index.md @@ -10,4 +10,41 @@ The following commands are available in the PSRule CLI: - [run](./run.md) - [module](./module.md) -- [restore](./restore.md) + +## Global options + +The following options are available in the PSRule CLI: + +### `--option` + +Specifies the path to an options file. +By default, the CLI will look for a file named `ps-rule.yaml` in the current directory. + +### `--version` + +Show the version information for the PSRule CLI. + +For example: + +```bash +ps-rule --version +``` + +### `--help` + +Display help and usage information for the PSRule CLI and commands. +To display help for a specific command, use `--help` with the command name. + +For example: + +```bash +ps-rule run --help +``` + +### `--verbose` + +Display verbose output for the selected command. + +### `--debug` + +Display debug output for the selected command. diff --git a/docs/concepts/cli/module.md b/docs/concepts/cli/module.md index a493606b81..279369ec4a 100644 --- a/docs/concepts/cli/module.md +++ b/docs/concepts/cli/module.md @@ -1,14 +1,69 @@ - # ps-rule module +Use this command to configure the module lock file (`ps-rule.lock.json`). +The module lock file, provides consistent module versions across multiple machines and environments. + +!!! Tip + The module lock file, provides consistent module versions across multiple machines and environments. + For more information, see [Lock file](../lockfile.md). + +The following subcommands are available: + +- [module init](#module-init) +- [module list](#module-list) +- [module add](#module-add) +- [module remove](#module-remove) +- [module restore](#module-restore) +- [module upgrade](#module-upgrade) + +## `module init` + +Initialize a new lock file based on existing options. +Using this command, the module lock file is created or updated. + +If `ps-rule.yaml` option are configured to included module (`Include.Modules`), these a automatically added to the lock file. +Any required version constraints set by the `Requires` option are taken into consideration. + +Optional parameters: + +- `--force` - Force the creation of a new lock file, even if one already exists. + +## `module list` + +List any module and the installed versions from the lock file. + ## `module add` -Add one or more modules to the lock file. +Add one or more modules to the module lock file. +If the lock file does not exist, it is created. + +By default, the latest stable version of the module is added. +Any required version constraints set by the `Requires` option are taken into consideration. + +To use a specific module version, use the `--version` argument. + +Optional parameters: + +- `--version` - Specifies a specific version of the module to add. + By default, the latest stable version of the module is added. + Any required version constraints set by the `Requires` option are taken into consideration. ## `module remove` Remove one or more modules from the lock file. +## `module restore` + +Restore modules from the module lock file (`ps-rule.lock.json`) and configured options. + +Optional parameters: + +- `--force` - Restore modules even when an existing version that meets constraints is already installed locally. + ## `module upgrade` Upgrade to the latest versions any modules within the lock file. + +## Next steps + +For more information on the module lock file, see [Lock file](../lockfile.md). diff --git a/docs/concepts/cli/restore.md b/docs/concepts/cli/restore.md deleted file mode 100644 index 08eb57732c..0000000000 --- a/docs/concepts/cli/restore.md +++ /dev/null @@ -1,3 +0,0 @@ -# ps-rule restore - -Restore modules defined in configuration locally. diff --git a/docs/concepts/lockfile.md b/docs/concepts/lockfile.md index bb36e34565..13719b5fa9 100644 --- a/docs/concepts/lockfile.md +++ b/docs/concepts/lockfile.md @@ -4,8 +4,6 @@ PSRule v3 and later uses a lock file to define the modules and versions used to run analysis. This article describes the lock file and how to manage it. -## Overview - An optional lock file can be used to define the modules and versions used to run analysis. Using the lock file ensures that the same modules and versions are used across multiple machines, improving consistency. diff --git a/docs/install-instructions.md b/docs/install.md similarity index 99% rename from docs/install-instructions.md rename to docs/install.md index 9e2653a06a..329e9907dd 100644 --- a/docs/install-instructions.md +++ b/docs/install.md @@ -1,9 +1,8 @@ --- -title: Instructions for installing PSRule author: BernieWhite --- -# Installation +# Install PSRule tools PSRule supports running within continuous integration (CI) systems or locally. It is shipped as a PowerShell module which makes it easy to install and distribute updates. diff --git a/mkdocs.yml b/mkdocs.yml index 887e56219f..eb160a2dbb 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -104,7 +104,6 @@ nav: - Index: concepts/cli/index.md - run: concepts/cli/run.md - module: concepts/cli/module.md - - restore: concepts/cli/restore.md - Assertion methods: concepts/PSRule/en-US/about_PSRule_Assert.md - Baselines: concepts/PSRule/en-US/about_PSRule_Baseline.md - Badges: concepts/PSRule/en-US/about_PSRule_Badges.md @@ -160,6 +159,7 @@ plugins: - redirects: redirect_maps: 'authoring/writing-rules.md': 'authoring/testing-infrastructure.md' + 'install-instructions.md': 'install.md' extra: version: diff --git a/ps-rule.lock.json b/ps-rule.lock.json index d95177725c..b568d69609 100644 --- a/ps-rule.lock.json +++ b/ps-rule.lock.json @@ -1,4 +1,5 @@ { + "version": 1, "modules": { "PSRule.Rules.MSFT.OSS": { "version": "1.1.0" diff --git a/schemas/PSRule-lock.schema.json b/schemas/PSRule-lock.schema.json index 4799ed66fc..543b7f430a 100644 --- a/schemas/PSRule-lock.schema.json +++ b/schemas/PSRule-lock.schema.json @@ -4,15 +4,25 @@ "title": "PSRule lock", "description": "A schema for the PSRule lock file.", "properties": { + "version": { + "type": "integer", + "title": "Version", + "description": "The version of the lock file schema.", + "enum": [ + 1 + ] + }, "modules": { "type": "object", "title": "Modules", + "description": "A list of specific module versions for PSRule to restore.", "additionalProperties": { "type": "object", "properties": { "version": { "type": "string", - "title": "Version" + "title": "Module version", + "description": "The version of the module to use." } }, "required": [ @@ -23,6 +33,7 @@ } }, "required": [ + "version", "modules" ], "additionalProperties": false diff --git a/src/PSRule.Tool/ClientBuilder.cs b/src/PSRule.Tool/ClientBuilder.cs index 01a60de625..ebe41dd5d8 100644 --- a/src/PSRule.Tool/ClientBuilder.cs +++ b/src/PSRule.Tool/ClientBuilder.cs @@ -2,20 +2,29 @@ // Licensed under the MIT License. using System.CommandLine; +using System.CommandLine.Invocation; using System.Reflection; using PSRule.Rules; +using PSRule.Tool.Commands; +using PSRule.Tool.Models; using PSRule.Tool.Resources; namespace PSRule.Tool; +/// +/// A helper to build the command-line commands and options offered to the caller. +/// internal sealed class ClientBuilder { + private const string ARG_FORCE = "--force"; + private static readonly string? _Version = (Assembly.GetEntryAssembly() ?? Assembly.GetExecutingAssembly()).GetCustomAttribute()?.InformationalVersion; private readonly Option _Option; private readonly Option _Verbose; private readonly Option _Debug; - private readonly Option _RestoreForce; + private readonly Option _ModuleRestoreForce; + private readonly Option _ModuleInitForce; private readonly Option _ModuleAddVersion; private readonly Option _ModuleAddForce; private readonly Option _ModuleAddSkipVerification; @@ -70,9 +79,10 @@ private ClientBuilder(RootCommand cmd) ).FromAmong("Pass", "Fail", "Error", "Processed", "Problem"); _Outcome.Arity = ArgumentArity.ZeroOrMore; - _RestoreForce = new Option( - new string[] { "--force" }, - CmdStrings.Restore_Force_Description + // Options for the module command. + _ModuleInitForce = new Option( + new string[] { ARG_FORCE }, + CmdStrings.Module_Init_Force_Description ); _ModuleAddVersion = new Option ( @@ -80,13 +90,17 @@ private ClientBuilder(RootCommand cmd) CmdStrings.Module_Add_Version_Description ); _ModuleAddForce = new Option( - new string[] { "--force" }, + new string[] { ARG_FORCE }, CmdStrings.Module_Add_Force_Description ); _ModuleAddSkipVerification = new Option( new string[] { "--skip-verification" }, CmdStrings.Module_Add_SkipVerification_Description ); + _ModuleRestoreForce = new Option( + new string[] { ARG_FORCE }, + CmdStrings.Module_Restore_Force_Description + ); cmd.AddGlobalOption(_Option); cmd.AddGlobalOption(_Verbose); @@ -104,10 +118,12 @@ public static Command New() var builder = new ClientBuilder(cmd); builder.AddRun(); builder.AddModule(); - builder.AddRestore(); return builder.Command; } + /// + /// Add the run command. + /// private void AddRun() { var cmd = new Command("run", CmdStrings.Run_Description); @@ -125,39 +141,18 @@ private void AddRun() Path = invocation.ParseResult.GetValueForOption(_Path), InputPath = invocation.ParseResult.GetValueForOption(_InputPath), Module = invocation.ParseResult.GetValueForOption(_Module), - Option = invocation.ParseResult.GetValueForOption(_Option), Baseline = invocation.ParseResult.GetValueForOption(_Baseline), Outcome = ParseOutcome(invocation.ParseResult.GetValueForOption(_Outcome)), - Verbose = invocation.ParseResult.GetValueForOption(_Verbose), - Debug = invocation.ParseResult.GetValueForOption(_Debug), - }; - var client = new ClientContext(); - invocation.ExitCode = ClientHelper.RunAnalyze(option, client, invocation); - }); - Command.AddCommand(cmd); - } - - private void AddRestore() - { - var cmd = new Command("restore", CmdStrings.Restore_Description); - cmd.AddOption(_Path); - cmd.AddOption(_RestoreForce); - cmd.SetHandler((invocation) => - { - var option = new RestoreOptions - { - Path = invocation.ParseResult.GetValueForOption(_Path), - Option = invocation.ParseResult.GetValueForOption(_Option), - Verbose = invocation.ParseResult.GetValueForOption(_Verbose), - Debug = invocation.ParseResult.GetValueForOption(_Debug), - Force = invocation.ParseResult.GetValueForOption(_RestoreForce), }; - var client = new ClientContext(); - invocation.ExitCode = ClientHelper.RunRestore(option, client, invocation); + var client = GetClientContext(invocation); + invocation.ExitCode = RunCommand.Run(option, client); }); Command.AddCommand(cmd); } + /// + /// Add the module command. + /// private void AddModule() { var cmd = new Command("module", CmdStrings.Module_Description); @@ -169,8 +164,50 @@ private void AddModule() ); moduleArg.Arity = ArgumentArity.OneOrMore; + // Init + var init = new Command + ( + "init", + CmdStrings.Module_Init_Description + ); + init.AddOption(_ModuleInitForce); + init.SetHandler((invocation) => + { + var option = new ModuleOptions + { + Path = invocation.ParseResult.GetValueForOption(_Path), + Version = invocation.ParseResult.GetValueForOption(_ModuleAddVersion), + Force = invocation.ParseResult.GetValueForOption(_ModuleAddForce), + SkipVerification = invocation.ParseResult.GetValueForOption(_ModuleAddSkipVerification), + }; + + var client = GetClientContext(invocation); + invocation.ExitCode = ModuleCommand.ModuleInit(option, client); + }); + + // List + var list = new Command + ( + "list", + CmdStrings.Module_List_Description + ); + list.SetHandler((invocation) => + { + var option = new ModuleOptions + { + Path = invocation.ParseResult.GetValueForOption(_Path), + Version = invocation.ParseResult.GetValueForOption(_ModuleAddVersion), + Force = invocation.ParseResult.GetValueForOption(_ModuleAddForce), + SkipVerification = invocation.ParseResult.GetValueForOption(_ModuleAddSkipVerification), + }; + + var client = GetClientContext(invocation); + invocation.ExitCode = ModuleCommand.ModuleList(option, client); + }); + // Add - var add = new Command( + var add = new Command + ( "add", CmdStrings.Module_Add_Description ); @@ -183,21 +220,19 @@ private void AddModule() var option = new ModuleOptions { Path = invocation.ParseResult.GetValueForOption(_Path), - Option = invocation.ParseResult.GetValueForOption(_Option), - Verbose = invocation.ParseResult.GetValueForOption(_Verbose), - Debug = invocation.ParseResult.GetValueForOption(_Debug), Module = invocation.ParseResult.GetValueForArgument(moduleArg), Version = invocation.ParseResult.GetValueForOption(_ModuleAddVersion), Force = invocation.ParseResult.GetValueForOption(_ModuleAddForce), SkipVerification = invocation.ParseResult.GetValueForOption(_ModuleAddSkipVerification), }; - var client = new ClientContext(); - invocation.ExitCode = ClientHelper.AddModule(option, client, invocation); + var client = GetClientContext(invocation); + invocation.ExitCode = ModuleCommand.ModuleAdd(option, client); }); // Remove - var remove = new Command( + var remove = new Command + ( "remove", CmdStrings.Module_Remove_Description ); @@ -207,18 +242,16 @@ private void AddModule() var option = new ModuleOptions { Path = invocation.ParseResult.GetValueForOption(_Path), - Option = invocation.ParseResult.GetValueForOption(_Option), - Verbose = invocation.ParseResult.GetValueForOption(_Verbose), - Debug = invocation.ParseResult.GetValueForOption(_Debug), - Module = invocation.ParseResult.GetValueForArgument(moduleArg) + Module = invocation.ParseResult.GetValueForArgument(moduleArg), }; - var client = new ClientContext(); - invocation.ExitCode = ClientHelper.RemoveModule(option, client, invocation); + var client = GetClientContext(invocation); + invocation.ExitCode = ModuleCommand.ModuleRemove(option, client); }); // Upgrade - var upgrade = new Command( + var upgrade = new Command + ( "upgrade", CmdStrings.Module_Upgrade_Description ); @@ -227,23 +260,53 @@ private void AddModule() var option = new ModuleOptions { Path = invocation.ParseResult.GetValueForOption(_Path), - Option = invocation.ParseResult.GetValueForOption(_Option), - Verbose = invocation.ParseResult.GetValueForOption(_Verbose), - Debug = invocation.ParseResult.GetValueForOption(_Debug) }; - var client = new ClientContext(); - invocation.ExitCode = ClientHelper.UpgradeModule(option, client, invocation); + var client = GetClientContext(invocation); + invocation.ExitCode = ModuleCommand.ModuleUpgrade(option, client); }); + // Restore + var restore = new Command("restore", CmdStrings.Module_Restore_Description); + // restore.AddOption(_Path); + restore.AddOption(_ModuleRestoreForce); + restore.SetHandler((invocation) => + { + var option = new RestoreOptions + { + Path = invocation.ParseResult.GetValueForOption(_Path), + Force = invocation.ParseResult.GetValueForOption(_ModuleRestoreForce), + }; + var client = GetClientContext(invocation); + invocation.ExitCode = ModuleCommand.ModuleRestore(option, client); + }); + + cmd.AddCommand(init); + cmd.AddCommand(list); cmd.AddCommand(add); cmd.AddCommand(remove); cmd.AddCommand(upgrade); + cmd.AddCommand(restore); cmd.AddOption(_Path); Command.AddCommand(cmd); } + private ClientContext GetClientContext(InvocationContext invocation) + { + var option = invocation.ParseResult.GetValueForOption(_Option); + var verbose = invocation.ParseResult.GetValueForOption(_Verbose); + var debug = invocation.ParseResult.GetValueForOption(_Debug); + + return new ClientContext + ( + invocation: invocation, + option: option, + verbose: verbose, + debug: debug + ); + } + /// /// Convert string arguments to flags of . /// diff --git a/src/PSRule.Tool/ClientContext.cs b/src/PSRule.Tool/ClientContext.cs index 0254f742e9..489b8dc777 100644 --- a/src/PSRule.Tool/ClientContext.cs +++ b/src/PSRule.Tool/ClientContext.cs @@ -1,14 +1,42 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System.CommandLine.Invocation; +using PSRule.Configuration; + namespace PSRule.Tool; internal sealed class ClientContext { - public ClientContext() + public ClientContext(InvocationContext invocation, string? option, bool verbose, bool debug) { Path = AppDomain.CurrentDomain.BaseDirectory; + Invocation = invocation ?? throw new ArgumentNullException(nameof(invocation)); + Verbose = verbose; + Debug = debug; + Host = new ClientHost(this, verbose, debug); + Option = GetOption(Host, option); } public string Path { get; } + + public InvocationContext Invocation { get; } + + public ClientHost Host { get; } + + public PSRuleOption Option { get; } + + public bool Verbose { get; } + + public bool Debug { get; } + + private static PSRuleOption GetOption(ClientHost host, string? path) + { + PSRuleOption.UseHostContext(host); + var option = PSRuleOption.FromFileOrEmpty(path); + option.Execution.InitialSessionState = Options.SessionState.Minimal; + option.Input.Format = InputFormat.File; + option.Output.Style ??= OutputStyle.Client; + return option; + } } diff --git a/src/PSRule.Tool/ClientContextExtensions.cs b/src/PSRule.Tool/ClientContextExtensions.cs new file mode 100644 index 0000000000..835337a3ad --- /dev/null +++ b/src/PSRule.Tool/ClientContextExtensions.cs @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.CommandLine; +using System.CommandLine.IO; + +namespace PSRule.Tool; + +internal static class ClientContextExtensions +{ + private const string LOG_VERBOSE = "VERBOSE: "; + private const string LOG_ERROR = "ERROR: "; + + public static void LogVerbose(this ClientContext context, string message, params object[] args) + { + if (context == null || string.IsNullOrEmpty(message) || !context.Verbose) + return; + + var s = args != null && args.Length > 0 ? string.Format(Thread.CurrentThread.CurrentCulture, message, args) : message; + context.Invocation.Console.WriteLine(string.Concat(LOG_VERBOSE, s)); + } + + public static void LogError(this ClientContext context, string message, params object[] args) + { + if (context == null || string.IsNullOrEmpty(message)) + return; + + var s = args != null && args.Length > 0 ? string.Format(Thread.CurrentThread.CurrentCulture, message, args) : message; + context.Invocation.Console.Error.WriteLine(string.Concat(LOG_ERROR, s)); + } +} diff --git a/src/PSRule.Tool/ClientHelper.cs b/src/PSRule.Tool/ClientHelper.cs deleted file mode 100644 index 77f3e51265..0000000000 --- a/src/PSRule.Tool/ClientHelper.cs +++ /dev/null @@ -1,340 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Collections; -using System.CommandLine.Invocation; -using System.Management.Automation; -using PSRule.Configuration; -using PSRule.Data; -using PSRule.Pipeline; -using PSRule.Pipeline.Dependencies; -using PSRule.Tool.Resources; -using SemanticVersion = PSRule.Data.SemanticVersion; - -namespace PSRule.Tool; - -internal sealed class ClientHelper -{ - private const string PUBLISHER = "CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US"; - - /// - /// A generic error. - /// - private const int ERROR_GENERIC = 1; - - /// - /// Failed to install a module. - /// - private const int ERROR_MODUILE_FAILEDTOINSTALL = 501; - - private const int ERROR_MODULE_FAILEDTOFIND = 502; - - private const int ERROR_MODULE_ADD_VIOLATES_CONSTRAINT = 503; - - - /// - /// One or more failures occurred. - /// - private const int ERROR_BREAK_ON_FAILURE = 100; - - private const string PARAM_NAME = "Name"; - private const string PARAM_VERSION = "Version"; - - private const string FIELD_PRERELEASE = "Prerelease"; - private const string FIELD_PSDATA = "PSData"; - private const string PRERELEASE_SEPARATOR = "-"; - - public static int RunAnalyze(RunOptions operationOptions, ClientContext clientContext, InvocationContext invocation) - { - var exitCode = 0; - var host = new ClientHost(invocation, operationOptions.Verbose, operationOptions.Debug); - var option = GetOption(host); - var file = LockFile.Read(null); - var inputPath = operationOptions.InputPath == null || operationOptions.InputPath.Length == 0 ? - new string[] { Environment.GetWorkingPath() } : operationOptions.InputPath; - - if (operationOptions.Path != null) - option.Include.Path = operationOptions.Path; - - if (operationOptions.Outcome != null && operationOptions.Outcome.Value != Rules.RuleOutcome.None) - option.Output.Outcome = operationOptions.Outcome; - - // Build command - var builder = CommandLineBuilder.Assert(operationOptions.Module ?? [], option, host, file); - builder.Baseline(BaselineOption.FromString(operationOptions.Baseline)); - builder.InputPath(inputPath); - builder.UnblockPublisher(PUBLISHER); - - using var pipeline = builder.Build(); - if (pipeline != null) - { - pipeline.Begin(); - pipeline.Process(null); - pipeline.End(); - if (pipeline.Result.HadFailures) - exitCode = ERROR_BREAK_ON_FAILURE; - } - return host.HadErrors || pipeline == null ? ERROR_GENERIC : exitCode; - } - - public static int RunRestore(RestoreOptions operationOptions, ClientContext clientContext, InvocationContext invocation) - { - var exitCode = 0; - var file = LockFile.Read(null); - - using var pwsh = PowerShell.Create(); - foreach (var kv in file.Modules) - { - var module = kv.Key; - var targetVersion = kv.Value.Version; - if (string.Equals(module, "PSRule", StringComparison.OrdinalIgnoreCase)) - continue; - - invocation.Log(Messages.UsingModule, module, targetVersion.ToString()); - if (IsInstalled(pwsh, module, targetVersion, out var installedVersion) && !operationOptions.Force) - { - if (operationOptions.Verbose) - { - invocation.Log($"The module {module} is already installed."); - } - continue; - } - - var idealVersion = FindVersion(pwsh, module, null, targetVersion, null); - if (idealVersion != null) - InstallVersion(invocation, pwsh, module, idealVersion.ToString()); - - if (pwsh.HadErrors || (idealVersion == null && installedVersion == null)) - { - exitCode = ERROR_MODUILE_FAILEDTOINSTALL; - invocation.LogError(Messages.Error_501, module, targetVersion); - foreach (var error in pwsh.Streams.Error) - { - invocation.LogError(error.Exception.Message); - } - } - } - return exitCode; - } - - /// - /// Add a module to the lock file. - /// - public static int AddModule(ModuleOptions operationOptions, ClientContext clientContext, InvocationContext invocation) - { - var exitCode = 0; - if (operationOptions.Module == null || operationOptions.Module.Length == 0) return exitCode; - - var host = new ClientHost(invocation, operationOptions.Verbose, operationOptions.Debug); - var option = GetOption(host); - var requires = option.Requires.ToDictionary(); - var file = LockFile.Read(null); - - using var pwsh = PowerShell.Create(); - foreach (var module in operationOptions.Module) - { - if (!file.Modules.TryGetValue(module, out var item) || operationOptions.Force) - { - // Get a constraint if set from options. - var moduleConstraint = requires.TryGetValue(module, out var c) ? c : null; - - // Get target version if specified in command-line. - var targetVersion = !string.IsNullOrEmpty(operationOptions.Version) && SemanticVersion.TryParseVersion(operationOptions.Version, out var v) && v != null ? v : null; - - // Check if the target version is valid with the constraint if set. - if (targetVersion != null && moduleConstraint != null && !moduleConstraint.Constraint.Equals(targetVersion)) - { - invocation.LogError(Messages.Error_503, operationOptions.Version!); - return ERROR_MODULE_ADD_VIOLATES_CONSTRAINT; - } - - // Find the ideal version. - var idealVersion = FindVersion(pwsh, module, moduleConstraint, targetVersion, null); - if (idealVersion == null && targetVersion != null && operationOptions.SkipVerification) - idealVersion = targetVersion; - - if (idealVersion == null) - { - invocation.LogError(Messages.Error_502, module); - return ERROR_MODULE_FAILEDTOFIND; - } - - invocation.Log(Messages.UsingModule, module, idealVersion.ToString()); - item = new LockEntry - { - Version = idealVersion - }; - file.Modules[module] = item; - } - else - { - - } - } - - file.Write(null); - return exitCode; - } - - /// - /// Remove a module from the lock file. - /// - public static int RemoveModule(ModuleOptions operationOptions, ClientContext clientContext, InvocationContext invocation) - { - var exitCode = 0; - if (operationOptions.Module == null || operationOptions.Module.Length == 0) return exitCode; - - var host = new ClientHost(invocation, operationOptions.Verbose, operationOptions.Debug); - var file = LockFile.Read(null); - - foreach (var module in operationOptions.Module) - { - if (file.Modules.TryGetValue(module, out var constraint)) - { - file.Modules.Remove(module); - } - else - { - - } - } - - file.Write(null); - return exitCode; - } - - /// - /// Upgrade a module within the lock file. - /// - public static int UpgradeModule(ModuleOptions operationOptions, ClientContext clientContext, InvocationContext invocation) - { - var exitCode = 0; - var host = new ClientHost(invocation, operationOptions.Verbose, operationOptions.Debug); - var option = GetOption(host); - var requires = option.Requires.ToDictionary(); - var file = LockFile.Read(null); - - using var pwsh = PowerShell.Create(); - foreach (var kv in file.Modules) - { - // Get a constraint if set from options. - var moduleConstraint = requires.TryGetValue(kv.Key, out var c) ? c : null; - - // Find the ideal version. - var idealVersion = FindVersion(pwsh, kv.Key, moduleConstraint, null, null); - if (idealVersion == null) - { - invocation.LogError(Messages.Error_502, kv.Key); - return ERROR_MODULE_FAILEDTOFIND; - } - - if (idealVersion == kv.Value.Version) - continue; - - invocation.Log(Messages.UsingModule, kv.Key, idealVersion.ToString()); - - kv.Value.Version = idealVersion; - file.Modules[kv.Key] = kv.Value; - } - - file.Write(null); - return exitCode; - } - - private static bool IsInstalled(PowerShell pwsh, string module, SemanticVersion.Version targetVersion, out SemanticVersion.Version? installedVersion) - { - pwsh.Commands.Clear(); - pwsh.Streams.ClearStreams(); - pwsh.AddCommand("Get-Module") - .AddParameter(PARAM_NAME, module) - .AddParameter("ListAvailable"); - - var versions = pwsh.Invoke(); - installedVersion = null; - foreach (var version in versions) - { - if (TryModuleInfo(version, out var versionString) && - versionString != null && - SemanticVersion.TryParseVersion(versionString, out var v) && - v != null && - (targetVersion == null || targetVersion.Equals(v)) && - v.CompareTo(installedVersion) > 0) - installedVersion = v; - } - return installedVersion != null; - } - - private static bool TryModuleInfo(PSObject value, out string? version) - { - version = null; - if (value?.BaseObject is not PSModuleInfo info) - return false; - - version = info.Version?.ToString(); - if (TryPrivateData(info, FIELD_PSDATA, out var psData) && psData != null && psData.ContainsKey(FIELD_PRERELEASE)) - version = string.Concat(version, PRERELEASE_SEPARATOR, psData[FIELD_PRERELEASE]?.ToString()); - - return version != null; - } - - private static bool TryPrivateData(PSModuleInfo info, string propertyName, out Hashtable? value) - { - value = null; - if (info.PrivateData is Hashtable privateData && privateData.ContainsKey(propertyName) && privateData[propertyName] is Hashtable data) - { - value = data; - return true; - } - return false; - } - - private static SemanticVersion.Version? FindVersion(PowerShell pwsh, string module, ModuleConstraint? constraint, SemanticVersion.Version? targetVersion, SemanticVersion.Version? installedVersion) - { - pwsh.Commands.Clear(); - pwsh.Streams.ClearStreams(); - pwsh.AddCommand("Find-Module") - .AddParameter(PARAM_NAME, module) - .AddParameter("AllVersions"); - - var versions = pwsh.Invoke(); - SemanticVersion.Version? result = null; - foreach (var version in versions) - { - if (version.Properties[PARAM_VERSION].Value is string versionString && - SemanticVersion.TryParseVersion(versionString, out var v) && - v != null && - (constraint == null || constraint.Constraint.Equals(v)) && - (targetVersion == null || targetVersion.Equals(v)) && - v.CompareTo(result) > 0 && - v.CompareTo(installedVersion) > 0) - result = v; - } - return result; - } - - private static void InstallVersion(InvocationContext invocation, PowerShell pwsh, string name, string version) - { - invocation.Log(Messages.InstallingModule, name, version); - - pwsh.Commands.Clear(); - pwsh.Streams.ClearStreams(); - pwsh.AddCommand("Install-Module") - .AddParameter(PARAM_NAME, name) - .AddParameter("RequiredVersion", version) - .AddParameter("Scope", "CurrentUser") - .AddParameter("AllowPrerelease") - .AddParameter("Force"); - - pwsh.Invoke(); - } - - private static PSRuleOption GetOption(ClientHost host) - { - PSRuleOption.UseHostContext(host); - var option = PSRuleOption.FromFileOrEmpty(); - option.Execution.InitialSessionState = Options.SessionState.Minimal; - option.Input.Format = InputFormat.File; - option.Output.Style ??= OutputStyle.Client; - return option; - } -} diff --git a/src/PSRule.Tool/ClientHost.cs b/src/PSRule.Tool/ClientHost.cs index ef2957b2fc..26221bf876 100644 --- a/src/PSRule.Tool/ClientHost.cs +++ b/src/PSRule.Tool/ClientHost.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. using System.CommandLine; -using System.CommandLine.Invocation; using System.CommandLine.IO; using System.Management.Automation; using PSRule.Pipeline; @@ -11,15 +10,15 @@ namespace PSRule.Tool; internal sealed class ClientHost : HostContext { - private readonly InvocationContext _Invocation; + private readonly ClientContext _Context; private readonly bool _Verbose; private readonly bool _Debug; private readonly ConsoleColor _BackgroundColor; private readonly ConsoleColor _ForegroundColor; - public ClientHost(InvocationContext invocation, bool verbose, bool debug) + public ClientHost(ClientContext context, bool verbose, bool debug) { - _Invocation = invocation; + _Context = context; _Verbose = verbose; _Debug = debug; _BackgroundColor = Console.BackgroundColor; @@ -41,13 +40,13 @@ public override ActionPreference GetPreferenceVariable(string variableName) public override void Error(ErrorRecord errorRecord) { - _Invocation.Console.Error.WriteLine(errorRecord.Exception.Message); + _Context.LogError(errorRecord.Exception.Message); base.Error(errorRecord); } public override void Warning(string text) { - _Invocation.Console.WriteLine(text); + _Context.Invocation.Console.WriteLine(text); } public override bool ShouldProcess(string target, string action) @@ -61,9 +60,9 @@ public override void Information(InformationRecord informationRecord) { SetConsole(info); if (info.NoNewLine.GetValueOrDefault(false)) - _Invocation.Console.Write(info.Message); + _Context.Invocation.Console.Write(info.Message); else - _Invocation.Console.WriteLine(info.Message); + _Context.Invocation.Console.WriteLine(info.Message); RevertConsole(); } @@ -86,7 +85,7 @@ public override void Verbose(string text) if (!_Verbose) return; - _Invocation.Console.WriteLine(text); + _Context.LogVerbose(text); } public override void Debug(string text) @@ -94,6 +93,6 @@ public override void Debug(string text) if (!_Debug) return; - _Invocation.Console.WriteLine(text); + _Context.Invocation.Console.WriteLine(text); } } diff --git a/src/PSRule.Tool/Commands/ModuleCommand.cs b/src/PSRule.Tool/Commands/ModuleCommand.cs new file mode 100644 index 0000000000..88e8462b22 --- /dev/null +++ b/src/PSRule.Tool/Commands/ModuleCommand.cs @@ -0,0 +1,460 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Collections; +using System.Diagnostics.CodeAnalysis; +using System.Management.Automation; +using Newtonsoft.Json; +using PSRule.Configuration; +using PSRule.Data; +using PSRule.Pipeline.Dependencies; +using PSRule.Tool.Models; +using PSRule.Tool.Resources; +using SemanticVersion = PSRule.Data.SemanticVersion; + +namespace PSRule.Tool.Commands; + +/// +/// Execute features of the module command through the CLI. +/// +internal sealed class ModuleCommand +{ + /// + /// Failed to install a module. + /// + private const int ERROR_MODULE_FAILED_TO_INSTALL = 501; + + private const int ERROR_MODULE_FAILED_TO_FIND = 502; + + private const int ERROR_MODULE_ADD_VIOLATES_CONSTRAINT = 503; + + private const string PARAM_NAME = "Name"; + private const string PARAM_VERSION = "Version"; + + private const string FIELD_PRERELEASE = "Prerelease"; + private const string FIELD_PSDATA = "PSData"; + private const string PRERELEASE_SEPARATOR = "-"; + + public static int ModuleRestore(RestoreOptions operationOptions, ClientContext clientContext) + { + var exitCode = 0; + var requires = clientContext.Option.Requires.ToDictionary(); + var file = LockFile.Read(null); + + using var pwsh = PowerShell.Create(); + + // Restore from the lock file. + foreach (var kv in file.Modules) + { + var module = kv.Key; + var targetVersion = kv.Value.Version; + if (string.Equals(module, "PSRule", StringComparison.OrdinalIgnoreCase)) + continue; + + // clientContext.LogVerbose(Messages.UsingModule, module, targetVersion.ToString()); + if (IsInstalled(pwsh, module, targetVersion, out var installedVersion) && !operationOptions.Force) + { + clientContext.LogVerbose($"The module {module} is already installed."); + continue; + } + + var idealVersion = FindVersion(pwsh, module, null, targetVersion, null); + if (idealVersion != null) + InstallVersion(clientContext, pwsh, module, idealVersion.ToString()); + + if (pwsh.HadErrors || (idealVersion == null && installedVersion == null)) + { + exitCode = ERROR_MODULE_FAILED_TO_INSTALL; + clientContext.LogError(Messages.Error_501, module, targetVersion); + foreach (var error in pwsh.Streams.Error) + { + clientContext.LogError(error.Exception.Message); + } + } + } + + // Restore from included modules. + foreach (var includeModule in clientContext.Option.Include.Module) + { + // Skip modules already in the lock unless force is used. + if (file.Modules.TryGetValue(includeModule, out var lockEntry)) + continue; + + // Get a constraint if set from options. + var moduleConstraint = requires.TryGetValue(includeModule, out var c) ? c : null; + + // Check if the installed version matches the constraint. + if (IsInstalled(pwsh, includeModule, null, out var installedVersion) && + !operationOptions.Force && + (moduleConstraint == null || moduleConstraint.Equals(installedVersion))) + { + // invocation.Log(Messages.UsingModule, includeModule, installedVersion.ToString()); + clientContext.LogVerbose($"The module {includeModule} is already installed."); + continue; + } + + // Find the ideal version. + var idealVersion = FindVersion(pwsh, includeModule, moduleConstraint, null, null); + if (idealVersion != null) + { + InstallVersion(clientContext, pwsh, includeModule, idealVersion.ToString()); + } + else if (idealVersion == null) + { + clientContext.LogError(Messages.Error_502, includeModule); + exitCode = ERROR_MODULE_FAILED_TO_FIND; + } + else if (pwsh.HadErrors) + { + exitCode = ERROR_MODULE_FAILED_TO_INSTALL; + clientContext.LogError(Messages.Error_501, includeModule, idealVersion); + foreach (var error in pwsh.Streams.Error) + { + clientContext.LogError(error.Exception.Message); + } + } + } + + if (exitCode == 0) + { + ListModules(clientContext, GetModules(pwsh, file, clientContext.Option)); + } + return exitCode; + } + + /// + /// Initialize a new lock file based on existing options. + /// + public static int ModuleInit(ModuleOptions operationOptions, ClientContext clientContext) + { + var exitCode = 0; + var requires = clientContext.Option.Requires.ToDictionary(); + var file = !operationOptions.Force ? LockFile.Read(null) : new LockFile(); + + using var pwsh = PowerShell.Create(); + + // Add for any included modules. + foreach (var includeModule in clientContext.Option.Include.Module) + { + // Skip modules already in the lock unless force is used. + if (file.Modules.TryGetValue(includeModule, out var lockEntry)) + continue; + + // Get a constraint if set from options. + var moduleConstraint = requires.TryGetValue(includeModule, out var c) ? c : null; + + // Find the ideal version. + var idealVersion = FindVersion(pwsh, includeModule, moduleConstraint, null, null); + if (idealVersion == null) + { + clientContext.LogError(Messages.Error_502, includeModule); + return ERROR_MODULE_FAILED_TO_FIND; + } + + if (lockEntry?.Version == idealVersion) + continue; + + // invocation.Log(Messages.UsingModule, includeModule, idealVersion.ToString()); + + file.Modules[includeModule] = new LockEntry + { + Version = idealVersion + }; + } + + file.Write(null); + + if (exitCode == 0) + { + ListModules(clientContext, GetModules(pwsh, file, clientContext.Option)); + } + return exitCode; + } + + /// + /// List any module and the installed versions from the lock file. + /// + public static int ModuleList(ModuleOptions operationOptions, ClientContext clientContext) + { + var exitCode = 0; + var requires = clientContext.Option.Requires.ToDictionary(); + var file = LockFile.Read(null); + + var pwsh = PowerShell.Create(); + + if (exitCode == 0) + { + ListModules(clientContext, GetModules(pwsh, file, clientContext.Option)); + } + return exitCode; + } + + /// + /// Add a module to the lock file. + /// + public static int ModuleAdd(ModuleOptions operationOptions, ClientContext clientContext) + { + var exitCode = 0; + if (operationOptions.Module == null || operationOptions.Module.Length == 0) return exitCode; + + var requires = clientContext.Option.Requires.ToDictionary(); + var file = LockFile.Read(null); + + using var pwsh = PowerShell.Create(); + foreach (var module in operationOptions.Module) + { + if (!file.Modules.TryGetValue(module, out var item) || operationOptions.Force) + { + // Get a constraint if set from options. + var moduleConstraint = requires.TryGetValue(module, out var c) ? c : null; + + // Get target version if specified in command-line. + var targetVersion = !string.IsNullOrEmpty(operationOptions.Version) && SemanticVersion.TryParseVersion(operationOptions.Version, out var v) && v != null ? v : null; + + // Check if the target version is valid with the constraint if set. + if (targetVersion != null && moduleConstraint != null && !moduleConstraint.Constraint.Equals(targetVersion)) + { + clientContext.LogError(Messages.Error_503, operationOptions.Version!); + return ERROR_MODULE_ADD_VIOLATES_CONSTRAINT; + } + + // Find the ideal version. + var idealVersion = FindVersion(pwsh, module, moduleConstraint, targetVersion, null); + if (idealVersion == null && targetVersion != null && operationOptions.SkipVerification) + idealVersion = targetVersion; + + if (idealVersion == null) + { + clientContext.LogError(Messages.Error_502, module); + return ERROR_MODULE_FAILED_TO_FIND; + } + + clientContext.LogVerbose(Messages.UsingModule, module, idealVersion.ToString()); + item = new LockEntry + { + Version = idealVersion + }; + file.Modules[module] = item; + } + else + { + + } + } + + file.Write(null); + + if (exitCode == 0) + { + ListModules(clientContext, GetModules(pwsh, file, clientContext.Option)); + } + return exitCode; + } + + /// + /// Remove a module from the lock file. + /// + public static int ModuleRemove(ModuleOptions operationOptions, ClientContext clientContext) + { + var exitCode = 0; + if (operationOptions.Module == null || operationOptions.Module.Length == 0) return exitCode; + + var file = LockFile.Read(null); + + using var pwsh = PowerShell.Create(); + foreach (var module in operationOptions.Module) + { + if (file.Modules.TryGetValue(module, out var constraint)) + { + file.Modules.Remove(module); + } + else + { + + } + } + + file.Write(null); + + if (exitCode == 0) + { + ListModules(clientContext, GetModules(pwsh, file, clientContext.Option)); + } + return exitCode; + } + + /// + /// Upgrade a module within the lock file. + /// + public static int ModuleUpgrade(ModuleOptions operationOptions, ClientContext clientContext) + { + var exitCode = 0; + var requires = clientContext.Option.Requires.ToDictionary(); + var file = LockFile.Read(null); + + using var pwsh = PowerShell.Create(); + foreach (var kv in file.Modules) + { + // Get a constraint if set from options. + var moduleConstraint = requires.TryGetValue(kv.Key, out var c) ? c : null; + + // Find the ideal version. + var idealVersion = FindVersion(pwsh, kv.Key, moduleConstraint, null, null); + if (idealVersion == null) + { + clientContext.LogError(Messages.Error_502, kv.Key); + return ERROR_MODULE_FAILED_TO_FIND; + } + + if (idealVersion == kv.Value.Version) + continue; + + clientContext.LogVerbose(Messages.UsingModule, kv.Key, idealVersion.ToString()); + + kv.Value.Version = idealVersion; + file.Modules[kv.Key] = kv.Value; + } + + file.Write(null); + + if (exitCode == 0) + { + ListModules(clientContext, GetModules(pwsh, file, clientContext.Option)); + } + return exitCode; + } + + #region Helper methods + + private static IEnumerable GetModules(PowerShell pwsh, LockFile file, PSRuleOption option) + { + var results = new List(); + + // Process modules in the lock file. + foreach (var kv in file.Modules) + { + var installed = IsInstalled(pwsh, kv.Key, kv.Value.Version, out var installedVersion); + results.Add(new ModuleRecord( + Name: kv.Key, + Version: kv.Value.Version.ToString(), + Installed: installed, + Locked: true + )); + } + + // Process included modules from options. + foreach (var includeModule in option.Include.Module) + { + // Skip modules already in the lock. + if (file.Modules.ContainsKey(includeModule)) + continue; + + var installed = IsInstalled(pwsh, includeModule, null, out var installedVersion); + results.Add(new ModuleRecord( + Name: includeModule, + Version: installedVersion?.ToString() ?? "latest", + Installed: installed, + Locked: false + )); + } + + return results; + } + + private static void ListModules(ClientContext context, IEnumerable results) + { + context.Invocation.Console.Out.Write(JsonConvert.SerializeObject(results, new JsonSerializerSettings + { + Formatting = Formatting.Indented + })); + } + + private static bool IsInstalled(PowerShell pwsh, string module, SemanticVersion.Version? targetVersion, [NotNullWhen(true)] out SemanticVersion.Version? installedVersion) + { + pwsh.Commands.Clear(); + pwsh.Streams.ClearStreams(); + pwsh.AddCommand("Get-Module") + .AddParameter(PARAM_NAME, module) + .AddParameter("ListAvailable"); + + var versions = pwsh.Invoke(); + installedVersion = null; + foreach (var version in versions) + { + if (TryModuleInfo(version, out var versionString) && + versionString != null && + SemanticVersion.TryParseVersion(versionString, out var v) && + v != null && + (targetVersion == null || targetVersion.Equals(v)) && + v.CompareTo(installedVersion) > 0) + installedVersion = v; + } + return installedVersion != null; + } + + private static bool TryModuleInfo(PSObject value, out string? version) + { + version = null; + if (value?.BaseObject is not PSModuleInfo info) + return false; + + version = info.Version?.ToString(); + if (TryPrivateData(info, FIELD_PSDATA, out var psData) && psData != null && psData.ContainsKey(FIELD_PRERELEASE)) + version = string.Concat(version, PRERELEASE_SEPARATOR, psData[FIELD_PRERELEASE]?.ToString()); + + return version != null; + } + + private static bool TryPrivateData(PSModuleInfo info, string propertyName, out Hashtable? value) + { + value = null; + if (info.PrivateData is Hashtable privateData && privateData.ContainsKey(propertyName) && privateData[propertyName] is Hashtable data) + { + value = data; + return true; + } + return false; + } + + private static SemanticVersion.Version? FindVersion(PowerShell pwsh, string module, ModuleConstraint? constraint, SemanticVersion.Version? targetVersion, SemanticVersion.Version? installedVersion) + { + pwsh.Commands.Clear(); + pwsh.Streams.ClearStreams(); + pwsh.AddCommand("Find-Module") + .AddParameter(PARAM_NAME, module) + .AddParameter("AllVersions"); + + var versions = pwsh.Invoke(); + SemanticVersion.Version? result = null; + foreach (var version in versions) + { + if (version.Properties[PARAM_VERSION].Value is string versionString && + SemanticVersion.TryParseVersion(versionString, out var v) && + v != null && + (constraint == null || constraint.Constraint.Equals(v)) && + (targetVersion == null || targetVersion.Equals(v)) && + v.CompareTo(result) > 0 && + v.CompareTo(installedVersion) > 0) + result = v; + } + return result; + } + + private static void InstallVersion([DisallowNull] ClientContext context, [DisallowNull] PowerShell pwsh, [DisallowNull] string name, [DisallowNull] string version) + { + context.LogVerbose(Messages.RestoringModule, name, version); + + pwsh.Commands.Clear(); + pwsh.Streams.ClearStreams(); + pwsh.AddCommand("Install-Module") + .AddParameter(PARAM_NAME, name) + .AddParameter("RequiredVersion", version) + .AddParameter("Scope", "CurrentUser") + .AddParameter("AllowPrerelease") + .AddParameter("Force"); + + pwsh.Invoke(); + } + + #endregion Helper methods +} diff --git a/src/PSRule.Tool/Commands/RunCommand.cs b/src/PSRule.Tool/Commands/RunCommand.cs new file mode 100644 index 0000000000..ce41975e6e --- /dev/null +++ b/src/PSRule.Tool/Commands/RunCommand.cs @@ -0,0 +1,58 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using PSRule.Configuration; +using PSRule.Pipeline; +using PSRule.Pipeline.Dependencies; +using PSRule.Tool.Models; + +namespace PSRule.Tool.Commands; + +/// +/// Execute features of the run command through the CLI. +/// +internal sealed class RunCommand +{ + private const string PUBLISHER = "CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US"; + + /// + /// A generic error. + /// + private const int ERROR_GENERIC = 1; + + /// + /// One or more failures occurred. + /// + private const int ERROR_BREAK_ON_FAILURE = 100; + + public static int Run(RunOptions operationOptions, ClientContext clientContext) + { + var exitCode = 0; + var file = LockFile.Read(null); + var inputPath = operationOptions.InputPath == null || operationOptions.InputPath.Length == 0 ? + new string[] { Environment.GetWorkingPath() } : operationOptions.InputPath; + + if (operationOptions.Path != null) + clientContext.Option.Include.Path = operationOptions.Path; + + if (operationOptions.Outcome != null && operationOptions.Outcome.Value != Rules.RuleOutcome.None) + clientContext.Option.Output.Outcome = operationOptions.Outcome; + + // Build command + var builder = CommandLineBuilder.Assert(operationOptions.Module ?? [], clientContext.Option, clientContext.Host, file); + builder.Baseline(BaselineOption.FromString(operationOptions.Baseline)); + builder.InputPath(inputPath); + builder.UnblockPublisher(PUBLISHER); + + using var pipeline = builder.Build(); + if (pipeline != null) + { + pipeline.Begin(); + pipeline.Process(null); + pipeline.End(); + if (pipeline.Result.HadFailures) + exitCode = ERROR_BREAK_ON_FAILURE; + } + return clientContext.Host.HadErrors || pipeline == null ? ERROR_GENERIC : exitCode; + } +} diff --git a/src/PSRule.Tool/InvocationExtensions.cs b/src/PSRule.Tool/InvocationExtensions.cs deleted file mode 100644 index 2e47feaba7..0000000000 --- a/src/PSRule.Tool/InvocationExtensions.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.CommandLine; -using System.CommandLine.Invocation; - -namespace PSRule.Tool; - -internal static class InvocationExtensions -{ - public static void Log(this InvocationContext context, string message, params object[] args) - { - if (context == null || string.IsNullOrEmpty(message)) - return; - - var s = args != null && args.Length > 0 ? string.Format(Thread.CurrentThread.CurrentCulture, message, args) : message; - context.Console.WriteLine(s); - } - - public static void LogError(this InvocationContext context, string message, params object[] args) - { - if (context == null || string.IsNullOrEmpty(message)) - return; - - var s = args != null && args.Length > 0 ? string.Format(Thread.CurrentThread.CurrentCulture, message, args) : message; - context.Console.Error.Write(s); - } -} diff --git a/src/PSRule.Tool/ModuleOptions.cs b/src/PSRule.Tool/Models/ModuleOptions.cs similarity index 70% rename from src/PSRule.Tool/ModuleOptions.cs rename to src/PSRule.Tool/Models/ModuleOptions.cs index acc92bb290..f36ee72989 100644 --- a/src/PSRule.Tool/ModuleOptions.cs +++ b/src/PSRule.Tool/Models/ModuleOptions.cs @@ -1,18 +1,12 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -namespace PSRule.Tool; +namespace PSRule.Tool.Models; internal sealed class ModuleOptions { public string[]? Path { get; set; } - public string? Option { get; set; } - - public bool Verbose { get; set; } - - public bool Debug { get; set; } - public string[]? Module { get; set; } public bool Force { get; set; } diff --git a/src/PSRule.Tool/Models/ModuleRecord.cs b/src/PSRule.Tool/Models/ModuleRecord.cs new file mode 100644 index 0000000000..c568dddafc --- /dev/null +++ b/src/PSRule.Tool/Models/ModuleRecord.cs @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace PSRule.Tool.Models; + +/// +/// A record of a module within the lock file. +/// +/// The name of the module. +/// The version of the module. +/// Is the version is installed. +/// Is the module tracked. +internal sealed record ModuleRecord(string Name, string Version, bool Installed, bool Locked); diff --git a/src/PSRule.Tool/RestoreOptions.cs b/src/PSRule.Tool/Models/RestoreOptions.cs similarity index 58% rename from src/PSRule.Tool/RestoreOptions.cs rename to src/PSRule.Tool/Models/RestoreOptions.cs index d06205849d..d20df143a3 100644 --- a/src/PSRule.Tool/RestoreOptions.cs +++ b/src/PSRule.Tool/Models/RestoreOptions.cs @@ -1,17 +1,11 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -namespace PSRule.Tool; +namespace PSRule.Tool.Models; internal sealed class RestoreOptions { public string[]? Path { get; set; } - public string? Option { get; set; } - - public bool Verbose { get; set; } - - public bool Debug { get; set; } - public bool Force { get; set; } } diff --git a/src/PSRule.Tool/RunOptions.cs b/src/PSRule.Tool/Models/RunOptions.cs similarity index 71% rename from src/PSRule.Tool/RunOptions.cs rename to src/PSRule.Tool/Models/RunOptions.cs index d4e84bab53..602b94c74a 100644 --- a/src/PSRule.Tool/RunOptions.cs +++ b/src/PSRule.Tool/Models/RunOptions.cs @@ -3,7 +3,7 @@ using PSRule.Rules; -namespace PSRule.Tool; +namespace PSRule.Tool.Models; internal sealed class RunOptions { @@ -11,15 +11,9 @@ internal sealed class RunOptions public string[]? Module { get; set; } - public string? Option { get; set; } - public string? Baseline { get; set; } public RuleOutcome? Outcome { get; set; } public string[]? InputPath { get; set; } - - public bool Verbose { get; set; } - - public bool Debug { get; set; } } diff --git a/src/PSRule.Tool/Resources/CmdStrings.Designer.cs b/src/PSRule.Tool/Resources/CmdStrings.Designer.cs index 73776aeabc..85a11713ca 100644 --- a/src/PSRule.Tool/Resources/CmdStrings.Designer.cs +++ b/src/PSRule.Tool/Resources/CmdStrings.Designer.cs @@ -106,7 +106,7 @@ internal static string Module_Add_Version_Description { } /// - /// Looks up a localized string similar to Manage modules tracked by the PSRule lock file. To install modules within the lock file, use the `restore` command instead.. + /// Looks up a localized string similar to Manage or restore modules tracked by the module lock file and configured options.. /// internal static string Module_Description { get { @@ -114,6 +114,33 @@ internal static string Module_Description { } } + /// + /// Looks up a localized string similar to Initialize a new or update an existing module lock file based on options.. + /// + internal static string Module_Init_Description { + get { + return ResourceManager.GetString("Module_Init_Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Force the creation of a new module lock file, even if one already exists.. + /// + internal static string Module_Init_Force_Description { + get { + return ResourceManager.GetString("Module_Init_Force_Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to List any module and the installed versions from the lock file.. + /// + internal static string Module_List_Description { + get { + return ResourceManager.GetString("Module_List_Description", resourceCulture); + } + } + /// /// Looks up a localized string similar to The name of one or more modules.. /// @@ -132,6 +159,24 @@ internal static string Module_Remove_Description { } } + /// + /// Looks up a localized string similar to Restore from the module lock file and configured options.. + /// + internal static string Module_Restore_Description { + get { + return ResourceManager.GetString("Module_Restore_Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Restore modules even when an existing version that meets constraints is already installed locally.. + /// + internal static string Module_Restore_Force_Description { + get { + return ResourceManager.GetString("Module_Restore_Force_Description", resourceCulture); + } + } + /// /// Looks up a localized string similar to Upgrade to the latest versions of any modules within the lock file.. /// @@ -186,24 +231,6 @@ internal static string Options_Verbose_Description { } } - /// - /// Looks up a localized string similar to Restore modules defined in configuration locally.. - /// - internal static string Restore_Description { - get { - return ResourceManager.GetString("Restore_Description", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Restore modules even when an existing version that meets constaints is already installed locally.. - /// - internal static string Restore_Force_Description { - get { - return ResourceManager.GetString("Restore_Force_Description", resourceCulture); - } - } - /// /// Looks up a localized string similar to The name of a specific baseline to use.. /// diff --git a/src/PSRule.Tool/Resources/CmdStrings.resx b/src/PSRule.Tool/Resources/CmdStrings.resx index 21c6ae6eb8..55134ff00b 100644 --- a/src/PSRule.Tool/Resources/CmdStrings.resx +++ b/src/PSRule.Tool/Resources/CmdStrings.resx @@ -120,6 +120,12 @@ PSRule CLI + + Initialize a new or update an existing module lock file based on options. + + + List any module and the installed versions from the lock file. + Add one or more modules to the lock file. @@ -133,7 +139,7 @@ The target version of the module to add. - Manage modules tracked by the PSRule lock file. To install modules within the lock file, use the `restore` command instead. + Manage or restore modules tracked by the module lock file and configured options. The name of one or more modules. @@ -159,11 +165,11 @@ Return verbose output. - - Restore modules defined in configuration locally. + + Restore from the module lock file and configured options. - - Restore modules even when an existing version that meets constaints is already installed locally. + + Restore modules even when an existing version that meets constraints is already installed locally. The name of a specific baseline to use. @@ -174,4 +180,7 @@ Specifies the rule results to show in output. By default, Pass/ Fail/ Error results are shown. + + Force the creation of a new module lock file, even if one already exists. + \ No newline at end of file diff --git a/src/PSRule.Tool/Resources/Messages.Designer.cs b/src/PSRule.Tool/Resources/Messages.Designer.cs index f16662dcf8..1ced68049d 100644 --- a/src/PSRule.Tool/Resources/Messages.Designer.cs +++ b/src/PSRule.Tool/Resources/Messages.Designer.cs @@ -61,7 +61,7 @@ internal Messages() { } /// - /// Looks up a localized string similar to Failed to install module: {0} -- v{1}. + /// Looks up a localized string similar to Failed to restore module: {0} -- v{1}. /// internal static string Error_501 { get { @@ -88,11 +88,11 @@ internal static string Error_503 { } /// - /// Looks up a localized string similar to Installing module: {0} -- v{1}. + /// Looks up a localized string similar to Restoring module: {0} -- v{1}. /// - internal static string InstallingModule { + internal static string RestoringModule { get { - return ResourceManager.GetString("InstallingModule", resourceCulture); + return ResourceManager.GetString("RestoringModule", resourceCulture); } } diff --git a/src/PSRule.Tool/Resources/Messages.resx b/src/PSRule.Tool/Resources/Messages.resx index b1b9cb4624..0be3d66e98 100644 --- a/src/PSRule.Tool/Resources/Messages.resx +++ b/src/PSRule.Tool/Resources/Messages.resx @@ -118,7 +118,7 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Failed to install module: {0} -- v{1} + Failed to restore module: {0} -- v{1} Failed to find a valid version of the specified module '{0}'. @@ -126,8 +126,8 @@ The specified verison v{0} does not meet the required constraint. - - Installing module: {0} -- v{1} + + Restoring module: {0} -- v{1} Using module: {0} -- v{1} diff --git a/src/PSRule/Pipeline/Dependencies/LockFile.cs b/src/PSRule/Pipeline/Dependencies/LockFile.cs index c72a24ff01..ff8a619749 100644 --- a/src/PSRule/Pipeline/Dependencies/LockFile.cs +++ b/src/PSRule/Pipeline/Dependencies/LockFile.cs @@ -27,11 +27,16 @@ public sealed class LockFile { private const string DEFAULT_FILE = "ps-rule.lock.json"; + /// + /// The version of the lock file schema. + /// + [JsonProperty("version")] + public int Version { get; set; } + /// /// A mapping lock file entries for each module. /// [JsonProperty("modules")] - //[JsonConverter(typeof(CaseInsensitiveDictionaryConverter))] public Dictionary Modules { get; set; } = new Dictionary(StringComparer.OrdinalIgnoreCase); /// @@ -64,6 +69,8 @@ public static LockFile Read(string path) /// An alternative path to the lock file. public void Write(string path) { + Version = 1; + path = Environment.GetRootedPath(path); path = Path.GetExtension(path) == "json" ? path : Path.Combine(path, DEFAULT_FILE); var json = JsonConvert.SerializeObject(this, Formatting.Indented, new JsonSerializerSettings diff --git a/tests/PSRule.Tool.Tests/CommandTests.cs b/tests/PSRule.Tool.Tests/CommandTests.cs index a7f8003467..03451e30af 100644 --- a/tests/PSRule.Tool.Tests/CommandTests.cs +++ b/tests/PSRule.Tool.Tests/CommandTests.cs @@ -25,13 +25,16 @@ public async Task Run() } [Fact] - public async Task Restore() + public async Task ModuleRestore() { var console = new TestConsole(); var builder = ClientBuilder.New(); - Assert.NotNull(builder.Subcommands.FirstOrDefault(c => c.Name == "restore")); + var module = builder.Subcommands.FirstOrDefault(c => c.Name == "module"); - await builder.InvokeAsync("restore", console); + Assert.NotNull(module); + Assert.NotNull(module.Subcommands.FirstOrDefault(c => c.Name == "restore")); + + await builder.InvokeAsync("module restore", console); var output = console.Out.ToString(); Assert.NotNull(output); From b71fe9f6c386731c0ffddf1325f016cf8257a37f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 16 Jan 2024 11:53:56 +1000 Subject: [PATCH 166/177] Bump mkdocs-material from 9.5.3 to 9.5.4 (#1733) Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.5.3 to 9.5.4. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.5.3...9.5.4) --- updated-dependencies: - dependency-name: mkdocs-material dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements-docs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-docs.txt b/requirements-docs.txt index 2ae749840f..21968184c5 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -1,5 +1,5 @@ mkdocs==1.5.3 -mkdocs-material==9.5.3 +mkdocs-material==9.5.4 pymdown-extensions==10.7 mike==2.0.0 mkdocs-simple-hooks==0.1.5 From a7f5f2642e77031cacf87402031c529ea9d8a86f Mon Sep 17 00:00:00 2001 From: Bernie White Date: Tue, 16 Jan 2024 13:32:16 +1000 Subject: [PATCH 167/177] Improvements to CLI docs and help (#1734) --- docs/concepts/cli/index.md | 4 +- docs/concepts/cli/module.md | 58 +++++- docs/concepts/cli/run.md | 28 ++- mkdocs.yml | 2 +- src/PSRule.Tool/ClientBuilder.cs | 177 +++++++++--------- .../Resources/CmdStrings.Designer.cs | 87 ++++++--- src/PSRule.Tool/Resources/CmdStrings.resx | 23 ++- 7 files changed, 248 insertions(+), 131 deletions(-) diff --git a/docs/concepts/cli/index.md b/docs/concepts/cli/index.md index f0ff5093c2..8e3dbd1d02 100644 --- a/docs/concepts/cli/index.md +++ b/docs/concepts/cli/index.md @@ -8,8 +8,8 @@ The following commands are available in the PSRule CLI: -- [run](./run.md) -- [module](./module.md) +- [run](./run.md) — Run rules against an input path and output the results. +- [module](./module.md) — Manage or restore modules tracked by the module lock file and configured options. ## Global options diff --git a/docs/concepts/cli/module.md b/docs/concepts/cli/module.md index 279369ec4a..295933dbda 100644 --- a/docs/concepts/cli/module.md +++ b/docs/concepts/cli/module.md @@ -1,13 +1,11 @@ # ps-rule module -Use this command to configure the module lock file (`ps-rule.lock.json`). -The module lock file, provides consistent module versions across multiple machines and environments. - -!!! Tip +!!! Abstract + Use the `module` command to manage or restore modules tracked by the module lock file and configured options. (`ps-rule.lock.json`). The module lock file, provides consistent module versions across multiple machines and environments. For more information, see [Lock file](../lockfile.md). -The following subcommands are available: +To use the `module` command, choose one of the available subcommands: - [module init](#module-init) - [module list](#module-list) @@ -28,6 +26,18 @@ Optional parameters: - `--force` - Force the creation of a new lock file, even if one already exists. +For example: + +```bash title="PSRule CLI command-line" +ps-rule module init +``` + +For example, force the creation of a new lock file, even if one already exists: + +```bash title="PSRule CLI command-line" +ps-rule module init --force +``` + ## `module list` List any module and the installed versions from the lock file. @@ -48,10 +58,28 @@ Optional parameters: By default, the latest stable version of the module is added. Any required version constraints set by the `Requires` option are taken into consideration. +For example: + +```bash title="PSRule CLI command-line" +ps-rule module add PSRule.Rules.Azure +``` + +For example, a specific version of the module is added: + +```bash title="PSRule CLI command-line" +ps-rule module add PSRule.Rules.Azure --version 1.32.1 +``` + ## `module remove` Remove one or more modules from the lock file. +For example: + +```bash title="PSRule CLI command-line" +ps-rule module remove PSRule.Rules.Azure +``` + ## `module restore` Restore modules from the module lock file (`ps-rule.lock.json`) and configured options. @@ -60,10 +88,30 @@ Optional parameters: - `--force` - Restore modules even when an existing version that meets constraints is already installed locally. +For example: + +```bash title="PSRule CLI command-line" +ps-rule module restore +``` + +For example, force restore of all modules: + +```bash title="PSRule CLI command-line" +ps-rule module restore --force +``` + ## `module upgrade` Upgrade to the latest versions any modules within the lock file. +For example: + +```bash title="PSRule CLI command-line" +ps-rule module upgrade +``` + ## Next steps For more information on the module lock file, see [Lock file](../lockfile.md). + +To find out more about the commands available with the PSRule CLI, see [PSRule CLI](./index.md). diff --git a/docs/concepts/cli/run.md b/docs/concepts/cli/run.md index b21ff13cdf..81004625c5 100644 --- a/docs/concepts/cli/run.md +++ b/docs/concepts/cli/run.md @@ -1,15 +1,29 @@ # ps-rule run -Run rule analysis. +!!! Abstract + Use the `run` command to run rules against an input path and output the results. ## Optional parameters +### `--input-path` | `-f` + +The file or directory path to search for input file to use during a run. +By default, this is the current working path. + +### `--module` | `-m` + +The name of one or more modules that contain rules or resources to use during a run. + ### `--baseline` The name of a specific baseline to use. +Currently, only a single baseline can be used during a run. ### `--outcome` +Specifies the rule results to show in output. +By default, `Pass`/ `Fail`/ `Error` results are shown. + Allows filtering of results by outcome. The supported values are: @@ -23,3 +37,15 @@ The supported values are: To specify multiple values, specify the parameter multiple times. For example: `--outcome Pass --Outcome Fail`. + +### `--output` | `-o` + +Specifies the format to use when outputting results. + +### `--output-path` + +Specifies a path to write results to. + +## Next steps + +To find out more about the commands available with the PSRule CLI, see [PSRule CLI](./index.md). diff --git a/mkdocs.yml b/mkdocs.yml index eb160a2dbb..493405d9a7 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -45,7 +45,7 @@ nav: - About: about.md - Features: features.md - FAQ: faq.md - - Installation: install-instructions.md + - Installation: install.md - Quickstarts: - Create a standalone rule: quickstart/standalone-rule.md - Authoring rules: diff --git a/src/PSRule.Tool/ClientBuilder.cs b/src/PSRule.Tool/ClientBuilder.cs index ebe41dd5d8..87a4141423 100644 --- a/src/PSRule.Tool/ClientBuilder.cs +++ b/src/PSRule.Tool/ClientBuilder.cs @@ -20,91 +20,98 @@ internal sealed class ClientBuilder private static readonly string? _Version = (Assembly.GetEntryAssembly() ?? Assembly.GetExecutingAssembly()).GetCustomAttribute()?.InformationalVersion; - private readonly Option _Option; - private readonly Option _Verbose; - private readonly Option _Debug; - private readonly Option _ModuleRestoreForce; - private readonly Option _ModuleInitForce; - private readonly Option _ModuleAddVersion; - private readonly Option _ModuleAddForce; - private readonly Option _ModuleAddSkipVerification; - private readonly Option _Path; - private readonly Option _OutputPath; - private readonly Option _OutputFormat; - private readonly Option _InputPath; - private readonly Option _Module; - private readonly Option _Baseline; - private readonly Option _Outcome; + private readonly Option _Global_Option; + private readonly Option _Global_Verbose; + private readonly Option _Global_Debug; + private readonly Option _Module_Restore_Force; + private readonly Option _Module_Init_Force; + private readonly Option _Module_Add_Version; + private readonly Option _Module_Add_Force; + private readonly Option _Module_Add_SkipVerification; + private readonly Option _Global_Path; + private readonly Option _Run_OutputPath; + private readonly Option _Run_OutputFormat; + private readonly Option _Run_InputPath; + private readonly Option _Run_Module; + private readonly Option _Run_Baseline; + private readonly Option _Run_Outcome; private ClientBuilder(RootCommand cmd) { Command = cmd; - _Option = new Option( + + // Global options. + _Global_Option = new Option( new string[] { "--option" }, getDefaultValue: () => "ps-rule.yaml", - description: CmdStrings.Options_Option_Description + description: CmdStrings.Global_Option_Description ); - _Verbose = new Option( + _Global_Verbose = new Option( new string[] { "--verbose" }, - CmdStrings.Options_Verbose_Description + description: CmdStrings.Global_Verbose_Description ); - _Debug = new Option( + _Global_Debug = new Option( new string[] { "--debug" }, - CmdStrings.Options_Debug_Description + description: CmdStrings.Global_Debug_Description ); - _Path = new Option( + _Global_Path = new Option( new string[] { "-p", "--path" }, - CmdStrings.Options_Path_Description + description: CmdStrings.Global_Path_Description ); - _OutputPath = new Option( - new string[] { "--output-path" } + + // Options for the run command. + _Run_OutputPath = new Option( + new string[] { "--output-path" }, + description: CmdStrings.Run_OutputPath_Description ); - _OutputFormat = new Option( - new string[] { "-o", "--output" } + _Run_OutputFormat = new Option( + new string[] { "-o", "--output" }, + description: CmdStrings.Run_OutputFormat_Description ); - _InputPath = new Option( - new string[] { "-f", "--input-path" } + _Run_InputPath = new Option( + new string[] { "-f", "--input-path" }, + description: CmdStrings.Run_InputPath_Description ); - _Module = new Option( + _Run_Module = new Option( new string[] { "-m", "--module" }, - CmdStrings.Options_Module_Description + description: CmdStrings.Run_Module_Description ); - _Baseline = new Option( + _Run_Baseline = new Option( new string[] { "--baseline" }, - CmdStrings.Run_Baseline_Description + description: CmdStrings.Run_Baseline_Description ); - _Outcome = new Option( + _Run_Outcome = new Option( new string[] { "--outcome" }, description: CmdStrings.Run_Outcome_Description ).FromAmong("Pass", "Fail", "Error", "Processed", "Problem"); - _Outcome.Arity = ArgumentArity.ZeroOrMore; + _Run_Outcome.Arity = ArgumentArity.ZeroOrMore; // Options for the module command. - _ModuleInitForce = new Option( + _Module_Init_Force = new Option( new string[] { ARG_FORCE }, - CmdStrings.Module_Init_Force_Description + description: CmdStrings.Module_Init_Force_Description ); - _ModuleAddVersion = new Option + _Module_Add_Version = new Option ( new string[] { "--version" }, - CmdStrings.Module_Add_Version_Description + description: CmdStrings.Module_Add_Version_Description ); - _ModuleAddForce = new Option( + _Module_Add_Force = new Option( new string[] { ARG_FORCE }, - CmdStrings.Module_Add_Force_Description + description: CmdStrings.Module_Add_Force_Description ); - _ModuleAddSkipVerification = new Option( + _Module_Add_SkipVerification = new Option( new string[] { "--skip-verification" }, - CmdStrings.Module_Add_SkipVerification_Description + description: CmdStrings.Module_Add_SkipVerification_Description ); - _ModuleRestoreForce = new Option( + _Module_Restore_Force = new Option( new string[] { ARG_FORCE }, - CmdStrings.Module_Restore_Force_Description + description: CmdStrings.Module_Restore_Force_Description ); - cmd.AddGlobalOption(_Option); - cmd.AddGlobalOption(_Verbose); - cmd.AddGlobalOption(_Debug); + cmd.AddGlobalOption(_Global_Option); + cmd.AddGlobalOption(_Global_Verbose); + cmd.AddGlobalOption(_Global_Debug); } public RootCommand Command { get; } @@ -127,22 +134,22 @@ public static Command New() private void AddRun() { var cmd = new Command("run", CmdStrings.Run_Description); - cmd.AddOption(_Path); - cmd.AddOption(_OutputPath); - cmd.AddOption(_OutputFormat); - cmd.AddOption(_InputPath); - cmd.AddOption(_Module); - cmd.AddOption(_Baseline); - cmd.AddOption(_Outcome); + cmd.AddOption(_Global_Path); + cmd.AddOption(_Run_OutputPath); + cmd.AddOption(_Run_OutputFormat); + cmd.AddOption(_Run_InputPath); + cmd.AddOption(_Run_Module); + cmd.AddOption(_Run_Baseline); + cmd.AddOption(_Run_Outcome); cmd.SetHandler((invocation) => { var option = new RunOptions { - Path = invocation.ParseResult.GetValueForOption(_Path), - InputPath = invocation.ParseResult.GetValueForOption(_InputPath), - Module = invocation.ParseResult.GetValueForOption(_Module), - Baseline = invocation.ParseResult.GetValueForOption(_Baseline), - Outcome = ParseOutcome(invocation.ParseResult.GetValueForOption(_Outcome)), + Path = invocation.ParseResult.GetValueForOption(_Global_Path), + InputPath = invocation.ParseResult.GetValueForOption(_Run_InputPath), + Module = invocation.ParseResult.GetValueForOption(_Run_Module), + Baseline = invocation.ParseResult.GetValueForOption(_Run_Baseline), + Outcome = ParseOutcome(invocation.ParseResult.GetValueForOption(_Run_Outcome)), }; var client = GetClientContext(invocation); invocation.ExitCode = RunCommand.Run(option, client); @@ -170,15 +177,15 @@ private void AddModule() "init", CmdStrings.Module_Init_Description ); - init.AddOption(_ModuleInitForce); + init.AddOption(_Module_Init_Force); init.SetHandler((invocation) => { var option = new ModuleOptions { - Path = invocation.ParseResult.GetValueForOption(_Path), - Version = invocation.ParseResult.GetValueForOption(_ModuleAddVersion), - Force = invocation.ParseResult.GetValueForOption(_ModuleAddForce), - SkipVerification = invocation.ParseResult.GetValueForOption(_ModuleAddSkipVerification), + Path = invocation.ParseResult.GetValueForOption(_Global_Path), + Version = invocation.ParseResult.GetValueForOption(_Module_Add_Version), + Force = invocation.ParseResult.GetValueForOption(_Module_Add_Force), + SkipVerification = invocation.ParseResult.GetValueForOption(_Module_Add_SkipVerification), }; var client = GetClientContext(invocation); @@ -195,10 +202,10 @@ private void AddModule() { var option = new ModuleOptions { - Path = invocation.ParseResult.GetValueForOption(_Path), - Version = invocation.ParseResult.GetValueForOption(_ModuleAddVersion), - Force = invocation.ParseResult.GetValueForOption(_ModuleAddForce), - SkipVerification = invocation.ParseResult.GetValueForOption(_ModuleAddSkipVerification), + Path = invocation.ParseResult.GetValueForOption(_Global_Path), + Version = invocation.ParseResult.GetValueForOption(_Module_Add_Version), + Force = invocation.ParseResult.GetValueForOption(_Module_Add_Force), + SkipVerification = invocation.ParseResult.GetValueForOption(_Module_Add_SkipVerification), }; var client = GetClientContext(invocation); @@ -212,18 +219,18 @@ private void AddModule() CmdStrings.Module_Add_Description ); add.AddArgument(moduleArg); - add.AddOption(_ModuleAddVersion); - add.AddOption(_ModuleAddForce); - add.AddOption(_ModuleAddSkipVerification); + add.AddOption(_Module_Add_Version); + add.AddOption(_Module_Add_Force); + add.AddOption(_Module_Add_SkipVerification); add.SetHandler((invocation) => { var option = new ModuleOptions { - Path = invocation.ParseResult.GetValueForOption(_Path), + Path = invocation.ParseResult.GetValueForOption(_Global_Path), Module = invocation.ParseResult.GetValueForArgument(moduleArg), - Version = invocation.ParseResult.GetValueForOption(_ModuleAddVersion), - Force = invocation.ParseResult.GetValueForOption(_ModuleAddForce), - SkipVerification = invocation.ParseResult.GetValueForOption(_ModuleAddSkipVerification), + Version = invocation.ParseResult.GetValueForOption(_Module_Add_Version), + Force = invocation.ParseResult.GetValueForOption(_Module_Add_Force), + SkipVerification = invocation.ParseResult.GetValueForOption(_Module_Add_SkipVerification), }; var client = GetClientContext(invocation); @@ -241,7 +248,7 @@ private void AddModule() { var option = new ModuleOptions { - Path = invocation.ParseResult.GetValueForOption(_Path), + Path = invocation.ParseResult.GetValueForOption(_Global_Path), Module = invocation.ParseResult.GetValueForArgument(moduleArg), }; @@ -259,7 +266,7 @@ private void AddModule() { var option = new ModuleOptions { - Path = invocation.ParseResult.GetValueForOption(_Path), + Path = invocation.ParseResult.GetValueForOption(_Global_Path), }; var client = GetClientContext(invocation); @@ -269,13 +276,13 @@ private void AddModule() // Restore var restore = new Command("restore", CmdStrings.Module_Restore_Description); // restore.AddOption(_Path); - restore.AddOption(_ModuleRestoreForce); + restore.AddOption(_Module_Restore_Force); restore.SetHandler((invocation) => { var option = new RestoreOptions { - Path = invocation.ParseResult.GetValueForOption(_Path), - Force = invocation.ParseResult.GetValueForOption(_ModuleRestoreForce), + Path = invocation.ParseResult.GetValueForOption(_Global_Path), + Force = invocation.ParseResult.GetValueForOption(_Module_Restore_Force), }; var client = GetClientContext(invocation); invocation.ExitCode = ModuleCommand.ModuleRestore(option, client); @@ -288,15 +295,15 @@ private void AddModule() cmd.AddCommand(upgrade); cmd.AddCommand(restore); - cmd.AddOption(_Path); + cmd.AddOption(_Global_Path); Command.AddCommand(cmd); } private ClientContext GetClientContext(InvocationContext invocation) { - var option = invocation.ParseResult.GetValueForOption(_Option); - var verbose = invocation.ParseResult.GetValueForOption(_Verbose); - var debug = invocation.ParseResult.GetValueForOption(_Debug); + var option = invocation.ParseResult.GetValueForOption(_Global_Option); + var verbose = invocation.ParseResult.GetValueForOption(_Global_Verbose); + var debug = invocation.ParseResult.GetValueForOption(_Global_Debug); return new ClientContext ( diff --git a/src/PSRule.Tool/Resources/CmdStrings.Designer.cs b/src/PSRule.Tool/Resources/CmdStrings.Designer.cs index 85a11713ca..2ef61e47f0 100644 --- a/src/PSRule.Tool/Resources/CmdStrings.Designer.cs +++ b/src/PSRule.Tool/Resources/CmdStrings.Designer.cs @@ -69,6 +69,42 @@ internal static string Cmd_Description { } } + /// + /// Looks up a localized string similar to Return debug output.. + /// + internal static string Global_Debug_Description { + get { + return ResourceManager.GetString("Global_Debug_Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Specifies the path to an options file.. + /// + internal static string Global_Option_Description { + get { + return ResourceManager.GetString("Global_Option_Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The path to run commands within. By default, this is the current working directory.. + /// + internal static string Global_Path_Description { + get { + return ResourceManager.GetString("Global_Path_Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Return verbose output.. + /// + internal static string Global_Verbose_Description { + get { + return ResourceManager.GetString("Global_Verbose_Description", resourceCulture); + } + } + /// /// Looks up a localized string similar to Add one or more modules to the lock file.. /// @@ -187,74 +223,65 @@ internal static string Module_Upgrade_Description { } /// - /// Looks up a localized string similar to Return debug output.. + /// Looks up a localized string similar to The name of a specific baseline to use.. /// - internal static string Options_Debug_Description { + internal static string Run_Baseline_Description { get { - return ResourceManager.GetString("Options_Debug_Description", resourceCulture); + return ResourceManager.GetString("Run_Baseline_Description", resourceCulture); } } /// - /// Looks up a localized string similar to The name of one or more modules to use during analysis.. + /// Looks up a localized string similar to Run rules against an input path and output the results.. /// - internal static string Options_Module_Description { + internal static string Run_Description { get { - return ResourceManager.GetString("Options_Module_Description", resourceCulture); + return ResourceManager.GetString("Run_Description", resourceCulture); } } /// - /// Looks up a localized string similar to Specifies the path to an options file.. + /// Looks up a localized string similar to The file or directory path to search for input file to use during a run. By default, this is the current working path.. /// - internal static string Options_Option_Description { + internal static string Run_InputPath_Description { get { - return ResourceManager.GetString("Options_Option_Description", resourceCulture); + return ResourceManager.GetString("Run_InputPath_Description", resourceCulture); } } /// - /// Looks up a localized string similar to The path to run commands within. By default, this is the current working directory.. + /// Looks up a localized string similar to The name of one or more modules that contain rules or resources to use during a run.. /// - internal static string Options_Path_Description { + internal static string Run_Module_Description { get { - return ResourceManager.GetString("Options_Path_Description", resourceCulture); + return ResourceManager.GetString("Run_Module_Description", resourceCulture); } } /// - /// Looks up a localized string similar to Return verbose output.. - /// - internal static string Options_Verbose_Description { - get { - return ResourceManager.GetString("Options_Verbose_Description", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The name of a specific baseline to use.. + /// Looks up a localized string similar to Specifies the rule results to show in output. By default, Pass/ Fail/ Error results are shown.. /// - internal static string Run_Baseline_Description { + internal static string Run_Outcome_Description { get { - return ResourceManager.GetString("Run_Baseline_Description", resourceCulture); + return ResourceManager.GetString("Run_Outcome_Description", resourceCulture); } } /// - /// Looks up a localized string similar to Run rule analysis.. + /// Looks up a localized string similar to Specifies the format to use when outputting results.. /// - internal static string Run_Description { + internal static string Run_OutputFormat_Description { get { - return ResourceManager.GetString("Run_Description", resourceCulture); + return ResourceManager.GetString("Run_OutputFormat_Description", resourceCulture); } } /// - /// Looks up a localized string similar to Specifies the rule results to show in output. By default, Pass/ Fail/ Error results are shown.. + /// Looks up a localized string similar to Specifies a path to write results to.. /// - internal static string Run_Outcome_Description { + internal static string Run_OutputPath_Description { get { - return ResourceManager.GetString("Run_Outcome_Description", resourceCulture); + return ResourceManager.GetString("Run_OutputPath_Description", resourceCulture); } } } diff --git a/src/PSRule.Tool/Resources/CmdStrings.resx b/src/PSRule.Tool/Resources/CmdStrings.resx index 55134ff00b..4df7b89b9d 100644 --- a/src/PSRule.Tool/Resources/CmdStrings.resx +++ b/src/PSRule.Tool/Resources/CmdStrings.resx @@ -150,19 +150,19 @@ Upgrade to the latest versions of any modules within the lock file. - + Return debug output. - - The name of one or more modules to use during analysis. + + The name of one or more modules that contain rules or resources to use during a run. - + Specifies the path to an options file. - + The path to run commands within. By default, this is the current working directory. - + Return verbose output. @@ -175,7 +175,7 @@ The name of a specific baseline to use. - Run rule analysis. + Run rules against an input path and output the results. Specifies the rule results to show in output. By default, Pass/ Fail/ Error results are shown. @@ -183,4 +183,13 @@ Force the creation of a new module lock file, even if one already exists. + + The file or directory path to search for input file to use during a run. By default, this is the current working path. + + + Specifies the format to use when outputting results. + + + Specifies a path to write results to. + \ No newline at end of file From 483b4b3ab91f5b791720eccc5019bf8191151c00 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 17 Jan 2024 09:03:37 +1000 Subject: [PATCH 168/177] Bump xunit from 2.6.5 to 2.6.6 (#1732) * Bump xunit from 2.6.5 to 2.6.6 Bumps [xunit](https://github.com/xunit/xunit) from 2.6.5 to 2.6.6. - [Commits](https://github.com/xunit/xunit/compare/2.6.5...2.6.6) --- updated-dependencies: - dependency-name: xunit dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * Bump change log --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Bernie White --- docs/CHANGELOG-v3.md | 4 ++-- tests/PSRule.Tests/PSRule.Tests.csproj | 2 +- tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/CHANGELOG-v3.md b/docs/CHANGELOG-v3.md index ca55e9f6bf..18e9aa52cb 100644 --- a/docs/CHANGELOG-v3.md +++ b/docs/CHANGELOG-v3.md @@ -43,8 +43,8 @@ What's changed since pre-release v3.0.0-B0122: [#1725](https://github.com/microsoft/PSRule/pull/1725) - Bump BenchmarkDotNet.Diagnostics.Windows to v0.13.12. [#1728](https://github.com/microsoft/PSRule/pull/1728) - - Bump xunit to v2.6.5. - [#1724](https://github.com/microsoft/PSRule/pull/1724) + - Bump xunit to v2.6.6. + [#1732](https://github.com/microsoft/PSRule/pull/1732) - Bump xunit.runner.visualstudio to v2.5.6. [#1717](https://github.com/microsoft/PSRule/pull/1717) - Bump System.Drawing.Common to v8.0.1. diff --git a/tests/PSRule.Tests/PSRule.Tests.csproj b/tests/PSRule.Tests/PSRule.Tests.csproj index e1efa943ec..df320cd486 100644 --- a/tests/PSRule.Tests/PSRule.Tests.csproj +++ b/tests/PSRule.Tests/PSRule.Tests.csproj @@ -13,7 +13,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj b/tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj index 1180d45325..17cd22054f 100644 --- a/tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj +++ b/tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj @@ -11,7 +11,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive From e30d754fe29992f93e8d33fd3ae894c0338c5700 Mon Sep 17 00:00:00 2001 From: Bernie White Date: Wed, 17 Jan 2024 09:40:01 +1000 Subject: [PATCH 169/177] Pre-release v3.0.0-B0137 (#1735) * Pre-release v3.0.0-B0137 * Doc updates --- docs/CHANGELOG-v3.md | 2 ++ docs/upgrade-notes.md | 31 +++++++++++++++++++++++-------- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/docs/CHANGELOG-v3.md b/docs/CHANGELOG-v3.md index 18e9aa52cb..96cef3f351 100644 --- a/docs/CHANGELOG-v3.md +++ b/docs/CHANGELOG-v3.md @@ -27,6 +27,8 @@ See [upgrade notes][1] for helpful information when upgrading from previous vers ## Unreleased +## v3.0.0-B0137 (pre-release) + What's changed since pre-release v3.0.0-B0122: - General improvements: diff --git a/docs/upgrade-notes.md b/docs/upgrade-notes.md index ea225bc85f..d6c31df78b 100644 --- a/docs/upgrade-notes.md +++ b/docs/upgrade-notes.md @@ -33,7 +33,7 @@ To resolve any issue caused by this change, you can: From _v3.0.0_, PSRule requires: -- Windows PowerShell 5.1 for running as a PowerShell module. OR +- Windows PowerShell 5.1 for running as a PowerShell module. _OR_ - PowerShell 7.3 or later for development, building locally, or running as a PowerShell module. Support for Windows PowerShell 5.1 is deprecated and will be removed in a future release of PSRule (v4). @@ -42,16 +42,29 @@ We recommend upgrading to PowerShell 7.3 or later. At the time of writing, PowerShell 7.3 is the most recent version of PowerShell. PowerShell 7.4 is expected, and PSRule v4 will aim to support the latest version of PowerShell as the minimum requirement. -### Rename of analyze CLI command to run +### Changes to CLI commands -Previously in PSRule _v2.8.0_ to _v2.9.0, the CLI command used to run rules was `analyze`. -From _v3.0.0_, the CLI command has been renamed to `run`. -This change was made to simplify the CLI usage and reduce confusion that might occur between locales. +From _v3.0.0_, the CLI command names have been renamed to simplify usage. +The following changes have been made: -For example: `ps-rule analyze` is now `ps-rule run`. +- To run rules, use `run` instead of `analyze`. i.e. `ps-rule run`. +- To restore modules for a workspace, use `module restore` instead of `restore`. i.e. `ps-rule module restore`. The `run` command provides similar output to the `Assert-PSRule` cmdlet in PowerShell. +Previously the `restore` command installed modules based on the configuration of the [Requires][3] option. +From _v3.0.0_, the `module restore` command installs modules based on: + +- The module lock file `ps-rule.lock.json` if set. + Use `module` [CLI commands][5] to manage the [lock file][6]. _AND_ +- Modules defined in the [Include.Module][4] option, if set. + Additionally the [Requires][3] option is used to constrain the version of modules installed. + + [3]: concepts/PSRule/en-US/about_PSRule_Options.md#requires + [4]: concepts/PSRule/en-US/about_PSRule_Options.md#includemodule + [5]: concepts/cli/module.md + [6]: concepts/lockfile.md + ## Upgrading to v2.0.0 ### Resources naming restrictions @@ -164,7 +177,8 @@ Currently this must be set to `github.com/microsoft/PSRule/v1`. ### Change in source file discovery for Get-PSRuleHelp -Previously in PSRule _v1.11.0_ and prior versions, rules would show up twice when running `Get-PSRuleHelp` in the context of a module and in the same working directory of the module. +Previously in PSRule _v1.11.0_ and prior versions, +rules would show up twice when running `Get-PSRuleHelp` in the context of a module and in the same working directory of the module. This behavior has now been removed from _v2.0.0_. Module files are now preferred over loose files, and rules are only shown once in the output. @@ -195,7 +209,8 @@ M1.Rule2 TestModule This is the default ### Require source discovery from current working directory to be explicitly included -Previously in PSRule _v1.11.0_ and prior versions, rule sources from the current working directory without the `-Path` and `-Module` parameters were automatically included. +Previously in PSRule _v1.11.0_ and prior versions, +rule sources from the current working directory without the `-Path` and `-Module` parameters were automatically included. This behavior has now been removed from _v2.0.0_. Rules sources in the current working directory are only included if `-Path .` or `-Path $PWD` is specified. From aae14bf8ab767d54bc83d920a6b892ac1d17bb4f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 24 Jan 2024 09:39:36 +1000 Subject: [PATCH 170/177] Bump YamlDotNet from 13.7.1 to 15.1.0 (#1737) * Bump YamlDotNet from 13.7.1 to 15.1.0 Bumps [YamlDotNet](https://github.com/aaubry/YamlDotNet) from 13.7.1 to 15.1.0. - [Release notes](https://github.com/aaubry/YamlDotNet/releases) - [Commits](https://github.com/aaubry/YamlDotNet/compare/v13.7.1...v15.1.0) --- updated-dependencies: - dependency-name: YamlDotNet dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] * Bump change log --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Bernie White --- docs/CHANGELOG-v3.md | 6 ++++++ src/PSRule.Benchmark/packages.lock.json | 6 +++--- src/PSRule.SDK/packages.lock.json | 6 +++--- src/PSRule.Tool/packages.lock.json | 6 +++--- src/PSRule.Types/PSRule.Types.csproj | 2 +- src/PSRule.Types/packages.lock.json | 6 +++--- src/PSRule/packages.lock.json | 6 +++--- 7 files changed, 22 insertions(+), 16 deletions(-) diff --git a/docs/CHANGELOG-v3.md b/docs/CHANGELOG-v3.md index 96cef3f351..384fa44c86 100644 --- a/docs/CHANGELOG-v3.md +++ b/docs/CHANGELOG-v3.md @@ -27,6 +27,12 @@ See [upgrade notes][1] for helpful information when upgrading from previous vers ## Unreleased +What's changed since pre-release v3.0.0-B0137: + +- Engineering: + - Bump YamlDotNet to v15.1.0. + [#1737](https://github.com/microsoft/PSRule/pull/1737) + ## v3.0.0-B0137 (pre-release) What's changed since pre-release v3.0.0-B0122: diff --git a/src/PSRule.Benchmark/packages.lock.json b/src/PSRule.Benchmark/packages.lock.json index 625bf5ce0c..8720e6f4b7 100644 --- a/src/PSRule.Benchmark/packages.lock.json +++ b/src/PSRule.Benchmark/packages.lock.json @@ -1792,8 +1792,8 @@ }, "YamlDotNet": { "type": "Transitive", - "resolved": "13.7.1", - "contentHash": "X4m1PnFcJwvAj1sCDMntg/eZcX96CJLrWMiYnq41KqhFVZPuw63ZTSxIGqgdCwHWHvCAyTxheELC/VDf1HsU2A==" + "resolved": "15.1.0", + "contentHash": "fXrqmKkzBtXeJiHEsZOPEWkonHweiwk/l0Hqhz4yMIZPh57kZy03Xbj2/e8HV1QIkTw7yeBe9bbphuE3YiI4vQ==" }, "Microsoft.PSRule.Badges": { "type": "Project", @@ -1814,7 +1814,7 @@ "type": "Project", "dependencies": { "Newtonsoft.Json": "[13.0.3, )", - "YamlDotNet": "[13.7.1, )" + "YamlDotNet": "[15.1.0, )" } } } diff --git a/src/PSRule.SDK/packages.lock.json b/src/PSRule.SDK/packages.lock.json index 72daeae1cb..1e5cedd17e 100644 --- a/src/PSRule.SDK/packages.lock.json +++ b/src/PSRule.SDK/packages.lock.json @@ -675,8 +675,8 @@ }, "YamlDotNet": { "type": "Transitive", - "resolved": "13.7.1", - "contentHash": "X4m1PnFcJwvAj1sCDMntg/eZcX96CJLrWMiYnq41KqhFVZPuw63ZTSxIGqgdCwHWHvCAyTxheELC/VDf1HsU2A==" + "resolved": "15.1.0", + "contentHash": "fXrqmKkzBtXeJiHEsZOPEWkonHweiwk/l0Hqhz4yMIZPh57kZy03Xbj2/e8HV1QIkTw7yeBe9bbphuE3YiI4vQ==" }, "Microsoft.PSRule.Badges": { "type": "Project", @@ -697,7 +697,7 @@ "type": "Project", "dependencies": { "Newtonsoft.Json": "[13.0.3, )", - "YamlDotNet": "[13.7.1, )" + "YamlDotNet": "[15.1.0, )" } } } diff --git a/src/PSRule.Tool/packages.lock.json b/src/PSRule.Tool/packages.lock.json index 23de967d47..321b5589d9 100644 --- a/src/PSRule.Tool/packages.lock.json +++ b/src/PSRule.Tool/packages.lock.json @@ -1331,8 +1331,8 @@ }, "YamlDotNet": { "type": "Transitive", - "resolved": "13.7.1", - "contentHash": "X4m1PnFcJwvAj1sCDMntg/eZcX96CJLrWMiYnq41KqhFVZPuw63ZTSxIGqgdCwHWHvCAyTxheELC/VDf1HsU2A==" + "resolved": "15.1.0", + "contentHash": "fXrqmKkzBtXeJiHEsZOPEWkonHweiwk/l0Hqhz4yMIZPh57kZy03Xbj2/e8HV1QIkTw7yeBe9bbphuE3YiI4vQ==" }, "Microsoft.PSRule.Badges": { "type": "Project", @@ -1359,7 +1359,7 @@ "type": "Project", "dependencies": { "Newtonsoft.Json": "[13.0.3, )", - "YamlDotNet": "[13.7.1, )" + "YamlDotNet": "[15.1.0, )" } } } diff --git a/src/PSRule.Types/PSRule.Types.csproj b/src/PSRule.Types/PSRule.Types.csproj index 7767750dfa..c4680359ce 100644 --- a/src/PSRule.Types/PSRule.Types.csproj +++ b/src/PSRule.Types/PSRule.Types.csproj @@ -17,7 +17,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/src/PSRule.Types/packages.lock.json b/src/PSRule.Types/packages.lock.json index cf426a0738..bc0188ba31 100644 --- a/src/PSRule.Types/packages.lock.json +++ b/src/PSRule.Types/packages.lock.json @@ -35,9 +35,9 @@ }, "YamlDotNet": { "type": "Direct", - "requested": "[13.7.1, )", - "resolved": "13.7.1", - "contentHash": "X4m1PnFcJwvAj1sCDMntg/eZcX96CJLrWMiYnq41KqhFVZPuw63ZTSxIGqgdCwHWHvCAyTxheELC/VDf1HsU2A==" + "requested": "[15.1.0, )", + "resolved": "15.1.0", + "contentHash": "fXrqmKkzBtXeJiHEsZOPEWkonHweiwk/l0Hqhz4yMIZPh57kZy03Xbj2/e8HV1QIkTw7yeBe9bbphuE3YiI4vQ==" }, "Microsoft.Build.Tasks.Git": { "type": "Transitive", diff --git a/src/PSRule/packages.lock.json b/src/PSRule/packages.lock.json index c1308d52bd..e7fd4e2d1d 100644 --- a/src/PSRule/packages.lock.json +++ b/src/PSRule/packages.lock.json @@ -689,8 +689,8 @@ }, "YamlDotNet": { "type": "Transitive", - "resolved": "13.7.1", - "contentHash": "X4m1PnFcJwvAj1sCDMntg/eZcX96CJLrWMiYnq41KqhFVZPuw63ZTSxIGqgdCwHWHvCAyTxheELC/VDf1HsU2A==" + "resolved": "15.1.0", + "contentHash": "fXrqmKkzBtXeJiHEsZOPEWkonHweiwk/l0Hqhz4yMIZPh57kZy03Xbj2/e8HV1QIkTw7yeBe9bbphuE3YiI4vQ==" }, "Microsoft.PSRule.Badges": { "type": "Project", @@ -702,7 +702,7 @@ "type": "Project", "dependencies": { "Newtonsoft.Json": "[13.0.3, )", - "YamlDotNet": "[13.7.1, )" + "YamlDotNet": "[15.1.0, )" } } } From c095da8aae6c96da3180037719a090b03a14d1bf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 25 Jan 2024 09:22:53 +1000 Subject: [PATCH 171/177] Bump mkdocs-material from 9.5.4 to 9.5.5 (#1738) Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.5.4 to 9.5.5. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.5.4...9.5.5) --- updated-dependencies: - dependency-name: mkdocs-material dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements-docs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-docs.txt b/requirements-docs.txt index 21968184c5..870e3d079d 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -1,5 +1,5 @@ mkdocs==1.5.3 -mkdocs-material==9.5.4 +mkdocs-material==9.5.5 pymdown-extensions==10.7 mike==2.0.0 mkdocs-simple-hooks==0.1.5 From cf9f33e15e8f373309416f699075e171095e57bf Mon Sep 17 00:00:00 2001 From: Bernie White Date: Fri, 26 Jan 2024 17:45:38 +1000 Subject: [PATCH 172/177] SARIF output format improvements #1739 #1740 (#1741) --- .vscode/settings.json | 4 + docs/CHANGELOG-v3.md | 5 + docs/analysis-output.md | 7 + .../Options/ExecutionActionPreference.cs | 4 + src/PSRule.Types/Options/LanguageMode.cs | 4 + src/PSRule.Types/Options/SessionState.cs | 4 + src/PSRule/Common/GitHelper.cs | 44 +- src/PSRule/Common/HashAlgorithmExtensions.cs | 20 +- src/PSRule/Common/JsonConverters.cs | 12 +- .../Configuration/ConfigurationOption.cs | 2 +- src/PSRule/Configuration/FieldMap.cs | 2 + src/PSRule/Pipeline/Output/SarifBuilder.cs | 475 ++++++++++++++++++ .../Pipeline/Output/SarifOutputWriter.cs | 322 ------------ src/PSRule/Pipeline/PipelineContext.cs | 10 +- tests/PSRule.Tests/OutputWriterTests.cs | 88 +++- 15 files changed, 641 insertions(+), 362 deletions(-) create mode 100644 src/PSRule/Pipeline/Output/SarifBuilder.cs diff --git a/.vscode/settings.json b/.vscode/settings.json index 954682c10d..a422e31f7e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -95,7 +95,9 @@ "deserializes", "Gitter", "hashtable", + "HEADREF", "Kubernetes", + "Multiformat", "Newtonsoft", "NOTCOUNT", "proxied", @@ -103,12 +105,14 @@ "PSRULE", "pwsh", "quickstart", + "REPOROOT", "runspace", "runspaces", "SARIF", "SBOM", "subselector", "unencrypted", + "Worktree", "xunit" ], "[csharp]": { diff --git a/docs/CHANGELOG-v3.md b/docs/CHANGELOG-v3.md index 384fa44c86..7eeb56a3a4 100644 --- a/docs/CHANGELOG-v3.md +++ b/docs/CHANGELOG-v3.md @@ -29,6 +29,11 @@ See [upgrade notes][1] for helpful information when upgrading from previous vers What's changed since pre-release v3.0.0-B0137: +- General improvements: + - SARIF output has been improved to include effective configuration from a run by @BernieWhite. + [#1739](https://github.com/microsoft/PSRule/issues/1739) + - SARIF output has been improved to include file hashes for source files from a run by @BernieWhite. + [#1740](https://github.com/microsoft/PSRule/issues/1740) - Engineering: - Bump YamlDotNet to v15.1.0. [#1737](https://github.com/microsoft/PSRule/pull/1737) diff --git a/docs/analysis-output.md b/docs/analysis-output.md index b203deaa3e..c7a0337883 100644 --- a/docs/analysis-output.md +++ b/docs/analysis-output.md @@ -257,4 +257,11 @@ To add the scans tab to build results the [SARIF SAST Scans Tab][2] extension ne [2]: https://marketplace.visualstudio.com/items?itemName=sariftools.scans +### Verifying configuration + +:octicons-milestone-24: v3.0.0 + +The configuration used to run PSRule is included in properties of the run. +This can be used to verify the configuration used to run PSRule. + *[SARIF]: Static Analysis Results Interchange Format diff --git a/src/PSRule.Types/Options/ExecutionActionPreference.cs b/src/PSRule.Types/Options/ExecutionActionPreference.cs index d2e6cd0df8..25715af4ca 100644 --- a/src/PSRule.Types/Options/ExecutionActionPreference.cs +++ b/src/PSRule.Types/Options/ExecutionActionPreference.cs @@ -1,12 +1,16 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using Newtonsoft.Json.Converters; +using Newtonsoft.Json; + namespace PSRule.Options; /// /// Determines the action to take for execution behaviors. /// See for the specific behaviors that are configurable. /// +[JsonConverter(typeof(StringEnumConverter))] public enum ExecutionActionPreference { /// diff --git a/src/PSRule.Types/Options/LanguageMode.cs b/src/PSRule.Types/Options/LanguageMode.cs index 5996b0e73c..d06a6de32c 100644 --- a/src/PSRule.Types/Options/LanguageMode.cs +++ b/src/PSRule.Types/Options/LanguageMode.cs @@ -1,6 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using Newtonsoft.Json.Converters; +using Newtonsoft.Json; + namespace PSRule.Options; /// @@ -8,6 +11,7 @@ namespace PSRule.Options; /// Does not affect YAML or JSON expressions. /// /// +[JsonConverter(typeof(StringEnumConverter))] public enum LanguageMode { /// diff --git a/src/PSRule.Types/Options/SessionState.cs b/src/PSRule.Types/Options/SessionState.cs index a96e27469f..a5b23de07d 100644 --- a/src/PSRule.Types/Options/SessionState.cs +++ b/src/PSRule.Types/Options/SessionState.cs @@ -1,11 +1,15 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using Newtonsoft.Json.Converters; +using Newtonsoft.Json; + namespace PSRule.Options; /// /// Configures how the initial PowerShell sandbox for executing rules is created. /// +[JsonConverter(typeof(StringEnumConverter))] public enum SessionState { /// diff --git a/src/PSRule/Common/GitHelper.cs b/src/PSRule/Common/GitHelper.cs index 8dacc73ae3..bbd7d98bf8 100644 --- a/src/PSRule/Common/GitHelper.cs +++ b/src/PSRule/Common/GitHelper.cs @@ -130,17 +130,17 @@ public static bool TryRepository(out string value, string path = null) } // Try .git/ - return false; + return TryGetOriginUrl(path, out value); } public static bool TryGetChangedFiles(string baseRef, string filter, string options, out string[] files) { // Get current tip - var source = TryRevision(out var source_sha) ? source_sha : "HEAD"; + var source = TryRevision(out var source_sha) ? source_sha : GIT_HEAD; var target = !string.IsNullOrEmpty(baseRef) ? baseRef : "HEAD^"; var bin = GetGitBinary(); - var args = GetGitArgs(target, source, filter, options); + var args = GetDiffArgs(target, source, filter, options); var tool = ExternalTool.Get(null, bin); files = Array.Empty(); @@ -157,7 +157,7 @@ private static string GetGitBinary() RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? "git" : "git.exe"; } - private static string GetGitArgs(string target, string source, string filter, string options) + private static string GetDiffArgs(string target, string source, string filter, string options) { return $"diff --diff-filter={filter} --ignore-submodules=all --name-only --no-renames {target}"; } @@ -195,8 +195,42 @@ private static bool TryCommit(string path, out string value, out bool isRef) if (lines == null || lines.Length == 0) return false; - isRef = lines[0].StartsWith(GIT_REF_PREFIX, System.StringComparison.OrdinalIgnoreCase); + isRef = lines[0].StartsWith(GIT_REF_PREFIX, StringComparison.OrdinalIgnoreCase); value = isRef ? lines[0].Substring(5) : lines[0]; return true; } + + /// + /// Try to get the origin URL from the git config. + /// + private static bool TryGetOriginUrl(string path, out string value) + { + value = null; + + try + { + var bin = GetGitBinary(); + var args = GetWorktreeConfigArgs(); + var tool = ExternalTool.Get(null, bin); + + string[] lines = null; + if (!tool.WaitForExit(args, out var exitCode) || exitCode != 0) + return false; + + lines = tool.GetOutput().Split(new string[] { System.Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries); + var origin = lines.Where(line => line.StartsWith("remote.origin.url=", StringComparison.OrdinalIgnoreCase)).FirstOrDefault()?.Split('=')?[1]; + value = origin; + } + catch + { + // Fail silently. + } + + return value != null; + } + + private static string GetWorktreeConfigArgs() + { + return "config --worktree --list"; + } } diff --git a/src/PSRule/Common/HashAlgorithmExtensions.cs b/src/PSRule/Common/HashAlgorithmExtensions.cs index 31a9e5125f..441c32961e 100644 --- a/src/PSRule/Common/HashAlgorithmExtensions.cs +++ b/src/PSRule/Common/HashAlgorithmExtensions.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using System.Security.Cryptography; @@ -12,4 +12,22 @@ public static string GetDigest(this HashAlgorithm algorithm, byte[] buffer) var hash = algorithm.ComputeHash(buffer); return string.Join("", hash.Select(b => b.ToString("x2")).ToArray()); } + + public static string GetFileDigest(this HashAlgorithm algorithm, string path) + { + return algorithm.GetDigest(File.ReadAllBytes(path)); + } + + public static HashAlgorithm GetHashAlgorithm(this Options.HashAlgorithm algorithm) + { + if (algorithm == Options.HashAlgorithm.SHA256) + return SHA256.Create(); + + return algorithm == Options.HashAlgorithm.SHA384 ? SHA384.Create() : SHA512.Create(); + } + + public static string GetHashAlgorithmName(this Options.HashAlgorithm algorithm) + { + return algorithm == Options.HashAlgorithm.SHA256 ? "sha-256" : algorithm == Options.HashAlgorithm.SHA384 ? "sha-384" : "sha-512"; + } } diff --git a/src/PSRule/Common/JsonConverters.cs b/src/PSRule/Common/JsonConverters.cs index ff7fec4562..aeae1c946c 100644 --- a/src/PSRule/Common/JsonConverters.cs +++ b/src/PSRule/Common/JsonConverters.cs @@ -536,7 +536,7 @@ internal sealed class FieldMapJsonConverter : JsonConverter { public override bool CanRead => true; - public override bool CanWrite => false; + public override bool CanWrite => true; public override bool CanConvert(Type objectType) { @@ -552,7 +552,15 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { - throw new NotImplementedException(); + if (value is not FieldMap map) return; + + writer.WriteStartObject(); + foreach (var field in map) + { + writer.WritePropertyName(field.Key); + serializer.Serialize(writer, field.Value); + } + writer.WriteEndObject(); } private static void ReadFieldMap(FieldMap map, JsonReader reader) diff --git a/src/PSRule/Configuration/ConfigurationOption.cs b/src/PSRule/Configuration/ConfigurationOption.cs index d20f2f4f16..6936e8db47 100644 --- a/src/PSRule/Configuration/ConfigurationOption.cs +++ b/src/PSRule/Configuration/ConfigurationOption.cs @@ -42,7 +42,7 @@ public static implicit operator ConfigurationOption(Hashtable hashtable) } /// - /// 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 ConfigurationOption Combine(ConfigurationOption o1, ConfigurationOption o2) diff --git a/src/PSRule/Configuration/FieldMap.cs b/src/PSRule/Configuration/FieldMap.cs index 0501720f92..9cfdab0f98 100644 --- a/src/PSRule/Configuration/FieldMap.cs +++ b/src/PSRule/Configuration/FieldMap.cs @@ -3,12 +3,14 @@ using System.Collections; using System.Dynamic; +using Newtonsoft.Json; namespace PSRule.Configuration; /// /// A mapping of fields to property names. /// +[JsonConverter(typeof(FieldMapJsonConverter))] public sealed class FieldMap : DynamicObject, IEnumerable> { private readonly Dictionary _Map; diff --git a/src/PSRule/Pipeline/Output/SarifBuilder.cs b/src/PSRule/Pipeline/Output/SarifBuilder.cs new file mode 100644 index 0000000000..29bb92438c --- /dev/null +++ b/src/PSRule/Pipeline/Output/SarifBuilder.cs @@ -0,0 +1,475 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Microsoft.CodeAnalysis.Sarif; +using PSRule.Configuration; +using PSRule.Data; +using PSRule.Definitions.Rules; +using PSRule.Definitions; +using PSRule.Resources; +using PSRule.Rules; +using PSRule.Options; +using Newtonsoft.Json.Linq; +using Newtonsoft.Json; +using System.Collections; + +namespace PSRule.Pipeline.Output; + +#nullable enable + +/// +/// A helper to build a SARIF log. +/// +internal sealed class SarifBuilder +{ + private const string TOOL_NAME = "PSRule"; + private const string TOOL_ORG = "Microsoft Corporation"; + private const string TOOL_GUID = "0130215d-58eb-4887-b6fa-31ed02500569"; + private const string RECOMMENDATION_MESSAGE_ID = "recommendation"; + private const string LOCATION_KIND_OBJECT = "object"; + private const string LOCATION_ID_REPOROOT = "REPO_ROOT"; + + private readonly Run _Run; + private readonly System.Security.Cryptography.HashAlgorithm _ConfiguredHashAlgorithm; + private readonly string _ConfiguredHashAlgorithmName; + private readonly System.Security.Cryptography.HashAlgorithm? _SHA265; + private readonly PSRuleOption _Option; + private readonly Dictionary _Rules; + private readonly Dictionary _Extensions; + private readonly Dictionary _Artifacts; + + public SarifBuilder(Source[] source, PSRuleOption option) + { + _Option = option; + _Rules = new Dictionary(); + _Extensions = new Dictionary(); + _Artifacts = new Dictionary(); + _Run = new Run + { + Tool = GetTool(source), + Results = new List(), + Invocations = GetInvocation(), + AutomationDetails = GetAutomationDetails(), + OriginalUriBaseIds = GetBaseIds(), + VersionControlProvenance = GetVersionControl(option.Repository), + }; + var algorithm = option.Execution.HashAlgorithm.GetValueOrDefault(ExecutionOption.Default.HashAlgorithm!.Value); + _ConfiguredHashAlgorithm = algorithm.GetHashAlgorithm(); + _ConfiguredHashAlgorithmName = algorithm.GetHashAlgorithmName(); + + // Always include SHA-256 to allow comparison with other tools and formats such as SPDX. + _SHA265 = algorithm != HashAlgorithm.SHA256 ? HashAlgorithm.SHA256.GetHashAlgorithm() : null; + } + + /// + /// Get information from version control system. + /// + /// + /// https://docs.oasis-open.org/sarif/sarif/v2.1.0/os/sarif-v2.1.0-os.html#_Toc34317497 + /// + private static List GetVersionControl(RepositoryOption option) + { + var repository = option.Url; + return new List() + { + new() { + RepositoryUri = !string.IsNullOrEmpty(repository) ? new Uri(repository) : null, + RevisionId = !string.IsNullOrEmpty(repository) && GitHelper.TryRevision(out var revision) ? revision : null, + Branch = !string.IsNullOrEmpty(repository) && GitHelper.TryHeadBranch(out var branch) ? branch : null, + MappedTo = new ArtifactLocation + { + UriBaseId = LOCATION_ID_REPOROOT + }, + } + }; + } + + private static Dictionary GetBaseIds() + { + return new Dictionary(1) + { + { + LOCATION_ID_REPOROOT, + new ArtifactLocation + { + Description = GetMessage(ReportStrings.SARIF_REPOROOT_Description), + } + } + }; + } + + public SarifLog Build() + { + AddArtifacts(); + AddOptions(); + + var log = new SarifLog + { + Runs = new List(1), + }; + log.Runs.Add(_Run); + return log; + } + + public void Add(RuleRecord record) + { + if (record == null) + return; + + var rule = GetRule(record); + var result = new Result + { + RuleId = rule.Id, + Rule = rule, + Kind = GetKind(record), + Level = GetLevel(record), + Message = new Message { Text = record.Recommendation }, + Locations = GetLocations(record), + }; + + AddFields(result, record); + AddAnnotations(result, record); + AddArtifacts(record); + + // SARIF2004: Use the RuleId property instead of Rule for standalone rules. + if (rule.ToolComponent.Guid == TOOL_GUID) + { + result.RuleId = rule.Id; + result.Rule = null; + } + _Run.Results.Add(result); + } + + /// + /// Add non-null fields from the record to the result. + /// + private static void AddFields(Result result, RuleRecord record) + { + if (result == null || record?.Field == null || record.Field.Count == 0) return; + + // Filter out null values. + var fields = new Hashtable(); + foreach (DictionaryEntry kv in record.Field) + { + if (kv.Value != null) + fields[kv.Key] = kv.Value; + } + + if (fields.Count > 0) + result.SetProperty("fields", fields); + } + + /// + /// Add non-null annotations from the record to the result. + /// + private static void AddAnnotations(Result result, RuleRecord record) + { + if (result == null || record?.Info?.Annotations == null || record.Info.Annotations.Count == 0) return; + + // Filter out null values. + var annotations = new Hashtable(); + foreach (DictionaryEntry kv in record.Info.Annotations) + { + if (kv.Value != null && !string.Equals("online version", kv.Key.ToString(), StringComparison.OrdinalIgnoreCase)) + annotations[kv.Key] = kv.Value; + } + + if (annotations.Count > 0) + result.SetProperty("annotations", annotations); + } + + /// + /// Get collected artifacts. + /// + private void AddArtifacts() + { + _Run.Artifacts = _Artifacts.Values.OrderBy(item => item.Location.Index).ToList(); + } + + /// + /// Add options to the run. + /// + private void AddOptions() + { + var s = new JsonSerializer(); + s.Converters.Add(new PSObjectJsonConverter()); + s.NullValueHandling = NullValueHandling.Ignore; + + var localScope = JObject.FromObject(_Option, s); + var options = new JObject + { + ["workspace"] = localScope + }; + + _Run.SetProperty("options", options); + } + + private void AddArtifacts(RuleRecord record) + { + if (record.Source == null || record.Source.Length == 0) return; + + foreach (var source in record.Source) + AddArtifact(source); + } + + private void AddArtifact(TargetSourceInfo source) + { + if (source == null || string.IsNullOrEmpty(source.File)) return; + + var relativePath = source.GetPath(useRelativePath: true); + var fullPath = source.GetPath(useRelativePath: false); + if (relativePath == null || fullPath == null || _Artifacts.ContainsKey(relativePath)) return; + + var location = new ArtifactLocation + ( + uri: new Uri(relativePath, uriKind: UriKind.Relative), + uriBaseId: LOCATION_ID_REPOROOT, + index: _Artifacts.Count, + description: null, + properties: null + ); + var artifact = new Artifact + { + Location = location, + Hashes = GetArtifactHash(fullPath) + }; + + _Artifacts.Add(relativePath, artifact); + } + + private Dictionary? GetArtifactHash(string path) + { + if (!File.Exists(path)) return null; + + var hash = _ConfiguredHashAlgorithm.GetFileDigest(path); + var result = new Dictionary + { + [_ConfiguredHashAlgorithmName] = hash + }; + if (_SHA265 != null) + { + result["sha-256"] = _SHA265.GetFileDigest(path); + } + return result; + } + + private ReportingDescriptorReference GetRule(RuleRecord record) + { + var id = record.Ref ?? record.RuleId; + if (!_Rules.TryGetValue(id, out var descriptorReference)) + descriptorReference = AddRule(record, id); + + return descriptorReference; + } + + private ReportingDescriptorReference AddRule(RuleRecord record, string id) + { + if (string.IsNullOrEmpty(record.Info.ModuleName) || !_Extensions.TryGetValue(record.Info.ModuleName, out var toolComponent)) + toolComponent = _Run.Tool.Driver; + + // Add the rule to the component. + var descriptor = new ReportingDescriptor + { + Id = id, + Name = record.RuleName, + ShortDescription = GetMessageString(record.Info.Synopsis), + HelpUri = record.Info.GetOnlineHelpUri(), + FullDescription = GetMessageString(record.Info.Description), + MessageStrings = GetMessageStrings(record), + DefaultConfiguration = new ReportingConfiguration + { + Enabled = true, + Level = GetLevel(record), + } + }; + + toolComponent.Rules.Add(descriptor); + + // Create a reference to the rule. + var descriptorReference = new ReportingDescriptorReference + { + Id = descriptor.Id, + ToolComponent = new ToolComponentReference + { + Guid = toolComponent.Guid, + Name = toolComponent.Name, + Index = _Run.Tool.Extensions == null ? -1 : _Run.Tool.Extensions.IndexOf(toolComponent), + } + }; + _Rules.Add(id, descriptorReference); + return descriptorReference; + } + + private static RunAutomationDetails? GetAutomationDetails() + { + return PipelineContext.CurrentThread == null ? null : new RunAutomationDetails + { + Id = PipelineContext.CurrentThread.RunId, + }; + } + + private static List GetInvocation() + { + var result = new List(1); + var invocation = new Invocation + { + + }; + result.Add(invocation); + return result; + } + + private static Message GetMessage(string text) + { + return new Message + { + Text = text + }; + } + + private static MultiformatMessageString GetMessageString(InfoString text) + { + return new MultiformatMessageString + { + Text = text.Text + }; + } + + private static MultiformatMessageString GetMessageString(string text) + { + return new MultiformatMessageString + { + Text = text + }; + } + + private static Dictionary GetMessageStrings(RuleRecord record) + { + return new Dictionary(1) + { + { + RECOMMENDATION_MESSAGE_ID, + new MultiformatMessageString + { + Text = record.Recommendation + } + } + }; + } + + private static List? GetLocations(RuleRecord record) + { + if (!record.HasSource()) + return null; + + var result = new List(record.Source.Length); + for (var i = 0; i < record.Source.Length; i++) + { + result.Add(new Location + { + PhysicalLocation = GetPhysicalLocation(record.Source[i]), + LogicalLocation = new LogicalLocation + { + Name = record.TargetName, + FullyQualifiedName = string.Concat(record.TargetType, "/", record.TargetName), + Kind = LOCATION_KIND_OBJECT, + } + }); + } + return result; + } + + private static PhysicalLocation GetPhysicalLocation(TargetSourceInfo info) + { + var region = new Region + { + StartLine = info.Line ?? 1, + StartColumn = info.Position ?? 0, + }; + var location = new PhysicalLocation + { + ArtifactLocation = new ArtifactLocation + { + Uri = new Uri(info.GetPath(useRelativePath: true), UriKind.Relative), + UriBaseId = LOCATION_ID_REPOROOT, + }, + Region = region, + }; + return location; + } + + private static ResultKind GetKind(RuleRecord record) + { + if (record.Outcome == RuleOutcome.Pass) + return ResultKind.Pass; + + if (record.Outcome == RuleOutcome.Fail) + return ResultKind.Fail; + + return record.Outcome == RuleOutcome.Error || + record.Outcome == RuleOutcome.None && record.OutcomeReason == RuleOutcomeReason.Inconclusive ? + ResultKind.Open : + ResultKind.None; + } + + private static FailureLevel GetLevel(RuleRecord record) + { + if (record.Outcome != RuleOutcome.Fail) + return FailureLevel.None; + + if (record.Level == SeverityLevel.Error) + return FailureLevel.Error; + + return record.Level == SeverityLevel.Warning ? FailureLevel.Warning : FailureLevel.Note; + } + + private Tool GetTool(Source[] source) + { + var version = Engine.GetVersion(); + return new Tool + { + Driver = new ToolComponent + { + Name = TOOL_NAME, + SemanticVersion = version, + Organization = TOOL_ORG, + Guid = TOOL_GUID, + Rules = new List(), + InformationUri = new Uri("https://aka.ms/ps-rule", UriKind.Absolute), + }, + Extensions = GetExtensions(source), + }; + } + + private List? GetExtensions(Source[] source) + { + if (source == null || source.Length == 0) + return null; + + var result = new List(); + for (var i = 0; i < source.Length; i++) + { + if (source[i].Module != null && !_Extensions.ContainsKey(source[i].Module.Name)) + { + var extension = new ToolComponent + { + Name = source[i].Module.Name, + Version = source[i].Module.Version, + Guid = source[i].Module.Guid, + AssociatedComponent = new ToolComponentReference + { + Name = TOOL_NAME, + }, + InformationUri = new Uri(source[i].Module.ProjectUri, UriKind.Absolute), + Organization = source[i].Module.CompanyName, + Rules = new List(), + }; + _Extensions.Add(extension.Name, extension); + result.Add(extension); + } + } + return result.Count > 0 ? result : null; + } +} + +#nullable restore diff --git a/src/PSRule/Pipeline/Output/SarifOutputWriter.cs b/src/PSRule/Pipeline/Output/SarifOutputWriter.cs index 2740502680..c1316f0c37 100644 --- a/src/PSRule/Pipeline/Output/SarifOutputWriter.cs +++ b/src/PSRule/Pipeline/Output/SarifOutputWriter.cs @@ -2,333 +2,11 @@ // Licensed under the MIT License. using System.Text; -using Microsoft.CodeAnalysis.Sarif; using PSRule.Configuration; -using PSRule.Data; -using PSRule.Definitions; -using PSRule.Definitions.Rules; -using PSRule.Resources; using PSRule.Rules; namespace PSRule.Pipeline.Output; -internal sealed class SarifBuilder -{ - private const string TOOL_NAME = "PSRule"; - private const string TOOL_ORG = "Microsoft Corporation"; - private const string TOOL_GUID = "0130215d-58eb-4887-b6fa-31ed02500569"; - private const string RECOMMENDATION_MESSAGE_ID = "recommendation"; - private const string LOCATION_KIND_OBJECT = "object"; - private const string LOCATION_ID_REPOROOT = "REPOROOT"; - - private readonly Run _Run; - private readonly Dictionary _Rules; - private readonly Dictionary _Extensions; - - public SarifBuilder(Source[] source, PSRuleOption option) - { - _Rules = new Dictionary(); - _Extensions = new Dictionary(); - _Run = new Run - { - Tool = GetTool(source), - Results = new List(), - Invocations = GetInvocation(), - AutomationDetails = GetAutomationDetails(), - OriginalUriBaseIds = GetBaseIds(), - VersionControlProvenance = GetVersionControl(option.Repository), - }; - } - - /// - /// Get information from version control system. - /// - /// - /// https://docs.oasis-open.org/sarif/sarif/v2.1.0/os/sarif-v2.1.0-os.html#_Toc34317497 - /// - private static IList GetVersionControl(RepositoryOption option) - { - var repository = option.Url; - return new List() - { - new() { - RepositoryUri = !string.IsNullOrEmpty(repository) ? new Uri(repository) : null, - RevisionId = !string.IsNullOrEmpty(repository) && GitHelper.TryRevision(out var revision) ? revision : null, - Branch = !string.IsNullOrEmpty(repository) && GitHelper.TryHeadBranch(out var branch) ? branch : null, - MappedTo = new ArtifactLocation - { - UriBaseId = LOCATION_ID_REPOROOT - }, - } - }; - } - - private static IDictionary GetBaseIds() - { - return new Dictionary(1) - { - { - LOCATION_ID_REPOROOT, - new ArtifactLocation - { - Description = GetMessage(ReportStrings.SARIF_REPOROOT_Description), - } - } - }; - } - - public SarifLog Build() - { - var log = new SarifLog - { - Runs = new List(1), - }; - log.Runs.Add(_Run); - return log; - } - - public void Add(RuleRecord record) - { - if (record == null) - return; - - var rule = GetRule(record); - var result = new Result - { - Rule = rule, - Kind = GetKind(record), - Level = GetLevel(record), - Message = new Message { Text = record.Recommendation }, - Locations = GetLocations(record), - }; - - // SARIF2004: Use the RuleId property instead of Rule for standalone rules. - if (rule.ToolComponent.Guid == TOOL_GUID) - { - result.RuleId = rule.Id; - result.Rule = null; - } - _Run.Results.Add(result); - } - - private ReportingDescriptorReference GetRule(RuleRecord record) - { - var id = record.Ref ?? record.RuleId; - if (!_Rules.TryGetValue(id, out var descriptorReference)) - descriptorReference = AddRule(record, id); - - return descriptorReference; - } - - private ReportingDescriptorReference AddRule(RuleRecord record, string id) - { - if (string.IsNullOrEmpty(record.Info.ModuleName) || !_Extensions.TryGetValue(record.Info.ModuleName, out var toolComponent)) - toolComponent = _Run.Tool.Driver; - - // Add the rule to the component - var descriptor = new ReportingDescriptor - { - Id = id, - Name = record.RuleName, - ShortDescription = GetMessageString(record.Info.Synopsis), - HelpUri = record.Info.GetOnlineHelpUri(), - FullDescription = GetMessageString(record.Info.Description), - MessageStrings = GetMessageStrings(record), - DefaultConfiguration = new ReportingConfiguration - { - Enabled = true, - Level = GetLevel(record), - } - }; - toolComponent.Rules.Add(descriptor); - - // Create a reference to the rule - var descriptorReference = new ReportingDescriptorReference - { - Id = descriptor.Id, - ToolComponent = new ToolComponentReference - { - Guid = toolComponent.Guid, - Name = toolComponent.Name, - Index = _Run.Tool.Extensions == null ? -1 : _Run.Tool.Extensions.IndexOf(toolComponent), - } - }; - _Rules.Add(id, descriptorReference); - return descriptorReference; - } - - private static RunAutomationDetails GetAutomationDetails() - { - return PipelineContext.CurrentThread == null ? null : new RunAutomationDetails - { - Id = PipelineContext.CurrentThread.RunId, - }; - } - - private static IList GetInvocation() - { - var result = new List(1); - var invocation = new Invocation - { - - }; - result.Add(invocation); - return result; - } - - private static Message GetMessage(string text) - { - return new Message - { - Text = text - }; - } - - private static MultiformatMessageString GetMessageString(InfoString text) - { - return new MultiformatMessageString - { - Text = text.Text - }; - } - - private static MultiformatMessageString GetMessageString(string text) - { - return new MultiformatMessageString - { - Text = text - }; - } - - private static IDictionary GetMessageStrings(RuleRecord record) - { - return new Dictionary(1) - { - { - RECOMMENDATION_MESSAGE_ID, - new MultiformatMessageString - { - Text = record.Recommendation - } - } - }; - } - - private static IList GetLocations(RuleRecord record) - { - if (!record.HasSource()) - return null; - - var result = new List(record.Source.Length); - for (var i = 0; i < record.Source.Length; i++) - { - result.Add(new Location - { - PhysicalLocation = GetPhysicalLocation(record.Source[i]), - LogicalLocation = new LogicalLocation - { - Name = record.TargetName, - FullyQualifiedName = string.Concat(record.TargetType, "/", record.TargetName), - Kind = LOCATION_KIND_OBJECT, - } - }); - } - return result; - } - - private static PhysicalLocation GetPhysicalLocation(TargetSourceInfo info) - { - var region = new Region - { - StartLine = info.Line ?? 1, - StartColumn = info.Position ?? 0, - }; - var location = new PhysicalLocation - { - ArtifactLocation = new ArtifactLocation - { - Uri = new Uri(info.GetPath(useRelativePath: true), UriKind.Relative), - UriBaseId = LOCATION_ID_REPOROOT, - }, - Region = region, - }; - return location; - } - - private static ResultKind GetKind(RuleRecord record) - { - if (record.Outcome == RuleOutcome.Pass) - return ResultKind.Pass; - - if (record.Outcome == RuleOutcome.Fail) - return ResultKind.Fail; - - return record.Outcome == RuleOutcome.Error || - record.Outcome == RuleOutcome.None && record.OutcomeReason == RuleOutcomeReason.Inconclusive ? - ResultKind.Open : - ResultKind.None; - } - - private static FailureLevel GetLevel(RuleRecord record) - { - if (record.Outcome != RuleOutcome.Fail) - return FailureLevel.None; - - if (record.Level == SeverityLevel.Error) - return FailureLevel.Error; - - return record.Level == SeverityLevel.Warning ? FailureLevel.Warning : FailureLevel.Note; - } - - private Tool GetTool(Source[] source) - { - var version = Engine.GetVersion(); - return new Tool - { - Driver = new ToolComponent - { - Name = TOOL_NAME, - SemanticVersion = version, - Organization = TOOL_ORG, - Guid = TOOL_GUID, - Rules = new List(), - InformationUri = new Uri("https://aka.ms/ps-rule", UriKind.Absolute), - }, - Extensions = GetExtensions(source), - }; - } - - private IList GetExtensions(Source[] source) - { - if (source == null || source.Length == 0) - return null; - - var result = new List(); - for (var i = 0; i < source.Length; i++) - { - if (source[i].Module != null && !_Extensions.ContainsKey(source[i].Module.Name)) - { - var extension = new ToolComponent - { - Name = source[i].Module.Name, - Version = source[i].Module.Version, - Guid = source[i].Module.Guid, - AssociatedComponent = new ToolComponentReference - { - Name = TOOL_NAME, - }, - InformationUri = new Uri(source[i].Module.ProjectUri, UriKind.Absolute), - Organization = source[i].Module.CompanyName, - Rules = new List(), - }; - _Extensions.Add(extension.Name, extension); - result.Add(extension); - } - } - return result.Count > 0 ? result : null; - } -} - internal sealed class SarifOutputWriter : SerializationOutputWriter { private readonly SarifBuilder _Builder; diff --git a/src/PSRule/Pipeline/PipelineContext.cs b/src/PSRule/Pipeline/PipelineContext.cs index c601b58552..5d594b9ca9 100644 --- a/src/PSRule/Pipeline/PipelineContext.cs +++ b/src/PSRule/Pipeline/PipelineContext.cs @@ -84,7 +84,7 @@ private PipelineContext(PSRuleOption option, IHostContext hostContext, PipelineI _Unresolved = unresolved ?? new List(); _TrackedIssues = new List(); - ObjectHashAlgorithm = GetHashAlgorithm(option.Execution.HashAlgorithm.GetValueOrDefault(ExecutionOption.Default.HashAlgorithm.Value)); + ObjectHashAlgorithm = option.Execution.HashAlgorithm.GetValueOrDefault(ExecutionOption.Default.HashAlgorithm.Value).GetHashAlgorithm(); RunId = Environment.GetRunId() ?? ObjectHashAlgorithm.GetDigest(Guid.NewGuid().ToByteArray()); RunTime = Stopwatch.StartNew(); _DefaultOptionContext = _OptionBuilder?.Build(null); @@ -291,14 +291,6 @@ private void ReportIssue(RunspaceContext runspaceContext) // runspaceContext.WarnMissingApiVersion(_TrackedIssues[i].Kind, _TrackedIssues[i].Id); } - private static System.Security.Cryptography.HashAlgorithm GetHashAlgorithm(Options.HashAlgorithm hashAlgorithm) - { - if (hashAlgorithm == Options.HashAlgorithm.SHA256) - return SHA256.Create(); - - return hashAlgorithm == Options.HashAlgorithm.SHA384 ? SHA384.Create() : SHA512.Create(); - } - #region IBindingContext public bool GetPathExpression(string path, out PathExpression expression) diff --git a/tests/PSRule.Tests/OutputWriterTests.cs b/tests/PSRule.Tests/OutputWriterTests.cs index 7cea4a0ea2..db1c57b0b5 100644 --- a/tests/PSRule.Tests/OutputWriterTests.cs +++ b/tests/PSRule.Tests/OutputWriterTests.cs @@ -38,25 +38,31 @@ public void Sarif() var actual = JsonConvert.DeserializeObject(output.Output.OfType().FirstOrDefault()); Assert.NotNull(actual); - Assert.Equal("PSRule", actual["runs"][0]["tool"]["driver"]["name"]); + Assert.Equal("PSRule", actual["runs"][0]["tool"]["driver"]["name"].Value()); Assert.Equal("0.0.1", actual["runs"][0]["tool"]["driver"]["semanticVersion"].Value().Split('+')[0]); Assert.Equal("https://github.com/microsoft/PSRule.UnitTest", actual["runs"][0]["versionControlProvenance"][0]["repositoryUri"].Value()); // Pass - Assert.Equal("TestModule\\rule-001", actual["runs"][0]["results"][0]["ruleId"]); - Assert.Equal("none", actual["runs"][0]["results"][0]["level"]); + Assert.Equal("TestModule\\rule-001", actual["runs"][0]["results"][0]["ruleId"].Value()); + Assert.Equal("none", actual["runs"][0]["results"][0]["level"].Value()); // Fail with error - Assert.Equal("rid-002", actual["runs"][0]["results"][1]["ruleId"]); - Assert.Equal("error", actual["runs"][0]["results"][1]["level"]); + Assert.Equal("rid-002", actual["runs"][0]["results"][1]["ruleId"].Value()); + Assert.Equal("error", actual["runs"][0]["results"][1]["level"].Value()); + Assert.Equal("Custom annotation", actual["runs"][0]["results"][1]["properties"]["annotations"]["annotation-data"].Value()); + Assert.Equal("Custom field data", actual["runs"][0]["results"][1]["properties"]["fields"]["field-data"].Value()); // Fail with warning - Assert.Equal("rid-003", actual["runs"][0]["results"][2]["ruleId"]); + Assert.Equal("rid-003", actual["runs"][0]["results"][2]["ruleId"].Value()); Assert.Null(actual["runs"][0]["results"][2]["level"]); // Fail with note - Assert.Equal("rid-004", actual["runs"][0]["results"][3]["ruleId"]); - Assert.Equal("note", actual["runs"][0]["results"][3]["level"]); + Assert.Equal("rid-004", actual["runs"][0]["results"][3]["ruleId"].Value()); + Assert.Equal("note", actual["runs"][0]["results"][3]["level"].Value()); + + // Check options + Assert.Equal(option.Repository.Url, actual["runs"][0]["properties"]["options"]["workspace"]["Repository"]["Url"].Value()); + Assert.False(actual["runs"][0]["properties"]["options"]["workspace"]["Output"]["SarifProblemsOnly"].Value()); } [Fact] @@ -76,20 +82,20 @@ public void SarifProblemsOnly() var actual = JsonConvert.DeserializeObject(output.Output.OfType().FirstOrDefault()); Assert.NotNull(actual); - Assert.Equal("PSRule", actual["runs"][0]["tool"]["driver"]["name"]); + Assert.Equal("PSRule", actual["runs"][0]["tool"]["driver"]["name"].Value()); Assert.Equal("0.0.1", actual["runs"][0]["tool"]["driver"]["semanticVersion"].Value().Split('+')[0]); // Fail with error - Assert.Equal("rid-002", actual["runs"][0]["results"][0]["ruleId"]); - Assert.Equal("error", actual["runs"][0]["results"][0]["level"]); + Assert.Equal("rid-002", actual["runs"][0]["results"][0]["ruleId"].Value()); + Assert.Equal("error", actual["runs"][0]["results"][0]["level"].Value()); // Fail with warning - Assert.Equal("rid-003", actual["runs"][0]["results"][1]["ruleId"]); + Assert.Equal("rid-003", actual["runs"][0]["results"][1]["ruleId"].Value()); Assert.Null(actual["runs"][0]["results"][1]["level"]); // Fail with note - Assert.Equal("rid-004", actual["runs"][0]["results"][2]["ruleId"]); - Assert.Equal("note", actual["runs"][0]["results"][2]["level"]); + Assert.Equal("rid-004", actual["runs"][0]["results"][2]["ruleId"].Value()); + Assert.Equal("note", actual["runs"][0]["results"][2]["level"].Value()); } [Fact] @@ -125,7 +131,11 @@ public void Yaml() time: 500 - detail: reason: [] + field: + field-data: Custom field data info: + annotations: + annotation-data: Custom annotation moduleName: TestModule recommendation: Recommendation for rule 002 level: Error @@ -141,7 +151,11 @@ public void Yaml() time: 1000 - detail: reason: [] + field: + field-data: Custom field data info: + annotations: + annotation-data: Custom annotation moduleName: TestModule recommendation: Recommendation for rule 002 level: Warning @@ -157,7 +171,11 @@ public void Yaml() time: 1000 - detail: reason: [] + field: + field-data: Custom field data info: + annotations: + annotation-data: Custom annotation moduleName: TestModule recommendation: Recommendation for rule 002 level: Information @@ -214,7 +232,13 @@ public void Json() }, { ""detail"": {}, + ""field"": { + ""field-data"": ""Custom field data"" + }, ""info"": { + ""annotations"": { + ""annotation-data"": ""Custom annotation"" + }, ""displayName"": ""Rule 002"", ""moduleName"": ""TestModule"", ""name"": ""rule-002"", @@ -235,7 +259,13 @@ public void Json() }, { ""detail"": {}, + ""field"": { + ""field-data"": ""Custom field data"" + }, ""info"": { + ""annotations"": { + ""annotation-data"": ""Custom annotation"" + }, ""displayName"": ""Rule 002"", ""moduleName"": ""TestModule"", ""name"": ""rule-002"", @@ -256,7 +286,13 @@ public void Json() }, { ""detail"": {}, + ""field"": { + ""field-data"": ""Custom field data"" + }, ""info"": { + ""annotations"": { + ""annotation-data"": ""Custom annotation"" + }, ""displayName"": ""Rule 002"", ""moduleName"": ""TestModule"", ""name"": ""rule-002"", @@ -359,6 +395,17 @@ private static RuleRecord GetPass() private static RuleRecord GetFail(string ruleRef = "rid-002", SeverityLevel level = SeverityLevel.Error, string synopsis = "This is rule 002.", string ruleId = "TestModule\\rule-002") { + var info = new RuleHelpInfo( + "rule-002", + "Rule 002", + "TestModule", + synopsis: new InfoString(synopsis), + recommendation: new InfoString("Recommendation for rule 002") + ); + info.Annotations = new Hashtable + { + ["annotation-data"] = "Custom annotation" + }; return new RuleRecord( runId: "run-001", ruleId: ResourceId.Parse(ruleId), @@ -367,14 +414,11 @@ private static RuleRecord GetFail(string ruleRef = "rid-002", SeverityLevel leve targetName: "TestObject1", targetType: "TestType", tag: new ResourceTags(), - info: new RuleHelpInfo( - "rule-002", - "Rule 002", - "TestModule", - synopsis: new InfoString(synopsis), - recommendation: new InfoString("Recommendation for rule 002") - ), - field: new Hashtable(), + info: info, + field: new Hashtable + { + ["field-data"] = "Custom field data" + }, level: level, extent: null, outcome: RuleOutcome.Fail, From c0c0600cc80b600f8324fa7b38fd03da8c9638ba Mon Sep 17 00:00:00 2001 From: Bernie White Date: Sat, 27 Jan 2024 16:34:49 +1000 Subject: [PATCH 173/177] Allow disabling PowerShell features #1742 (#1743) --- .vscode/settings.json | 1 + README.md | 1 + docs/CHANGELOG-v3.md | 3 + .../PSRule/en-US/about_PSRule_Options.md | 123 ++++++++++++++--- schemas/PSRule-options.schema.json | 32 +++-- src/PSRule.Badges/BadgeResources.cs | 2 +- .../Options/ExecutionActionPreference.cs | 2 +- src/PSRule.Types/Options/ExecutionOption.cs | 34 ++++- src/PSRule.Types/Options/LanguageMode.cs | 6 +- .../Options/RestrictScriptSource.cs | 29 ++++ src/PSRule.Types/Options/SessionState.cs | 2 +- src/PSRule/Host/HostHelper.cs | 19 +-- src/PSRule/PSRule.psm1 | 20 +++ src/PSRule/Pipeline/InvokePipelineBuilder.cs | 4 +- src/PSRule/Pipeline/Output/SarifBuilder.cs | 10 +- src/PSRule/Pipeline/PipelineBuilder.cs | 8 ++ src/PSRule/Pipeline/PipelineContext.cs | 1 - src/PSRule/Pipeline/SourcePipeline.cs | 57 +++++--- tests/PSRule.Tests/PSRule.Options.Tests.ps1 | 39 ++++++ tests/PSRule.Tests/PSRule.Tests.yml | 1 + .../SourcePipelineBuilderTests.cs | 128 ++++++++++++++++++ 21 files changed, 451 insertions(+), 71 deletions(-) create mode 100644 src/PSRule.Types/Options/RestrictScriptSource.cs create mode 100644 tests/PSRule.Tests/SourcePipelineBuilderTests.cs diff --git a/.vscode/settings.json b/.vscode/settings.json index a422e31f7e..8e83ca3436 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -93,6 +93,7 @@ "deserialize", "deserialized", "deserializes", + "eastus", "Gitter", "hashtable", "HEADREF", diff --git a/README.md b/README.md index 781391b348..f18b4186c3 100644 --- a/README.md +++ b/README.md @@ -297,6 +297,7 @@ The following conceptual topics exist in the `PSRule` module: - [Execution.LanguageMode](https://aka.ms/ps-rule/options#executionlanguagemode) - [Execution.InvariantCulture](https://aka.ms/ps-rule/options#executioninvariantculture) - [Execution.InitialSessionState](https://aka.ms/ps-rule/options#executioninitialsessionstate) + - [Execution.RestrictScriptSource](https://aka.ms/ps-rule/options#executionrestrictscriptsource) - [Execution.RuleInconclusive](https://aka.ms/ps-rule/options#executionruleinconclusive) - [Execution.SuppressionGroupExpired](https://aka.ms/ps-rule/options#executionsuppressiongroupexpired) - [Execution.UnprocessedObject](https://aka.ms/ps-rule/options#executionunprocessedobject) diff --git a/docs/CHANGELOG-v3.md b/docs/CHANGELOG-v3.md index 7eeb56a3a4..013c15f821 100644 --- a/docs/CHANGELOG-v3.md +++ b/docs/CHANGELOG-v3.md @@ -34,6 +34,9 @@ What's changed since pre-release v3.0.0-B0137: [#1739](https://github.com/microsoft/PSRule/issues/1739) - SARIF output has been improved to include file hashes for source files from a run by @BernieWhite. [#1740](https://github.com/microsoft/PSRule/issues/1740) + - Added support to allow disabling PowerShell features that can be run from a repository by @BernieWhite. + [#1742](https://github.com/microsoft/PSRule/issues/1742) + - Added the `Execution.RestrictScriptSource` option to disable running scripts from a repository. - Engineering: - Bump YamlDotNet to v15.1.0. [#1737](https://github.com/microsoft/PSRule/pull/1737) diff --git a/docs/concepts/PSRule/en-US/about_PSRule_Options.md b/docs/concepts/PSRule/en-US/about_PSRule_Options.md index 52896e2c65..ea2e9e4a4f 100644 --- a/docs/concepts/PSRule/en-US/about_PSRule_Options.md +++ b/docs/concepts/PSRule/en-US/about_PSRule_Options.md @@ -21,6 +21,7 @@ The following workspace options are available for use: - [Execution.LanguageMode](#executionlanguagemode) - [Execution.InvariantCulture](#executioninvariantculture) - [Execution.InitialSessionState](#executioninitialsessionstate) +- [Execution.RestrictScriptSource](#executionrestrictscriptsource) - [Execution.RuleInconclusive](#executionruleinconclusive) - [Execution.SuppressionGroupExpired](#executionsuppressiongroupexpired) - [Execution.UnprocessedObject](#executionunprocessedobject) @@ -230,7 +231,8 @@ PSRule uses the following logic to determine which property should be used for b - If **none** of the configured property names exist, the field will be skipped. - If more then one property name is configured, the order they are specified in the configuration determines precedence. - i.e. The first configured property name will take precedence over the second property name. - - By default the property name will be matched ignoring case sensitivity. To use a case sensitive match, configure the [Binding.IgnoreCase](#bindingignorecase) option. + - By default the property name will be matched ignoring case sensitivity. + To use a case sensitive match, configure the [Binding.IgnoreCase](#bindingignorecase) option. Custom field bindings can be specified using: @@ -735,7 +737,7 @@ variables: :octicons-milestone-24: v2.9.0 Determines how to handle when an alias to a resource is used. -By defaut, a warning is generated, however this behaviour can be modified by this option. +By default, a warning is generated, however this behavior can be modified by this option. The following preferences are available: @@ -747,6 +749,8 @@ The following preferences are available: - `Error` (3) - Abort and throw an error. - `Debug` (4) - Continue to execute but log a debug message. +This option can be specified using: + ```powershell # PowerShell: Using the ExecutionAliasReference parameter $option = New-PSRuleOption -ExecutionAliasReference 'Error'; @@ -792,7 +796,7 @@ variables: Determines how to handle duplicate resources identifiers during execution. A duplicate resource identifier may exist if two resources are defined with the same name, ref, or alias. -By defaut, an error is thrown, however this behaviour can be modified by this option. +By default, an error is thrown, however this behavior can be modified by this option. If this option is configured to `Warn` or `Ignore` only the first resource will be used, however PSRule will continue to execute. @@ -807,6 +811,8 @@ The following preferences are available: This is the default. - `Debug` (4) - Continue to execute but log a debug message. +This option can be specified using: + ```powershell # PowerShell: Using the DuplicateResourceId parameter $option = New-PSRuleOption -DuplicateResourceId 'Warn'; @@ -857,9 +863,9 @@ By default, the _SHA512_ algorithm is used. The following algorithms are available for use in PSRule: -- SHA512 -- SHA384 -- SHA256 +- `SHA512` +- `SHA384` +- `SHA256` This option can be specified using: @@ -901,8 +907,9 @@ When PSRule is executed in an environment configured for Device Guard, only cons The following language modes are available for use in PSRule: -- FullLanguage -- ConstrainedLanguage +- `FullLanguage` - Executes with all language features. + This is the default. +- `ConstrainedLanguage` - Executes in constrained language mode that restricts the types and methods that can be used. This option can be specified using: @@ -940,7 +947,7 @@ variables: :octicons-milestone-24: v2.9.0 Determines how to report when an invariant culture is used. -By defaut, a warning is generated, however this behaviour can be modified by this option. +By default, a warning is generated, however this behavior can be modified by this option. The following preferences are available: @@ -952,6 +959,8 @@ The following preferences are available: - `Error` (3) - Abort and throw an error. - `Debug` (4) - Continue to execute but log a debug message. +This option can be specified using: + ```powershell # PowerShell: Using the ExecutionInvariantCulture parameter $option = New-PSRuleOption -ExecutionInvariantCulture 'Error'; @@ -1003,6 +1012,8 @@ The following preferences are available: This is the default. - `Minimal` (1) - Create the initial session state with only a minimum set of cmdlets loaded. +This option can be specified using: + ```powershell # PowerShell: Using the InitialSessionState parameter $option = New-PSRuleOption -InitialSessionState 'Minimal'; @@ -1042,12 +1053,73 @@ variables: value: Minimal ``` +### Execution.RestrictScriptSource + +:octicons-milestone-24: v3.0.0 + +Configures where to allow PowerShell language features (such as rules and conventions) to run from. +In locked down environments, running PowerShell scripts from the workspace may not be allowed. +Only run scripts from a trusted source. + +This option does not affect YAML or JSON based rules and resources. + +The following script source restrictions are available: + +- `Unrestricted` - PowerShell language features are allowed from workspace and modules. + This is the default. +- `ModuleOnly` - PowerShell language features are allowed from loaded modules, + but script files within the workspace are ignored. +- `DisablePowerShell` - No PowerShell language features are used during PSRule run. + When this mode is used, rules and conventions written in PowerShell will not execute. + Modules that use PowerShell rules and conventions may not work as expected. + +This option can be specified using: + +```powershell +# PowerShell: Using the RestrictScriptSource parameter +$option = New-PSRuleOption -RestrictScriptSource 'ModuleOnly'; +``` + +```powershell +# PowerShell: Using the Execution.RestrictScriptSource hashtable key +$option = New-PSRuleOption -Option @{ 'Execution.RestrictScriptSource' = 'ModuleOnly' }; +``` + +```powershell +# PowerShell: Using the RestrictScriptSource parameter to set YAML +Set-PSRuleOption -RestrictScriptSource 'ModuleOnly'; +``` + +```yaml +# YAML: Using the execution/restrictScriptSource property +execution: + restrictScriptSource: ModuleOnly +``` + +```bash +# Bash: Using environment variable +export PSRULE_EXECUTION_RESTRICTSCRIPTSOURCE=ModuleOnly +``` + +```yaml +# GitHub Actions: Using environment variable +env: + PSRULE_EXECUTION_RESTRICTSCRIPTSOURCE: ModuleOnly +``` + +```yaml +# Azure Pipelines: Using environment variable +variables: +- name: PSRULE_EXECUTION_RESTRICTSCRIPTSOURCE + value: ModuleOnly +``` + ### Execution.RuleInconclusive :octicons-milestone-24: v2.9.0 Determines how to handle rules that generate inconclusive results. -By defaut, a warning is generated, however this behaviour can be modified by this option. +By default, a warning is generated, however this behavior can be modified by this option. The following preferences are available: @@ -1059,6 +1131,8 @@ The following preferences are available: - `Error` (3) - Abort and throw an error. - `Debug` (4) - Continue to execute but log a debug message. +This option can be specified using: + ```powershell # PowerShell: Using the ExecutionRuleInconclusive parameter $option = New-PSRuleOption -ExecutionRuleInconclusive 'Error'; @@ -1104,7 +1178,7 @@ variables: Determines how to handle expired suppression groups. Regardless of the value, an expired suppression group will be ignored. -By defaut, a warning is generated, however this behaviour can be modified by this option. +By default, a warning is generated, however this behavior can be modified by this option. The following preferences are available: @@ -1116,6 +1190,8 @@ The following preferences are available: - `Error` (3) - Abort and throw an error. - `Debug` (4) - Continue to execute but log a debug message. +This option can be specified using: + ```powershell # PowerShell: Using the SuppressionGroupExpired parameter $option = New-PSRuleOption -SuppressionGroupExpired 'Error'; @@ -1161,7 +1237,7 @@ variables: Determines how to handle excluded rules. Regardless of the value, excluded rules are ignored. -By defaut, a rule is excluded silently, however this behaviour can be modified by this option. +By default, a rule is excluded silently, however this behavior can be modified by this option. The following preferences are available: @@ -1173,6 +1249,8 @@ The following preferences are available: - `Error` (3) - Abort and throw an error. - `Debug` (4) - Continue to execute but log a debug message. +This option can be specified using: + ```powershell # PowerShell: Using the ExecutionRuleExcluded parameter $option = New-PSRuleOption -ExecutionRuleExcluded 'Warn'; @@ -1218,7 +1296,7 @@ variables: Determines how to handle suppressed rules. Regardless of the value, a suppressed rule is ignored. -By defaut, a warning is generated, however this behaviour can be modified by this option. +By default, a warning is generated, however this behavior can be modified by this option. The following preferences are available: @@ -1274,7 +1352,7 @@ variables: :octicons-milestone-24: v2.9.0 Determines how to report objects that are not processed by any rule. -By defaut, a warning is generated, however this behaviour can be modified by this option. +By default, a warning is generated, however this behavior can be modified by this option. The following preferences are available: @@ -1286,6 +1364,8 @@ The following preferences are available: - `Error` (3) - Abort and throw an error. - `Debug` (4) - Continue to execute but log a debug message. +This option can be specified using: + ```powershell # PowerShell: Using the ExecutionUnprocessedObject parameter $option = New-PSRuleOption -ExecutionUnprocessedObject 'Ignore'; @@ -1748,7 +1828,7 @@ If the property specified by `ObjectPath` is a collection/ array, then each item If the property specified by `ObjectPath` does not exist, PSRule skips the object. -When using `Invoke-PSRule`, `Test-PSRuleTarget` and `Assert-PSRule` the `-ObjectPath` parameter will override any value set in configuration. +When using `Invoke-PSRule`, `Test-PSRuleTarget`, and `Assert-PSRule` the `-ObjectPath` parameter will override any value set in configuration. This option can be specified using: @@ -1874,7 +1954,7 @@ By default, all objects are processed. To change the field TargetType is bound to set the `Binding.TargetType` option. -When using `Invoke-PSRule`, `Test-PSRuleTarget` and `Assert-PSRule` the `-TargetType` parameter will override any value set in configuration. +When using `Invoke-PSRule`, `Test-PSRuleTarget`, and `Assert-PSRule` the `-TargetType` parameter will override any value set in configuration. This option can be specified using: @@ -1962,7 +2042,8 @@ logging: Limits verbose messages to a list of named verbose scopes. When using the `-Verbose` switch or preference variable, by default PSRule cmdlets log all verbose output. -When using verbose output for troubleshooting a specific rule, it may be helpful to limit verbose messages to a specific rule. +When using verbose output for troubleshooting a specific rule, +it may be helpful to limit verbose messages to a specific rule. To identify a rule to include in verbose output use the rule name. @@ -2001,7 +2082,8 @@ logging: When an object fails a rule condition the results are written to output as a structured object marked with the outcome of _Fail_. If the rule executed successfully regardless of outcome no other informational messages are shown by default. -In some circumstances such as a continuous integration (CI) pipeline, it may be preferable to see informational messages or abort the CI process if one or more _Fail_ outcomes are returned. +In some circumstances such as a continuous integration (CI) pipeline, +it may be preferable to see informational messages or abort the CI process if one or more _Fail_ outcomes are returned. By settings this option, error, warning or information messages will be generated for each rule _fail_ outcome in addition to structured output. By default, outcomes are not logged to an informational stream (i.e. None). @@ -3223,9 +3305,11 @@ convention: # Configure execution options execution: + hashAlgorithm: SHA256 duplicateResourceId: Warn languageMode: ConstrainedLanguage suppressionGroupExpired: Error + restrictScriptSource: ModuleOnly # Configure include options include: @@ -3330,10 +3414,13 @@ convention: # Configure execution options execution: + hashAlgorithm: SHA512 aliasReference: Warn duplicateResourceId: Error invariantCulture: Warn languageMode: FullLanguage + initialSessionState: BuiltIn + restrictScriptSource: Unrestricted ruleInconclusive: Warn ruleSuppressed: Warn suppressionGroupExpired: Warn diff --git a/schemas/PSRule-options.schema.json b/schemas/PSRule-options.schema.json index afd70b2763..af40a9d01e 100644 --- a/schemas/PSRule-options.schema.json +++ b/schemas/PSRule-options.schema.json @@ -52,7 +52,7 @@ "title": "AKS cluster minimum version", "description": "This configuration option determines the minimum version of Kubernetes for AKS clusters and node pools. Rules that check the Kubernetes version fail when the version is older than the version specified.", "markdownDescription": "This configuration option determines the minimum version of Kubernetes for AKS clusters and node pools. Rules that check the Kubernetes version fail when the version is older than the version specified. [See help](https://azure.github.io/PSRule.Rules.Azure/setup/configuring-rules/#aks-minimum-kubernetes-version)\r\rApplies to: [PSRule for Azure](https://aka.ms/ps-rule-azure)", - "default": "1.25.4" + "default": "1.27.7" }, "AZURE_PARAMETER_FILE_METADATA_LINK": { "type": "boolean", @@ -310,8 +310,8 @@ "duplicateResourceId": { "type": "string", "title": "Duplicate resource identifiers", - "description": "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. When set to Warn, a warning is generated. When set to Ignore, no output will be displayed.", - "markdownDescription": "Determines how to handle duplicate resources identifiers during execution.\n\nRegardless of the value, only the first resource will be used. By defaut, an error is thrown.\n\n- When set to `Warn`, a warning is generated.\n- When set to `Debug`, a message is written to the debug log.\n- When set to `Ignore`, no output will be displayed.\n\n[See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Options/#executionduplicateresourceid)", + "description": "Determines how to handle duplicate resources identifiers during execution. Regardless of the value, only the first resource will be used. By default, an error is thrown. When set to Warn, a warning is generated. When set to Ignore, no output will be displayed.", + "markdownDescription": "Determines how to handle duplicate resources identifiers during execution.\n\nRegardless of the value, only the first resource will be used. By default, an error is thrown.\n\n- When set to `Warn`, a warning is generated.\n- When set to `Debug`, a message is written to the debug log.\n- When set to `Ignore`, no output will be displayed.\n\n[See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Options/#executionduplicateresourceid)", "enum": [ "Ignore", "Warn", @@ -323,8 +323,8 @@ "hashAlgorithm": { "type": "string", "title": "Hash Algorithm", - "description": "Configures the hashing algorithm used by the PSRule runtime. By default, SHA512 is used.", - "markdownDescription": "Configures the hashing algorithm used by the PSRule runtime.\n\nBy default, `SHA512` is used.\n\n[See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Options/#executionhashalgorithm)", + "description": "Configures the hashing algorithm used by the PSRule runtime. The default is SHA512.", + "markdownDescription": "Configures the hashing algorithm used by the PSRule runtime. The default is `SHA512`.\n- When set to `SHA512` the SHA-512 algorithm is used for hashing functions.\n- When set to `SHA384` the SHA-384 algorithm is used for hashing functions.\n- When set to `SHA256` the SHA-256 algorithm is used for hashing functions.\n\n[See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Options/#executionhashalgorithm)", "enum": [ "SHA512", "SHA384", @@ -334,15 +334,27 @@ }, "languageMode": { "type": "string", - "title": "Language mode", + "title": "Language Mode", "description": "The PowerShell language mode to use for rule execution. The default is FullLanguage.", - "markdownDescription": "The PowerShell language mode to use for rule execution. The default is `FullLanguage`. [See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Options/#executionlanguagemode)", + "markdownDescription": "The PowerShell language mode to use for rule execution. The default is `FullLanguage`.\n- `FullLanguage` - Executes with all language features.\n- `ConstrainedLanguage` - Executes in constrained language mode that restricts the types and methods that can be used.\n\n[See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Options/#executionlanguagemode)", "enum": [ "FullLanguage", "ConstrainedLanguage" ], "default": "FullLanguage" }, + "restrictScriptSource": { + "type": "string", + "title": "Restrict Script Source", + "description": "Configures where to allow PowerShell language features (such as rules and conventions) to run from. The default is Unrestricted.", + "markdownDescription": "Configures where to allow PowerShell language features (such as rules and conventions) to run from. The default is `Unrestricted`.\n- When set to `Unrestricted` PowerShell language features are allowed from workspace and modules.\n- When set to `ModuleOnly` PowerShell language features are allowed from loaded modules, but script files within the workspace are ignored.\n- When set to `DisablePowerShell` no PowerShell language features are used during PSRule run.\n\n[See help](https://microsoft.github.io/PSRule/v3/concepts/PSRule/en-US/about_PSRule_Options/#executionrestrictscriptsource)", + "enum": [ + "Unrestricted", + "ModuleOnly", + "DisablePowerShell" + ], + "default": "Unrestricted" + }, "initialSessionState": { "type": "string", "title": "Initial Session State", @@ -933,14 +945,14 @@ "baseRef": { "type": "string", "title": "Base Reference", - "description": "Sets the repository base ref used for comparisons of changed files. By default, the base ref is detected from environment vairables set by the build system.", - "markdownDescription": "Sets the repository base ref used for comparisons of changed files. By default, the base ref is detected from environment vairables set by the build system. [See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Options/#repositorybaseref)" + "description": "Sets the repository base ref used for comparisons of changed files. By default, the base ref is detected from environment variables set by the build system.", + "markdownDescription": "Sets the repository base ref used for comparisons of changed files.\n\nBy default, the base ref is detected from environment variables set by the build system.\n\n[See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Options/#repositorybaseref)" }, "url": { "type": "string", "title": "Repository URL", "description": "Sets the repository URL reported in output. By default, the repository URL is detected from environment variables set by the build system.", - "markdownDescription": "Sets the repository URL reported in output. By default, the repository URL is detected from environment variables set by the build system. [See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Options/#repositoryurl)" + "markdownDescription": "Sets the repository URL reported in output.\n\nBy default, the repository URL is detected from environment variables set by the build system.\n\n[See help](https://microsoft.github.io/PSRule/v2/concepts/PSRule/en-US/about_PSRule_Options/#repositoryurl)" } }, "additionalProperties": false diff --git a/src/PSRule.Badges/BadgeResources.cs b/src/PSRule.Badges/BadgeResources.cs index 55dbbb5eb6..24867e06a4 100644 --- a/src/PSRule.Badges/BadgeResources.cs +++ b/src/PSRule.Badges/BadgeResources.cs @@ -56,7 +56,7 @@ private static double GetWidth(char c) /// private static double Find(char c) { - if (_Char == null || _Width == null) return 0d; + if (_Char == null || _Width == null) return 0d; var index = Array.BinarySearch(_Char, c); return index >= 0 ? _Width[index] : 0d; diff --git a/src/PSRule.Types/Options/ExecutionActionPreference.cs b/src/PSRule.Types/Options/ExecutionActionPreference.cs index 25715af4ca..8fafac772a 100644 --- a/src/PSRule.Types/Options/ExecutionActionPreference.cs +++ b/src/PSRule.Types/Options/ExecutionActionPreference.cs @@ -1,8 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using Newtonsoft.Json.Converters; using Newtonsoft.Json; +using Newtonsoft.Json.Converters; namespace PSRule.Options; diff --git a/src/PSRule.Types/Options/ExecutionOption.cs b/src/PSRule.Types/Options/ExecutionOption.cs index e1c17e8a18..50271829ef 100644 --- a/src/PSRule.Types/Options/ExecutionOption.cs +++ b/src/PSRule.Types/Options/ExecutionOption.cs @@ -25,11 +25,13 @@ public interface IExecutionOption : IOption /// /// Configures the hashing algorithm used by the PSRule runtime. + /// The default is . /// HashAlgorithm HashAlgorithm { get; } /// - /// The langauge mode to execute PowerShell code with. + /// The language mode to execute PowerShell code with. + /// The default is . /// LanguageMode LanguageMode { get; } @@ -39,6 +41,12 @@ public interface IExecutionOption : IOption /// SessionState InitialSessionState { get; } + /// + /// Configures where to allow PowerShell language features (such as rules and conventions) to run from. + /// The default is . + /// + RestrictScriptSource RestrictScriptSource { get; } + /// /// Determines how to handle expired suppression groups. /// Regardless of the value, an expired suppression group will be ignored. @@ -115,6 +123,7 @@ public sealed class ExecutionOption : IEquatable, IExecutionOpt private const LanguageMode DEFAULT_LANGUAGEMODE = Options.LanguageMode.FullLanguage; private const ExecutionActionPreference DEFAULT_DUPLICATERESOURCEID = ExecutionActionPreference.Error; private const SessionState DEFAULT_INITIALSESSIONSTATE = SessionState.BuiltIn; + private const RestrictScriptSource DEFAULT_RESTRICTSCRIPTSOURCE = Options.RestrictScriptSource.Unrestricted; private const ExecutionActionPreference DEFAULT_SUPPRESSIONGROUPEXPIRED = ExecutionActionPreference.Warn; private const ExecutionActionPreference DEFAULT_RULEEXCLUDED = ExecutionActionPreference.Ignore; private const ExecutionActionPreference DEFAULT_RULESUPPRESSED = ExecutionActionPreference.Warn; @@ -130,6 +139,7 @@ public sealed class ExecutionOption : IEquatable, IExecutionOpt HashAlgorithm = DEFAULT_HASHALGORITHM, LanguageMode = DEFAULT_LANGUAGEMODE, InitialSessionState = DEFAULT_INITIALSESSIONSTATE, + RestrictScriptSource = DEFAULT_RESTRICTSCRIPTSOURCE, SuppressionGroupExpired = DEFAULT_SUPPRESSIONGROUPEXPIRED, RuleExcluded = DEFAULT_RULEEXCLUDED, RuleSuppressed = DEFAULT_RULESUPPRESSED, @@ -148,6 +158,7 @@ public ExecutionOption() HashAlgorithm = null; LanguageMode = null; InitialSessionState = null; + RestrictScriptSource = null; SuppressionGroupExpired = null; RuleExcluded = null; RuleSuppressed = null; @@ -170,6 +181,7 @@ public ExecutionOption(ExecutionOption option) HashAlgorithm = option.HashAlgorithm; LanguageMode = option.LanguageMode; InitialSessionState = option.InitialSessionState; + RestrictScriptSource = option.RestrictScriptSource; SuppressionGroupExpired = option.SuppressionGroupExpired; RuleExcluded = option.RuleExcluded; RuleSuppressed = option.RuleSuppressed; @@ -193,6 +205,7 @@ public bool Equals(ExecutionOption other) HashAlgorithm == other.HashAlgorithm && LanguageMode == other.LanguageMode && InitialSessionState == other.InitialSessionState && + RestrictScriptSource == other.RestrictScriptSource && SuppressionGroupExpired == other.SuppressionGroupExpired && RuleExcluded == other.RuleExcluded && RuleSuppressed == other.RuleSuppressed && @@ -212,6 +225,7 @@ public override int GetHashCode() hash = hash * 23 + (HashAlgorithm.HasValue ? HashAlgorithm.Value.GetHashCode() : 0); hash = hash * 23 + (LanguageMode.HasValue ? LanguageMode.Value.GetHashCode() : 0); hash = hash * 23 + (InitialSessionState.HasValue ? InitialSessionState.Value.GetHashCode() : 0); + hash = hash * 23 + (RestrictScriptSource.HasValue ? RestrictScriptSource.Value.GetHashCode() : 0); hash = hash * 23 + (SuppressionGroupExpired.HasValue ? SuppressionGroupExpired.Value.GetHashCode() : 0); hash = hash * 23 + (RuleExcluded.HasValue ? RuleExcluded.Value.GetHashCode() : 0); hash = hash * 23 + (RuleSuppressed.HasValue ? RuleSuppressed.Value.GetHashCode() : 0); @@ -235,6 +249,7 @@ internal static ExecutionOption Combine(ExecutionOption o1, ExecutionOption o2) HashAlgorithm = o1?.HashAlgorithm ?? o2?.HashAlgorithm, LanguageMode = o1?.LanguageMode ?? o2?.LanguageMode, InitialSessionState = o1?.InitialSessionState ?? o2?.InitialSessionState, + RestrictScriptSource = o1?.RestrictScriptSource ?? o2?.RestrictScriptSource, SuppressionGroupExpired = o1?.SuppressionGroupExpired ?? o2?.SuppressionGroupExpired, RuleExcluded = o1?.RuleExcluded ?? o2?.RuleExcluded, RuleSuppressed = o1?.RuleSuppressed ?? o2?.RuleSuppressed, @@ -264,7 +279,7 @@ internal static ExecutionOption Combine(ExecutionOption o1, ExecutionOption o2) public HashAlgorithm? HashAlgorithm { get; set; } /// - /// The langauge mode to execute PowerShell code with. + /// The language mode to execute PowerShell code with. /// [DefaultValue(null)] public LanguageMode? LanguageMode { get; set; } @@ -276,6 +291,13 @@ internal static ExecutionOption Combine(ExecutionOption o1, ExecutionOption o2) [DefaultValue(null)] public SessionState? InitialSessionState { get; set; } + /// + /// Configures where to allow PowerShell language features (such as rules and conventions) to run from. + /// The default is . + /// + [DefaultValue(null)] + public RestrictScriptSource? RestrictScriptSource { get; set; } + /// /// Determines how to handle expired suppression groups. /// Regardless of the value, an expired suppression group will be ignored. @@ -357,6 +379,8 @@ internal static ExecutionOption Combine(ExecutionOption o1, ExecutionOption o2) SessionState IExecutionOption.InitialSessionState => InitialSessionState ?? DEFAULT_INITIALSESSIONSTATE; + RestrictScriptSource IExecutionOption.RestrictScriptSource => RestrictScriptSource ?? DEFAULT_RESTRICTSCRIPTSOURCE; + ExecutionActionPreference IExecutionOption.SuppressionGroupExpired => SuppressionGroupExpired ?? DEFAULT_SUPPRESSIONGROUPEXPIRED; ExecutionActionPreference IExecutionOption.RuleExcluded => RuleExcluded ?? DEFAULT_RULEEXCLUDED; @@ -390,6 +414,9 @@ internal void Load() if (Environment.TryEnum("PSRULE_EXECUTION_INITIALSESSIONSTATE", out SessionState initialSessionState)) InitialSessionState = initialSessionState; + if (Environment.TryEnum("PSRULE_EXECUTION_RESTRICTSCRIPTSOURCE", out RestrictScriptSource restrictScriptSource)) + RestrictScriptSource = restrictScriptSource; + if (Environment.TryEnum("PSRULE_EXECUTION_SUPPRESSIONGROUPEXPIRED", out ExecutionActionPreference suppressionGroupExpired)) SuppressionGroupExpired = suppressionGroupExpired; @@ -429,6 +456,9 @@ internal void Load(Dictionary index) if (index.TryPopEnum("Execution.InitialSessionState", out SessionState initialSessionState)) InitialSessionState = initialSessionState; + if (index.TryPopEnum("Execution.RestrictScriptSource", out RestrictScriptSource restrictScriptSource)) + RestrictScriptSource = restrictScriptSource; + if (index.TryPopEnum("Execution.SuppressionGroupExpired", out ExecutionActionPreference suppressionGroupExpired)) SuppressionGroupExpired = suppressionGroupExpired; diff --git a/src/PSRule.Types/Options/LanguageMode.cs b/src/PSRule.Types/Options/LanguageMode.cs index d06a6de32c..7825e851e2 100644 --- a/src/PSRule.Types/Options/LanguageMode.cs +++ b/src/PSRule.Types/Options/LanguageMode.cs @@ -1,8 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using Newtonsoft.Json.Converters; using Newtonsoft.Json; +using Newtonsoft.Json.Converters; namespace PSRule.Options; @@ -15,12 +15,12 @@ namespace PSRule.Options; public enum LanguageMode { /// - /// PowerShell code executes unconstrained. + /// Executes with all language features. /// FullLanguage = 0, /// - /// PowerShell code executes in constrained language mode that restricts the types and methods that can be used. + /// Executes in constrained language mode that restricts the types and methods that can be used. /// ConstrainedLanguage = 1 } diff --git a/src/PSRule.Types/Options/RestrictScriptSource.cs b/src/PSRule.Types/Options/RestrictScriptSource.cs new file mode 100644 index 0000000000..dda275299b --- /dev/null +++ b/src/PSRule.Types/Options/RestrictScriptSource.cs @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace PSRule.Options; + +/// +/// Configures where to allow PowerShell language features (such as rules and conventions) to run from. +/// +[JsonConverter(typeof(StringEnumConverter))] +public enum RestrictScriptSource +{ + /// + /// PowerShell language features are allowed from workspace and modules. + /// + Unrestricted = 0, + + /// + /// PowerShell language features are allowed from loaded modules, but PowerShell files within the workspace are ignored. + /// + ModuleOnly = 1, + + /// + /// No PowerShell language features are used during PSRule run. + /// + DisablePowerShell = 2 +} diff --git a/src/PSRule.Types/Options/SessionState.cs b/src/PSRule.Types/Options/SessionState.cs index a5b23de07d..3a1f360586 100644 --- a/src/PSRule.Types/Options/SessionState.cs +++ b/src/PSRule.Types/Options/SessionState.cs @@ -1,8 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using Newtonsoft.Json.Converters; using Newtonsoft.Json; +using Newtonsoft.Json.Converters; namespace PSRule.Options; diff --git a/src/PSRule/Host/HostHelper.cs b/src/PSRule/Host/HostHelper.cs index 4b859770f0..2fdb215b7a 100644 --- a/src/PSRule/Host/HostHelper.cs +++ b/src/PSRule/Host/HostHelper.cs @@ -167,6 +167,9 @@ private static ILanguageBlock[] GetLanguageBlock(RunspaceContext context, Source /// private static ILanguageBlock[] GetPSLanguageBlocks(RunspaceContext context, Source[] sources) { + if (context.Pipeline.Option.Execution.RestrictScriptSource == Options.RestrictScriptSource.DisablePowerShell) + return Array.Empty(); + var results = new List(); var ps = context.GetPowerShell(); @@ -250,7 +253,7 @@ private static ILanguageBlock[] GetYamlLanguageBlocks(Source[] sources, Runspace .WithNamingConvention(CamelCaseNamingConvention.Instance) .WithTypeConverter(new FieldMapYamlTypeConverter()) .WithTypeConverter(new StringArrayMapConverter()) - .WithTypeConverter(new Converters.Yaml.StringArrayConverter()) + .WithTypeConverter(new StringArrayConverter()) .WithTypeConverter(new PSObjectYamlTypeConverter()) .WithNodeTypeResolver(new PSOptionYamlTypeResolver()) .WithNodeDeserializer( @@ -652,7 +655,7 @@ private static SuppressionGroupV1[] ToSuppressionGroupV1(IEnumerable(); - // Index suppression groups by Id + // Index suppression groups by Id. var results = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (var block in blocks.OfType().ToArray()) @@ -660,7 +663,7 @@ private static SuppressionGroupV1[] ToSuppressionGroupV1(IEnumerable blo if (blocks == null) return Array.Empty(); - // Index configurations by Name + // Index configurations by Name. var results = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (var block in blocks.OfType().ToArray()) { @@ -696,7 +699,7 @@ private static ModuleConfigV1[] ToModuleConfigV1(IEnumerable blo /// private static IConvention[] GetConventions(ILanguageBlock[] blocks, RunspaceContext context) { - // Index by Id + // Index by Id. var index = new HashSet(StringComparer.OrdinalIgnoreCase); var results = new List(blocks.Length); @@ -705,7 +708,7 @@ private static IConvention[] GetConventions(ILanguageBlock[] blocks, RunspaceCon context.EnterLanguageScope(block.Source); try { - // Ignore blocks that don't match + // Ignore blocks that don't match. if (!Match(context, block)) continue; @@ -726,7 +729,7 @@ private static SelectorV1[] ToSelectorV1(IEnumerable blocks, Run if (blocks == null) return Array.Empty(); - // Index selectors by Id + // Index selectors by Id. var results = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (var block in blocks.OfType().ToArray()) @@ -734,7 +737,7 @@ private static SelectorV1[] ToSelectorV1(IEnumerable blocks, Run context.EnterLanguageScope(block.Source); try { - // Ignore selectors that don't match + // Ignore selectors that don't match. if (!Match(context, block)) continue; diff --git a/src/PSRule/PSRule.psm1 b/src/PSRule/PSRule.psm1 index 2e356da71e..6e83179806 100644 --- a/src/PSRule/PSRule.psm1 +++ b/src/PSRule/PSRule.psm1 @@ -1173,6 +1173,11 @@ function New-PSRuleOption { [Alias('ExecutionInitialSessionState')] [PSRule.Options.SessionState]$InitialSessionState = [PSRule.Options.SessionState]::BuiltIn, + # Sets the Execution.RestrictScriptSource option + [Parameter(Mandatory = $False)] + [Alias('ExecutionRestrictScriptSource')] + [PSRule.Options.RestrictScriptSource]$RestrictScriptSource = [PSRule.Options.RestrictScriptSource]::Unrestricted, + # Sets the Execution.SuppressionGroupExpired option [Parameter(Mandatory = $False)] [Alias('ExecutionSuppressionGroupExpired')] @@ -1477,6 +1482,11 @@ function Set-PSRuleOption { [Alias('ExecutionInitialSessionState')] [PSRule.Options.SessionState]$InitialSessionState = [PSRule.Options.SessionState]::BuiltIn, + # Sets the Execution.RestrictScriptSource option + [Parameter(Mandatory = $False)] + [Alias('ExecutionRestrictScriptSource')] + [PSRule.Options.RestrictScriptSource]$RestrictScriptSource = [PSRule.Options.RestrictScriptSource]::Unrestricted, + # Sets the Execution.SuppressionGroupExpired option [Parameter(Mandatory = $False)] [Alias('ExecutionSuppressionGroupExpired')] @@ -2228,6 +2238,11 @@ function SetOptions { [Alias('ExecutionInitialSessionState')] [PSRule.Options.SessionState]$InitialSessionState = [PSRule.Options.SessionState]::BuiltIn, + # Sets the Execution.RestrictScriptSource option + [Parameter(Mandatory = $False)] + [Alias('ExecutionRestrictScriptSource')] + [PSRule.Options.RestrictScriptSource]$RestrictScriptSource = [PSRule.Options.RestrictScriptSource]::Unrestricted, + # Sets the Execution.SuppressionGroupExpired option [Parameter(Mandatory = $False)] [Alias('ExecutionSuppressionGroupExpired')] @@ -2444,6 +2459,11 @@ function SetOptions { $Option.Execution.InitialSessionState = $InitialSessionState; } + # Sets option Execution.RestrictScriptSource + if ($PSBoundParameters.ContainsKey('RestrictScriptSource')) { + $Option.Execution.RestrictScriptSource = $RestrictScriptSource; + } + # Sets option Execution.SuppressionGroupExpired if ($PSBoundParameters.ContainsKey('SuppressionGroupExpired')) { $Option.Execution.SuppressionGroupExpired = $SuppressionGroupExpired; diff --git a/src/PSRule/Pipeline/InvokePipelineBuilder.cs b/src/PSRule/Pipeline/InvokePipelineBuilder.cs index 3e07256b31..9a7987d019 100644 --- a/src/PSRule/Pipeline/InvokePipelineBuilder.cs +++ b/src/PSRule/Pipeline/InvokePipelineBuilder.cs @@ -132,8 +132,10 @@ protected void Unblock(IPipelineWriter writer) files.Add(Source[i].File[j].Path); } } - if (files.Count > 0) + if (files.Count > 0 && Option.Execution.RestrictScriptSource != Options.RestrictScriptSource.DisablePowerShell) + { HostHelper.UnblockFile(writer, _TrustedPublishers.ToArray(), files.ToArray()); + } } private static bool IsBlocked(string path) diff --git a/src/PSRule/Pipeline/Output/SarifBuilder.cs b/src/PSRule/Pipeline/Output/SarifBuilder.cs index 29bb92438c..187aadb002 100644 --- a/src/PSRule/Pipeline/Output/SarifBuilder.cs +++ b/src/PSRule/Pipeline/Output/SarifBuilder.cs @@ -1,17 +1,17 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System.Collections; using Microsoft.CodeAnalysis.Sarif; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using PSRule.Configuration; using PSRule.Data; -using PSRule.Definitions.Rules; using PSRule.Definitions; +using PSRule.Definitions.Rules; +using PSRule.Options; using PSRule.Resources; using PSRule.Rules; -using PSRule.Options; -using Newtonsoft.Json.Linq; -using Newtonsoft.Json; -using System.Collections; namespace PSRule.Pipeline.Output; diff --git a/src/PSRule/Pipeline/PipelineBuilder.cs b/src/PSRule/Pipeline/PipelineBuilder.cs index e4ee3d3ae2..4b002f0fa1 100644 --- a/src/PSRule/Pipeline/PipelineBuilder.cs +++ b/src/PSRule/Pipeline/PipelineBuilder.cs @@ -554,8 +554,16 @@ protected static RepositoryOption GetRepository(RepositoryOption option) protected static ExecutionOption GetExecutionOption(ExecutionOption option) { var result = ExecutionOption.Combine(option, ExecutionOption.Default); + + // Handle when preference is set to none. The default should be used. + result.AliasReference = result.AliasReference == ExecutionActionPreference.None ? ExecutionOption.Default.AliasReference.Value : result.AliasReference; result.DuplicateResourceId = result.DuplicateResourceId == ExecutionActionPreference.None ? ExecutionOption.Default.DuplicateResourceId.Value : result.DuplicateResourceId; + result.InvariantCulture = result.InvariantCulture == ExecutionActionPreference.None ? ExecutionOption.Default.InvariantCulture.Value : result.InvariantCulture; + result.RuleExcluded = result.RuleExcluded == ExecutionActionPreference.None ? ExecutionOption.Default.RuleExcluded.Value : result.RuleExcluded; + result.RuleInconclusive = result.RuleInconclusive == ExecutionActionPreference.None ? ExecutionOption.Default.RuleInconclusive.Value : result.RuleInconclusive; + result.RuleSuppressed = result.RuleSuppressed == ExecutionActionPreference.None ? ExecutionOption.Default.RuleSuppressed.Value : result.RuleSuppressed; result.SuppressionGroupExpired = result.SuppressionGroupExpired == ExecutionActionPreference.None ? ExecutionOption.Default.SuppressionGroupExpired.Value : result.SuppressionGroupExpired; + result.UnprocessedObject = result.UnprocessedObject == ExecutionActionPreference.None ? ExecutionOption.Default.UnprocessedObject.Value : result.UnprocessedObject; return result; } diff --git a/src/PSRule/Pipeline/PipelineContext.cs b/src/PSRule/Pipeline/PipelineContext.cs index 5d594b9ca9..47899809ac 100644 --- a/src/PSRule/Pipeline/PipelineContext.cs +++ b/src/PSRule/Pipeline/PipelineContext.cs @@ -5,7 +5,6 @@ using System.Diagnostics; using System.Management.Automation; using System.Management.Automation.Runspaces; -using System.Security.Cryptography; using System.Text; using PSRule.Configuration; using PSRule.Definitions; diff --git a/src/PSRule/Pipeline/SourcePipeline.cs b/src/PSRule/Pipeline/SourcePipeline.cs index 0154366757..4b33cbb146 100644 --- a/src/PSRule/Pipeline/SourcePipeline.cs +++ b/src/PSRule/Pipeline/SourcePipeline.cs @@ -5,6 +5,7 @@ using System.Management.Automation; using System.Reflection; using PSRule.Configuration; +using PSRule.Options; using PSRule.Pipeline.Output; using PSRule.Resources; @@ -114,6 +115,8 @@ public sealed class SourcePipelineBuilder : ISourcePipelineBuilder, ISourceComma private readonly HostPipelineWriter _Writer; private readonly bool _UseDefaultPath; private readonly string _LocalPath; + private readonly RestrictScriptSource _RestrictScriptSource; + private readonly string _WorkspacePath; internal SourcePipelineBuilder(IHostContext hostContext, PSRuleOption option, string localPath = null) { @@ -123,6 +126,8 @@ internal SourcePipelineBuilder(IHostContext hostContext, PSRuleOption option, st _Writer.EnterScope("[Discovery.Source]"); _UseDefaultPath = option == null || option.Include == null || option.Include.Path == null; _LocalPath = localPath; + _RestrictScriptSource = option?.Execution?.RestrictScriptSource ?? ExecutionOption.Default.RestrictScriptSource.Value; + _WorkspacePath = Environment.GetRootedBasePath(null); // Include paths from options if (!_UseDefaultPath) @@ -188,7 +193,7 @@ public void Directory(string path, bool excludeDefaultRulePath = false) VerboseScanSource(path); path = Environment.GetRootedPath(path); - var files = GetFiles(path, null, excludeDefaultRulePath); + var files = GetFiles(path, null, excludeDefaultRulePath, restrictScriptSource: _RestrictScriptSource, workspacePath: _WorkspacePath); if (files == null || files.Length == 0) return; @@ -208,16 +213,11 @@ public void Module(PSModuleInfo[] module) /// public void ModuleByName(string name, string version = null) { - var basePath = FindModule(name, version); - if (basePath == null) - throw new PipelineBuilderException(PSRuleResources.ModuleNotFound); - - var info = LoadManifest(basePath); - if (info == null) - throw new PipelineBuilderException(PSRuleResources.ModuleNotFound); + var basePath = FindModule(name, version) ?? throw new PipelineBuilderException(PSRuleResources.ModuleNotFound); + var info = LoadManifest(basePath) ?? throw new PipelineBuilderException(PSRuleResources.ModuleNotFound); VerboseScanModule(info.Name); - var files = GetFiles(basePath, basePath, excludeDefaultRulePath: false, info.Name); + var files = GetFiles(basePath, basePath, excludeDefaultRulePath: false, restrictScriptSource: _RestrictScriptSource, info.Name); if (files == null || files.Length == 0) return; @@ -373,7 +373,7 @@ private void Module(PSModuleInfo module, bool dependency) return; VerboseScanModule(module.Name); - var files = GetFiles(module.ModuleBase, module.ModuleBase, excludeDefaultRulePath: false, module.Name); + var files = GetFiles(module.ModuleBase, module.ModuleBase, excludeDefaultRulePath: false, restrictScriptSource: _RestrictScriptSource, workspacePath: _WorkspacePath, moduleName: module.Name); if (files == null || files.Length == 0) return; @@ -422,17 +422,17 @@ private void Source(Source source) _Source[key] = source; } - private static SourceFile[] GetFiles(string path, string helpPath, bool excludeDefaultRulePath, string moduleName = null) + private static SourceFile[] GetFiles(string path, string helpPath, bool excludeDefaultRulePath, RestrictScriptSource restrictScriptSource, string workspacePath, string moduleName = null) { var rootedPath = Environment.GetRootedPath(path); var extension = Path.GetExtension(rootedPath); if (IsSourceFile(extension)) { - return IncludeFile(rootedPath, helpPath); + return IncludeFile(rootedPath, helpPath, restrictScriptSource, workspacePath); } else if (System.IO.Directory.Exists(rootedPath)) { - return IncludePath(rootedPath, helpPath, moduleName, excludeDefaultRulePath); + return IncludePath(rootedPath, helpPath, moduleName, excludeDefaultRulePath, restrictScriptSource, workspacePath); } return null; } @@ -446,24 +446,28 @@ private static bool ShouldInclude(string path) path.EndsWith(".rule.jsonc", StringComparison.OrdinalIgnoreCase); } - private static SourceFile[] IncludeFile(string path, string helpPath) + private static SourceFile[] IncludeFile(string path, string helpPath, RestrictScriptSource restrictScriptSource, string workspacePath) { if (!File.Exists(path)) throw new FileNotFoundException(PSRuleResources.SourceNotFound, path); + var sourceType = GetSourceType(path); + if (sourceType == SourceType.Script && IgnoreScript(path, restrictScriptSource, workspacePath)) + return null; + helpPath ??= Path.GetDirectoryName(path); - return new SourceFile[] { new(path, null, GetSourceType(path), helpPath) }; + return new SourceFile[] { new(path, null, sourceType, helpPath) }; } - private static SourceFile[] IncludePath(string path, string helpPath, string moduleName, bool excludeDefaultRulePath) + private static SourceFile[] IncludePath(string path, string helpPath, string moduleName, bool excludeDefaultRulePath, RestrictScriptSource restrictScriptSource, string workspacePath) { if (!excludeDefaultRulePath) { var allFiles = System.IO.Directory.EnumerateFiles(path, SOURCE_FILE_PATTERN, SearchOption.AllDirectories); - return GetSourceFiles(allFiles, helpPath, moduleName); + return GetSourceFiles(allFiles, helpPath, moduleName, restrictScriptSource, workspacePath); } var filteredFiles = FilterFiles(path, SOURCE_FILE_PATTERN, dir => !PathContainsDefaultRulePath(dir)); - return GetSourceFiles(filteredFiles, helpPath, moduleName); + return GetSourceFiles(filteredFiles, helpPath, moduleName, restrictScriptSource, workspacePath); } private static bool PathContainsDefaultRulePath(string path) @@ -471,20 +475,33 @@ private static bool PathContainsDefaultRulePath(string path) return path.Contains(DEFAULT_RULE_PATH.TrimEnd(Path.AltDirectorySeparatorChar), StringComparison.OrdinalIgnoreCase); } - private static SourceFile[] GetSourceFiles(IEnumerable files, string helpPath, string moduleName) + private static SourceFile[] GetSourceFiles(IEnumerable files, string helpPath, string moduleName, RestrictScriptSource restrictScriptSource, string workspacePath) { var result = new List(); foreach (var file in files) { if (ShouldInclude(file)) { + var sourceType = GetSourceType(file); + if (sourceType == SourceType.Script && IgnoreScript(file, restrictScriptSource, workspacePath)) + continue; + helpPath ??= Path.GetDirectoryName(file); - result.Add(new SourceFile(file, moduleName, GetSourceType(file), helpPath)); + result.Add(new SourceFile(file, moduleName, sourceType, helpPath)); } } return result.ToArray(); } + private static bool IgnoreScript(string path, RestrictScriptSource restrictScriptSource, string workspacePath) + { + if (restrictScriptSource == RestrictScriptSource.DisablePowerShell) return true; + if (restrictScriptSource == RestrictScriptSource.Unrestricted) return false; + + path = Environment.GetRootedPath(path); + return path.StartsWith(workspacePath); + } + private static IEnumerable FilterFiles(string path, string filePattern, Func directoryFilter) { foreach (var file in System.IO.Directory.GetFiles(path, filePattern, SearchOption.TopDirectoryOnly)) diff --git a/tests/PSRule.Tests/PSRule.Options.Tests.ps1 b/tests/PSRule.Tests/PSRule.Options.Tests.ps1 index edf249f999..ab3e57a555 100644 --- a/tests/PSRule.Tests/PSRule.Options.Tests.ps1 +++ b/tests/PSRule.Tests/PSRule.Options.Tests.ps1 @@ -1101,6 +1101,45 @@ Describe 'New-PSRuleOption' -Tag 'Option','New-PSRuleOption' { } } + Context 'Read Execution.RestrictScriptSource' { + It 'from default' { + $option = New-PSRuleOption -Default; + $option.Execution.RestrictScriptSource | Should -Be 'Unrestricted'; + } + + It 'from Hashtable' { + $option = New-PSRuleOption -Option @{ 'Execution.RestrictScriptSource' = 'ModuleOnly' }; + $option.Execution.RestrictScriptSource | Should -Be 'ModuleOnly'; + } + + It 'from YAML' { + $option = New-PSRuleOption -Option (Join-Path -Path $here -ChildPath 'PSRule.Tests.yml'); + $option.Execution.RestrictScriptSource | Should -Be 'ModuleOnly'; + } + + It 'from Environment' { + try { + # With bool + $Env:PSRULE_EXECUTION_RESTRICTSCRIPTSOURCE = 'moduleonly'; + $option = New-PSRuleOption; + $option.Execution.RestrictScriptSource | Should -Be 'ModuleOnly'; + + # With int + $Env:PSRULE_EXECUTION_RESTRICTSCRIPTSOURCE = '1'; + $option = New-PSRuleOption; + $option.Execution.RestrictScriptSource | Should -Be 'ModuleOnly'; + } + finally { + Remove-Item 'Env:PSRULE_EXECUTION_RESTRICTSCRIPTSOURCE' -Force; + } + } + + It 'from parameter' { + $option = New-PSRuleOption -RestrictScriptSource 'ModuleOnly' -Path $emptyOptionsFilePath; + $option.Execution.RestrictScriptSource | Should -Be 'ModuleOnly'; + } + } + Context 'Read Include.Path' { It 'from default' { $option = New-PSRuleOption -Default; diff --git a/tests/PSRule.Tests/PSRule.Tests.yml b/tests/PSRule.Tests/PSRule.Tests.yml index 6619ef4993..4de80a586b 100644 --- a/tests/PSRule.Tests/PSRule.Tests.yml +++ b/tests/PSRule.Tests/PSRule.Tests.yml @@ -57,6 +57,7 @@ execution: duplicateResourceId: Warn hashAlgorithm: SHA256 languageMode: ConstrainedLanguage + restrictScriptSource: ModuleOnly invariantCulture: Ignore initialSessionState: Minimal suppressionGroupExpired: Debug diff --git a/tests/PSRule.Tests/SourcePipelineBuilderTests.cs b/tests/PSRule.Tests/SourcePipelineBuilderTests.cs new file mode 100644 index 0000000000..1e66c319d4 --- /dev/null +++ b/tests/PSRule.Tests/SourcePipelineBuilderTests.cs @@ -0,0 +1,128 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.IO; +using PSRule.Configuration; +using PSRule.Options; +using PSRule.Pipeline; + +namespace PSRule; + +/// +/// Tests for . +/// +public sealed class SourcePipelineBuilderTests +{ + [Fact] + public void Add_single_file() + { + var builder = new SourcePipelineBuilder(null, null); + builder.Directory(GetSourcePath("FromFile.Rule.ps1")); + var sources = builder.Build(); + + Assert.Single(sources); + Assert.Single(sources[0].File); + } + + [Fact] + public void Add_directory() + { + var builder = new SourcePipelineBuilder(null, null); + builder.Directory(GetSourcePath("")); + var sources = builder.Build(); + + Assert.Single(sources); + Assert.Equal(23, sources[0].File.Length); + } + + [Fact] + public void Add_script_file_with_disable_powershell() + { + var option = GetOption(); + option.Execution.RestrictScriptSource = RestrictScriptSource.DisablePowerShell; + var builder = new SourcePipelineBuilder(null, option); + builder.Directory(GetSourcePath("FromFile.Rule.ps1")); + var sources = builder.Build(); + + Assert.Empty(sources); + } + + [Fact] + public void Add_script_file_with_module_only() + { + var option = GetOption(); + option.Execution.RestrictScriptSource = RestrictScriptSource.ModuleOnly; + var builder = new SourcePipelineBuilder(null, option); + builder.Directory(GetSourcePath("FromFile.Rule.ps1")); + var sources = builder.Build(); + + Assert.Empty(sources); + } + + [Fact] + public void Add_yaml_file_with_disable_powershell() + { + var option = GetOption(); + option.Execution.RestrictScriptSource = RestrictScriptSource.DisablePowerShell; + var builder = new SourcePipelineBuilder(null, option); + builder.Directory(GetSourcePath("FromFile.Rule.yaml")); + var sources = builder.Build(); + + Assert.Single(sources); + Assert.Single(sources[0].File); + } + + [Fact] + public void Add_yaml_file_with_module_only() + { + var option = GetOption(); + option.Execution.RestrictScriptSource = RestrictScriptSource.ModuleOnly; + var builder = new SourcePipelineBuilder(null, option); + builder.Directory(GetSourcePath("FromFile.Rule.yaml")); + var sources = builder.Build(); + + Assert.Single(sources); + Assert.Single(sources[0].File); + } + + [Fact] + public void Add_directory_with_disable_powershell() + { + var option = GetOption(); + option.Execution.RestrictScriptSource = RestrictScriptSource.DisablePowerShell; + var builder = new SourcePipelineBuilder(null, option); + builder.Directory(GetSourcePath("")); + var sources = builder.Build(); + + Assert.Single(sources); + Assert.Equal(18, sources[0].File.Length); + } + + [Fact] + public void Add_directory_with_module_only() + { + var option = GetOption(); + option.Execution.RestrictScriptSource = RestrictScriptSource.ModuleOnly; + var builder = new SourcePipelineBuilder(null, option); + builder.Directory(GetSourcePath("")); + var sources = builder.Build(); + + Assert.Single(sources); + Assert.Equal(18, sources[0].File.Length); + } + + #region Helper methods + + private static string GetSourcePath(string fileName) + { + return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName); + } + + private static PSRuleOption GetOption() + { + return new PSRuleOption(); + } + + #endregion Helper methods +} From 48cf81f5ddc1d36a9ebb1371a40aabb8b59ec6df Mon Sep 17 00:00:00 2001 From: Bernie White Date: Sat, 27 Jan 2024 17:22:29 +1000 Subject: [PATCH 174/177] Pre-release v3.0.0-B0141 (#1744) --- docs/CHANGELOG-v3.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/CHANGELOG-v3.md b/docs/CHANGELOG-v3.md index 013c15f821..ade27b95a8 100644 --- a/docs/CHANGELOG-v3.md +++ b/docs/CHANGELOG-v3.md @@ -27,6 +27,8 @@ See [upgrade notes][1] for helpful information when upgrading from previous vers ## Unreleased +## v3.0.0-B0141 (pre-release) + What's changed since pre-release v3.0.0-B0137: - General improvements: From 9be354e55aaeeee4597903c31ece86c9d1283429 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 30 Jan 2024 23:03:47 +1000 Subject: [PATCH 175/177] Bump mkdocs-material from 9.5.5 to 9.5.6 (#1745) Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.5.5 to 9.5.6. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.5.5...9.5.6) --- updated-dependencies: - dependency-name: mkdocs-material dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements-docs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-docs.txt b/requirements-docs.txt index 870e3d079d..de460e3c1d 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -1,5 +1,5 @@ mkdocs==1.5.3 -mkdocs-material==9.5.5 +mkdocs-material==9.5.6 pymdown-extensions==10.7 mike==2.0.0 mkdocs-simple-hooks==0.1.5 From ca1ef711f27194b2f1a9b7970cda74168504ca15 Mon Sep 17 00:00:00 2001 From: Bernie White Date: Sun, 4 Feb 2024 00:09:01 +1000 Subject: [PATCH 176/177] Fixed null reference in CLI #1746 (#1747) --- docs/CHANGELOG-v3.md | 6 + src/PSRule.Tool/Commands/ModuleCommand.cs | 110 ++++++++++--------- src/PSRule/Pipeline/Dependencies/LockFile.cs | 1 - tests/PSRule.Tool.Tests/CommandTests.cs | 16 +++ 4 files changed, 80 insertions(+), 53 deletions(-) diff --git a/docs/CHANGELOG-v3.md b/docs/CHANGELOG-v3.md index ade27b95a8..7adcdf4c5c 100644 --- a/docs/CHANGELOG-v3.md +++ b/docs/CHANGELOG-v3.md @@ -27,6 +27,12 @@ See [upgrade notes][1] for helpful information when upgrading from previous vers ## Unreleased +What's changed since pre-release v3.0.0-B0141: + +- Bug fixes: + - Fixed CLI null reference when include module is undefined by @BernieWhite. + [#1746](https://github.com/microsoft/PSRule/issues/1746) + ## v3.0.0-B0141 (pre-release) What's changed since pre-release v3.0.0-B0137: diff --git a/src/PSRule.Tool/Commands/ModuleCommand.cs b/src/PSRule.Tool/Commands/ModuleCommand.cs index 88e8462b22..236b5f51f5 100644 --- a/src/PSRule.Tool/Commands/ModuleCommand.cs +++ b/src/PSRule.Tool/Commands/ModuleCommand.cs @@ -74,43 +74,46 @@ public static int ModuleRestore(RestoreOptions operationOptions, ClientContext c } // Restore from included modules. - foreach (var includeModule in clientContext.Option.Include.Module) + if (clientContext.Option.Include?.Module != null && clientContext.Option.Include.Module.Length > 0) { - // Skip modules already in the lock unless force is used. - if (file.Modules.TryGetValue(includeModule, out var lockEntry)) - continue; + foreach (var includeModule in clientContext.Option.Include.Module) + { + // Skip modules already in the lock unless force is used. + if (file.Modules.TryGetValue(includeModule, out var lockEntry)) + continue; - // Get a constraint if set from options. - var moduleConstraint = requires.TryGetValue(includeModule, out var c) ? c : null; + // Get a constraint if set from options. + var moduleConstraint = requires.TryGetValue(includeModule, out var c) ? c : null; - // Check if the installed version matches the constraint. - if (IsInstalled(pwsh, includeModule, null, out var installedVersion) && - !operationOptions.Force && - (moduleConstraint == null || moduleConstraint.Equals(installedVersion))) - { - // invocation.Log(Messages.UsingModule, includeModule, installedVersion.ToString()); - clientContext.LogVerbose($"The module {includeModule} is already installed."); - continue; - } + // Check if the installed version matches the constraint. + if (IsInstalled(pwsh, includeModule, null, out var installedVersion) && + !operationOptions.Force && + (moduleConstraint == null || moduleConstraint.Equals(installedVersion))) + { + // invocation.Log(Messages.UsingModule, includeModule, installedVersion.ToString()); + clientContext.LogVerbose($"The module {includeModule} is already installed."); + continue; + } - // Find the ideal version. - var idealVersion = FindVersion(pwsh, includeModule, moduleConstraint, null, null); - if (idealVersion != null) - { - InstallVersion(clientContext, pwsh, includeModule, idealVersion.ToString()); - } - else if (idealVersion == null) - { - clientContext.LogError(Messages.Error_502, includeModule); - exitCode = ERROR_MODULE_FAILED_TO_FIND; - } - else if (pwsh.HadErrors) - { - exitCode = ERROR_MODULE_FAILED_TO_INSTALL; - clientContext.LogError(Messages.Error_501, includeModule, idealVersion); - foreach (var error in pwsh.Streams.Error) + // Find the ideal version. + var idealVersion = FindVersion(pwsh, includeModule, moduleConstraint, null, null); + if (idealVersion != null) { - clientContext.LogError(error.Exception.Message); + InstallVersion(clientContext, pwsh, includeModule, idealVersion.ToString()); + } + else if (idealVersion == null) + { + clientContext.LogError(Messages.Error_502, includeModule); + exitCode = ERROR_MODULE_FAILED_TO_FIND; + } + else if (pwsh.HadErrors) + { + exitCode = ERROR_MODULE_FAILED_TO_INSTALL; + clientContext.LogError(Messages.Error_501, includeModule, idealVersion); + foreach (var error in pwsh.Streams.Error) + { + clientContext.LogError(error.Exception.Message); + } } } } @@ -134,32 +137,35 @@ public static int ModuleInit(ModuleOptions operationOptions, ClientContext clien using var pwsh = PowerShell.Create(); // Add for any included modules. - foreach (var includeModule in clientContext.Option.Include.Module) + if (clientContext.Option.Include?.Module != null && clientContext.Option.Include.Module.Length > 0) { - // Skip modules already in the lock unless force is used. - if (file.Modules.TryGetValue(includeModule, out var lockEntry)) - continue; + foreach (var includeModule in clientContext.Option.Include.Module) + { + // Skip modules already in the lock unless force is used. + if (file.Modules.TryGetValue(includeModule, out var lockEntry)) + continue; - // Get a constraint if set from options. - var moduleConstraint = requires.TryGetValue(includeModule, out var c) ? c : null; + // Get a constraint if set from options. + var moduleConstraint = requires.TryGetValue(includeModule, out var c) ? c : null; - // Find the ideal version. - var idealVersion = FindVersion(pwsh, includeModule, moduleConstraint, null, null); - if (idealVersion == null) - { - clientContext.LogError(Messages.Error_502, includeModule); - return ERROR_MODULE_FAILED_TO_FIND; - } + // Find the ideal version. + var idealVersion = FindVersion(pwsh, includeModule, moduleConstraint, null, null); + if (idealVersion == null) + { + clientContext.LogError(Messages.Error_502, includeModule); + return ERROR_MODULE_FAILED_TO_FIND; + } - if (lockEntry?.Version == idealVersion) - continue; + if (lockEntry?.Version == idealVersion) + continue; - // invocation.Log(Messages.UsingModule, includeModule, idealVersion.ToString()); + // invocation.Log(Messages.UsingModule, includeModule, idealVersion.ToString()); - file.Modules[includeModule] = new LockEntry - { - Version = idealVersion - }; + file.Modules[includeModule] = new LockEntry + { + Version = idealVersion + }; + } } file.Write(null); diff --git a/src/PSRule/Pipeline/Dependencies/LockFile.cs b/src/PSRule/Pipeline/Dependencies/LockFile.cs index ff8a619749..e681b95fb2 100644 --- a/src/PSRule/Pipeline/Dependencies/LockFile.cs +++ b/src/PSRule/Pipeline/Dependencies/LockFile.cs @@ -57,7 +57,6 @@ public static LockFile Read(string path) { new SemanticVersionConverter() }, - }); } return new LockFile(); diff --git a/tests/PSRule.Tool.Tests/CommandTests.cs b/tests/PSRule.Tool.Tests/CommandTests.cs index 03451e30af..4aaf16c639 100644 --- a/tests/PSRule.Tool.Tests/CommandTests.cs +++ b/tests/PSRule.Tool.Tests/CommandTests.cs @@ -24,6 +24,22 @@ public async Task Run() Assert.Contains($"Using PSRule v0.0.1{System.Environment.NewLine}", output); } + [Fact] + public async Task ModuleInit() + { + var console = new TestConsole(); + var builder = ClientBuilder.New(); + var module = builder.Subcommands.FirstOrDefault(c => c.Name == "module"); + + Assert.NotNull(module); + Assert.NotNull(module.Subcommands.FirstOrDefault(c => c.Name == "init")); + + await builder.InvokeAsync("module init", console); + + var output = console.Out.ToString(); + Assert.NotNull(output); + } + [Fact] public async Task ModuleRestore() { From 71726003979ea040a7338c1624a57cdf27a43437 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 4 Feb 2024 00:32:46 +1000 Subject: [PATCH 177/177] Bump to .NET 8.0 (#1673) * Bump System.Management.Automation from 7.3.7 to 7.4.0 Bumps [System.Management.Automation](https://github.com/PowerShell/PowerShell) from 7.3.7 to 7.4.0. - [Release notes](https://github.com/PowerShell/PowerShell/releases) - [Commits](https://github.com/PowerShell/PowerShell/compare/v7.3.7...v7.4.0) --- updated-dependencies: - dependency-name: System.Management.Automation dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * Updates to container * Fixes * Fixes * Fixes --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Bernie White --- .devcontainer/Dockerfile | 12 - .devcontainer/container-build.ps1 | 6 + .devcontainer/devcontainer.json | 23 +- .github/dependabot.yaml | 42 -- .github/dependabot.yml | 48 ++ .github/workflows/analyze.yaml | 2 +- .github/workflows/build.yaml | 4 +- docs/CHANGELOG-v3.md | 5 + docs/install.md | 14 +- pipeline.build.ps1 | 4 +- src/PSRule.Benchmark/PSRule.Benchmark.csproj | 6 +- src/PSRule.Benchmark/packages.lock.json | 646 +++++++++--------- src/PSRule.BuildTool/PSRule.BuildTool.csproj | 2 +- src/PSRule.BuildTool/packages.lock.json | 2 +- src/PSRule.Common.props | 2 +- src/PSRule.Tool/PSRule.Tool.csproj | 6 +- src/PSRule.Tool/packages.lock.json | 643 +++++++++-------- tests/PSRule.Tests/InputPathBuilderTests.cs | 4 +- tests/PSRule.Tests/PSRule.Tests.csproj | 6 +- .../PSRule.Tool.Tests.csproj | 2 +- 20 files changed, 742 insertions(+), 737 deletions(-) delete mode 100644 .devcontainer/Dockerfile delete mode 100644 .github/dependabot.yaml create mode 100644 .github/dependabot.yml diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile deleted file mode 100644 index b31b8cc0dd..0000000000 --- a/.devcontainer/Dockerfile +++ /dev/null @@ -1,12 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. - -# Visual Studio Code image with .NET - -# NOTE: -# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.245.2/containers/dotnet/.devcontainer/base.Dockerfile - -ARG VARIANT="6.0-bullseye-slim" -FROM mcr.microsoft.com/vscode/devcontainers/dotnet:0-${VARIANT} - -RUN sudo apt-get update && sudo apt-get install python3-pip -y diff --git a/.devcontainer/container-build.ps1 b/.devcontainer/container-build.ps1 index 50eb65efc3..f43d8708ac 100644 --- a/.devcontainer/container-build.ps1 +++ b/.devcontainer/container-build.ps1 @@ -10,6 +10,12 @@ sudo apt install python3-pip -y sudo python3 -m pip install --upgrade pip sudo python3 -m pip install wheel +# Install Python packages +pip install -r requirements-docs.txt + +# Restore .NET packages +dotnet restore + # Install PowerShell dependencies $ProgressPreference = [System.Management.Automation.ActionPreference]::SilentlyContinue; if ($Null -eq (Get-PackageProvider -Name NuGet -ErrorAction Ignore)) { diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index e092fb588f..81c4390a78 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,6 +1,9 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/dotnet { - "$schema": "https://raw.githubusercontent.com/devcontainers/spec/main/schemas/devContainer.schema.json", - "name": "PSRule dev", + "name": "PSRule development environment", + // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile + "image": "mcr.microsoft.com/devcontainers/dotnet:1-8.0-bookworm", "customizations": { "vscode": { "settings": { @@ -23,20 +26,16 @@ } }, "features": { - "ghcr.io/devcontainers/features/github-cli": { + "ghcr.io/devcontainers/features/github-cli:1": { "version": "latest" }, - "ghcr.io/devcontainers/features/powershell": { + "ghcr.io/devcontainers/features/powershell:1": { + "version": "latest" + }, + "ghcr.io/devcontainers/features/python:1": { "version": "latest" } }, "onCreateCommand": "/opt/microsoft/powershell/7/pwsh -f .devcontainer/container-build.ps1", - "postStartCommand": "/opt/microsoft/powershell/7/pwsh -f .devcontainer/container-start.ps1", - "build": { - "dockerfile": "Dockerfile", - "args": { - "VARIANT": "6.0-bullseye-slim" - } - }, - "remoteUser": "vscode" + "postStartCommand": "/opt/microsoft/powershell/7/pwsh -f .devcontainer/container-start.ps1" } diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml deleted file mode 100644 index a9445469a5..0000000000 --- a/.github/dependabot.yaml +++ /dev/null @@ -1,42 +0,0 @@ -# -# Dependabot configuration -# - -# Please see the documentation for all configuration options: -# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates - -version: 2 -updates: - # Maintain dependencies for NuGet - - package-ecosystem: 'nuget' # See documentation for possible values - directory: '/' # Location of package manifests - schedule: - interval: 'daily' - labels: - - 'dependencies' - reviewers: - - 'microsoft/psrule' - ignore: - # Ignore upgrades to PS 7.1 for tool chain components at this time - # Testing against PS 7.1 is already completed - - dependency-name: 'Microsoft.PowerShell.SDK' - - # Maintain dependencies for GitHub Actions - - package-ecosystem: 'github-actions' - directory: '/' - schedule: - interval: 'daily' - labels: - - 'ci-quality' - reviewers: - - 'microsoft/psrule' - - # Maintain dependencies for Python - - package-ecosystem: 'pip' - directory: '/' - schedule: - interval: 'daily' - labels: - - 'ci-quality' - reviewers: - - 'azure/psrule' diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000..f460982500 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,48 @@ +# +# Dependabot configuration +# + +# Please see the documentation for all configuration options: +# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + # Maintain dependencies for NuGet + - package-ecosystem: nuget + directory: '/' + schedule: + interval: daily + labels: + - dependencies + reviewers: + - microsoft/psrule + + # Maintain dependencies for GitHub Actions + - package-ecosystem: github-actions + directory: '/' + schedule: + interval: daily + labels: + - ci-quality + reviewers: + - microsoft/psrule + + # Maintain dependencies for Python + - package-ecosystem: pip + directory: '/' + schedule: + interval: daily + labels: + - ci-quality + reviewers: + - microsoft/psrule + + # Maintain dependencies for Dev Containers + - package-ecosystem: devcontainers + directory: '/' + schedule: + interval: weekly + labels: + - ci-quality + reviewers: + - microsoft/psrule diff --git a/.github/workflows/analyze.yaml b/.github/workflows/analyze.yaml index cf20ed03b3..7f16f10b6d 100644 --- a/.github/workflows/analyze.yaml +++ b/.github/workflows/analyze.yaml @@ -100,7 +100,7 @@ jobs: - name: Initialize CodeQL uses: github/codeql-action/init@v3 with: - languages: 'csharp' + languages: csharp - name: Autobuild uses: github/codeql-action/autobuild@v3 diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index b70a6d8b13..a8e21000bc 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -33,7 +33,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v4 with: - dotnet-version: 7.x + dotnet-version: 8.x - name: Install dependencies shell: pwsh @@ -110,7 +110,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v4 with: - dotnet-version: 7.x + dotnet-version: 8.x - if: ${{ matrix.shell == 'pwsh' }} name: Install dependencies (PowerShell) diff --git a/docs/CHANGELOG-v3.md b/docs/CHANGELOG-v3.md index 7adcdf4c5c..c7e8cf04f3 100644 --- a/docs/CHANGELOG-v3.md +++ b/docs/CHANGELOG-v3.md @@ -29,6 +29,11 @@ See [upgrade notes][1] for helpful information when upgrading from previous vers What's changed since pre-release v3.0.0-B0141: +- Engineering: + - **Breaking change:** Bump development tools to .NET 8.0 SDK by @BernieWhite. + [#1673](https://github.com/microsoft/PSRule/pull/1673) + - Running PSRule from PowerShell 7.x is supported on 7.4 and above. + - Running PSRule from Windows PowerShell 5.1 is still supported but deprecated and will be removed in PSRule v4. - Bug fixes: - Fixed CLI null reference when include module is undefined by @BernieWhite. [#1746](https://github.com/microsoft/PSRule/issues/1746) diff --git a/docs/install.md b/docs/install.md index 329e9907dd..2190144004 100644 --- a/docs/install.md +++ b/docs/install.md @@ -56,10 +56,10 @@ version: 2 updates: # Maintain GitHub Actions - - package-ecosystem: "github-actions" + - package-ecosystem: github-actions directory: "/" schedule: - interval: "daily" + interval: daily ``` [7]: https://docs.github.com/code-security/dependabot/working-with-dependabot @@ -90,12 +90,12 @@ You can also use this option to install on CI workers that are not natively supp The following platforms are supported: - Windows PowerShell 5.1 with .NET Framework 4.7.2 or greater. -- PowerShell 7.3 or greater on MacOS, Linux, and Windows. +- PowerShell 7.4 or greater on MacOS, Linux, and Windows. ### Installing PowerShell PowerShell 7.x can be installed on MacOS, Linux, and Windows but is not installed by default. -For a list of platforms that PowerShell 7.3 is supported on and install instructions see [Get PowerShell][3]. +For a list of platforms that PowerShell 7.4 is supported on and install instructions see [Get PowerShell][3]. !!! Note If you are using Windows PowerShell you may need to bootstrap NuGet before you can install modules. @@ -198,9 +198,9 @@ The following PowerShell modules will be automatically install if the required v These additional modules are only required for building PSRule. -Additionally .NET SDK v7 is required. +Additionally .NET SDK v8 is required. .NET will not be automatically downloaded and installed. -To download and install the latest SDK see [Download .NET 7.0][dotnet]. +To download and install the latest SDK see [Download .NET 8.0][dotnet]. ### Limited access networks @@ -242,4 +242,4 @@ After downloading the modules, copy the module directories to devices with restr *[CI]: continuous integration [module]: https://www.powershellgallery.com/packages/PSRule -[dotnet]: https://dotnet.microsoft.com/download/dotnet/7.0 +[dotnet]: https://dotnet.microsoft.com/download/dotnet/8.0 diff --git a/pipeline.build.ps1 b/pipeline.build.ps1 index f8adc1a8f7..e55ed848a3 100644 --- a/pipeline.build.ps1 +++ b/pipeline.build.ps1 @@ -104,7 +104,7 @@ task BuildDotNet { } exec { # Build library - dotnet build src/PSRule.Tool -c $Configuration -f net7.0 -p:version=$Build + dotnet build src/PSRule.Tool -c $Configuration -f net8.0 -p:version=$Build dotnet build src/PSRule.SDK -c $Configuration -f netstandard2.0 -p:version=$Build dotnet publish src/PSRule -c $Configuration -f netstandard2.0 -o $(Join-Path -Path $PWD -ChildPath out/modules/PSRule) -p:version=$Build } @@ -327,7 +327,7 @@ task Rules { task Benchmark { if ($Benchmark -or $BuildTask -eq 'Benchmark') { - dotnet run --project src/PSRule.Benchmark -f net7.0 -c Release -- benchmark --output $PWD; + dotnet run --project src/PSRule.Benchmark -f net8.0 -c Release -- benchmark --output $PWD; } } diff --git a/src/PSRule.Benchmark/PSRule.Benchmark.csproj b/src/PSRule.Benchmark/PSRule.Benchmark.csproj index 4b45078bea..28ab48447b 100644 --- a/src/PSRule.Benchmark/PSRule.Benchmark.csproj +++ b/src/PSRule.Benchmark/PSRule.Benchmark.csproj @@ -2,7 +2,7 @@ Exe - net7.0 + net8.0 AnyCPU {3ec0912f-bfc7-4b53-a1a1-0ba993c6282e} false @@ -23,8 +23,8 @@ - - + + diff --git a/src/PSRule.Benchmark/packages.lock.json b/src/PSRule.Benchmark/packages.lock.json index 8720e6f4b7..f0ef5cd63d 100644 --- a/src/PSRule.Benchmark/packages.lock.json +++ b/src/PSRule.Benchmark/packages.lock.json @@ -1,7 +1,7 @@ { "version": 1, "dependencies": { - "net7.0": { + "net8.0": { "BenchmarkDotNet.Diagnostics.Windows": { "type": "Direct", "requested": "[0.13.12, )", @@ -23,35 +23,31 @@ }, "Microsoft.PowerShell.SDK": { "type": "Direct", - "requested": "[7.3.7, )", - "resolved": "7.3.7", - "contentHash": "5SJlr0NojZTlYmBBSHAvAQP8k6nI/GcFCds4LOj0hH4h24EHW9gYXOeFMM4LUvmg2SuBe0F4fdv4ZFHTaTpZJA==", - "dependencies": { - "Microsoft.Bcl.AsyncInterfaces": "7.0.0", - "Microsoft.Extensions.ObjectPool": "7.0.11", - "Microsoft.Management.Infrastructure.CimCmdlets": "7.3.7", - "Microsoft.PowerShell.Commands.Diagnostics": "7.3.7", - "Microsoft.PowerShell.Commands.Management": "7.3.7", - "Microsoft.PowerShell.Commands.Utility": "7.3.7", - "Microsoft.PowerShell.ConsoleHost": "7.3.7", - "Microsoft.PowerShell.Security": "7.3.7", - "Microsoft.WSMan.Management": "7.3.7", - "Microsoft.Win32.Registry": "5.0.0", - "Microsoft.Windows.Compatibility": "7.0.5", - "System.Data.SqlClient": "4.8.5", - "System.IO.Packaging": "7.0.0", - "System.Management.Automation": "7.3.7", - "System.Net.Http.WinHttpHandler": "7.0.0", - "System.Private.ServiceModel": "4.10.2", - "System.Security.Cryptography.ProtectedData": "7.0.1", - "System.Security.Cryptography.Xml": "7.0.1", - "System.ServiceModel.Duplex": "4.10.2", - "System.ServiceModel.Http": "4.10.2", - "System.ServiceModel.NetTcp": "4.10.2", - "System.ServiceModel.Primitives": "4.10.2", - "System.ServiceModel.Security": "4.10.2", - "System.Text.Encodings.Web": "7.0.0", - "System.Web.Services.Description": "4.10.2" + "requested": "[7.4.1, )", + "resolved": "7.4.1", + "contentHash": "1h0KixYhgGUuRQssGWdqvMCxyHYerw8VPK0XEu2OllUj704yGNTsLb2MjUPV5m35UR/R3JSJL6l9VDx1DdG0fw==", + "dependencies": { + "Microsoft.Extensions.ObjectPool": "5.0.17", + "Microsoft.Management.Infrastructure.CimCmdlets": "7.4.1", + "Microsoft.PowerShell.Commands.Diagnostics": "7.4.1", + "Microsoft.PowerShell.Commands.Management": "7.4.1", + "Microsoft.PowerShell.Commands.Utility": "7.4.1", + "Microsoft.PowerShell.ConsoleHost": "7.4.1", + "Microsoft.PowerShell.Security": "7.4.1", + "Microsoft.WSMan.Management": "7.4.1", + "Microsoft.Windows.Compatibility": "8.0.1", + "System.Data.SqlClient": "4.8.6", + "System.IO.Packaging": "8.0.0", + "System.Management.Automation": "7.4.1", + "System.Net.Http.WinHttpHandler": "8.0.0", + "System.Private.ServiceModel": "4.10.3", + "System.ServiceModel.Duplex": "4.10.3", + "System.ServiceModel.Http": "4.10.3", + "System.ServiceModel.NetTcp": "4.10.3", + "System.ServiceModel.Primitives": "4.10.3", + "System.ServiceModel.Security": "4.10.3", + "System.Text.Encodings.Web": "8.0.0", + "System.Web.Services.Description": "4.10.3" } }, "Microsoft.SourceLink.GitHub": { @@ -66,27 +62,25 @@ }, "System.Management.Automation": { "type": "Direct", - "requested": "[7.3.7, )", - "resolved": "7.3.7", - "contentHash": "2RXHukEXtvizHACZPsLHIpWU1IZ6dasuQ5JyAIr69saqRSqmWNFUj6Qj8dgWlC89WoWNVra0n9Ty4xjg6xVOfg==", + "requested": "[7.4.1, )", + "resolved": "7.4.1", + "contentHash": "EYUMyAoYAw4Zt+cxKRMjZxzoa6gI++O4sK+cSg8HUhC1HfrJoMhD1u1Fo5CvlGv1KX3OmoJSyukgkDmRHFLQiw==", "dependencies": { "Microsoft.ApplicationInsights": "2.21.0", - "Microsoft.CSharp": "4.7.0", - "Microsoft.Management.Infrastructure": "2.0.0", - "Microsoft.PowerShell.CoreCLR.Eventing": "7.3.7", - "Microsoft.PowerShell.Native": "7.3.2", + "Microsoft.Management.Infrastructure": "3.0.0", + "Microsoft.PowerShell.CoreCLR.Eventing": "7.4.1", + "Microsoft.PowerShell.Native": "7.4.0", "Microsoft.Security.Extensions": "1.2.0", - "Microsoft.Win32.Registry.AccessControl": "7.0.0", + "Microsoft.Win32.Registry.AccessControl": "8.0.0", "Newtonsoft.Json": "13.0.3", - "System.Configuration.ConfigurationManager": "7.0.0", - "System.Diagnostics.DiagnosticSource": "7.0.2", - "System.DirectoryServices": "7.0.1", - "System.Management": "7.0.2", + "System.Configuration.ConfigurationManager": "8.0.0", + "System.Diagnostics.DiagnosticSource": "8.0.0", + "System.DirectoryServices": "8.0.0", + "System.Management": "8.0.0", "System.Security.AccessControl": "6.0.0", - "System.Security.Cryptography.Pkcs": "7.0.3", - "System.Security.Cryptography.ProtectedData": "7.0.1", - "System.Security.Permissions": "7.0.0", - "System.Text.Encoding.CodePages": "7.0.0" + "System.Security.Cryptography.Pkcs": "8.0.0", + "System.Security.Permissions": "8.0.0", + "System.Text.Encoding.CodePages": "8.0.0" } }, "BenchmarkDotNet": { @@ -126,6 +120,37 @@ "resolved": "1.17.0", "contentHash": "8x+HCVTl/HHTGpscH3vMBhV8sknN/muZFw9s3TsI8SA6+c43cOTCi2+jE4KsU8pNLbJ++iF2ZFcpcXHXtDglnw==" }, + "JetBrains.Annotations": { + "type": "Transitive", + "resolved": "2021.2.0", + "contentHash": "kKSyoVfndMriKHLfYGmr0uzQuI4jcc3TKGyww7buJFCYeHb/X0kodYBPL7n9454q7v6ASiRmDgpPGaDGerg/Hg==" + }, + "Json.More.Net": { + "type": "Transitive", + "resolved": "1.9.3", + "contentHash": "BKIsKHXR2Jq+LdLdxPo3L09Lv0ld9xs1fAMvSAe2cf2YOl3at9vw0RrMlhC2ookDi7VtrgHXzc2Et5mVBOAUdw==", + "dependencies": { + "System.Text.Json": "6.0.2" + } + }, + "JsonPointer.Net": { + "type": "Transitive", + "resolved": "3.0.3", + "contentHash": "mCGQc15lHLp1R2CVhWiipnZurHXm93+LbPPAT/vXQm5PdHt6WQuYLhaEF8VZ+aXL9P2I6bGND6pDTEfqFs6gig==", + "dependencies": { + "Json.More.Net": "1.8.0" + } + }, + "JsonSchema.Net": { + "type": "Transitive", + "resolved": "5.2.7", + "contentHash": "8un7Xq2MoKiWNo0HQtf2sPr3764W9NjNELIx3l9d3fIKEjg3tYtrZmxN+CgXKtzku4g52CqYUZuI+o0ue226vw==", + "dependencies": { + "JetBrains.Annotations": "2021.2.0", + "Json.More.Net": "1.9.0", + "JsonPointer.Net": "3.0.3" + } + }, "Manatee.Json": { "type": "Transitive", "resolved": "13.0.5", @@ -139,8 +164,8 @@ }, "Markdig.Signed": { "type": "Transitive", - "resolved": "0.31.0", - "contentHash": "u05eQvNRunYLR+J0SZAgt8ia+qCF3cMfyYh7LR4jSjG5Tg+0HuRrv7u/Gox9kOItWlSacMITcHBio7jas/zaEQ==" + "resolved": "0.33.0", + "contentHash": "/BE/XANxmocgEqajbWB/ur4Jei+j1FkXppWH9JFmEuoq8T3xJndkQKZVCW/7lTdc9Ru6kfEAkwSXFOv30EkU2Q==" }, "Microsoft.ApplicationInsights": { "type": "Transitive", @@ -152,8 +177,8 @@ }, "Microsoft.Bcl.AsyncInterfaces": { "type": "Transitive", - "resolved": "7.0.0", - "contentHash": "3aeMZ1N0lJoSyzqiP03hqemtb1BijhsJADdobn/4nsMJ8V1H+CrpuduUe4hlRdx+ikBQju1VGjMD1GJ3Sk05Eg==" + "resolved": "5.0.0", + "contentHash": "W8DPQjkMScOMTtJbPwmPyj9c3zYSFGawDW3jwlBOOsnY+EzZFLgNQ/UMkK35JmkNOVPdCyPr2Tw7Vv9N+KA3ZQ==" }, "Microsoft.Build.Tasks.Git": { "type": "Transitive", @@ -162,36 +187,28 @@ }, "Microsoft.CodeAnalysis.Analyzers": { "type": "Transitive", - "resolved": "3.3.3", - "contentHash": "j/rOZtLMVJjrfLRlAMckJLPW/1rze9MT1yfWqSIbUPGRu1m1P0fuo9PmqapwsmePfGB5PJrudQLvmUOAMF0DqQ==" + "resolved": "3.3.4", + "contentHash": "AxkxcPR+rheX0SmvpLVIGLhOUXAKG56a64kV9VQZ4y9gR9ZmPXnqZvHJnmwLSwzrEP6junUF11vuc+aqo5r68g==" }, "Microsoft.CodeAnalysis.Common": { "type": "Transitive", - "resolved": "4.4.0", - "contentHash": "JfHupS/B7Jb5MZoYkFFABn3mux0wQgxi2D8F/rJYZeRBK2ZOyk7TjQ2Kq9rh6W/DCh0KNbbSbn5qoFar+ueHqw==", + "resolved": "4.8.0", + "contentHash": "/jR+e/9aT+BApoQJABlVCKnnggGQbvGh7BKq2/wI1LamxC+LbzhcLj4Vj7gXCofl1n4E521YfF9w0WcASGg/KA==", "dependencies": { - "Microsoft.CodeAnalysis.Analyzers": "3.3.3", - "System.Collections.Immutable": "6.0.0", - "System.Memory": "4.5.5", - "System.Reflection.Metadata": "5.0.0", - "System.Runtime.CompilerServices.Unsafe": "6.0.0", - "System.Text.Encoding.CodePages": "6.0.0", - "System.Threading.Tasks.Extensions": "4.5.4" + "Microsoft.CodeAnalysis.Analyzers": "3.3.4", + "System.Collections.Immutable": "7.0.0", + "System.Reflection.Metadata": "7.0.0", + "System.Runtime.CompilerServices.Unsafe": "6.0.0" } }, "Microsoft.CodeAnalysis.CSharp": { "type": "Transitive", - "resolved": "4.4.0", - "contentHash": "eD2w0xHRoaqK07hjlOKGR9eLNy3nimiGNeCClNax1NDgS/+DBtBqCjXelOa+TNy99kIB3nHhUqDmr46nDXy/RQ==", + "resolved": "4.8.0", + "contentHash": "+3+qfdb/aaGD8PZRCrsdobbzGs1m9u119SkkJt8e/mk3xLJz/udLtS2T6nY27OTXxBBw10HzAbC8Z9w08VyP/g==", "dependencies": { - "Microsoft.CodeAnalysis.Common": "[4.4.0]" + "Microsoft.CodeAnalysis.Common": "[4.8.0]" } }, - "Microsoft.CSharp": { - "type": "Transitive", - "resolved": "4.7.0", - "contentHash": "pTj+D3uJWyN3My70i2Hqo+OXixq3Os2D1nJ2x92FFo6sk8fYS1m1WLNTs0Dc1uPaViH0YvEEwvzddQ7y4rhXmA==" - }, "Microsoft.Diagnostics.NETCore.Client": { "type": "Transitive", "resolved": "0.2.251802", @@ -271,8 +288,8 @@ }, "Microsoft.Extensions.ObjectPool": { "type": "Transitive", - "resolved": "7.0.11", - "contentHash": "DfzSvWdWl4b5VYGQI6tzxJWXSoyLE88C0W+whseE8RuYvC5wEGQLuIPiMXQtqxCBGN4rt0PTuDhdGAjtz4JZzw==" + "resolved": "5.0.17", + "contentHash": "EkIghF7cRBcogXKrfhopcCRjMs6b19THqSvACV5Oppp0nDA8oNyTLpAsfBQJ1hLgOjHfc5eNKFaFocKdg9nmnA==" }, "Microsoft.Extensions.Options": { "type": "Transitive", @@ -294,30 +311,30 @@ }, "Microsoft.Management.Infrastructure": { "type": "Transitive", - "resolved": "2.0.0", - "contentHash": "IaKZRNBBv3sdrmBWd+aqwHq8cVHk/3WgWFAN/dt40MRY9rbtHiDfTTmaEN0tGTmQqGCGDo/ncntA8MvFMvcsRw==", + "resolved": "3.0.0", + "contentHash": "cGZi0q5IujCTVYKo9h22Pw+UwfZDV82HXO8HTxMG2HqntPlT3Ls8jY6punLp4YzCypJNpfCAu2kae3TIyuAiJw==", "dependencies": { - "Microsoft.Management.Infrastructure.Runtime.Unix": "2.0.0", - "Microsoft.Management.Infrastructure.Runtime.Win": "2.0.0" + "Microsoft.Management.Infrastructure.Runtime.Unix": "3.0.0", + "Microsoft.Management.Infrastructure.Runtime.Win": "3.0.0" } }, "Microsoft.Management.Infrastructure.CimCmdlets": { "type": "Transitive", - "resolved": "7.3.7", - "contentHash": "Gf5kH5gBwq41jB6VMBwfjJSJLbc0A+B+f+6buH5TmJ4+M62/Ye44mbUKMMPWAd4tSlsK8cfhUrPp4u6Br/wDJw==", + "resolved": "7.4.1", + "contentHash": "y8ssJEx6pd+8Nsebupt8dKyyc+kLutacaaZBIRLKfUc84BE0Pv88hs6v8TF1M3c7pk2xkZDmEXMdVDCpWdJ/YQ==", "dependencies": { - "System.Management.Automation": "7.3.7" + "System.Management.Automation": "7.4.1" } }, "Microsoft.Management.Infrastructure.Runtime.Unix": { "type": "Transitive", - "resolved": "2.0.0", - "contentHash": "p0lslMX5bdWLxO2P7ao+rjAMOB0LEwPYpzvdCQ2OEYgX2NxFpQ8ILvqPGnYlTAb53rT8gu5DyIol1HboHFYfxQ==" + "resolved": "3.0.0", + "contentHash": "QZE3uEDvZ0m7LabQvcmNOYHp7v1QPBVMpB/ild0WEE8zqUVAP5y9rRI5we37ImI1bQmW5pZ+3HNC70POPm0jBQ==" }, "Microsoft.Management.Infrastructure.Runtime.Win": { "type": "Transitive", - "resolved": "2.0.0", - "contentHash": "vjBWQeDOjgernkrOdbEgn7M70SF7hof7ORdKPSlL06Uc15+oYdth5dZju9KsgUoti/cwnkZTiwtDx/lRtay0sA==" + "resolved": "3.0.0", + "contentHash": "uwMyWN33+iQ8Wm/n1yoPXgFoiYNd0HzJyoqSVhaQZyJfaQrJR3udgcIHjqa1qbc3lS6kvfuUMN4TrF4U4refCQ==" }, "Microsoft.NETCore.Platforms": { "type": "Transitive", @@ -331,50 +348,51 @@ }, "Microsoft.PowerShell.Commands.Diagnostics": { "type": "Transitive", - "resolved": "7.3.7", - "contentHash": "/luilQ1m5xwwfmw0P/d+7VCdfVsqFwGFXo6rfk6EePzpveY+NMrKteWMFK8Z/ofzpEqCfDKQoQ02PnPjLHl8ZA==", + "resolved": "7.4.1", + "contentHash": "TSNoWEO9xQPcQG5TYgi3puJgpwjVbMe5VesgzfZ4+lGWJurgI3y2vanXZSQoyRzlreqEtjBNggS/8TpHOO3nZA==", "dependencies": { - "System.Management.Automation": "7.3.7" + "System.Management.Automation": "7.4.1" } }, "Microsoft.PowerShell.Commands.Management": { "type": "Transitive", - "resolved": "7.3.7", - "contentHash": "lPJVT77kxKpCQmSNRDjjUrL/RuNw6hm3CpJZ533uPxALhkoemXgCGtukOqYh0mCL7CuDWEcRs6HGOLh0AdEpaA==", + "resolved": "7.4.1", + "contentHash": "LtyD1q6dHsiYeVsWLEbBajP1AKy83uXYNwyvRxU6uxO3q4N3+Ntt0wHQsZmSBoS3jvEvHDJklMxmW/SHoNgSKw==", "dependencies": { - "Microsoft.PowerShell.Security": "7.3.7", - "System.ServiceProcess.ServiceController": "7.0.1" + "Microsoft.PowerShell.Security": "7.4.1", + "System.ServiceProcess.ServiceController": "8.0.0" } }, "Microsoft.PowerShell.Commands.Utility": { "type": "Transitive", - "resolved": "7.3.7", - "contentHash": "y0Hqr61WeXL1mQdYXilPBFUGLOFesKCXNpf/qScvkMpA7HVZywsIpe/JnSnEXcAVFOqqCKfqWBGhoHDxo/XU1A==", + "resolved": "7.4.1", + "contentHash": "P2Fynn9+ooRiAKP5zB2e/fo0hhp/Ss81fqASAHH3mevYZPmnAPNxZMAkvMiayA8Pe6o6GVswXj3vyf5JUtN+mg==", "dependencies": { - "Markdig.Signed": "0.31.0", - "Microsoft.CodeAnalysis.CSharp": "4.4.0", + "Json.More.Net": "1.9.3", + "JsonSchema.Net": "5.2.7", + "Markdig.Signed": "0.33.0", + "Microsoft.CodeAnalysis.CSharp": "4.8.0", "Microsoft.PowerShell.MarkdownRender": "7.2.1", - "NJsonSchema": "10.8.0", - "Namotion.Reflection": "2.1.2", - "System.Drawing.Common": "7.0.0", - "System.Management.Automation": "7.3.7", - "System.Threading.AccessControl": "7.0.1" + "System.Drawing.Common": "8.0.1", + "System.Management.Automation": "7.4.1", + "System.Text.Json": "6.0.9", + "System.Threading.AccessControl": "8.0.0" } }, "Microsoft.PowerShell.ConsoleHost": { "type": "Transitive", - "resolved": "7.3.7", - "contentHash": "fRk4MFn5kv8RkKmQ+FhA7CPmKWxgYwa6y6VpyrUyIdnWDJYuzWocV9CnHJcjZ1GKpapa+g0CMVv1ZZSfMuOC0Q==", + "resolved": "7.4.1", + "contentHash": "IXyTckU0QE0+YdHyR7MvcUXPUQAGMQISX0M0594fV1gemkiIgRQ2Q7la3lEjE09GGwxAkDEfWFxE3Z/cDjV77w==", "dependencies": { - "System.Management.Automation": "7.3.7" + "System.Management.Automation": "7.4.1" } }, "Microsoft.PowerShell.CoreCLR.Eventing": { "type": "Transitive", - "resolved": "7.3.7", - "contentHash": "NQ/c1Ar+HezhDP95DJBX4xk1/EfvQ+J2I6scjqd7zKW8r5ZoQnDAtCCPPmXriHXjbYLvsQcVvRfVEc1UcELvug==", + "resolved": "7.4.1", + "contentHash": "uyByMNZ3XVUrJAxdHrXM/75vcKdfbs04J5iIZfDA8m9z8TJDViRMjyHNcp8K/ZXyzpT2Lua2d7g+dP47E9wAcg==", "dependencies": { - "System.Diagnostics.EventLog": "7.0.0" + "System.Diagnostics.EventLog": "8.0.0" } }, "Microsoft.PowerShell.MarkdownRender": { @@ -387,15 +405,15 @@ }, "Microsoft.PowerShell.Native": { "type": "Transitive", - "resolved": "7.3.2", - "contentHash": "MlLhJgzrUlxijTKJ19Eht++iGTUdg/F1jSbqwzjnc2Q8XStkUYNh8/81aUcNxWcg+0z1Yj/iUjW7czgWUYdV6Q==" + "resolved": "7.4.0", + "contentHash": "FlaJ3JBWhqFToYT0ycMb/Xxzoof7oTQbNyI4UikgubC7AMWt5ptBNKjIAMPvOcvEHr+ohaO9GvRWp3tiyS3sKw==" }, "Microsoft.PowerShell.Security": { "type": "Transitive", - "resolved": "7.3.7", - "contentHash": "5HiwXbj6ZXUGVBu3vMmorNJxoDEM5UYGFBRqrQ9ZiEryz3fmnAgJ2/KN2H9pvHWS/5o9PQLctjlPRCcDduX2qQ==", + "resolved": "7.4.1", + "contentHash": "fEMvsyVqDdGFMSLjNpFHdEGo/OGO2WaQlpyqUCKgRknpnpO+MBaWZBGcLu81sidN3iDtTotClzQOQwVXMeNEVQ==", "dependencies": { - "System.Management.Automation": "7.3.7" + "System.Management.Automation": "7.4.1" } }, "Microsoft.Security.Extensions": { @@ -420,87 +438,79 @@ }, "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { "type": "Transitive", - "resolved": "7.0.0", - "contentHash": "JwM65WXVca58WzqY/Rpz7FGyHbN/SMdyr/3EI2CwPIYkB55EIRJUdPQJwO64x3ntOwPQoqCATKuDYA9K7Np5Ww==" + "resolved": "8.0.0", + "contentHash": "u8PB9/v02C8mBXzl0vJ7bOyC020zOP+T1mRct+KA46DqZkB40XtsNn9pGD0QowTRsT6R4jPCghn+yAODn2UMMw==" }, "Microsoft.Win32.SystemEvents": { "type": "Transitive", - "resolved": "7.0.0", - "contentHash": "2nXPrhdAyAzir0gLl8Yy8S5Mnm/uBSQQA7jEsILOS1MTyS7DbmV1NgViMtvV1sfCD1ebITpNwb1NIinKeJgUVQ==" + "resolved": "8.0.0", + "contentHash": "9opKRyOKMCi2xJ7Bj7kxtZ1r9vbzosMvRrdEhVhDz8j8MoBGgB+WmC94yH839NPH+BclAjtQ/pyagvi/8gDLkw==" }, "Microsoft.Windows.Compatibility": { "type": "Transitive", - "resolved": "7.0.5", - "contentHash": "N4aTGZVV1PYPLVLtNn6jsh2b20oS87jegwkB1yDbV4Fy8bs2FZvsjEjjQg1wc7E29JKuwdNXOUYd9ww9LKuLtA==", - "dependencies": { - "Microsoft.Win32.Registry.AccessControl": "7.0.0", - "Microsoft.Win32.SystemEvents": "7.0.0", - "System.CodeDom": "7.0.0", - "System.ComponentModel.Composition": "7.0.0", - "System.ComponentModel.Composition.Registration": "7.0.0", - "System.Configuration.ConfigurationManager": "7.0.0", - "System.Data.Odbc": "7.0.0", - "System.Data.OleDb": "7.0.0", + "resolved": "8.0.1", + "contentHash": "Gr6QvHy9Y5PK5/ijl+cnCnfLr9HdCVByHtbPR5CxOAPeshURdJ/SNMi1t7qbUMBFCU9zsBlXSE1q/TsOUS0u8A==", + "dependencies": { + "Microsoft.Win32.Registry.AccessControl": "8.0.0", + "Microsoft.Win32.SystemEvents": "8.0.0", + "System.CodeDom": "8.0.0", + "System.ComponentModel.Composition": "8.0.0", + "System.ComponentModel.Composition.Registration": "8.0.0", + "System.Configuration.ConfigurationManager": "8.0.0", + "System.Data.Odbc": "8.0.0", + "System.Data.OleDb": "8.0.0", "System.Data.SqlClient": "4.8.5", - "System.Diagnostics.EventLog": "7.0.0", - "System.Diagnostics.PerformanceCounter": "7.0.0", - "System.DirectoryServices": "7.0.1", - "System.DirectoryServices.AccountManagement": "7.0.1", - "System.DirectoryServices.Protocols": "7.0.1", - "System.Drawing.Common": "7.0.0", - "System.IO.Packaging": "7.0.0", - "System.IO.Ports": "7.0.0", - "System.Management": "7.0.2", - "System.Reflection.Context": "7.0.0", - "System.Runtime.Caching": "7.0.0", - "System.Security.Cryptography.Pkcs": "7.0.2", - "System.Security.Cryptography.ProtectedData": "7.0.1", - "System.Security.Cryptography.Xml": "7.0.1", - "System.Security.Permissions": "7.0.0", - "System.ServiceModel.Duplex": "4.9.0", - "System.ServiceModel.Http": "4.9.0", - "System.ServiceModel.NetTcp": "4.9.0", - "System.ServiceModel.Primitives": "4.9.0", - "System.ServiceModel.Security": "4.9.0", - "System.ServiceModel.Syndication": "7.0.0", - "System.ServiceProcess.ServiceController": "7.0.1", - "System.Speech": "7.0.0", - "System.Text.Encoding.CodePages": "7.0.0", - "System.Threading.AccessControl": "7.0.1", - "System.Web.Services.Description": "4.9.0" + "System.Diagnostics.EventLog": "8.0.0", + "System.Diagnostics.PerformanceCounter": "8.0.0", + "System.DirectoryServices": "8.0.0", + "System.DirectoryServices.AccountManagement": "8.0.0", + "System.DirectoryServices.Protocols": "8.0.0", + "System.Drawing.Common": "8.0.1", + "System.IO.Packaging": "8.0.0", + "System.IO.Ports": "8.0.0", + "System.Management": "8.0.0", + "System.Reflection.Context": "8.0.0", + "System.Runtime.Caching": "8.0.0", + "System.Security.Cryptography.Pkcs": "8.0.0", + "System.Security.Cryptography.ProtectedData": "8.0.0", + "System.Security.Cryptography.Xml": "8.0.0", + "System.Security.Permissions": "8.0.0", + "System.ServiceModel.Duplex": "4.10.0", + "System.ServiceModel.Http": "4.10.0", + "System.ServiceModel.NetTcp": "4.10.0", + "System.ServiceModel.Primitives": "4.10.0", + "System.ServiceModel.Security": "4.10.0", + "System.ServiceModel.Syndication": "8.0.0", + "System.ServiceProcess.ServiceController": "8.0.0", + "System.Speech": "8.0.0", + "System.Text.Encoding.CodePages": "8.0.0", + "System.Threading.AccessControl": "8.0.0", + "System.Web.Services.Description": "4.10.0" } }, "Microsoft.WSMan.Management": { "type": "Transitive", - "resolved": "7.3.7", - "contentHash": "PAIPSykF8HrRhw8OvfWrKPvfKDVHH5geenSjpcoE2UpDHt2EpQsoS05ufc8drNTSzltr7qGYY3yue+Y09eoJAA==", + "resolved": "7.4.1", + "contentHash": "fkngCgs8WB0yk4vB+2Q3r3GQWkWq5y1X6Cn3/Y/UFxmpeSm6UFG10Tl5gi0rD2ZNvQgBP9++VVfxoQgSGqqTNQ==", "dependencies": { - "Microsoft.WSMan.Runtime": "7.3.7", - "System.Management.Automation": "7.3.7", - "System.ServiceProcess.ServiceController": "7.0.1" + "Microsoft.WSMan.Runtime": "7.4.1", + "System.Management.Automation": "7.4.1", + "System.ServiceProcess.ServiceController": "8.0.0" } }, "Microsoft.WSMan.Runtime": { "type": "Transitive", - "resolved": "7.3.7", - "contentHash": "auQMUGq8oVuqYj6O7zJX0Q22Kjdw54gQIeo88tjsADkdtgyfSe1XFFONX/JHPCn3KbYjJrIXI1kYPCwr0wGftQ==" - }, - "Namotion.Reflection": { - "type": "Transitive", - "resolved": "2.1.2", - "contentHash": "7tSHAzX8GWKy0qrW6OgQWD7kAZiqzhq+m1503qczuwuK6ZYhOGCQUxw+F3F4KkRM70aB6RMslsRVSCFeouIehw==", - "dependencies": { - "Microsoft.CSharp": "4.3.0" - } + "resolved": "7.4.1", + "contentHash": "m7NZmuQ7WHcosiU1Fpu82VV7bfI36p4bvMmhZOp2ECCgPYy3RvxVPf/SFKJQ5y721e3Ruindb5mVXxXQZ05TaA==" }, "NETStandard.Library": { "type": "Transitive", @@ -558,15 +568,6 @@ "resolved": "13.0.3", "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==" }, - "NJsonSchema": { - "type": "Transitive", - "resolved": "10.8.0", - "contentHash": "lChjsLWaxyvElh4WJjVhdIiCtx7rimYGFTxtSi2pAkZf0ZnKaXYIX484HCVyzbDDHejDZPgOrcfAJ3kqNSTONw==", - "dependencies": { - "Namotion.Reflection": "2.1.0", - "Newtonsoft.Json": "9.0.1" - } - }, "Perfolizer": { "type": "Transitive", "resolved": "0.2.1", @@ -592,18 +593,18 @@ }, "runtime.linux-arm.runtime.native.System.IO.Ports": { "type": "Transitive", - "resolved": "7.0.0", - "contentHash": "CBvgRaF+M0xGLDv2Geb/0v0LEADheH8aK72GRAUJdnqnJVsQO60ki1XO8M3keEhnjm+T5NvLm41pNXAVYAPiSg==" + "resolved": "8.0.0", + "contentHash": "gK720fg6HemDg8sXcfy+xCMZ9+hF78Gc7BmREbmkS4noqlu1BAr9qZtuWGhLzFjBfgecmdtl4+SYVwJ1VneZBQ==" }, "runtime.linux-arm64.runtime.native.System.IO.Ports": { "type": "Transitive", - "resolved": "7.0.0", - "contentHash": "5VCyRCtCIYU8FR/W8oo7ouFuJ8tmAg9ddsuXhfCKZfZrbaVZSKxkmNBa6fxkfYPueD0jQfOvwFBmE5c6zalCSw==" + "resolved": "8.0.0", + "contentHash": "KYG6/3ojhEWbb3FwQAKgGWPHrY+HKUXXdVjJlrtyCLn3EMcNTaNcPadb2c0ndQzixZSmAxZKopXJr0nLwhOrpQ==" }, "runtime.linux-x64.runtime.native.System.IO.Ports": { "type": "Transitive", - "resolved": "7.0.0", - "contentHash": "DV9dWDUs23OoZqMWl5IhLr3D+b9koDiSHQxFKdYgWnQbnthv8c/yDjrlrI8nMrDc71RAKCO8jlUojzuPMX04gg==" + "resolved": "8.0.0", + "contentHash": "Wnw5vhA4mgGbIFoo6l9Fk3iEcwRSq49a1aKwJgXUCUtEQLCSUDjTGSxqy/oMUuOyyn7uLHsH8KgZzQ1y3lReiQ==" }, "runtime.native.System": { "type": "Transitive", @@ -635,14 +636,14 @@ }, "runtime.native.System.IO.Ports": { "type": "Transitive", - "resolved": "7.0.0", - "contentHash": "L4Ivegqc3B0Fee7VifFy2JST9nndm+uvJ0viLIZUaImDfnr+JmRin9Tbqd56KuMtm0eVxHpNOWZBPtKrA/1h5Q==", + "resolved": "8.0.0", + "contentHash": "Ee7Sz5llLpTgyKIWzKI/GeuRSbFkOABgJRY00SqTY0OkTYtkB+9l5rFZfE7fxPA3c22RfytCBYkUdAkcmwMjQg==", "dependencies": { - "runtime.linux-arm.runtime.native.System.IO.Ports": "7.0.0", - "runtime.linux-arm64.runtime.native.System.IO.Ports": "7.0.0", - "runtime.linux-x64.runtime.native.System.IO.Ports": "7.0.0", - "runtime.osx-arm64.runtime.native.System.IO.Ports": "7.0.0", - "runtime.osx-x64.runtime.native.System.IO.Ports": "7.0.0" + "runtime.linux-arm.runtime.native.System.IO.Ports": "8.0.0", + "runtime.linux-arm64.runtime.native.System.IO.Ports": "8.0.0", + "runtime.linux-x64.runtime.native.System.IO.Ports": "8.0.0", + "runtime.osx-arm64.runtime.native.System.IO.Ports": "8.0.0", + "runtime.osx-x64.runtime.native.System.IO.Ports": "8.0.0" } }, "runtime.native.System.Net.Http": { @@ -691,13 +692,13 @@ }, "runtime.osx-arm64.runtime.native.System.IO.Ports": { "type": "Transitive", - "resolved": "7.0.0", - "contentHash": "jFwh4sKSXZ7al5XrItEO4GdGWa6XNxvNx+LhEHjrSzOwawO1znwJ+Dy+VjnrkySX9Qi4bnHNLoiqOXbqMuka4g==" + "resolved": "8.0.0", + "contentHash": "rbUBLAaFW9oVkbsb0+XSrAo2QdhBeAyzLl5KQ6Oci9L/u626uXGKInsVJG6B9Z5EO8bmplC8tsMiaHK8wOBZ+w==" }, "runtime.osx-x64.runtime.native.System.IO.Ports": { "type": "Transitive", - "resolved": "7.0.0", - "contentHash": "X4LrHEfke/z9+z+iuVr35NlkhdZldY8JGNMYUN+sfPK/U/6TcE+vP44I0Yv0ir1v0bqIzq3v6Qdv1c1vmp8s4g==" + "resolved": "8.0.0", + "contentHash": "IcfB4jKtM9pkzP9OpYelEcUX1MiDt0IJPBh3XYYdEISFF+6Mc+T8WWi0dr9wVh1gtcdVjubVEIBgB8BHESlGfQ==" }, "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": { "type": "Transitive", @@ -775,8 +776,8 @@ }, "System.CodeDom": { "type": "Transitive", - "resolved": "7.0.0", - "contentHash": "GLltyqEsE5/3IE+zYRP5sNa1l44qKl9v+bfdMcwg+M9qnQf47wK3H0SUR/T+3N4JEQXF3vV4CSuuo0rsg+nq2A==" + "resolved": "8.0.0", + "contentHash": "WTlRjL6KWIMr/pAaq3rYqh0TJlzpouaQ/W1eelssHgtlwHAH25jXTkUphTYx9HaIIf7XA6qs/0+YhtLEQRkJ+Q==" }, "System.Collections": { "type": "Transitive", @@ -807,11 +808,8 @@ }, "System.Collections.Immutable": { "type": "Transitive", - "resolved": "6.0.0", - "contentHash": "l4zZJ1WU2hqpQQHXz1rvC3etVZN+2DLmQMO79FhOTZHMn8tDRr+WU287sbomD0BETlmKDn0ygUgVy9k5xkkJdA==", - "dependencies": { - "System.Runtime.CompilerServices.Unsafe": "6.0.0" - } + "resolved": "7.0.0", + "contentHash": "dQPcs0U1IKnBdRDBkrCTi1FoajSTBzLcVTpjO4MBCMC7f4pDOIPzgBoX8JjG7X6uZRJ8EBxsi8+DR1JuwjnzOQ==" }, "System.ComponentModel.Annotations": { "type": "Transitive", @@ -820,26 +818,25 @@ }, "System.ComponentModel.Composition": { "type": "Transitive", - "resolved": "7.0.0", - "contentHash": "orv0h38ZVPCPo/FW0LGv8/TigXwX8cIwXeQcaNYhikkqELDm8sUFLMcof/Sjcq5EvYCm5NA7MV3hG4u75H44UQ==" + "resolved": "8.0.0", + "contentHash": "bGhUX5BTivJ9Wax0qnJy7uGq7dn/TQkEpJ2Fpu1etg8dbPwyDkUzNPc1d3I2/jUr9y4wDI3a1dkSmi8X21Pzbw==" }, "System.ComponentModel.Composition.Registration": { "type": "Transitive", - "resolved": "7.0.0", - "contentHash": "yy/xYOznnc7Hfg2/LeVqAMlJGv1v7b1ILxFShzx5PWUv53PwU0MaKPG8Dh9DC3gxayzw44UVuQJImhw7LtMKlw==", + "resolved": "8.0.0", + "contentHash": "BVMXYqX7Z0Zdq3tc94UKJL/cOWq4LF3ufexfdPuUDrDl4ekbbfwPVzsusVbx+aq6Yx60CJnmJLyHtM3V2Q7BBQ==", "dependencies": { - "System.ComponentModel.Composition": "7.0.0", - "System.Reflection.Context": "7.0.0" + "System.ComponentModel.Composition": "8.0.0", + "System.Reflection.Context": "8.0.0" } }, "System.Configuration.ConfigurationManager": { "type": "Transitive", - "resolved": "7.0.0", - "contentHash": "WvRUdlL1lB0dTRZSs5XcQOd5q9MYNk90GkbmRmiCvRHThWiojkpGqWdmEDJdXyHbxG/BhE5hmVbMfRLXW9FJVA==", + "resolved": "8.0.0", + "contentHash": "JlYi9XVvIREURRUlGMr1F6vOFLk7YSY4p1vHo4kX3tQ0AGrjqlRWHDi66ImHhy6qwXBG3BJ6Y1QlYQ+Qz6Xgww==", "dependencies": { - "System.Diagnostics.EventLog": "7.0.0", - "System.Security.Cryptography.ProtectedData": "7.0.0", - "System.Security.Permissions": "7.0.0" + "System.Diagnostics.EventLog": "8.0.0", + "System.Security.Cryptography.ProtectedData": "8.0.0" } }, "System.Console": { @@ -856,25 +853,25 @@ }, "System.Data.Odbc": { "type": "Transitive", - "resolved": "7.0.0", - "contentHash": "siwu7NoCsfHa9bfw2a2wSeTt2c/rhk3X8I28nJln1dlxdW3KqhRp0aW87yH1XkCo9h8zO1qcIfdTHO7YvvWLEA==", + "resolved": "8.0.0", + "contentHash": "c+GfnZt2/HyU+voKw2fctLZClcNjPZPWS+mnIhGvDknRMqL/fwWlREWPgA4csbp9ZkQIgB4qkufgdh/oh5Ubow==", "dependencies": { - "System.Text.Encoding.CodePages": "7.0.0" + "System.Text.Encoding.CodePages": "8.0.0" } }, "System.Data.OleDb": { "type": "Transitive", - "resolved": "7.0.0", - "contentHash": "bhAs+5X5acgg3zQ6N4HqxqfwwmqWJzgt54BC8iwygcqa2jktxDFzxwN83GNvqgoTcTs2tenDS/jmhC+AQsmcyg==", + "resolved": "8.0.0", + "contentHash": "FpUTcQ0E8mFvcYp8UZA3NX8wgmhmsCue56g1zfkr1xdOnT5FrYYmC5DWQ9xCw8o8zuxVBKLZvliqEGgmeoalaQ==", "dependencies": { - "System.Configuration.ConfigurationManager": "7.0.0", - "System.Diagnostics.PerformanceCounter": "7.0.0" + "System.Configuration.ConfigurationManager": "8.0.0", + "System.Diagnostics.PerformanceCounter": "8.0.0" } }, "System.Data.SqlClient": { "type": "Transitive", - "resolved": "4.8.5", - "contentHash": "fRqxut4lrndPHrXD+ht1XRmCL3obuKldm4XjCRYS9p5f7FSR7shBxAwTkDrpFMsHC9BhNgjjmUtiIjvehn5zkg==", + "resolved": "4.8.6", + "contentHash": "2Ij/LCaTQRyAi5lAv7UUTV9R2FobC8xN9mE0fXBZohum/xLl8IZVmE98Rq5ugQHjCgTBRKqpXRb4ORulRdA6Ig==", "dependencies": { "Microsoft.Win32.Registry": "4.7.0", "System.Security.Principal.Windows": "4.7.0", @@ -893,20 +890,20 @@ }, "System.Diagnostics.DiagnosticSource": { "type": "Transitive", - "resolved": "7.0.2", - "contentHash": "hYr3I9N9811e0Bjf2WNwAGGyTuAFbbTgX1RPLt/3Wbm68x3IGcX5Cl75CMmgT6WlNwLQ2tCCWfqYPpypjaf2xA==" + "resolved": "8.0.0", + "contentHash": "c9xLpVz6PL9lp/djOWtk5KPDZq3cSYpmXoJQY524EOtuFl5z9ZtsotpsyrDW40U1DRnQSYvcPKEUV0X//u6gkQ==" }, "System.Diagnostics.EventLog": { "type": "Transitive", - "resolved": "7.0.0", - "contentHash": "eUDP47obqQm3SFJfP6z+Fx2nJ4KKTQbXB4Q9Uesnzw9SbYdhjyoGXuvDn/gEmFY6N5Z3bFFbpAQGA7m6hrYJCw==" + "resolved": "8.0.0", + "contentHash": "fdYxcRjQqTTacKId/2IECojlDSFvp7LP5N78+0z/xH7v/Tuw5ZAxu23Y6PTCRinqyu2ePx+Gn1098NC6jM6d+A==" }, "System.Diagnostics.PerformanceCounter": { "type": "Transitive", - "resolved": "7.0.0", - "contentHash": "L+zIMEaXp1vA4wZk1KLMpk6tvU0xy94R0IfmhkmTWeC4KwShsmAfbg5I19LgjsCTYp6GVdXZ2aHluVWL0QqBdA==", + "resolved": "8.0.0", + "contentHash": "lX6DXxtJqVGWw7N/QmVoiCyVQ+Q/Xp+jVXPr3gLK1jJExSn1qmAjJQeb8gnOYeeBTG3E3PmG1nu92eYj/TEjpg==", "dependencies": { - "System.Configuration.ConfigurationManager": "7.0.0" + "System.Configuration.ConfigurationManager": "8.0.0" } }, "System.Diagnostics.Tools": { @@ -931,39 +928,36 @@ }, "System.DirectoryServices": { "type": "Transitive", - "resolved": "7.0.1", - "contentHash": "Z4FVdUJEVXbf7/f/hU6cFZDtxN5ozUVKJMzXoHmC+GCeTcqzlxqmWtxurejxG3K+kZ6H0UKwNshoK1CYnmJ1sg==", - "dependencies": { - "System.Security.Permissions": "7.0.0" - } + "resolved": "8.0.0", + "contentHash": "7nit//efUTy1OsAKco2f02PMrwsR2S234N0dVVp84udC77YcvpOQDz5znAWMtgMWBzY1aRJvUW61jo/7vQRfXg==" }, "System.DirectoryServices.AccountManagement": { "type": "Transitive", - "resolved": "7.0.1", - "contentHash": "UNytHYwA5IF55WQhashsMG57ize83JUGJxD8YJlOyO9ZlMTOD4Nt7y+A6mvmrU/swDoYWaVL+TNwE6hk9lyvbA==", + "resolved": "8.0.0", + "contentHash": "dCT8BYeeisx0IzAf6x+FSVWK3gz2fKI9pgLV16c7dY/lckw4aodNrgXqsFqyqJN5Kfxc3oklG+SCMYkRfg1V7A==", "dependencies": { - "System.Configuration.ConfigurationManager": "7.0.0", - "System.DirectoryServices": "7.0.1", - "System.DirectoryServices.Protocols": "7.0.1" + "System.Configuration.ConfigurationManager": "8.0.0", + "System.DirectoryServices": "8.0.0", + "System.DirectoryServices.Protocols": "8.0.0" } }, "System.DirectoryServices.Protocols": { "type": "Transitive", - "resolved": "7.0.1", - "contentHash": "t9hsL+UYRzNs30pnT2Tdx6ngX8McFUjru0a0ekNgu/YXfkXN+dx5OvSEv0/p7H2q3pdJLH7TJPWX7e55J8QB9A==" + "resolved": "8.0.0", + "contentHash": "puwJxURHDrYLGTQdsHyeMS72ClTqYa4lDYz6LHSbkZEk5hq8H8JfsO4MyYhB5BMMxg93jsQzLUwrnCumj11UIg==" }, "System.Drawing.Common": { "type": "Transitive", - "resolved": "7.0.0", - "contentHash": "KIX+oBU38pxkKPxvLcLfIkOV5Ien8ReN78wro7OF5/erwcmortzeFx+iBswlh2Vz6gVne0khocQudGwaO1Ey6A==", + "resolved": "8.0.1", + "contentHash": "x0rAZECxIGx/YVjN28YRdpqka0+H7YMN9741FUDzipXPDzesd60gef/LI0ZCOcYSDsacTLTHvMAvxHG+TjbNNQ==", "dependencies": { - "Microsoft.Win32.SystemEvents": "7.0.0" + "Microsoft.Win32.SystemEvents": "8.0.0" } }, "System.Formats.Asn1": { "type": "Transitive", - "resolved": "7.0.0", - "contentHash": "+nfpV0afLmvJW8+pLlHxRjz3oZJw4fkyU9MMEaMhCsHi/SN9bGF9q79ROubDiwTiCHezmK0uCWkPP7tGFP/4yg==" + "resolved": "8.0.0", + "contentHash": "AJukBuLoe3QeAF+mfaRKQb2dgyrvt340iMBHYv+VdBzCUM06IxGlvl0o/uPOS7lHnXPN6u8fFRHSHudx5aTi8w==" }, "System.Globalization": { "type": "Transitive", @@ -1074,15 +1068,15 @@ }, "System.IO.Packaging": { "type": "Transitive", - "resolved": "7.0.0", - "contentHash": "+j5ezLP7785/pd4taKQhXAWsymsIW2nTnE/U3/jpGZzcJx5lip6qkj6UrxSE7ZYZfL0GaLuymwGLqwJV/c7O7Q==" + "resolved": "8.0.0", + "contentHash": "8g1V4YRpdGAxFcK8v9OjuMdIOJSpF30Zb1JGicwVZhly3I994WFyBdV6mQEo8d3T+URQe55/M0U0eIH0Hts1bg==" }, "System.IO.Ports": { "type": "Transitive", - "resolved": "7.0.0", - "contentHash": "0nWQjM5IofaIGpvkifN+LLuYwBG6BHlpmphLhhOJepcW12G8qToGuNDRgBzeTVBZzp33wVsESSZ8hUOCfq+8QA==", + "resolved": "8.0.0", + "contentHash": "MaiPbx2/QXZc62gm/DrajRrGPG1lU4m08GWMoWiymPYM+ba4kfACp2PbiYpqJ4QiFGhHD00zX3RoVDTucjWe9g==", "dependencies": { - "runtime.native.System.IO.Ports": "7.0.0" + "runtime.native.System.IO.Ports": "8.0.0" } }, "System.Linq": { @@ -1123,16 +1117,16 @@ }, "System.Management": { "type": "Transitive", - "resolved": "7.0.2", - "contentHash": "/qEUN91mP/MUQmJnM5y5BdT7ZoPuVrtxnFlbJ8a3kBJGhe2wCzBfnPFtK2wTtEEcf3DMGR9J00GZZfg6HRI6yA==", + "resolved": "8.0.0", + "contentHash": "jrK22i5LRzxZCfGb+tGmke2VH7oE0DvcDlJ1HAKYU8cPmD8XnpUT0bYn2Gy98GEhGjtfbR/sxKTVb+dE770pfA==", "dependencies": { - "System.CodeDom": "7.0.0" + "System.CodeDom": "8.0.0" } }, "System.Memory": { "type": "Transitive", - "resolved": "4.5.5", - "contentHash": "XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw==" + "resolved": "4.5.3", + "contentHash": "3oDzvc/zzetpTKWMShs1AADwZjQ/36HnsufHRPcOjyRAAMLDlu2iD33MBI2opxnezcVUtXyqDXXjoFMOU9c7SA==" }, "System.Net.Http": { "type": "Transitive", @@ -1169,8 +1163,8 @@ }, "System.Net.Http.WinHttpHandler": { "type": "Transitive", - "resolved": "7.0.0", - "contentHash": "DJjlxpGJMHd7WxTo/WRb9q1tahjZvjer7Xo4rsOKAocrwewpA9L0YLxcKEx0nHQnruiWGgbngSzYt3YUwxc+2A==" + "resolved": "8.0.0", + "contentHash": "dAtcyQzDpi34VdR1BeEV8yCOeXVEyekYYK6lJZIzG/N5aqEGgT6AB2DsbiidMp8cB6Y7DqqcmQFZaSGUdoubvQ==" }, "System.Net.Primitives": { "type": "Transitive", @@ -1215,8 +1209,8 @@ }, "System.Private.ServiceModel": { "type": "Transitive", - "resolved": "4.10.2", - "contentHash": "bi2/w2EDXqxno8zfbt6vHcrpGw0Pav8tEMzmJraHwJvWYJd45wcqr7gNa2IUs91j4z+BNGMooStaWS6pm2Lq0A==", + "resolved": "4.10.3", + "contentHash": "BcUV7OERlLqGxDXZuIyIMMmk1PbqBblLRbAoigmzIUx/M8A+8epvyPyXRpbgoucKH7QmfYdQIev04Phx2Co08A==", "dependencies": { "Microsoft.Bcl.AsyncInterfaces": "5.0.0", "Microsoft.Extensions.ObjectPool": "5.0.10", @@ -1240,8 +1234,8 @@ }, "System.Reflection.Context": { "type": "Transitive", - "resolved": "7.0.0", - "contentHash": "rVf4vEyGQphXTITF39uXlgTcp8Ekcu2aNwxyVLU7fDyNOk0W+/PPpj9PoC2cFL4wgJZJltiss5eQptE2C4f1Sw==" + "resolved": "8.0.0", + "contentHash": "k76ubeIBOeIVg7vkQ4I+LoB8sY1EzFIc3oHEtoiNLhXleb7TBLXUQu0CFZ4sPlXJzWNabRf+gn1T7lyhOBxIMA==" }, "System.Reflection.DispatchProxy": { "type": "Transitive", @@ -1294,8 +1288,11 @@ }, "System.Reflection.Metadata": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "5NecZgXktdGg34rh1OenY1rFNDCI8xSjFr+Z4OU4cU06AQHUdRnIIEeWENu3Wl4YowbzkymAIMvi3WyK9U53pQ==" + "resolved": "7.0.0", + "contentHash": "MclTG61lsD9sYdpNz9xsKBzjsmsfCtcMZYXz/IUr2zlhaTaABonlr1ESeompTgM+Xk+IwtGYU7/voh3YWB/fWw==", + "dependencies": { + "System.Collections.Immutable": "7.0.0" + } }, "System.Reflection.Primitives": { "type": "Transitive", @@ -1339,10 +1336,10 @@ }, "System.Runtime.Caching": { "type": "Transitive", - "resolved": "7.0.0", - "contentHash": "M0riW7Zgxca3Elp1iZVhzH7PWWT5bPSrdMFGCAGoH1n9YLuXOYE78ryui051Icf3swWWa8feBRoSxOCYwgMy8w==", + "resolved": "8.0.0", + "contentHash": "4TmlmvGp4kzZomm7J2HJn6IIx0UUrQyhBDyb5O1XiunZlQImXW+B8b7W/sTPcXhSf9rp5NR5aDtQllwbB5elOQ==", "dependencies": { - "System.Configuration.ConfigurationManager": "7.0.0" + "System.Configuration.ConfigurationManager": "8.0.0" } }, "System.Runtime.CompilerServices.Unsafe": { @@ -1513,10 +1510,10 @@ }, "System.Security.Cryptography.Pkcs": { "type": "Transitive", - "resolved": "7.0.3", - "contentHash": "yhwEHH5Gzl/VoADrXtt5XC95OFoSjNSWLHNutE7GwdOgefZVRvEXRSooSpL8HHm3qmdd9epqzsWg28UJemt22w==", + "resolved": "8.0.0", + "contentHash": "ULmp3xoOwNYjOYp4JZ2NK/6NdTgiN1GQXzVVN1njQ7LOZ0d0B9vyMnhyqbIi9Qw4JXj1JgCsitkTShboHRx7Eg==", "dependencies": { - "System.Formats.Asn1": "7.0.0" + "System.Formats.Asn1": "8.0.0" } }, "System.Security.Cryptography.Primitives": { @@ -1535,8 +1532,8 @@ }, "System.Security.Cryptography.ProtectedData": { "type": "Transitive", - "resolved": "7.0.1", - "contentHash": "3evI3sBfKqwYSwuBcYgShbmEgtXcg8N5Qu+jExLdkBXPty2yGDXq5m1/4sx9Exb8dqdeMPUs/d9DQ0wy/9Adwg==" + "resolved": "8.0.0", + "contentHash": "+TUFINV2q2ifyXauQXRwy4CiBhqvDEDZeVJU7qfxya4aRYOKzVBpN+4acx25VcPB9ywUN6C0n8drWl110PhZEg==" }, "System.Security.Cryptography.X509Certificates": { "type": "Transitive", @@ -1572,18 +1569,18 @@ }, "System.Security.Cryptography.Xml": { "type": "Transitive", - "resolved": "7.0.1", - "contentHash": "MCxBCtH0GrDuvU63ZODwQHQZPchb24pUAX3MfZ6b13qg246ZD10PRdOvay8C9HBPfCXkymUNwFPEegud7ax2zg==", + "resolved": "8.0.0", + "contentHash": "HQSFbakswZ1OXFz2Bt3AJlC6ENDqWeVpgqhf213xqQUMDifzydOHIKVb1RV4prayobvR3ETIScMaQdDF2hwGZA==", "dependencies": { - "System.Security.Cryptography.Pkcs": "7.0.0" + "System.Security.Cryptography.Pkcs": "8.0.0" } }, "System.Security.Permissions": { "type": "Transitive", - "resolved": "7.0.0", - "contentHash": "Vmp0iRmCEno9BWiskOW5pxJ3d9n+jUqKxvX4GhLwFhnQaySZmBN2FuC0N5gjFHgyFMUjC5sfIJ8KZfoJwkcMmA==", + "resolved": "8.0.0", + "contentHash": "v/BBylw7XevuAsHXoX9dDUUfmBIcUf7Lkz8K3ZXIKz3YRKpw8YftpSir4n4e/jDTKFoaK37AsC3xnk+GNFI1Ow==", "dependencies": { - "System.Windows.Extensions": "7.0.0" + "System.Windows.Extensions": "8.0.0" } }, "System.Security.Principal.Windows": { @@ -1593,65 +1590,65 @@ }, "System.ServiceModel.Duplex": { "type": "Transitive", - "resolved": "4.10.2", - "contentHash": "FjFGC7DOTNrkItAYQZVyrDTqXezqFIzmdKL1YaN1ZHdbq5oeyo//iyR+OGXLAfbBxyHultFgV1DetarYBSvIxw==", + "resolved": "4.10.3", + "contentHash": "IZ8ZahvTenWML7/jGUXSCm6jHlxpMbcb+Hy+h5p1WP9YVtb+Er7FHRRGizqQMINEdK6HhWpD6rzr5PdxNyusdg==", "dependencies": { - "System.Private.ServiceModel": "4.10.2", - "System.ServiceModel.Primitives": "4.10.2" + "System.Private.ServiceModel": "4.10.3", + "System.ServiceModel.Primitives": "4.10.3" } }, "System.ServiceModel.Http": { "type": "Transitive", - "resolved": "4.10.2", - "contentHash": "1AhiJwPc+90GjBd/sDkT93RVuRj688ZQInXzWfd56AEjDzWieUcAUh9ppXhRuEVpT+x48D5GBYJc1VxDP4IT+Q==", + "resolved": "4.10.3", + "contentHash": "hodkn0rPTYmoZ9EIPwcleUrOi1gZBPvU0uFvzmJbyxl1lIpVM5GxTrs/pCETStjOXCiXhBDoZQYajquOEfeW/w==", "dependencies": { - "System.Private.ServiceModel": "4.10.2", - "System.ServiceModel.Primitives": "4.10.2" + "System.Private.ServiceModel": "4.10.3", + "System.ServiceModel.Primitives": "4.10.3" } }, "System.ServiceModel.NetTcp": { "type": "Transitive", - "resolved": "4.10.2", - "contentHash": "jWNKeXfccRqkbjTkVyKD0QBBO+FnYWQrNrJByT+oOab17UBLfvUdt1kLhPe3qZalIlBoqvRFtw6yR/oRs5r3Vw==", + "resolved": "4.10.3", + "contentHash": "tP7GN7ehqSIQEz7yOJEtY8ziTpfavf2IQMPKa7r9KGQ75+uEW6/wSlWez7oKQwGYuAHbcGhpJvdG6WoVMKYgkw==", "dependencies": { - "System.Private.ServiceModel": "4.10.2", - "System.ServiceModel.Primitives": "4.10.2" + "System.Private.ServiceModel": "4.10.3", + "System.ServiceModel.Primitives": "4.10.3" } }, "System.ServiceModel.Primitives": { "type": "Transitive", - "resolved": "4.10.2", - "contentHash": "8QOguIqHtWYywBt7SubPhdICE2LClHzqOMDy0LQIui4T3QJOae7g6UR+alCW61nEufYNtO8Uss41EbXqD8hdww==", + "resolved": "4.10.3", + "contentHash": "aNcdry95wIP1J+/HcLQM/f/AA73LnBQDNc2uCoZ+c1//KpVRp8nMZv5ApMwK+eDNVdCK8G0NLInF+xG3mfQL+g==", "dependencies": { - "System.Private.ServiceModel": "4.10.2" + "System.Private.ServiceModel": "4.10.3" } }, "System.ServiceModel.Security": { "type": "Transitive", - "resolved": "4.10.2", - "contentHash": "BdYdPHRJLqwFAJYbmdl9M5qqjJ6O7TVw9AMcW0h7+oc0ah0ufYuSIrN75BhNNAIcaYcviWzC3PDT5H0QkNX7ug==", + "resolved": "4.10.3", + "contentHash": "vqelKb7DvP2inb6LDJ5Igi8wpOYdtLXn5luDW5qEaqkV2sYO1pKlVYBpr6g6m5SevzbdZlVNu67dQiD/H6EdGQ==", "dependencies": { - "System.Private.ServiceModel": "4.10.2", - "System.ServiceModel.Primitives": "4.10.2" + "System.Private.ServiceModel": "4.10.3", + "System.ServiceModel.Primitives": "4.10.3" } }, "System.ServiceModel.Syndication": { "type": "Transitive", - "resolved": "7.0.0", - "contentHash": "V3q1Jr3KWo+i201/vUUPfg83rjJLhL5+ROh16PtPhaUJRHwoEBoGWtg0r6pFBRPaDqNY6hXvNgHktDj0gvMEpA==" + "resolved": "8.0.0", + "contentHash": "CJxIUwpBkMCPmIx46tFVOt0zpRrYurUHLW6tJBcmyj+MyWpKc6MMcS69B7IdlV/bgtgys073wMIHZX9QOQ1OFA==" }, "System.ServiceProcess.ServiceController": { "type": "Transitive", - "resolved": "7.0.1", - "contentHash": "rPfXTJzYU46AmWYXRATQzQQ01hICrkl3GuUHgpAr9mnUwAVSsga5x3mBxanFPlJBV9ilzqMXbQyDLJQAbyTnSw==", + "resolved": "8.0.0", + "contentHash": "jtYVG3bpw2n/NvNnP2g/JLri0D4UtfusTvLeH6cZPNAEjJXJVGspS3wLgVvjNbm+wjaYkFgsXejMTocV1T5DIQ==", "dependencies": { - "System.Diagnostics.EventLog": "7.0.0" + "System.Diagnostics.EventLog": "8.0.0" } }, "System.Speech": { "type": "Transitive", - "resolved": "7.0.0", - "contentHash": "7E0uB92Cx2sXR67HW9rMKJqDACdLuz9t3I3OwZUFDzAgwKXWuY6CYeRT/NiypHcyZO2be9+0H0w0M6fn7HQtgQ==" + "resolved": "8.0.0", + "contentHash": "CNuiA6vb95Oe5PRjClZEBiaju31vwB8OIeCgeSBXyZL6+MS4RVVB2X/C11z0xCkooHE3Vy91nM2z76emIzR+sg==" }, "System.Text.Encoding": { "type": "Transitive", @@ -1665,8 +1662,8 @@ }, "System.Text.Encoding.CodePages": { "type": "Transitive", - "resolved": "7.0.0", - "contentHash": "LSyCblMpvOe0N3E+8e0skHcrIhgV2huaNcjUUEa8hRtgEAm36aGkRoC8Jxlb6Ra6GSfF29ftduPNywin8XolzQ==" + "resolved": "8.0.0", + "contentHash": "OZIsVplFGaVY90G2SbpgU7EnCoOO5pw1t4ic21dBF3/1omrJFpAGoNAVpPyMVOC90/hvgkGG3VFqR13YgZMQfg==" }, "System.Text.Encoding.Extensions": { "type": "Transitive", @@ -1681,8 +1678,17 @@ }, "System.Text.Encodings.Web": { "type": "Transitive", - "resolved": "7.0.0", - "contentHash": "OP6umVGxc0Z0MvZQBVigj4/U31Pw72ITihDWP9WiWDm+q5aoe0GaJivsfYGq53o6dxH7DcXWiCTl7+0o2CGdmg==" + "resolved": "8.0.0", + "contentHash": "yev/k9GHAEGx2Rg3/tU6MQh4HGBXJs70y7j1LaM1i/ER9po+6nnQ6RRqTJn1E7Xu0fbIFK80Nh5EoODxrbxwBQ==" + }, + "System.Text.Json": { + "type": "Transitive", + "resolved": "6.0.9", + "contentHash": "2j16oUgtIzl7Xtk7demG0i/v5aU/ZvULcAnJvPb63U3ZhXJ494UYcxuEj5Fs49i3XDrk5kU/8I+6l9zRCw3cJw==", + "dependencies": { + "System.Runtime.CompilerServices.Unsafe": "6.0.0", + "System.Text.Encodings.Web": "6.0.0" + } }, "System.Text.RegularExpressions": { "type": "Transitive", @@ -1703,8 +1709,8 @@ }, "System.Threading.AccessControl": { "type": "Transitive", - "resolved": "7.0.1", - "contentHash": "uh6LWSk8Dlp1cavk4XQYtDHOMZpSa5KiqM0VBiflhXWGT63RGV+NhNsVxiEykL4S/0LVcgy+/AxC5ITQ9QLo8w==" + "resolved": "8.0.0", + "contentHash": "cIed5+HuYz+eV9yu9TH95zPkqmm1J9Qps9wxjB335sU8tsqc2kGdlTEH9FZzZeCS8a7mNSEsN8ZkyhQp1gfdEw==" }, "System.Threading.Tasks": { "type": "Transitive", @@ -1718,8 +1724,13 @@ }, "System.Threading.Tasks.Extensions": { "type": "Transitive", - "resolved": "4.5.4", - "contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==" + "resolved": "4.3.0", + "contentHash": "npvJkVKl5rKXrtl1Kkm6OhOUaYGEiF9wFbppFRWSMoApKzt2PiPHT2Bb8a5sAWxprvdOAtvaARS9QYMznEUtug==", + "dependencies": { + "System.Collections": "4.3.0", + "System.Runtime": "4.3.0", + "System.Threading.Tasks": "4.3.0" + } }, "System.Threading.Timer": { "type": "Transitive", @@ -1738,16 +1749,13 @@ }, "System.Web.Services.Description": { "type": "Transitive", - "resolved": "4.10.2", - "contentHash": "AuovAoDhSaXAn7zhOboWW9G5P7LfTEEMOLzeZS87QuGZ5r4kFmJ3+NrzxeS6tumE+KQCQ/K5tNeTXCiVddErxQ==" + "resolved": "4.10.3", + "contentHash": "ORCkTkUo9f1o4ACG+H6SV+0XSxVZ461w3cHzYxEU41y6aKWp1CeNTMYbtdxMw1we6c6t4Hqq15PdcLVcdqno/g==" }, "System.Windows.Extensions": { "type": "Transitive", - "resolved": "7.0.0", - "contentHash": "bR4qdCmssMMbo9Fatci49An5B1UaVJZHKNq70PRgzoLYIlitb8Tj7ns/Xt5Pz1CkERiTjcVBDU2y1AVrPBYkaw==", - "dependencies": { - "System.Drawing.Common": "7.0.0" - } + "resolved": "8.0.0", + "contentHash": "Obg3a90MkOw9mYKxrardLpY2u0axDMrSmy4JCdq2cYbelM2cUwmUir5Bomvd1yxmPL9h5LVHU1tuKBZpUjfASg==" }, "System.Xml.ReaderWriter": { "type": "Transitive", diff --git a/src/PSRule.BuildTool/PSRule.BuildTool.csproj b/src/PSRule.BuildTool/PSRule.BuildTool.csproj index c2c10f0dcd..574fa66046 100644 --- a/src/PSRule.BuildTool/PSRule.BuildTool.csproj +++ b/src/PSRule.BuildTool/PSRule.BuildTool.csproj @@ -2,7 +2,7 @@ Exe - net6.0 + net8.0 PSRule.BuildTool false false diff --git a/src/PSRule.BuildTool/packages.lock.json b/src/PSRule.BuildTool/packages.lock.json index c692a3946f..8009834a20 100644 --- a/src/PSRule.BuildTool/packages.lock.json +++ b/src/PSRule.BuildTool/packages.lock.json @@ -1,7 +1,7 @@ { "version": 1, "dependencies": { - "net6.0": { + "net8.0": { "Microsoft.SourceLink.GitHub": { "type": "Direct", "requested": "[8.0.0, )", diff --git a/src/PSRule.Common.props b/src/PSRule.Common.props index 32bae439f1..22b41a8220 100644 --- a/src/PSRule.Common.props +++ b/src/PSRule.Common.props @@ -3,7 +3,7 @@ netstandard2.0 - 11.0 + 12.0 enable en-US diff --git a/src/PSRule.Tool/PSRule.Tool.csproj b/src/PSRule.Tool/PSRule.Tool.csproj index 4fd2e4b842..a3bf9e7b2b 100644 --- a/src/PSRule.Tool/PSRule.Tool.csproj +++ b/src/PSRule.Tool/PSRule.Tool.csproj @@ -8,7 +8,7 @@ {bddbfdb8-614f-4b8a-930c-dcb60144598c} 12.0 enable - net7.0 + net8.0 PSRule.Tool.Program true true @@ -19,8 +19,8 @@ - - + + all diff --git a/src/PSRule.Tool/packages.lock.json b/src/PSRule.Tool/packages.lock.json index 321b5589d9..4eb48dad50 100644 --- a/src/PSRule.Tool/packages.lock.json +++ b/src/PSRule.Tool/packages.lock.json @@ -1,7 +1,7 @@ { "version": 1, "dependencies": { - "net7.0": { + "net8.0": { "Microsoft.CodeAnalysis.NetAnalyzers": { "type": "Direct", "requested": "[8.0.0, )", @@ -10,35 +10,31 @@ }, "Microsoft.PowerShell.SDK": { "type": "Direct", - "requested": "[7.3.7, )", - "resolved": "7.3.7", - "contentHash": "5SJlr0NojZTlYmBBSHAvAQP8k6nI/GcFCds4LOj0hH4h24EHW9gYXOeFMM4LUvmg2SuBe0F4fdv4ZFHTaTpZJA==", - "dependencies": { - "Microsoft.Bcl.AsyncInterfaces": "7.0.0", - "Microsoft.Extensions.ObjectPool": "7.0.11", - "Microsoft.Management.Infrastructure.CimCmdlets": "7.3.7", - "Microsoft.PowerShell.Commands.Diagnostics": "7.3.7", - "Microsoft.PowerShell.Commands.Management": "7.3.7", - "Microsoft.PowerShell.Commands.Utility": "7.3.7", - "Microsoft.PowerShell.ConsoleHost": "7.3.7", - "Microsoft.PowerShell.Security": "7.3.7", - "Microsoft.WSMan.Management": "7.3.7", - "Microsoft.Win32.Registry": "5.0.0", - "Microsoft.Windows.Compatibility": "7.0.5", - "System.Data.SqlClient": "4.8.5", - "System.IO.Packaging": "7.0.0", - "System.Management.Automation": "7.3.7", - "System.Net.Http.WinHttpHandler": "7.0.0", - "System.Private.ServiceModel": "4.10.2", - "System.Security.Cryptography.ProtectedData": "7.0.1", - "System.Security.Cryptography.Xml": "7.0.1", - "System.ServiceModel.Duplex": "4.10.2", - "System.ServiceModel.Http": "4.10.2", - "System.ServiceModel.NetTcp": "4.10.2", - "System.ServiceModel.Primitives": "4.10.2", - "System.ServiceModel.Security": "4.10.2", - "System.Text.Encodings.Web": "7.0.0", - "System.Web.Services.Description": "4.10.2" + "requested": "[7.4.1, )", + "resolved": "7.4.1", + "contentHash": "1h0KixYhgGUuRQssGWdqvMCxyHYerw8VPK0XEu2OllUj704yGNTsLb2MjUPV5m35UR/R3JSJL6l9VDx1DdG0fw==", + "dependencies": { + "Microsoft.Extensions.ObjectPool": "5.0.17", + "Microsoft.Management.Infrastructure.CimCmdlets": "7.4.1", + "Microsoft.PowerShell.Commands.Diagnostics": "7.4.1", + "Microsoft.PowerShell.Commands.Management": "7.4.1", + "Microsoft.PowerShell.Commands.Utility": "7.4.1", + "Microsoft.PowerShell.ConsoleHost": "7.4.1", + "Microsoft.PowerShell.Security": "7.4.1", + "Microsoft.WSMan.Management": "7.4.1", + "Microsoft.Windows.Compatibility": "8.0.1", + "System.Data.SqlClient": "4.8.6", + "System.IO.Packaging": "8.0.0", + "System.Management.Automation": "7.4.1", + "System.Net.Http.WinHttpHandler": "8.0.0", + "System.Private.ServiceModel": "4.10.3", + "System.ServiceModel.Duplex": "4.10.3", + "System.ServiceModel.Http": "4.10.3", + "System.ServiceModel.NetTcp": "4.10.3", + "System.ServiceModel.Primitives": "4.10.3", + "System.ServiceModel.Security": "4.10.3", + "System.Text.Encodings.Web": "8.0.0", + "System.Web.Services.Description": "4.10.3" } }, "Microsoft.SourceLink.GitHub": { @@ -59,27 +55,56 @@ }, "System.Management.Automation": { "type": "Direct", - "requested": "[7.3.7, )", - "resolved": "7.3.7", - "contentHash": "2RXHukEXtvizHACZPsLHIpWU1IZ6dasuQ5JyAIr69saqRSqmWNFUj6Qj8dgWlC89WoWNVra0n9Ty4xjg6xVOfg==", + "requested": "[7.4.1, )", + "resolved": "7.4.1", + "contentHash": "EYUMyAoYAw4Zt+cxKRMjZxzoa6gI++O4sK+cSg8HUhC1HfrJoMhD1u1Fo5CvlGv1KX3OmoJSyukgkDmRHFLQiw==", "dependencies": { "Microsoft.ApplicationInsights": "2.21.0", - "Microsoft.CSharp": "4.7.0", - "Microsoft.Management.Infrastructure": "2.0.0", - "Microsoft.PowerShell.CoreCLR.Eventing": "7.3.7", - "Microsoft.PowerShell.Native": "7.3.2", + "Microsoft.Management.Infrastructure": "3.0.0", + "Microsoft.PowerShell.CoreCLR.Eventing": "7.4.1", + "Microsoft.PowerShell.Native": "7.4.0", "Microsoft.Security.Extensions": "1.2.0", - "Microsoft.Win32.Registry.AccessControl": "7.0.0", + "Microsoft.Win32.Registry.AccessControl": "8.0.0", "Newtonsoft.Json": "13.0.3", - "System.Configuration.ConfigurationManager": "7.0.0", - "System.Diagnostics.DiagnosticSource": "7.0.2", - "System.DirectoryServices": "7.0.1", - "System.Management": "7.0.2", + "System.Configuration.ConfigurationManager": "8.0.0", + "System.Diagnostics.DiagnosticSource": "8.0.0", + "System.DirectoryServices": "8.0.0", + "System.Management": "8.0.0", "System.Security.AccessControl": "6.0.0", - "System.Security.Cryptography.Pkcs": "7.0.3", - "System.Security.Cryptography.ProtectedData": "7.0.1", - "System.Security.Permissions": "7.0.0", - "System.Text.Encoding.CodePages": "7.0.0" + "System.Security.Cryptography.Pkcs": "8.0.0", + "System.Security.Permissions": "8.0.0", + "System.Text.Encoding.CodePages": "8.0.0" + } + }, + "JetBrains.Annotations": { + "type": "Transitive", + "resolved": "2021.2.0", + "contentHash": "kKSyoVfndMriKHLfYGmr0uzQuI4jcc3TKGyww7buJFCYeHb/X0kodYBPL7n9454q7v6ASiRmDgpPGaDGerg/Hg==" + }, + "Json.More.Net": { + "type": "Transitive", + "resolved": "1.9.3", + "contentHash": "BKIsKHXR2Jq+LdLdxPo3L09Lv0ld9xs1fAMvSAe2cf2YOl3at9vw0RrMlhC2ookDi7VtrgHXzc2Et5mVBOAUdw==", + "dependencies": { + "System.Text.Json": "6.0.2" + } + }, + "JsonPointer.Net": { + "type": "Transitive", + "resolved": "3.0.3", + "contentHash": "mCGQc15lHLp1R2CVhWiipnZurHXm93+LbPPAT/vXQm5PdHt6WQuYLhaEF8VZ+aXL9P2I6bGND6pDTEfqFs6gig==", + "dependencies": { + "Json.More.Net": "1.8.0" + } + }, + "JsonSchema.Net": { + "type": "Transitive", + "resolved": "5.2.7", + "contentHash": "8un7Xq2MoKiWNo0HQtf2sPr3764W9NjNELIx3l9d3fIKEjg3tYtrZmxN+CgXKtzku4g52CqYUZuI+o0ue226vw==", + "dependencies": { + "JetBrains.Annotations": "2021.2.0", + "Json.More.Net": "1.9.0", + "JsonPointer.Net": "3.0.3" } }, "Manatee.Json": { @@ -95,8 +120,8 @@ }, "Markdig.Signed": { "type": "Transitive", - "resolved": "0.31.0", - "contentHash": "u05eQvNRunYLR+J0SZAgt8ia+qCF3cMfyYh7LR4jSjG5Tg+0HuRrv7u/Gox9kOItWlSacMITcHBio7jas/zaEQ==" + "resolved": "0.33.0", + "contentHash": "/BE/XANxmocgEqajbWB/ur4Jei+j1FkXppWH9JFmEuoq8T3xJndkQKZVCW/7lTdc9Ru6kfEAkwSXFOv30EkU2Q==" }, "Microsoft.ApplicationInsights": { "type": "Transitive", @@ -108,8 +133,8 @@ }, "Microsoft.Bcl.AsyncInterfaces": { "type": "Transitive", - "resolved": "7.0.0", - "contentHash": "3aeMZ1N0lJoSyzqiP03hqemtb1BijhsJADdobn/4nsMJ8V1H+CrpuduUe4hlRdx+ikBQju1VGjMD1GJ3Sk05Eg==" + "resolved": "5.0.0", + "contentHash": "W8DPQjkMScOMTtJbPwmPyj9c3zYSFGawDW3jwlBOOsnY+EzZFLgNQ/UMkK35JmkNOVPdCyPr2Tw7Vv9N+KA3ZQ==" }, "Microsoft.Build.Tasks.Git": { "type": "Transitive", @@ -118,67 +143,59 @@ }, "Microsoft.CodeAnalysis.Analyzers": { "type": "Transitive", - "resolved": "3.3.3", - "contentHash": "j/rOZtLMVJjrfLRlAMckJLPW/1rze9MT1yfWqSIbUPGRu1m1P0fuo9PmqapwsmePfGB5PJrudQLvmUOAMF0DqQ==" + "resolved": "3.3.4", + "contentHash": "AxkxcPR+rheX0SmvpLVIGLhOUXAKG56a64kV9VQZ4y9gR9ZmPXnqZvHJnmwLSwzrEP6junUF11vuc+aqo5r68g==" }, "Microsoft.CodeAnalysis.Common": { "type": "Transitive", - "resolved": "4.4.0", - "contentHash": "JfHupS/B7Jb5MZoYkFFABn3mux0wQgxi2D8F/rJYZeRBK2ZOyk7TjQ2Kq9rh6W/DCh0KNbbSbn5qoFar+ueHqw==", + "resolved": "4.8.0", + "contentHash": "/jR+e/9aT+BApoQJABlVCKnnggGQbvGh7BKq2/wI1LamxC+LbzhcLj4Vj7gXCofl1n4E521YfF9w0WcASGg/KA==", "dependencies": { - "Microsoft.CodeAnalysis.Analyzers": "3.3.3", - "System.Collections.Immutable": "6.0.0", - "System.Memory": "4.5.5", - "System.Reflection.Metadata": "5.0.0", - "System.Runtime.CompilerServices.Unsafe": "6.0.0", - "System.Text.Encoding.CodePages": "6.0.0", - "System.Threading.Tasks.Extensions": "4.5.4" + "Microsoft.CodeAnalysis.Analyzers": "3.3.4", + "System.Collections.Immutable": "7.0.0", + "System.Reflection.Metadata": "7.0.0", + "System.Runtime.CompilerServices.Unsafe": "6.0.0" } }, "Microsoft.CodeAnalysis.CSharp": { "type": "Transitive", - "resolved": "4.4.0", - "contentHash": "eD2w0xHRoaqK07hjlOKGR9eLNy3nimiGNeCClNax1NDgS/+DBtBqCjXelOa+TNy99kIB3nHhUqDmr46nDXy/RQ==", + "resolved": "4.8.0", + "contentHash": "+3+qfdb/aaGD8PZRCrsdobbzGs1m9u119SkkJt8e/mk3xLJz/udLtS2T6nY27OTXxBBw10HzAbC8Z9w08VyP/g==", "dependencies": { - "Microsoft.CodeAnalysis.Common": "[4.4.0]" + "Microsoft.CodeAnalysis.Common": "[4.8.0]" } }, - "Microsoft.CSharp": { - "type": "Transitive", - "resolved": "4.7.0", - "contentHash": "pTj+D3uJWyN3My70i2Hqo+OXixq3Os2D1nJ2x92FFo6sk8fYS1m1WLNTs0Dc1uPaViH0YvEEwvzddQ7y4rhXmA==" - }, "Microsoft.Extensions.ObjectPool": { "type": "Transitive", - "resolved": "7.0.11", - "contentHash": "DfzSvWdWl4b5VYGQI6tzxJWXSoyLE88C0W+whseE8RuYvC5wEGQLuIPiMXQtqxCBGN4rt0PTuDhdGAjtz4JZzw==" + "resolved": "5.0.17", + "contentHash": "EkIghF7cRBcogXKrfhopcCRjMs6b19THqSvACV5Oppp0nDA8oNyTLpAsfBQJ1hLgOjHfc5eNKFaFocKdg9nmnA==" }, "Microsoft.Management.Infrastructure": { "type": "Transitive", - "resolved": "2.0.0", - "contentHash": "IaKZRNBBv3sdrmBWd+aqwHq8cVHk/3WgWFAN/dt40MRY9rbtHiDfTTmaEN0tGTmQqGCGDo/ncntA8MvFMvcsRw==", + "resolved": "3.0.0", + "contentHash": "cGZi0q5IujCTVYKo9h22Pw+UwfZDV82HXO8HTxMG2HqntPlT3Ls8jY6punLp4YzCypJNpfCAu2kae3TIyuAiJw==", "dependencies": { - "Microsoft.Management.Infrastructure.Runtime.Unix": "2.0.0", - "Microsoft.Management.Infrastructure.Runtime.Win": "2.0.0" + "Microsoft.Management.Infrastructure.Runtime.Unix": "3.0.0", + "Microsoft.Management.Infrastructure.Runtime.Win": "3.0.0" } }, "Microsoft.Management.Infrastructure.CimCmdlets": { "type": "Transitive", - "resolved": "7.3.7", - "contentHash": "Gf5kH5gBwq41jB6VMBwfjJSJLbc0A+B+f+6buH5TmJ4+M62/Ye44mbUKMMPWAd4tSlsK8cfhUrPp4u6Br/wDJw==", + "resolved": "7.4.1", + "contentHash": "y8ssJEx6pd+8Nsebupt8dKyyc+kLutacaaZBIRLKfUc84BE0Pv88hs6v8TF1M3c7pk2xkZDmEXMdVDCpWdJ/YQ==", "dependencies": { - "System.Management.Automation": "7.3.7" + "System.Management.Automation": "7.4.1" } }, "Microsoft.Management.Infrastructure.Runtime.Unix": { "type": "Transitive", - "resolved": "2.0.0", - "contentHash": "p0lslMX5bdWLxO2P7ao+rjAMOB0LEwPYpzvdCQ2OEYgX2NxFpQ8ILvqPGnYlTAb53rT8gu5DyIol1HboHFYfxQ==" + "resolved": "3.0.0", + "contentHash": "QZE3uEDvZ0m7LabQvcmNOYHp7v1QPBVMpB/ild0WEE8zqUVAP5y9rRI5we37ImI1bQmW5pZ+3HNC70POPm0jBQ==" }, "Microsoft.Management.Infrastructure.Runtime.Win": { "type": "Transitive", - "resolved": "2.0.0", - "contentHash": "vjBWQeDOjgernkrOdbEgn7M70SF7hof7ORdKPSlL06Uc15+oYdth5dZju9KsgUoti/cwnkZTiwtDx/lRtay0sA==" + "resolved": "3.0.0", + "contentHash": "uwMyWN33+iQ8Wm/n1yoPXgFoiYNd0HzJyoqSVhaQZyJfaQrJR3udgcIHjqa1qbc3lS6kvfuUMN4TrF4U4refCQ==" }, "Microsoft.NETCore.Platforms": { "type": "Transitive", @@ -192,50 +209,51 @@ }, "Microsoft.PowerShell.Commands.Diagnostics": { "type": "Transitive", - "resolved": "7.3.7", - "contentHash": "/luilQ1m5xwwfmw0P/d+7VCdfVsqFwGFXo6rfk6EePzpveY+NMrKteWMFK8Z/ofzpEqCfDKQoQ02PnPjLHl8ZA==", + "resolved": "7.4.1", + "contentHash": "TSNoWEO9xQPcQG5TYgi3puJgpwjVbMe5VesgzfZ4+lGWJurgI3y2vanXZSQoyRzlreqEtjBNggS/8TpHOO3nZA==", "dependencies": { - "System.Management.Automation": "7.3.7" + "System.Management.Automation": "7.4.1" } }, "Microsoft.PowerShell.Commands.Management": { "type": "Transitive", - "resolved": "7.3.7", - "contentHash": "lPJVT77kxKpCQmSNRDjjUrL/RuNw6hm3CpJZ533uPxALhkoemXgCGtukOqYh0mCL7CuDWEcRs6HGOLh0AdEpaA==", + "resolved": "7.4.1", + "contentHash": "LtyD1q6dHsiYeVsWLEbBajP1AKy83uXYNwyvRxU6uxO3q4N3+Ntt0wHQsZmSBoS3jvEvHDJklMxmW/SHoNgSKw==", "dependencies": { - "Microsoft.PowerShell.Security": "7.3.7", - "System.ServiceProcess.ServiceController": "7.0.1" + "Microsoft.PowerShell.Security": "7.4.1", + "System.ServiceProcess.ServiceController": "8.0.0" } }, "Microsoft.PowerShell.Commands.Utility": { "type": "Transitive", - "resolved": "7.3.7", - "contentHash": "y0Hqr61WeXL1mQdYXilPBFUGLOFesKCXNpf/qScvkMpA7HVZywsIpe/JnSnEXcAVFOqqCKfqWBGhoHDxo/XU1A==", + "resolved": "7.4.1", + "contentHash": "P2Fynn9+ooRiAKP5zB2e/fo0hhp/Ss81fqASAHH3mevYZPmnAPNxZMAkvMiayA8Pe6o6GVswXj3vyf5JUtN+mg==", "dependencies": { - "Markdig.Signed": "0.31.0", - "Microsoft.CodeAnalysis.CSharp": "4.4.0", + "Json.More.Net": "1.9.3", + "JsonSchema.Net": "5.2.7", + "Markdig.Signed": "0.33.0", + "Microsoft.CodeAnalysis.CSharp": "4.8.0", "Microsoft.PowerShell.MarkdownRender": "7.2.1", - "NJsonSchema": "10.8.0", - "Namotion.Reflection": "2.1.2", - "System.Drawing.Common": "7.0.0", - "System.Management.Automation": "7.3.7", - "System.Threading.AccessControl": "7.0.1" + "System.Drawing.Common": "8.0.1", + "System.Management.Automation": "7.4.1", + "System.Text.Json": "6.0.9", + "System.Threading.AccessControl": "8.0.0" } }, "Microsoft.PowerShell.ConsoleHost": { "type": "Transitive", - "resolved": "7.3.7", - "contentHash": "fRk4MFn5kv8RkKmQ+FhA7CPmKWxgYwa6y6VpyrUyIdnWDJYuzWocV9CnHJcjZ1GKpapa+g0CMVv1ZZSfMuOC0Q==", + "resolved": "7.4.1", + "contentHash": "IXyTckU0QE0+YdHyR7MvcUXPUQAGMQISX0M0594fV1gemkiIgRQ2Q7la3lEjE09GGwxAkDEfWFxE3Z/cDjV77w==", "dependencies": { - "System.Management.Automation": "7.3.7" + "System.Management.Automation": "7.4.1" } }, "Microsoft.PowerShell.CoreCLR.Eventing": { "type": "Transitive", - "resolved": "7.3.7", - "contentHash": "NQ/c1Ar+HezhDP95DJBX4xk1/EfvQ+J2I6scjqd7zKW8r5ZoQnDAtCCPPmXriHXjbYLvsQcVvRfVEc1UcELvug==", + "resolved": "7.4.1", + "contentHash": "uyByMNZ3XVUrJAxdHrXM/75vcKdfbs04J5iIZfDA8m9z8TJDViRMjyHNcp8K/ZXyzpT2Lua2d7g+dP47E9wAcg==", "dependencies": { - "System.Diagnostics.EventLog": "7.0.0" + "System.Diagnostics.EventLog": "8.0.0" } }, "Microsoft.PowerShell.MarkdownRender": { @@ -248,15 +266,15 @@ }, "Microsoft.PowerShell.Native": { "type": "Transitive", - "resolved": "7.3.2", - "contentHash": "MlLhJgzrUlxijTKJ19Eht++iGTUdg/F1jSbqwzjnc2Q8XStkUYNh8/81aUcNxWcg+0z1Yj/iUjW7czgWUYdV6Q==" + "resolved": "7.4.0", + "contentHash": "FlaJ3JBWhqFToYT0ycMb/Xxzoof7oTQbNyI4UikgubC7AMWt5ptBNKjIAMPvOcvEHr+ohaO9GvRWp3tiyS3sKw==" }, "Microsoft.PowerShell.Security": { "type": "Transitive", - "resolved": "7.3.7", - "contentHash": "5HiwXbj6ZXUGVBu3vMmorNJxoDEM5UYGFBRqrQ9ZiEryz3fmnAgJ2/KN2H9pvHWS/5o9PQLctjlPRCcDduX2qQ==", + "resolved": "7.4.1", + "contentHash": "fEMvsyVqDdGFMSLjNpFHdEGo/OGO2WaQlpyqUCKgRknpnpO+MBaWZBGcLu81sidN3iDtTotClzQOQwVXMeNEVQ==", "dependencies": { - "System.Management.Automation": "7.3.7" + "System.Management.Automation": "7.4.1" } }, "Microsoft.Security.Extensions": { @@ -271,102 +289,85 @@ }, "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "4.7.0", + "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Security.AccessControl": "4.7.0", + "System.Security.Principal.Windows": "4.7.0" } }, "Microsoft.Win32.Registry.AccessControl": { "type": "Transitive", - "resolved": "7.0.0", - "contentHash": "JwM65WXVca58WzqY/Rpz7FGyHbN/SMdyr/3EI2CwPIYkB55EIRJUdPQJwO64x3ntOwPQoqCATKuDYA9K7Np5Ww==" + "resolved": "8.0.0", + "contentHash": "u8PB9/v02C8mBXzl0vJ7bOyC020zOP+T1mRct+KA46DqZkB40XtsNn9pGD0QowTRsT6R4jPCghn+yAODn2UMMw==" }, "Microsoft.Win32.SystemEvents": { "type": "Transitive", - "resolved": "7.0.0", - "contentHash": "2nXPrhdAyAzir0gLl8Yy8S5Mnm/uBSQQA7jEsILOS1MTyS7DbmV1NgViMtvV1sfCD1ebITpNwb1NIinKeJgUVQ==" + "resolved": "8.0.0", + "contentHash": "9opKRyOKMCi2xJ7Bj7kxtZ1r9vbzosMvRrdEhVhDz8j8MoBGgB+WmC94yH839NPH+BclAjtQ/pyagvi/8gDLkw==" }, "Microsoft.Windows.Compatibility": { "type": "Transitive", - "resolved": "7.0.5", - "contentHash": "N4aTGZVV1PYPLVLtNn6jsh2b20oS87jegwkB1yDbV4Fy8bs2FZvsjEjjQg1wc7E29JKuwdNXOUYd9ww9LKuLtA==", - "dependencies": { - "Microsoft.Win32.Registry.AccessControl": "7.0.0", - "Microsoft.Win32.SystemEvents": "7.0.0", - "System.CodeDom": "7.0.0", - "System.ComponentModel.Composition": "7.0.0", - "System.ComponentModel.Composition.Registration": "7.0.0", - "System.Configuration.ConfigurationManager": "7.0.0", - "System.Data.Odbc": "7.0.0", - "System.Data.OleDb": "7.0.0", + "resolved": "8.0.1", + "contentHash": "Gr6QvHy9Y5PK5/ijl+cnCnfLr9HdCVByHtbPR5CxOAPeshURdJ/SNMi1t7qbUMBFCU9zsBlXSE1q/TsOUS0u8A==", + "dependencies": { + "Microsoft.Win32.Registry.AccessControl": "8.0.0", + "Microsoft.Win32.SystemEvents": "8.0.0", + "System.CodeDom": "8.0.0", + "System.ComponentModel.Composition": "8.0.0", + "System.ComponentModel.Composition.Registration": "8.0.0", + "System.Configuration.ConfigurationManager": "8.0.0", + "System.Data.Odbc": "8.0.0", + "System.Data.OleDb": "8.0.0", "System.Data.SqlClient": "4.8.5", - "System.Diagnostics.EventLog": "7.0.0", - "System.Diagnostics.PerformanceCounter": "7.0.0", - "System.DirectoryServices": "7.0.1", - "System.DirectoryServices.AccountManagement": "7.0.1", - "System.DirectoryServices.Protocols": "7.0.1", - "System.Drawing.Common": "7.0.0", - "System.IO.Packaging": "7.0.0", - "System.IO.Ports": "7.0.0", - "System.Management": "7.0.2", - "System.Reflection.Context": "7.0.0", - "System.Runtime.Caching": "7.0.0", - "System.Security.Cryptography.Pkcs": "7.0.2", - "System.Security.Cryptography.ProtectedData": "7.0.1", - "System.Security.Cryptography.Xml": "7.0.1", - "System.Security.Permissions": "7.0.0", - "System.ServiceModel.Duplex": "4.9.0", - "System.ServiceModel.Http": "4.9.0", - "System.ServiceModel.NetTcp": "4.9.0", - "System.ServiceModel.Primitives": "4.9.0", - "System.ServiceModel.Security": "4.9.0", - "System.ServiceModel.Syndication": "7.0.0", - "System.ServiceProcess.ServiceController": "7.0.1", - "System.Speech": "7.0.0", - "System.Text.Encoding.CodePages": "7.0.0", - "System.Threading.AccessControl": "7.0.1", - "System.Web.Services.Description": "4.9.0" + "System.Diagnostics.EventLog": "8.0.0", + "System.Diagnostics.PerformanceCounter": "8.0.0", + "System.DirectoryServices": "8.0.0", + "System.DirectoryServices.AccountManagement": "8.0.0", + "System.DirectoryServices.Protocols": "8.0.0", + "System.Drawing.Common": "8.0.1", + "System.IO.Packaging": "8.0.0", + "System.IO.Ports": "8.0.0", + "System.Management": "8.0.0", + "System.Reflection.Context": "8.0.0", + "System.Runtime.Caching": "8.0.0", + "System.Security.Cryptography.Pkcs": "8.0.0", + "System.Security.Cryptography.ProtectedData": "8.0.0", + "System.Security.Cryptography.Xml": "8.0.0", + "System.Security.Permissions": "8.0.0", + "System.ServiceModel.Duplex": "4.10.0", + "System.ServiceModel.Http": "4.10.0", + "System.ServiceModel.NetTcp": "4.10.0", + "System.ServiceModel.Primitives": "4.10.0", + "System.ServiceModel.Security": "4.10.0", + "System.ServiceModel.Syndication": "8.0.0", + "System.ServiceProcess.ServiceController": "8.0.0", + "System.Speech": "8.0.0", + "System.Text.Encoding.CodePages": "8.0.0", + "System.Threading.AccessControl": "8.0.0", + "System.Web.Services.Description": "4.10.0" } }, "Microsoft.WSMan.Management": { "type": "Transitive", - "resolved": "7.3.7", - "contentHash": "PAIPSykF8HrRhw8OvfWrKPvfKDVHH5geenSjpcoE2UpDHt2EpQsoS05ufc8drNTSzltr7qGYY3yue+Y09eoJAA==", + "resolved": "7.4.1", + "contentHash": "fkngCgs8WB0yk4vB+2Q3r3GQWkWq5y1X6Cn3/Y/UFxmpeSm6UFG10Tl5gi0rD2ZNvQgBP9++VVfxoQgSGqqTNQ==", "dependencies": { - "Microsoft.WSMan.Runtime": "7.3.7", - "System.Management.Automation": "7.3.7", - "System.ServiceProcess.ServiceController": "7.0.1" + "Microsoft.WSMan.Runtime": "7.4.1", + "System.Management.Automation": "7.4.1", + "System.ServiceProcess.ServiceController": "8.0.0" } }, "Microsoft.WSMan.Runtime": { "type": "Transitive", - "resolved": "7.3.7", - "contentHash": "auQMUGq8oVuqYj6O7zJX0Q22Kjdw54gQIeo88tjsADkdtgyfSe1XFFONX/JHPCn3KbYjJrIXI1kYPCwr0wGftQ==" - }, - "Namotion.Reflection": { - "type": "Transitive", - "resolved": "2.1.2", - "contentHash": "7tSHAzX8GWKy0qrW6OgQWD7kAZiqzhq+m1503qczuwuK6ZYhOGCQUxw+F3F4KkRM70aB6RMslsRVSCFeouIehw==", - "dependencies": { - "Microsoft.CSharp": "4.3.0" - } + "resolved": "7.4.1", + "contentHash": "m7NZmuQ7WHcosiU1Fpu82VV7bfI36p4bvMmhZOp2ECCgPYy3RvxVPf/SFKJQ5y721e3Ruindb5mVXxXQZ05TaA==" }, "Newtonsoft.Json": { "type": "Transitive", "resolved": "13.0.3", "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==" }, - "NJsonSchema": { - "type": "Transitive", - "resolved": "10.8.0", - "contentHash": "lChjsLWaxyvElh4WJjVhdIiCtx7rimYGFTxtSi2pAkZf0ZnKaXYIX484HCVyzbDDHejDZPgOrcfAJ3kqNSTONw==", - "dependencies": { - "Namotion.Reflection": "2.1.0", - "Newtonsoft.Json": "9.0.1" - } - }, "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": { "type": "Transitive", "resolved": "4.3.2", @@ -384,18 +385,18 @@ }, "runtime.linux-arm.runtime.native.System.IO.Ports": { "type": "Transitive", - "resolved": "7.0.0", - "contentHash": "CBvgRaF+M0xGLDv2Geb/0v0LEADheH8aK72GRAUJdnqnJVsQO60ki1XO8M3keEhnjm+T5NvLm41pNXAVYAPiSg==" + "resolved": "8.0.0", + "contentHash": "gK720fg6HemDg8sXcfy+xCMZ9+hF78Gc7BmREbmkS4noqlu1BAr9qZtuWGhLzFjBfgecmdtl4+SYVwJ1VneZBQ==" }, "runtime.linux-arm64.runtime.native.System.IO.Ports": { "type": "Transitive", - "resolved": "7.0.0", - "contentHash": "5VCyRCtCIYU8FR/W8oo7ouFuJ8tmAg9ddsuXhfCKZfZrbaVZSKxkmNBa6fxkfYPueD0jQfOvwFBmE5c6zalCSw==" + "resolved": "8.0.0", + "contentHash": "KYG6/3ojhEWbb3FwQAKgGWPHrY+HKUXXdVjJlrtyCLn3EMcNTaNcPadb2c0ndQzixZSmAxZKopXJr0nLwhOrpQ==" }, "runtime.linux-x64.runtime.native.System.IO.Ports": { "type": "Transitive", - "resolved": "7.0.0", - "contentHash": "DV9dWDUs23OoZqMWl5IhLr3D+b9koDiSHQxFKdYgWnQbnthv8c/yDjrlrI8nMrDc71RAKCO8jlUojzuPMX04gg==" + "resolved": "8.0.0", + "contentHash": "Wnw5vhA4mgGbIFoo6l9Fk3iEcwRSq49a1aKwJgXUCUtEQLCSUDjTGSxqy/oMUuOyyn7uLHsH8KgZzQ1y3lReiQ==" }, "runtime.native.System": { "type": "Transitive", @@ -418,14 +419,14 @@ }, "runtime.native.System.IO.Ports": { "type": "Transitive", - "resolved": "7.0.0", - "contentHash": "L4Ivegqc3B0Fee7VifFy2JST9nndm+uvJ0viLIZUaImDfnr+JmRin9Tbqd56KuMtm0eVxHpNOWZBPtKrA/1h5Q==", + "resolved": "8.0.0", + "contentHash": "Ee7Sz5llLpTgyKIWzKI/GeuRSbFkOABgJRY00SqTY0OkTYtkB+9l5rFZfE7fxPA3c22RfytCBYkUdAkcmwMjQg==", "dependencies": { - "runtime.linux-arm.runtime.native.System.IO.Ports": "7.0.0", - "runtime.linux-arm64.runtime.native.System.IO.Ports": "7.0.0", - "runtime.linux-x64.runtime.native.System.IO.Ports": "7.0.0", - "runtime.osx-arm64.runtime.native.System.IO.Ports": "7.0.0", - "runtime.osx-x64.runtime.native.System.IO.Ports": "7.0.0" + "runtime.linux-arm.runtime.native.System.IO.Ports": "8.0.0", + "runtime.linux-arm64.runtime.native.System.IO.Ports": "8.0.0", + "runtime.linux-x64.runtime.native.System.IO.Ports": "8.0.0", + "runtime.osx-arm64.runtime.native.System.IO.Ports": "8.0.0", + "runtime.osx-x64.runtime.native.System.IO.Ports": "8.0.0" } }, "runtime.native.System.Net.Http": { @@ -474,13 +475,13 @@ }, "runtime.osx-arm64.runtime.native.System.IO.Ports": { "type": "Transitive", - "resolved": "7.0.0", - "contentHash": "jFwh4sKSXZ7al5XrItEO4GdGWa6XNxvNx+LhEHjrSzOwawO1znwJ+Dy+VjnrkySX9Qi4bnHNLoiqOXbqMuka4g==" + "resolved": "8.0.0", + "contentHash": "rbUBLAaFW9oVkbsb0+XSrAo2QdhBeAyzLl5KQ6Oci9L/u626uXGKInsVJG6B9Z5EO8bmplC8tsMiaHK8wOBZ+w==" }, "runtime.osx-x64.runtime.native.System.IO.Ports": { "type": "Transitive", - "resolved": "7.0.0", - "contentHash": "X4LrHEfke/z9+z+iuVr35NlkhdZldY8JGNMYUN+sfPK/U/6TcE+vP44I0Yv0ir1v0bqIzq3v6Qdv1c1vmp8s4g==" + "resolved": "8.0.0", + "contentHash": "IcfB4jKtM9pkzP9OpYelEcUX1MiDt0IJPBh3XYYdEISFF+6Mc+T8WWi0dr9wVh1gtcdVjubVEIBgB8BHESlGfQ==" }, "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": { "type": "Transitive", @@ -538,8 +539,8 @@ }, "System.CodeDom": { "type": "Transitive", - "resolved": "7.0.0", - "contentHash": "GLltyqEsE5/3IE+zYRP5sNa1l44qKl9v+bfdMcwg+M9qnQf47wK3H0SUR/T+3N4JEQXF3vV4CSuuo0rsg+nq2A==" + "resolved": "8.0.0", + "contentHash": "WTlRjL6KWIMr/pAaq3rYqh0TJlzpouaQ/W1eelssHgtlwHAH25jXTkUphTYx9HaIIf7XA6qs/0+YhtLEQRkJ+Q==" }, "System.Collections": { "type": "Transitive", @@ -570,11 +571,8 @@ }, "System.Collections.Immutable": { "type": "Transitive", - "resolved": "6.0.0", - "contentHash": "l4zZJ1WU2hqpQQHXz1rvC3etVZN+2DLmQMO79FhOTZHMn8tDRr+WU287sbomD0BETlmKDn0ygUgVy9k5xkkJdA==", - "dependencies": { - "System.Runtime.CompilerServices.Unsafe": "6.0.0" - } + "resolved": "7.0.0", + "contentHash": "dQPcs0U1IKnBdRDBkrCTi1FoajSTBzLcVTpjO4MBCMC7f4pDOIPzgBoX8JjG7X6uZRJ8EBxsi8+DR1JuwjnzOQ==" }, "System.ComponentModel.Annotations": { "type": "Transitive", @@ -583,49 +581,48 @@ }, "System.ComponentModel.Composition": { "type": "Transitive", - "resolved": "7.0.0", - "contentHash": "orv0h38ZVPCPo/FW0LGv8/TigXwX8cIwXeQcaNYhikkqELDm8sUFLMcof/Sjcq5EvYCm5NA7MV3hG4u75H44UQ==" + "resolved": "8.0.0", + "contentHash": "bGhUX5BTivJ9Wax0qnJy7uGq7dn/TQkEpJ2Fpu1etg8dbPwyDkUzNPc1d3I2/jUr9y4wDI3a1dkSmi8X21Pzbw==" }, "System.ComponentModel.Composition.Registration": { "type": "Transitive", - "resolved": "7.0.0", - "contentHash": "yy/xYOznnc7Hfg2/LeVqAMlJGv1v7b1ILxFShzx5PWUv53PwU0MaKPG8Dh9DC3gxayzw44UVuQJImhw7LtMKlw==", + "resolved": "8.0.0", + "contentHash": "BVMXYqX7Z0Zdq3tc94UKJL/cOWq4LF3ufexfdPuUDrDl4ekbbfwPVzsusVbx+aq6Yx60CJnmJLyHtM3V2Q7BBQ==", "dependencies": { - "System.ComponentModel.Composition": "7.0.0", - "System.Reflection.Context": "7.0.0" + "System.ComponentModel.Composition": "8.0.0", + "System.Reflection.Context": "8.0.0" } }, "System.Configuration.ConfigurationManager": { "type": "Transitive", - "resolved": "7.0.0", - "contentHash": "WvRUdlL1lB0dTRZSs5XcQOd5q9MYNk90GkbmRmiCvRHThWiojkpGqWdmEDJdXyHbxG/BhE5hmVbMfRLXW9FJVA==", + "resolved": "8.0.0", + "contentHash": "JlYi9XVvIREURRUlGMr1F6vOFLk7YSY4p1vHo4kX3tQ0AGrjqlRWHDi66ImHhy6qwXBG3BJ6Y1QlYQ+Qz6Xgww==", "dependencies": { - "System.Diagnostics.EventLog": "7.0.0", - "System.Security.Cryptography.ProtectedData": "7.0.0", - "System.Security.Permissions": "7.0.0" + "System.Diagnostics.EventLog": "8.0.0", + "System.Security.Cryptography.ProtectedData": "8.0.0" } }, "System.Data.Odbc": { "type": "Transitive", - "resolved": "7.0.0", - "contentHash": "siwu7NoCsfHa9bfw2a2wSeTt2c/rhk3X8I28nJln1dlxdW3KqhRp0aW87yH1XkCo9h8zO1qcIfdTHO7YvvWLEA==", + "resolved": "8.0.0", + "contentHash": "c+GfnZt2/HyU+voKw2fctLZClcNjPZPWS+mnIhGvDknRMqL/fwWlREWPgA4csbp9ZkQIgB4qkufgdh/oh5Ubow==", "dependencies": { - "System.Text.Encoding.CodePages": "7.0.0" + "System.Text.Encoding.CodePages": "8.0.0" } }, "System.Data.OleDb": { "type": "Transitive", - "resolved": "7.0.0", - "contentHash": "bhAs+5X5acgg3zQ6N4HqxqfwwmqWJzgt54BC8iwygcqa2jktxDFzxwN83GNvqgoTcTs2tenDS/jmhC+AQsmcyg==", + "resolved": "8.0.0", + "contentHash": "FpUTcQ0E8mFvcYp8UZA3NX8wgmhmsCue56g1zfkr1xdOnT5FrYYmC5DWQ9xCw8o8zuxVBKLZvliqEGgmeoalaQ==", "dependencies": { - "System.Configuration.ConfigurationManager": "7.0.0", - "System.Diagnostics.PerformanceCounter": "7.0.0" + "System.Configuration.ConfigurationManager": "8.0.0", + "System.Diagnostics.PerformanceCounter": "8.0.0" } }, "System.Data.SqlClient": { "type": "Transitive", - "resolved": "4.8.5", - "contentHash": "fRqxut4lrndPHrXD+ht1XRmCL3obuKldm4XjCRYS9p5f7FSR7shBxAwTkDrpFMsHC9BhNgjjmUtiIjvehn5zkg==", + "resolved": "4.8.6", + "contentHash": "2Ij/LCaTQRyAi5lAv7UUTV9R2FobC8xN9mE0fXBZohum/xLl8IZVmE98Rq5ugQHjCgTBRKqpXRb4ORulRdA6Ig==", "dependencies": { "Microsoft.Win32.Registry": "4.7.0", "System.Security.Principal.Windows": "4.7.0", @@ -644,20 +641,20 @@ }, "System.Diagnostics.DiagnosticSource": { "type": "Transitive", - "resolved": "7.0.2", - "contentHash": "hYr3I9N9811e0Bjf2WNwAGGyTuAFbbTgX1RPLt/3Wbm68x3IGcX5Cl75CMmgT6WlNwLQ2tCCWfqYPpypjaf2xA==" + "resolved": "8.0.0", + "contentHash": "c9xLpVz6PL9lp/djOWtk5KPDZq3cSYpmXoJQY524EOtuFl5z9ZtsotpsyrDW40U1DRnQSYvcPKEUV0X//u6gkQ==" }, "System.Diagnostics.EventLog": { "type": "Transitive", - "resolved": "7.0.0", - "contentHash": "eUDP47obqQm3SFJfP6z+Fx2nJ4KKTQbXB4Q9Uesnzw9SbYdhjyoGXuvDn/gEmFY6N5Z3bFFbpAQGA7m6hrYJCw==" + "resolved": "8.0.0", + "contentHash": "fdYxcRjQqTTacKId/2IECojlDSFvp7LP5N78+0z/xH7v/Tuw5ZAxu23Y6PTCRinqyu2ePx+Gn1098NC6jM6d+A==" }, "System.Diagnostics.PerformanceCounter": { "type": "Transitive", - "resolved": "7.0.0", - "contentHash": "L+zIMEaXp1vA4wZk1KLMpk6tvU0xy94R0IfmhkmTWeC4KwShsmAfbg5I19LgjsCTYp6GVdXZ2aHluVWL0QqBdA==", + "resolved": "8.0.0", + "contentHash": "lX6DXxtJqVGWw7N/QmVoiCyVQ+Q/Xp+jVXPr3gLK1jJExSn1qmAjJQeb8gnOYeeBTG3E3PmG1nu92eYj/TEjpg==", "dependencies": { - "System.Configuration.ConfigurationManager": "7.0.0" + "System.Configuration.ConfigurationManager": "8.0.0" } }, "System.Diagnostics.Tracing": { @@ -672,39 +669,36 @@ }, "System.DirectoryServices": { "type": "Transitive", - "resolved": "7.0.1", - "contentHash": "Z4FVdUJEVXbf7/f/hU6cFZDtxN5ozUVKJMzXoHmC+GCeTcqzlxqmWtxurejxG3K+kZ6H0UKwNshoK1CYnmJ1sg==", - "dependencies": { - "System.Security.Permissions": "7.0.0" - } + "resolved": "8.0.0", + "contentHash": "7nit//efUTy1OsAKco2f02PMrwsR2S234N0dVVp84udC77YcvpOQDz5znAWMtgMWBzY1aRJvUW61jo/7vQRfXg==" }, "System.DirectoryServices.AccountManagement": { "type": "Transitive", - "resolved": "7.0.1", - "contentHash": "UNytHYwA5IF55WQhashsMG57ize83JUGJxD8YJlOyO9ZlMTOD4Nt7y+A6mvmrU/swDoYWaVL+TNwE6hk9lyvbA==", + "resolved": "8.0.0", + "contentHash": "dCT8BYeeisx0IzAf6x+FSVWK3gz2fKI9pgLV16c7dY/lckw4aodNrgXqsFqyqJN5Kfxc3oklG+SCMYkRfg1V7A==", "dependencies": { - "System.Configuration.ConfigurationManager": "7.0.0", - "System.DirectoryServices": "7.0.1", - "System.DirectoryServices.Protocols": "7.0.1" + "System.Configuration.ConfigurationManager": "8.0.0", + "System.DirectoryServices": "8.0.0", + "System.DirectoryServices.Protocols": "8.0.0" } }, "System.DirectoryServices.Protocols": { "type": "Transitive", - "resolved": "7.0.1", - "contentHash": "t9hsL+UYRzNs30pnT2Tdx6ngX8McFUjru0a0ekNgu/YXfkXN+dx5OvSEv0/p7H2q3pdJLH7TJPWX7e55J8QB9A==" + "resolved": "8.0.0", + "contentHash": "puwJxURHDrYLGTQdsHyeMS72ClTqYa4lDYz6LHSbkZEk5hq8H8JfsO4MyYhB5BMMxg93jsQzLUwrnCumj11UIg==" }, "System.Drawing.Common": { "type": "Transitive", - "resolved": "7.0.0", - "contentHash": "KIX+oBU38pxkKPxvLcLfIkOV5Ien8ReN78wro7OF5/erwcmortzeFx+iBswlh2Vz6gVne0khocQudGwaO1Ey6A==", + "resolved": "8.0.1", + "contentHash": "x0rAZECxIGx/YVjN28YRdpqka0+H7YMN9741FUDzipXPDzesd60gef/LI0ZCOcYSDsacTLTHvMAvxHG+TjbNNQ==", "dependencies": { - "Microsoft.Win32.SystemEvents": "7.0.0" + "Microsoft.Win32.SystemEvents": "8.0.0" } }, "System.Formats.Asn1": { "type": "Transitive", - "resolved": "7.0.0", - "contentHash": "+nfpV0afLmvJW8+pLlHxRjz3oZJw4fkyU9MMEaMhCsHi/SN9bGF9q79ROubDiwTiCHezmK0uCWkPP7tGFP/4yg==" + "resolved": "8.0.0", + "contentHash": "AJukBuLoe3QeAF+mfaRKQb2dgyrvt340iMBHYv+VdBzCUM06IxGlvl0o/uPOS7lHnXPN6u8fFRHSHudx5aTi8w==" }, "System.Globalization": { "type": "Transitive", @@ -777,15 +771,15 @@ }, "System.IO.Packaging": { "type": "Transitive", - "resolved": "7.0.0", - "contentHash": "+j5ezLP7785/pd4taKQhXAWsymsIW2nTnE/U3/jpGZzcJx5lip6qkj6UrxSE7ZYZfL0GaLuymwGLqwJV/c7O7Q==" + "resolved": "8.0.0", + "contentHash": "8g1V4YRpdGAxFcK8v9OjuMdIOJSpF30Zb1JGicwVZhly3I994WFyBdV6mQEo8d3T+URQe55/M0U0eIH0Hts1bg==" }, "System.IO.Ports": { "type": "Transitive", - "resolved": "7.0.0", - "contentHash": "0nWQjM5IofaIGpvkifN+LLuYwBG6BHlpmphLhhOJepcW12G8qToGuNDRgBzeTVBZzp33wVsESSZ8hUOCfq+8QA==", + "resolved": "8.0.0", + "contentHash": "MaiPbx2/QXZc62gm/DrajRrGPG1lU4m08GWMoWiymPYM+ba4kfACp2PbiYpqJ4QiFGhHD00zX3RoVDTucjWe9g==", "dependencies": { - "runtime.native.System.IO.Ports": "7.0.0" + "runtime.native.System.IO.Ports": "8.0.0" } }, "System.Linq": { @@ -802,17 +796,12 @@ }, "System.Management": { "type": "Transitive", - "resolved": "7.0.2", - "contentHash": "/qEUN91mP/MUQmJnM5y5BdT7ZoPuVrtxnFlbJ8a3kBJGhe2wCzBfnPFtK2wTtEEcf3DMGR9J00GZZfg6HRI6yA==", + "resolved": "8.0.0", + "contentHash": "jrK22i5LRzxZCfGb+tGmke2VH7oE0DvcDlJ1HAKYU8cPmD8XnpUT0bYn2Gy98GEhGjtfbR/sxKTVb+dE770pfA==", "dependencies": { - "System.CodeDom": "7.0.0" + "System.CodeDom": "8.0.0" } }, - "System.Memory": { - "type": "Transitive", - "resolved": "4.5.5", - "contentHash": "XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw==" - }, "System.Net.Http": { "type": "Transitive", "resolved": "4.3.3", @@ -848,8 +837,8 @@ }, "System.Net.Http.WinHttpHandler": { "type": "Transitive", - "resolved": "7.0.0", - "contentHash": "DJjlxpGJMHd7WxTo/WRb9q1tahjZvjer7Xo4rsOKAocrwewpA9L0YLxcKEx0nHQnruiWGgbngSzYt3YUwxc+2A==" + "resolved": "8.0.0", + "contentHash": "dAtcyQzDpi34VdR1BeEV8yCOeXVEyekYYK6lJZIzG/N5aqEGgT6AB2DsbiidMp8cB6Y7DqqcmQFZaSGUdoubvQ==" }, "System.Net.Primitives": { "type": "Transitive", @@ -869,8 +858,8 @@ }, "System.Private.ServiceModel": { "type": "Transitive", - "resolved": "4.10.2", - "contentHash": "bi2/w2EDXqxno8zfbt6vHcrpGw0Pav8tEMzmJraHwJvWYJd45wcqr7gNa2IUs91j4z+BNGMooStaWS6pm2Lq0A==", + "resolved": "4.10.3", + "contentHash": "BcUV7OERlLqGxDXZuIyIMMmk1PbqBblLRbAoigmzIUx/M8A+8epvyPyXRpbgoucKH7QmfYdQIev04Phx2Co08A==", "dependencies": { "Microsoft.Bcl.AsyncInterfaces": "5.0.0", "Microsoft.Extensions.ObjectPool": "5.0.10", @@ -894,8 +883,8 @@ }, "System.Reflection.Context": { "type": "Transitive", - "resolved": "7.0.0", - "contentHash": "rVf4vEyGQphXTITF39uXlgTcp8Ekcu2aNwxyVLU7fDyNOk0W+/PPpj9PoC2cFL4wgJZJltiss5eQptE2C4f1Sw==" + "resolved": "8.0.0", + "contentHash": "k76ubeIBOeIVg7vkQ4I+LoB8sY1EzFIc3oHEtoiNLhXleb7TBLXUQu0CFZ4sPlXJzWNabRf+gn1T7lyhOBxIMA==" }, "System.Reflection.DispatchProxy": { "type": "Transitive", @@ -926,8 +915,11 @@ }, "System.Reflection.Metadata": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "5NecZgXktdGg34rh1OenY1rFNDCI8xSjFr+Z4OU4cU06AQHUdRnIIEeWENu3Wl4YowbzkymAIMvi3WyK9U53pQ==" + "resolved": "7.0.0", + "contentHash": "MclTG61lsD9sYdpNz9xsKBzjsmsfCtcMZYXz/IUr2zlhaTaABonlr1ESeompTgM+Xk+IwtGYU7/voh3YWB/fWw==", + "dependencies": { + "System.Collections.Immutable": "7.0.0" + } }, "System.Reflection.Primitives": { "type": "Transitive", @@ -962,10 +954,10 @@ }, "System.Runtime.Caching": { "type": "Transitive", - "resolved": "7.0.0", - "contentHash": "M0riW7Zgxca3Elp1iZVhzH7PWWT5bPSrdMFGCAGoH1n9YLuXOYE78ryui051Icf3swWWa8feBRoSxOCYwgMy8w==", + "resolved": "8.0.0", + "contentHash": "4TmlmvGp4kzZomm7J2HJn6IIx0UUrQyhBDyb5O1XiunZlQImXW+B8b7W/sTPcXhSf9rp5NR5aDtQllwbB5elOQ==", "dependencies": { - "System.Configuration.ConfigurationManager": "7.0.0" + "System.Configuration.ConfigurationManager": "8.0.0" } }, "System.Runtime.CompilerServices.Unsafe": { @@ -1122,10 +1114,10 @@ }, "System.Security.Cryptography.Pkcs": { "type": "Transitive", - "resolved": "7.0.3", - "contentHash": "yhwEHH5Gzl/VoADrXtt5XC95OFoSjNSWLHNutE7GwdOgefZVRvEXRSooSpL8HHm3qmdd9epqzsWg28UJemt22w==", + "resolved": "8.0.0", + "contentHash": "ULmp3xoOwNYjOYp4JZ2NK/6NdTgiN1GQXzVVN1njQ7LOZ0d0B9vyMnhyqbIi9Qw4JXj1JgCsitkTShboHRx7Eg==", "dependencies": { - "System.Formats.Asn1": "7.0.0" + "System.Formats.Asn1": "8.0.0" } }, "System.Security.Cryptography.Primitives": { @@ -1144,8 +1136,8 @@ }, "System.Security.Cryptography.ProtectedData": { "type": "Transitive", - "resolved": "7.0.1", - "contentHash": "3evI3sBfKqwYSwuBcYgShbmEgtXcg8N5Qu+jExLdkBXPty2yGDXq5m1/4sx9Exb8dqdeMPUs/d9DQ0wy/9Adwg==" + "resolved": "8.0.0", + "contentHash": "+TUFINV2q2ifyXauQXRwy4CiBhqvDEDZeVJU7qfxya4aRYOKzVBpN+4acx25VcPB9ywUN6C0n8drWl110PhZEg==" }, "System.Security.Cryptography.X509Certificates": { "type": "Transitive", @@ -1181,18 +1173,18 @@ }, "System.Security.Cryptography.Xml": { "type": "Transitive", - "resolved": "7.0.1", - "contentHash": "MCxBCtH0GrDuvU63ZODwQHQZPchb24pUAX3MfZ6b13qg246ZD10PRdOvay8C9HBPfCXkymUNwFPEegud7ax2zg==", + "resolved": "8.0.0", + "contentHash": "HQSFbakswZ1OXFz2Bt3AJlC6ENDqWeVpgqhf213xqQUMDifzydOHIKVb1RV4prayobvR3ETIScMaQdDF2hwGZA==", "dependencies": { - "System.Security.Cryptography.Pkcs": "7.0.0" + "System.Security.Cryptography.Pkcs": "8.0.0" } }, "System.Security.Permissions": { "type": "Transitive", - "resolved": "7.0.0", - "contentHash": "Vmp0iRmCEno9BWiskOW5pxJ3d9n+jUqKxvX4GhLwFhnQaySZmBN2FuC0N5gjFHgyFMUjC5sfIJ8KZfoJwkcMmA==", + "resolved": "8.0.0", + "contentHash": "v/BBylw7XevuAsHXoX9dDUUfmBIcUf7Lkz8K3ZXIKz3YRKpw8YftpSir4n4e/jDTKFoaK37AsC3xnk+GNFI1Ow==", "dependencies": { - "System.Windows.Extensions": "7.0.0" + "System.Windows.Extensions": "8.0.0" } }, "System.Security.Principal.Windows": { @@ -1202,65 +1194,65 @@ }, "System.ServiceModel.Duplex": { "type": "Transitive", - "resolved": "4.10.2", - "contentHash": "FjFGC7DOTNrkItAYQZVyrDTqXezqFIzmdKL1YaN1ZHdbq5oeyo//iyR+OGXLAfbBxyHultFgV1DetarYBSvIxw==", + "resolved": "4.10.3", + "contentHash": "IZ8ZahvTenWML7/jGUXSCm6jHlxpMbcb+Hy+h5p1WP9YVtb+Er7FHRRGizqQMINEdK6HhWpD6rzr5PdxNyusdg==", "dependencies": { - "System.Private.ServiceModel": "4.10.2", - "System.ServiceModel.Primitives": "4.10.2" + "System.Private.ServiceModel": "4.10.3", + "System.ServiceModel.Primitives": "4.10.3" } }, "System.ServiceModel.Http": { "type": "Transitive", - "resolved": "4.10.2", - "contentHash": "1AhiJwPc+90GjBd/sDkT93RVuRj688ZQInXzWfd56AEjDzWieUcAUh9ppXhRuEVpT+x48D5GBYJc1VxDP4IT+Q==", + "resolved": "4.10.3", + "contentHash": "hodkn0rPTYmoZ9EIPwcleUrOi1gZBPvU0uFvzmJbyxl1lIpVM5GxTrs/pCETStjOXCiXhBDoZQYajquOEfeW/w==", "dependencies": { - "System.Private.ServiceModel": "4.10.2", - "System.ServiceModel.Primitives": "4.10.2" + "System.Private.ServiceModel": "4.10.3", + "System.ServiceModel.Primitives": "4.10.3" } }, "System.ServiceModel.NetTcp": { "type": "Transitive", - "resolved": "4.10.2", - "contentHash": "jWNKeXfccRqkbjTkVyKD0QBBO+FnYWQrNrJByT+oOab17UBLfvUdt1kLhPe3qZalIlBoqvRFtw6yR/oRs5r3Vw==", + "resolved": "4.10.3", + "contentHash": "tP7GN7ehqSIQEz7yOJEtY8ziTpfavf2IQMPKa7r9KGQ75+uEW6/wSlWez7oKQwGYuAHbcGhpJvdG6WoVMKYgkw==", "dependencies": { - "System.Private.ServiceModel": "4.10.2", - "System.ServiceModel.Primitives": "4.10.2" + "System.Private.ServiceModel": "4.10.3", + "System.ServiceModel.Primitives": "4.10.3" } }, "System.ServiceModel.Primitives": { "type": "Transitive", - "resolved": "4.10.2", - "contentHash": "8QOguIqHtWYywBt7SubPhdICE2LClHzqOMDy0LQIui4T3QJOae7g6UR+alCW61nEufYNtO8Uss41EbXqD8hdww==", + "resolved": "4.10.3", + "contentHash": "aNcdry95wIP1J+/HcLQM/f/AA73LnBQDNc2uCoZ+c1//KpVRp8nMZv5ApMwK+eDNVdCK8G0NLInF+xG3mfQL+g==", "dependencies": { - "System.Private.ServiceModel": "4.10.2" + "System.Private.ServiceModel": "4.10.3" } }, "System.ServiceModel.Security": { "type": "Transitive", - "resolved": "4.10.2", - "contentHash": "BdYdPHRJLqwFAJYbmdl9M5qqjJ6O7TVw9AMcW0h7+oc0ah0ufYuSIrN75BhNNAIcaYcviWzC3PDT5H0QkNX7ug==", + "resolved": "4.10.3", + "contentHash": "vqelKb7DvP2inb6LDJ5Igi8wpOYdtLXn5luDW5qEaqkV2sYO1pKlVYBpr6g6m5SevzbdZlVNu67dQiD/H6EdGQ==", "dependencies": { - "System.Private.ServiceModel": "4.10.2", - "System.ServiceModel.Primitives": "4.10.2" + "System.Private.ServiceModel": "4.10.3", + "System.ServiceModel.Primitives": "4.10.3" } }, "System.ServiceModel.Syndication": { "type": "Transitive", - "resolved": "7.0.0", - "contentHash": "V3q1Jr3KWo+i201/vUUPfg83rjJLhL5+ROh16PtPhaUJRHwoEBoGWtg0r6pFBRPaDqNY6hXvNgHktDj0gvMEpA==" + "resolved": "8.0.0", + "contentHash": "CJxIUwpBkMCPmIx46tFVOt0zpRrYurUHLW6tJBcmyj+MyWpKc6MMcS69B7IdlV/bgtgys073wMIHZX9QOQ1OFA==" }, "System.ServiceProcess.ServiceController": { "type": "Transitive", - "resolved": "7.0.1", - "contentHash": "rPfXTJzYU46AmWYXRATQzQQ01hICrkl3GuUHgpAr9mnUwAVSsga5x3mBxanFPlJBV9ilzqMXbQyDLJQAbyTnSw==", + "resolved": "8.0.0", + "contentHash": "jtYVG3bpw2n/NvNnP2g/JLri0D4UtfusTvLeH6cZPNAEjJXJVGspS3wLgVvjNbm+wjaYkFgsXejMTocV1T5DIQ==", "dependencies": { - "System.Diagnostics.EventLog": "7.0.0" + "System.Diagnostics.EventLog": "8.0.0" } }, "System.Speech": { "type": "Transitive", - "resolved": "7.0.0", - "contentHash": "7E0uB92Cx2sXR67HW9rMKJqDACdLuz9t3I3OwZUFDzAgwKXWuY6CYeRT/NiypHcyZO2be9+0H0w0M6fn7HQtgQ==" + "resolved": "8.0.0", + "contentHash": "CNuiA6vb95Oe5PRjClZEBiaju31vwB8OIeCgeSBXyZL6+MS4RVVB2X/C11z0xCkooHE3Vy91nM2z76emIzR+sg==" }, "System.Text.Encoding": { "type": "Transitive", @@ -1274,13 +1266,22 @@ }, "System.Text.Encoding.CodePages": { "type": "Transitive", - "resolved": "7.0.0", - "contentHash": "LSyCblMpvOe0N3E+8e0skHcrIhgV2huaNcjUUEa8hRtgEAm36aGkRoC8Jxlb6Ra6GSfF29ftduPNywin8XolzQ==" + "resolved": "8.0.0", + "contentHash": "OZIsVplFGaVY90G2SbpgU7EnCoOO5pw1t4ic21dBF3/1omrJFpAGoNAVpPyMVOC90/hvgkGG3VFqR13YgZMQfg==" }, "System.Text.Encodings.Web": { "type": "Transitive", - "resolved": "7.0.0", - "contentHash": "OP6umVGxc0Z0MvZQBVigj4/U31Pw72ITihDWP9WiWDm+q5aoe0GaJivsfYGq53o6dxH7DcXWiCTl7+0o2CGdmg==" + "resolved": "8.0.0", + "contentHash": "yev/k9GHAEGx2Rg3/tU6MQh4HGBXJs70y7j1LaM1i/ER9po+6nnQ6RRqTJn1E7Xu0fbIFK80Nh5EoODxrbxwBQ==" + }, + "System.Text.Json": { + "type": "Transitive", + "resolved": "6.0.9", + "contentHash": "2j16oUgtIzl7Xtk7demG0i/v5aU/ZvULcAnJvPb63U3ZhXJ494UYcxuEj5Fs49i3XDrk5kU/8I+6l9zRCw3cJw==", + "dependencies": { + "System.Runtime.CompilerServices.Unsafe": "6.0.0", + "System.Text.Encodings.Web": "6.0.0" + } }, "System.Threading": { "type": "Transitive", @@ -1293,8 +1294,8 @@ }, "System.Threading.AccessControl": { "type": "Transitive", - "resolved": "7.0.1", - "contentHash": "uh6LWSk8Dlp1cavk4XQYtDHOMZpSa5KiqM0VBiflhXWGT63RGV+NhNsVxiEykL4S/0LVcgy+/AxC5ITQ9QLo8w==" + "resolved": "8.0.0", + "contentHash": "cIed5+HuYz+eV9yu9TH95zPkqmm1J9Qps9wxjB335sU8tsqc2kGdlTEH9FZzZeCS8a7mNSEsN8ZkyhQp1gfdEw==" }, "System.Threading.Tasks": { "type": "Transitive", @@ -1306,11 +1307,6 @@ "System.Runtime": "4.3.0" } }, - "System.Threading.Tasks.Extensions": { - "type": "Transitive", - "resolved": "4.5.4", - "contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==" - }, "System.ValueTuple": { "type": "Transitive", "resolved": "4.5.0", @@ -1318,16 +1314,13 @@ }, "System.Web.Services.Description": { "type": "Transitive", - "resolved": "4.10.2", - "contentHash": "AuovAoDhSaXAn7zhOboWW9G5P7LfTEEMOLzeZS87QuGZ5r4kFmJ3+NrzxeS6tumE+KQCQ/K5tNeTXCiVddErxQ==" + "resolved": "4.10.3", + "contentHash": "ORCkTkUo9f1o4ACG+H6SV+0XSxVZ461w3cHzYxEU41y6aKWp1CeNTMYbtdxMw1we6c6t4Hqq15PdcLVcdqno/g==" }, "System.Windows.Extensions": { "type": "Transitive", - "resolved": "7.0.0", - "contentHash": "bR4qdCmssMMbo9Fatci49An5B1UaVJZHKNq70PRgzoLYIlitb8Tj7ns/Xt5Pz1CkERiTjcVBDU2y1AVrPBYkaw==", - "dependencies": { - "System.Drawing.Common": "7.0.0" - } + "resolved": "8.0.0", + "contentHash": "Obg3a90MkOw9mYKxrardLpY2u0axDMrSmy4JCdq2cYbelM2cUwmUir5Bomvd1yxmPL9h5LVHU1tuKBZpUjfASg==" }, "YamlDotNet": { "type": "Transitive", diff --git a/tests/PSRule.Tests/InputPathBuilderTests.cs b/tests/PSRule.Tests/InputPathBuilderTests.cs index 8d0a663020..4b2918af84 100644 --- a/tests/PSRule.Tests/InputPathBuilderTests.cs +++ b/tests/PSRule.Tests/InputPathBuilderTests.cs @@ -34,13 +34,13 @@ public void GetPath() actual = builder.Build(); Assert.True(actual.Length > 100); - builder.Add("./.github/*.yaml"); + builder.Add("./.github/*.yml"); actual = builder.Build(); Assert.Single(actual); builder.Add("./.github/**/*.yaml"); actual = builder.Build(); - Assert.Equal(7, actual.Length); + Assert.Equal(6, actual.Length); builder.Add("./.github/"); actual = builder.Build(); diff --git a/tests/PSRule.Tests/PSRule.Tests.csproj b/tests/PSRule.Tests/PSRule.Tests.csproj index df320cd486..bf9b6be581 100644 --- a/tests/PSRule.Tests/PSRule.Tests.csproj +++ b/tests/PSRule.Tests/PSRule.Tests.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 {d3488ce2-779f-4474-b38a-f894a4b689f7} true false @@ -11,8 +11,8 @@ - - + + all diff --git a/tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj b/tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj index 17cd22054f..9882d0b9ae 100644 --- a/tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj +++ b/tests/PSRule.Tool.Tests/PSRule.Tool.Tests.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 {DA46C891-08F1-4D01-9F98-1F8BB10CAFEC} true false