Added sub-selectors for YAML and JSON #1024 #1045 (#1238)

This commit is contained in:
Bernie White 2022-08-26 15:13:44 +10:00 коммит произвёл GitHub
Родитель 6d59f18be8
Коммит 85fa989a41
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
44 изменённых файлов: 3781 добавлений и 2035 удалений

Просмотреть файл

@ -20,7 +20,7 @@
"no-reversed-links": true,
"no-multiple-blanks": true,
"line-length": {
"line_length": 100,
"line_length": 120,
"code_blocks": false,
"tables": false,
"headers": true

4
.vscode/settings.json поставляемый
Просмотреть файл

@ -22,7 +22,8 @@
"./schemas/PSRule-language.schema.json": [
"/tests/PSRule.Tests/**.Rule.yaml",
"/tests/PSRule.Tests/**/**.Rule.yaml",
"/docs/scenarios/*/*.Rule.yaml"
"/docs/scenarios/*/*.Rule.yaml",
"/docs/expressions/**/*.Rule.yaml"
]
},
"json.schemas": [
@ -30,6 +31,7 @@
"fileMatch": [
"/tests/PSRule.Tests/**.Rule.jsonc",
"/tests/PSRule.Tests/**/**.Rule.jsonc",
"/docs/expressions/**/*.Rule.jsonc"
],
"url": "./schemas/PSRule-resources.schema.json"
}

Просмотреть файл

@ -13,10 +13,26 @@ See [upgrade notes][1] for helpful information when upgrading from previous vers
**Experimental features**:
- Functions within YAML expressions can be used to perform manipulation prior to testing a condition.
- 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.
[3]: expressions/functions.md
[4]: expressions/sub-selectors.md
## Unreleased
What's changed since pre-release v2.4.0-B0039:
- New features:
- **Experimental**: Added support for sub-selectors YAML and JSON expressions by @BernieWhite.
[#1024](https://github.com/microsoft/PSRule/issues/1024)
[#1045](https://github.com/microsoft/PSRule/issues/1045)
- Sub-selector pre-conditions add an additional expression to determine if a rule is executed.
- Sub-selector object filters provide an way to filter items from list properties.
- See [sub-selectors][4] for more information.
## v2.4.0-B0039 (pre-release)
What's changed since pre-release v2.4.0-B0022:
@ -27,6 +43,7 @@ What's changed since pre-release v2.4.0-B0022:
- Added conversion functions `boolean`, `string`, and `integer`.
- Added lookup functions `configuration`, and `path`.
- Added string functions `concat`, `substring`.
- See [functions][3] for more information.
- Bug fixes:
- Fixed reporting of duplicate identifiers which were not generating an error for all cases by @BernieWhite.
[#1229](https://github.com/microsoft/PSRule/issues/1229)

Просмотреть файл

@ -0,0 +1,74 @@
[
{
// Synopsis: A rule with a sub-selector precondition.
"apiVersion": "github.com/microsoft/PSRule/v1",
"kind": "Rule",
"metadata": {
"name": "Json.Subselector.Precondition"
},
"spec": {
"where": {
"field": "kind",
"equals": "api"
},
"condition": {
"field": "resources",
"count": 10
}
}
},
{
// Synopsis: A rule with a sub-selector filter.
"apiVersion": "github.com/microsoft/PSRule/v1",
"kind": "Rule",
"metadata": {
"name": "Json.Subselector.Filter"
},
"spec": {
"condition": {
"field": "resources",
"where": {
"type": ".",
"equals": "Microsoft.Web/sites/config"
},
"allOf": [
{
"field": "properties.detailedErrorLoggingEnabled",
"equals": true
}
]
}
}
},
{
// Synopsis: A rule with a sub-selector filter.
"apiVersion": "github.com/microsoft/PSRule/v1",
"kind": "Rule",
"metadata": {
"name": "Json.Subselector.FilterOr"
},
"spec": {
"condition": {
"anyOf": [
{
"field": "resources",
"where": {
"type": ".",
"equals": "Microsoft.Web/sites/config"
},
"allOf": [
{
"field": "properties.detailedErrorLoggingEnabled",
"equals": true
}
]
},
{
"field": "resources",
"exists": false
}
]
}
}
}
]

Просмотреть файл

@ -0,0 +1,57 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
#
# YAML-based rules for documentation
#
---
# Synopsis: A rule with a sub-selector precondition.
apiVersion: github.com/microsoft/PSRule/v1
kind: Rule
metadata:
name: Yaml.Subselector.Precondition
spec:
where:
field: 'kind'
equals: 'api'
condition:
field: resources
count: 10
---
# Synopsis: A rule with a sub-selector filter.
apiVersion: github.com/microsoft/PSRule/v1
kind: Rule
metadata:
name: Yaml.Subselector.Filter
spec:
condition:
field: resources
where:
type: '.'
equals: 'Microsoft.Web/sites/config'
allOf:
- field: properties.detailedErrorLoggingEnabled
equals: true
---
# Synopsis: A rule with a sub-selector filter.
apiVersion: github.com/microsoft/PSRule/v1
kind: Rule
metadata:
name: Yaml.Subselector.FilterOr
spec:
condition:
anyOf:
- field: resources
where:
type: '.'
equals: 'Microsoft.Web/sites/config'
allOf:
- field: properties.detailedErrorLoggingEnabled
equals: true
- field: resources
exists: false

Просмотреть файл

@ -1,16 +1,22 @@
# Expression functions
# Functions
!!! Abstract
Functions are an advanced lanaguage feature specific to YAML and JSON resources.
_Functions_ are an advanced lanaguage feature specific to YAML and JSON expressions.
That extend the language to allow for more complex use cases with expressions.
Functions don't apply to script expressions because PowerShell already has rich support for complex manipulation.
!!! Experimental
Functions are a work in progress and subject to change.
_Functions_ are a work in progress and subject to change.
We hope to add more functions, broader support, and more detailed documentation in the future.
[Join or start a disucssion][1] to let us know how we can improve this feature going forward.
[1]: https://github.com/microsoft/PSRule/discussions
Functions cover two (2) main scenarios:
- **Transformation** — you need to perform minor transformation before a condition.
- **Configuration** — you want to configure an input into a condition.
## Using functions
It may be necessary to perform minor transformation before evaluating a condition.

Просмотреть файл

@ -0,0 +1,272 @@
# Sub-selectors
!!! Abstract
This topic covers _sub-selectors_ which are a PSRule language feature specific to YAML and JSON expressions.
They are useful for filtering out objects that you do not want to evaluate.
Sub-selectors don't apply to script expressions because PowerShell already has rich support for filtering.
!!! Experimental
_Sub-selectors_ are a work in progress and subject to change.
We hope to add broader support, and more detailed documentation in the future.
[Join or start a disucssion][1] to let us know how we can improve this feature going forward.
[1]: https://github.com/microsoft/PSRule/discussions
Sub-selectors cover two (2) main scenarios:
- **Pre-conditions** — you want to filtering out objects before a rule is run.
- **Object filtering** — you want to limit a condition to specific elements in a list of items.
## Pre-conditions
PSRule can process many different types of objects.
Rules however, are normally written to test a specific property or type of object.
So it is important that rules only run on objects that you want to evaluate.
Pre-condition sub-selectors are one way you can determine if a rule should be run.
To use a sub-selector as a pre-condition, use the `where` property, directly under the `spec`.
The expressions in the sub-selector follow the same form that you can use in rules.
For example:
=== "YAML"
```yaml hl_lines="8-10"
---
# Synopsis: A rule with a sub-selector precondition.
apiVersion: github.com/microsoft/PSRule/v1
kind: Rule
metadata:
name: Yaml.Subselector.Precondition
spec:
where:
field: 'kind'
equals: 'api'
condition:
field: resources
count: 10
```
=== "JSON"
```json hl_lines="9-12"
{
// Synopsis: A rule with a sub-selector precondition.
"apiVersion": "github.com/microsoft/PSRule/v1",
"kind": "Rule",
"metadata": {
"name": "Json.Subselector.Precondition"
},
"spec": {
"where": {
"field": "kind",
"equals": "api"
},
"condition": {
"field": "resources",
"count": 10
}
}
}
```
In the example:
1. The `where` property is the start of a sub-selector.
2. The sub-selector checks if the `kind` property equals `api`.
The rule does not run if the:
- The object does not have a `kind` property. **OR**
- The value of the `kind` property is not `api`.
!!! Tip
Other types of pre-conditions also exist that allow you to filter based on type or by a shared selector.
## Object filter
When you are evaluating an object, you can use sub-selectors to limit the condition.
This is helpful when dealing with properties that are a list of items.
Properties that contain a list of items may contain a sub-set of items that you want to evaluate.
For example, the object may look like this:
=== "YAML"
```yaml
name: app1
type: Microsoft.Web/sites
resources:
- name: web
type: Microsoft.Web/sites/config
properties:
detailedErrorLoggingEnabled: true
```
=== "JSON"
```json
{
"name": "app1",
"type": "Microsoft.Web/sites",
"resources": [
{
"name": "web",
"type": "Microsoft.Web/sites/config",
"properties": {
"detailedErrorLoggingEnabled": true
}
}
]
}
```
A rule to test if any sub-resources with the `detailedErrorLoggingEnabled` set to `true` exist might look like this:
=== "YAML"
```yaml hl_lines="10-12"
---
# Synopsis: A rule with a sub-selector filter.
apiVersion: github.com/microsoft/PSRule/v1
kind: Rule
metadata:
name: Yaml.Subselector.Filter
spec:
condition:
field: resources
where:
type: '.'
equals: 'Microsoft.Web/sites/config'
allOf:
- field: properties.detailedErrorLoggingEnabled
equals: true
```
=== "JSON"
```json
{
// Synopsis: A rule with a sub-selector filter.
"apiVersion": "github.com/microsoft/PSRule/v1",
"kind": "Rule",
"metadata": {
"name": "Json.Subselector.Filter"
},
"spec": {
"condition": {
"field": "resources",
"where": {
"type": ".",
"equals": "Microsoft.Web/sites/config"
},
"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.
- Items without the `properties.detailedErrorLoggingEnabled` property fail.
- Items with the `properties.detailedErrorLoggingEnabled` property set to a value other then `true` fail.
- If the `resources` property does not exist, the rule fails.
- 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.
### When there are no results
Given the example, is important to understand what happens if:
- The `resources` property doesn't exist.
- The `resources` property doesn't contain any items that match the sub-selector condition.
In either of these two cases, the sub-selector will return `false` and fail the rule.
The rule fails because there is no secondary conditions that could be used instead.
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.
For example:
=== "YAML"
```yaml hl_lines="9 11-14 19-20"
---
# Synopsis: A rule with a sub-selector filter.
apiVersion: github.com/microsoft/PSRule/v1
kind: Rule
metadata:
name: Yaml.Subselector.FilterOr
spec:
condition:
anyOf:
- field: resources
where:
type: '.'
equals: 'Microsoft.Web/sites/config'
allOf:
- field: properties.detailedErrorLoggingEnabled
equals: true
- field: resources
exists: false
```
=== "JSON"
```json hl_lines="10 12-16 25-26"
{
// Synopsis: A rule with a sub-selector filter.
"apiVersion": "github.com/microsoft/PSRule/v1",
"kind": "Rule",
"metadata": {
"name": "Json.Subselector.FilterOr"
},
"spec": {
"condition": {
"anyOf": [
{
"field": "resources",
"where": {
"type": ".",
"equals": "Microsoft.Web/sites/config"
},
"allOf": [
{
"field": "properties.detailedErrorLoggingEnabled",
"equals": true
}
]
},
{
"field": "resources",
"exists": false
}
]
}
}
}
```
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.
- Items without the `properties.detailedErrorLoggingEnabled` property fail.
- Items with the `properties.detailedErrorLoggingEnabled` property set to a value other then `true` fail.
- If the `resources` property does not exist, the rule passes.
- 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.

37
docs/versioning.md Normal file
Просмотреть файл

@ -0,0 +1,37 @@
# Changes and versioning
PSRule uses [semantic versioning][1] to declare breaking changes.
The latest module version can be installed from the PowerShell Gallery.
For a list of module changes please see the [change log][2].
[1]: https://semver.org/
[2]: https://aka.ms/ps-rule/changelog
## Pre-releases
Pre-release module versions are created on major commits and can be installed from the PowerShell Gallery.
Module versions and change log details for pre-releases will be removed as stable releases are made available.
!!! Important
Pre-release versions should be considered work in progress.
These releases should not be used in production.
We may introduce breaking changes between a pre-release as we work towards a stable version release.
## Experimental features
From time to time we may ship experimential features.
These features are generally marked experimential in the change log as these features ship.
Experimental features may ship in stable releases, however to use them you may need to:
- Enabled or explictly reference them.
!!! Important
Experimental features should be considered work in progress.
These features may be incomplete and should not be used in production.
We may introduce breaking changes for experimental features as we work towards a general release for the feature.
## Reporting bugs
If you experience an issue with an pre-release or experimental feature please let us know by logging an issue as a [bug][3].
[3]: https://github.com/microsoft/PSRule/issues

Просмотреть файл

@ -29,6 +29,7 @@ theme:
level: 1
- navigation.tabs
- content.code.annotate
- content.tabs.link
nav:
- Home: index.md
@ -55,8 +56,9 @@ nav:
- Azure resource tagging example: scenarios/azure-tags/azure-tags.md
- Kubernetes resource validation example: scenarios/kubernetes-resources/kubernetes-resources.md
- Concepts:
- Expressions:
- Functions: expressions/functions.md
- Functions: expressions/functions.md
- Sub-selectors: expressions/sub-selectors.md
- Scenarios:
- Using within continuous integration: scenarios/validation-pipeline/validation-pipeline.md
# - Troubleshooting: troubleshooting.md
- License and contributing: license-contributing.md
@ -68,6 +70,7 @@ nav:
- v0: 'CHANGELOG-v0.md'
- Upgrade notes: upgrade-notes.md
- Deprecations: deprecations.md
- Changes and versioning: versioning.md
- Support: support.md
# - Setup:
# - Configuring options: setup/configuring-options.md

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using PSRule.Definitions;
@ -9,12 +9,17 @@ namespace PSRule
{
public static bool AllOf(this IConditionResult result)
{
return 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.Pass > 0;
return result != null && result.Pass > 0;
}
public static bool Skipped(this IConditionResult result)
{
return result == null;
}
}
}

Просмотреть файл

@ -645,7 +645,7 @@ namespace PSRule
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var expression = MapOperator(OPERATOR_IF, reader);
var expression = MapOperator(OPERATOR_IF, null, null, reader);
return new LanguageIf(expression);
}
@ -657,40 +657,44 @@ namespace PSRule
/// <summary>
/// Map an operator.
/// </summary>
private LanguageExpression MapOperator(string type, JsonReader reader)
private LanguageOperator MapOperator(string type, LanguageExpression.PropertyBag properties, LanguageExpression subselector, JsonReader reader)
{
if (TryExpression(type, out LanguageOperator result))
if (TryExpression(type, properties, out LanguageOperator result))
{
// If and Not
if (reader.TokenType == JsonToken.StartObject)
if (reader.TryConsume(JsonToken.StartObject))
{
result.Add(MapExpression(reader));
reader.Read();
//reader.Consume(JsonToken.EndObject);
}
// AllOf and AnyOf
else if (reader.TokenType == JsonToken.StartArray && reader.Read())
else if (reader.TryConsume(JsonToken.StartArray))
{
while (reader.TokenType != JsonToken.EndArray)
{
if (reader.SkipComments(out var hasComments) && hasComments)
continue;
result.Add(MapExpression(reader));
reader.Read();
if (reader.TryConsume(JsonToken.StartObject))
{
result.Add(MapExpression(reader));
reader.Consume(JsonToken.EndObject);
}
}
reader.Read();
reader.Consume(JsonToken.EndArray);
}
result.Subselector = subselector;
}
return result;
}
private LanguageExpression MapCondition(string type, LanguageExpression.PropertyBag properties, JsonReader reader)
{
if (TryExpression(type, out LanguageCondition result))
if (TryExpression(type, null, out LanguageCondition result))
{
while (reader.TokenType != JsonToken.EndObject)
{
MapProperty(properties, reader, out _);
MapProperty(properties, reader, out _, out _);
}
result.Add(properties);
}
@ -701,7 +705,7 @@ namespace PSRule
{
LanguageExpression result = null;
var properties = new LanguageExpression.PropertyBag();
MapProperty(properties, reader, out var key);
MapProperty(properties, reader, out var key, out var subselector);
if (key != null && TryCondition(key))
{
result = MapCondition(key, properties, reader);
@ -709,7 +713,12 @@ namespace PSRule
else if ((reader.TokenType == JsonToken.StartObject || reader.TokenType == JsonToken.StartArray) &&
TryOperator(key))
{
result = MapOperator(key, reader);
var op = MapOperator(key, properties, subselector, reader);
MapProperty(properties, reader, out _, out subselector);
if (subselector != null)
op.Subselector = subselector;
result = op;
}
return result;
}
@ -766,13 +775,14 @@ namespace PSRule
return result.ToArray();
}
private void MapProperty(LanguageExpression.PropertyBag properties, JsonReader reader, out string name)
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.ReadJsonFailed);
//if (reader.TokenType != JsonToken.StartObject || !reader.Read())
// throw new PipelineSerializationException(PSRuleResources.ReadJsonFailedExpectedToken, Enum.GetName(typeof(JsonToken), reader.TokenType));
name = null;
while (reader.TokenType != JsonToken.EndObject)
subselector = null;
while (reader.TokenType == JsonToken.PropertyName)
{
var key = reader.Value.ToString();
if (TryCondition(key) || TryOperator(key))
@ -780,18 +790,29 @@ namespace PSRule
if (reader.Read())
{
// 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)
{
break;
}
else if (reader.TokenType == JsonToken.StartArray)
{
if (!TryCondition(key))
@ -808,17 +829,22 @@ namespace PSRule
objects.Add(item);
}
properties.Add(key, objects.ToArray());
reader.Consume(JsonToken.EndArray);
}
else
{
properties.Add(key, reader.Value);
reader.Read();
}
}
reader.Read();
}
}
private bool TrySubSelector(string key)
{
return _Factory.IsSubselector(key);
}
private bool TryOperator(string key)
{
return _Factory.IsOperator(key);
@ -867,7 +893,7 @@ namespace PSRule
s == "$";
}
private bool TryExpression<T>(string type, out T expression) where T : LanguageExpression
private bool TryExpression<T>(string type, LanguageExpression.PropertyBag properties, out T expression) where T : LanguageExpression
{
expression = null;
@ -875,7 +901,7 @@ namespace PSRule
{
expression = (T)descriptor.CreateInstance(
source: RunspaceContext.CurrentThread.Source.File,
properties: null
properties: properties
);
return expression != null;

Просмотреть файл

@ -34,11 +34,6 @@ namespace PSRule
: PSObject.AsPSObject(o.Properties[propertyName].Value);
}
public static string ValueAsString(this PSObject o, string propertyName, bool caseSensitive)
{
return ObjectHelper.GetPath(o, propertyName, caseSensitive, out var value) && value != null ? value.ToString() : null;
}
public static bool HasProperty(this PSObject o, string propertyName)
{
return o.Properties[propertyName] != null;

Просмотреть файл

@ -620,7 +620,7 @@ namespace PSRule
{
if (typeof(LanguageExpression).IsAssignableFrom(expectedType))
{
var resource = MapOperator(OPERATOR_IF, reader, nestedObjectDeserializer);
var resource = MapOperator(OPERATOR_IF, null, null, reader, nestedObjectDeserializer);
value = new LanguageIf(resource);
return true;
}
@ -633,9 +633,9 @@ namespace PSRule
/// <summary>
/// Map an operator.
/// </summary>
private LanguageExpression MapOperator(string type, IParser reader, Func<IParser, Type, object> nestedObjectDeserializer)
private LanguageOperator MapOperator(string type, LanguageExpression.PropertyBag properties, LanguageExpression subselector, IParser reader, Func<IParser, Type, object> nestedObjectDeserializer)
{
if (TryExpression(reader, type, nestedObjectDeserializer, out LanguageOperator result))
if (TryExpression(reader, type, properties, nestedObjectDeserializer, out LanguageOperator result))
{
// If and Not
if (reader.TryConsume<MappingStart>(out _))
@ -656,17 +656,18 @@ namespace PSRule
reader.Require<SequenceEnd>();
reader.MoveNext();
}
result.Subselector = subselector;
}
return result;
}
private LanguageExpression MapCondition(string type, LanguageExpression.PropertyBag properties, IParser reader, Func<IParser, Type, object> nestedObjectDeserializer)
{
if (TryExpression(reader, type, nestedObjectDeserializer, out LanguageCondition result))
if (TryExpression(reader, type, null, nestedObjectDeserializer, out LanguageCondition result))
{
while (!reader.Accept(out MappingEnd end))
{
MapProperty(properties, reader, nestedObjectDeserializer, out _);
MapProperty(properties, reader, nestedObjectDeserializer, out _, out _);
}
result.Add(properties);
}
@ -677,18 +678,25 @@ namespace PSRule
{
LanguageExpression result = null;
var properties = new LanguageExpression.PropertyBag();
MapProperty(properties, reader, nestedObjectDeserializer, out var key);
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<MappingStart>(out _))
{
result = MapOperator(key, reader, nestedObjectDeserializer);
var op = MapOperator(key, properties, subselector, reader, nestedObjectDeserializer);
MapProperty(properties, reader, nestedObjectDeserializer, out _, out _);
result = op;
}
else if (TryOperator(key) && reader.Accept<SequenceStart>(out _))
{
result = MapOperator(key, reader, nestedObjectDeserializer);
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;
}
@ -754,9 +762,10 @@ namespace PSRule
return result.ToArray();
}
private void MapProperty(LanguageExpression.PropertyBag properties, IParser reader, Func<IParser, Type, object> nestedObjectDeserializer, out string name)
private void MapProperty(LanguageExpression.PropertyBag properties, IParser reader, Func<IParser, Type, object> nestedObjectDeserializer, out string name, out LanguageExpression subselector)
{
name = null;
subselector = null;
while (reader.TryConsume(out Scalar scalar))
{
var key = scalar.Value;
@ -767,6 +776,7 @@ namespace PSRule
{
properties[key] = scalar.Value;
}
// value:
else if (TryValue(key, reader, nestedObjectDeserializer, out var value))
{
properties[key] = value;
@ -788,9 +798,20 @@ namespace PSRule
}
properties[key] = objects.ToArray();
}
// where:
else if (TrySubSelector(key) && reader.TryConsume<MappingStart>(out _))
{
subselector = MapExpression(reader, nestedObjectDeserializer);
reader.Consume<MappingEnd>();
}
}
}
private bool TrySubSelector(string key)
{
return _Factory.IsSubselector(key);
}
private bool TryOperator(string key)
{
return _Factory.IsOperator(key);
@ -835,12 +856,12 @@ namespace PSRule
return reader.Accept<Scalar>(out var scalar) || scalar.Value == "$";
}
private bool TryExpression<T>(IParser reader, string type, Func<IParser, Type, object> nestedObjectDeserializer, out T expression) where T : LanguageExpression
private bool TryExpression<T>(IParser reader, string type, LanguageExpression.PropertyBag properties, Func<IParser, Type, object> nestedObjectDeserializer, out T expression) where T : LanguageExpression
{
expression = null;
if (_Factory.TryDescriptor(type, out var descriptor))
{
expression = (T)descriptor.CreateInstance(RunspaceContext.CurrentThread.Source.File, null);
expression = (T)descriptor.CreateInstance(RunspaceContext.CurrentThread.Source.File, properties);
return expression != null;
}
return false;

Просмотреть файл

@ -2,17 +2,16 @@
// Licensed under the MIT License.
using System.Collections.Generic;
using System.Management.Automation;
namespace PSRule.Configuration
{
/// <summary>
/// Used by custom binding functions.
/// </summary>
public delegate string BindTargetName(PSObject targetObject);
public delegate string BindTargetName(object targetObject);
internal delegate string BindTargetMethod(string[] propertyNames, bool caseSensitive, bool preferTargetInfo, PSObject targetObject, out string path);
internal delegate string BindTargetFunc(string[] propertyNames, bool caseSensitive, bool preferTargetInfo, PSObject targetObject, BindTargetMethod next, out string path);
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);
/// <summary>
/// Hooks that provide customize pipeline execution.

Просмотреть файл

@ -18,7 +18,7 @@ namespace PSRule.Definitions.Expressions
object Current { get; }
RunspaceContext GetContext();
RunspaceContext Context { get; }
}
internal sealed class ExpressionContext : IExpressionContext, IBindingContext
@ -27,8 +27,9 @@ namespace PSRule.Definitions.Expressions
private List<ResultReason> _Reason;
internal ExpressionContext(SourceFile source, ResourceKind kind, object current)
internal ExpressionContext(RunspaceContext context, SourceFile source, ResourceKind kind, object current)
{
Context = context;
Source = source;
LanguageScope = source.Module;
Kind = kind;
@ -44,6 +45,8 @@ namespace PSRule.Definitions.Expressions
public object Current { get; }
public RunspaceContext Context { get; }
[DebuggerStepThrough]
void IBindingContext.CachePathExpression(string path, PathExpression expression)
{
@ -81,7 +84,7 @@ namespace PSRule.Definitions.Expressions
return;
_Reason ??= new List<ResultReason>();
_Reason.Add(new ResultReason(RunspaceContext.CurrentThread?.TargetObject?.Path, operand, text, args));
_Reason.Add(new ResultReason(Context.TargetObject?.Path, operand, text, args));
}
public void Reason(string text, params object[] args)
@ -90,17 +93,12 @@ namespace PSRule.Definitions.Expressions
return;
_Reason ??= new List<ResultReason>();
_Reason.Add(new ResultReason(RunspaceContext.CurrentThread?.TargetObject?.Path, null, text, args));
_Reason.Add(new ResultReason(Context.TargetObject?.Path, null, text, args));
}
internal ResultReason[] GetReasons()
{
return _Reason == null || _Reason.Count == 0 ? Array.Empty<ResultReason>() : _Reason.ToArray();
}
public RunspaceContext GetContext()
{
return RunspaceContext.CurrentThread;
}
}
}

Просмотреть файл

@ -91,7 +91,7 @@ namespace PSRule.Definitions.Expressions
// Lookup a configuration value.
return (context) =>
{
return context.GetContext().TryGetConfigurationValue(name, out var value) ? value : null;
return context.Context.TryGetConfigurationValue(name, out var value) ? value : null;
};
}

Просмотреть файл

@ -54,6 +54,11 @@ namespace PSRule.Definitions.Expressions
_Descriptors.TryGetValue(name, out descriptor);
}
public bool IsSubselector(string name)
{
return name == "where";
}
public bool IsOperator(string name)
{
return TryDescriptor(name, out var d) && d != null && d.Type == LanguageExpressionType.Operator;
@ -80,12 +85,14 @@ namespace PSRule.Definitions.Expressions
{
private const char Dot = '.';
private const char OpenBracket = '[';
private const char CloseBracket = '[';
private const char CloseBracket = ']';
private const string Where = ".where";
private readonly bool _Debugger;
private string[] _With;
private string[] _Type;
private LanguageExpression _When;
private string[] _Rule;
public LanguageExpressionBuilder(bool debugger = true)
@ -111,6 +118,15 @@ namespace PSRule.Definitions.Expressions
return this;
}
public LanguageExpressionBuilder WithSubselector(LanguageIf subselector)
{
if (subselector == null || subselector.Expression == null)
return this;
_When = subselector.Expression;
return this;
}
public LanguageExpressionBuilder WithRule(string[] rule)
{
if (rule == null || rule.Length == 0)
@ -120,12 +136,12 @@ namespace PSRule.Definitions.Expressions
return this;
}
public LanguageExpressionOuterFn Build(LanguageIf selectorIf)
public LanguageExpressionOuterFn Build(LanguageIf condition)
{
return Precondition(Expression(string.Empty, selectorIf.Expression), _With, _Type, _Rule);
return Precondition(Expression(string.Empty, condition.Expression), _With, _Type, Expression(string.Empty, _When), _Rule);
}
private static LanguageExpressionOuterFn Precondition(LanguageExpressionOuterFn expression, string[] with, string[] type, string[] rule)
private static LanguageExpressionOuterFn Precondition(LanguageExpressionOuterFn expression, string[] with, string[] type, LanguageExpressionOuterFn when, string[] rule)
{
var fn = expression;
if (type != null)
@ -134,6 +150,9 @@ namespace PSRule.Definitions.Expressions
if (with != null)
fn = PreconditionSelector(with, fn);
if (when != null)
fn = PreconditionSubselector(when, fn);
if (rule != null)
fn = PreconditionRule(rule, fn);
@ -182,8 +201,25 @@ namespace PSRule.Definitions.Expressions
};
}
private static LanguageExpressionOuterFn PreconditionSubselector(LanguageExpressionOuterFn subselector, LanguageExpressionOuterFn fn)
{
return (context, o) =>
{
// Evalute sub-selector pre-condition
if (!AcceptsSubselector(context, subselector, o))
{
context.Debug(PSRuleResources.DebugTargetSubselectorMismatch);
return null;
}
return fn(context, o);
};
}
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));
@ -226,7 +262,38 @@ namespace PSRule.Definitions.Expressions
}
var innerA = inner.ToArray();
var info = new ExpressionInfo(path);
return (context, o) => expression.Descriptor.Fn(context, info, innerA, o);
// 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, Where), expression.Subselector) : null;
return (context, o) =>
{
if (!ObjectHelper.GetPath(context, o, Value<string>(context, expression.Property["field"]), caseSensitive: false, out object[] items) ||
items == null || items.Length == 0)
return false;
// If any fail, all fail
for (var i = 0; i < items.Length; i++)
{
if (subselector == null || subselector(context, items[i]).GetValueOrDefault(true))
{
if (!expression.Descriptor.Fn(context, info, innerA, items[i]))
return false;
}
}
return true;
};
}
}
private string Value<T>(ExpressionContext context, object v)
{
return v as string;
}
private LanguageExpressionOuterFn Debugger(LanguageExpressionOuterFn expression, string path)
@ -273,6 +340,11 @@ namespace PSRule.Definitions.Expressions
return 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)
@ -1520,38 +1592,36 @@ namespace PSRule.Definitions.Expressions
return operand != null || NotHasField(context, field);
}
private static bool TryName(IExpressionContext context, LanguageExpression.PropertyBag properties, 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))
{
if (svalue != ".")
if (svalue != "." || context?.Context?.LanguageScope == null)
return Invalid(context, svalue);
var binding = context.GetContext()?.TargetBinder?.Using(context.LanguageScope);
var name = binding?.TargetName;
if (string.IsNullOrEmpty(name))
if (!context.Context.LanguageScope.TryGetName(o, out var name, out var path) ||
string.IsNullOrEmpty(name))
return Invalid(context, svalue);
operand = Operand.FromName(name, binding.TargetNamePath);
operand = Operand.FromName(name, path);
}
return operand != null;
}
private static bool TryType(IExpressionContext context, LanguageExpression.PropertyBag properties, 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))
{
if (svalue != ".")
if (svalue != "." || context?.Context?.LanguageScope == null)
return Invalid(context, svalue);
var binding = context.GetContext()?.TargetBinder?.Using(context.LanguageScope);
var type = binding?.TargetType;
if (string.IsNullOrEmpty(type))
if (!context.Context.LanguageScope.TryGetType(o, out var type, out var path) ||
string.IsNullOrEmpty(type))
return Invalid(context, svalue);
operand = Operand.FromType(type, binding.TargetTypePath);
operand = Operand.FromType(type, path);
}
return operand != null;
}
@ -1561,7 +1631,7 @@ namespace PSRule.Definitions.Expressions
operand = null;
if (properties.TryGetString(SOURCE, out var sourceValue))
{
var source = context?.GetContext()?.TargetObject?.Source[sourceValue];
var source = context?.Context?.TargetObject?.Source[sourceValue];
if (source == null)
return Invalid(context, sourceValue);
@ -1627,8 +1697,8 @@ namespace PSRule.Definitions.Expressions
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, out operand) ||
TryName(context, properties, out operand) ||
TryType(context, properties, o, out operand) ||
TryName(context, properties, o, out operand) ||
TrySource(context, properties, out operand) ||
TryValue(context, properties, out operand) ||
Invalid(context, name);

Просмотреть файл

@ -34,7 +34,7 @@ namespace PSRule.Definitions.Expressions
public LanguageExpression CreateInstance(SourceFile source, LanguageExpression.PropertyBag properties)
{
if (Type == LanguageExpressionType.Operator)
return new LanguageOperator(this);
return new LanguageOperator(this, properties);
if (Type == LanguageExpressionType.Condition)
return new LanguageCondition(this, properties);
@ -74,12 +74,17 @@ namespace PSRule.Definitions.Expressions
[DebuggerDisplay("Selector {Descriptor.Name}")]
internal sealed class LanguageOperator : LanguageExpression
{
internal LanguageOperator(LanguageExpresssionDescriptor descriptor)
internal LanguageOperator(LanguageExpresssionDescriptor descriptor, PropertyBag properties)
: base(descriptor)
{
Property = properties ?? new PropertyBag();
Children = new List<LanguageExpression>();
}
public LanguageExpression Subselector { get; set; }
public PropertyBag Property { get; }
public List<LanguageExpression> Children { get; }
public void Add(LanguageExpression item)
@ -109,9 +114,6 @@ namespace PSRule.Definitions.Expressions
internal sealed class LanguageFunction : LanguageExpression
{
internal LanguageFunction(LanguageExpresssionDescriptor descriptor)
: base(descriptor)
{
}
: base(descriptor) { }
}
}

Просмотреть файл

@ -6,16 +6,33 @@ using PSRule.Pipeline;
namespace PSRule.Definitions
{
/// <summary>
/// A language block.
/// </summary>
public interface ILanguageBlock
{
/// <summary>
/// The unique identifier for the block.
/// </summary>
ResourceId Id { get; }
/// <summary>
/// Obsolete. The source file path.
/// Replaced by <see cref="Source"/>.
/// </summary>
[Obsolete("Use Source property instead.")]
string SourcePath { get; }
/// <summary>
/// Obsolete. The source module.
/// Replaced by <see cref="Source"/>.
/// </summary>
[Obsolete("Use Source property instead.")]
string Module { get; }
/// <summary>
/// The source location for the block.
/// </summary>
SourceFile Source { get; }
}
}

Просмотреть файл

@ -18,8 +18,14 @@ using YamlDotNet.Serialization.NodeDeserializers;
namespace PSRule.Definitions
{
/// <summary>
/// The type of resource.
/// </summary>
public enum ResourceKind
{
/// <summary>
/// Unknown or empty.
/// </summary>
None = 0,
/// <summary>
@ -53,14 +59,26 @@ namespace PSRule.Definitions
SuppressionGroup = 6
}
/// <summary>
/// Additional flags that indicate the status of the resource.
/// </summary>
[Flags]
public enum ResourceFlags
{
/// <summary>
/// No flags are set.
/// </summary>
None = 0,
/// <summary>
/// The resource is obsolete.
/// </summary>
Obsolete = 1
}
/// <summary>
/// A resource langange block.
/// </summary>
public interface IResource : ILanguageBlock
{
/// <summary>
@ -142,6 +160,9 @@ namespace PSRule.Definitions
}
/// <summary>
/// A resource object.
/// </summary>
public sealed class ResourceObject
{
internal ResourceObject(IResource block)
@ -196,15 +217,24 @@ namespace PSRule.Definitions
}
}
/// <summary>
/// Additional resource annotations.
/// </summary>
public sealed class ResourceAnnotations : Dictionary<string, object>
{
}
/// <summary>
/// Additional resource tags.
/// </summary>
public sealed class ResourceTags : Dictionary<string, string>
{
private Hashtable _Hashtable;
/// <summary>
/// Create an empty set of resource tags.
/// </summary>
public ResourceTags() : base(StringComparer.OrdinalIgnoreCase) { }
/// <summary>
@ -287,6 +317,10 @@ namespace PSRule.Definitions
return false;
}
/// <summary>
/// Convert the resourecs tags to a display string for PowerShell views.
/// </summary>
/// <returns></returns>
public string ToViewString()
{
var sb = new StringBuilder();
@ -308,8 +342,14 @@ namespace PSRule.Definitions
}
}
/// <summary>
/// Additional resource metadata.
/// </summary>
public sealed class ResourceMetadata
{
/// <summary>
/// Create an empty set of metadata.
/// </summary>
public ResourceMetadata()
{
Annotations = new ResourceAnnotations();
@ -321,8 +361,14 @@ namespace PSRule.Definitions
/// </summary>
public string Name { get; set; }
/// <summary>
/// A opaque reference for the resource.
/// </summary>
public string Ref { get; set; }
/// <summary>
/// Additional aliases for the resource.
/// </summary>
public string[] Alias { get; set; }
/// <summary>
@ -338,16 +384,32 @@ namespace PSRule.Definitions
public ResourceTags Tags { get; set; }
}
/// <summary>
/// The source location of the resource.
/// </summary>
public sealed class ResourceExtent
{
/// <summary>
/// The file where the resource is located.
/// </summary>
public string File { get; set; }
/// <summary>
/// The name of the module if the resource is contained within a module.
/// </summary>
public string Module { get; set; }
}
/// <summary>
/// A base class for resources.
/// </summary>
/// <typeparam name="TSpec">The type for the resource specification.</typeparam>
[DebuggerDisplay("Kind = {Kind}, Id = {Id}")]
public abstract class Resource<TSpec> where TSpec : Spec, new()
{
/// <summary>
/// Create a resource.
/// </summary>
internal protected Resource(ResourceKind kind, string apiVersion, SourceFile source, ResourceMetadata metadata, IResourceHelpInfo info, ISourceExtent extent, TSpec spec)
{
Kind = kind;
@ -361,6 +423,9 @@ namespace PSRule.Definitions
Id = new ResourceId(source.Module, Name, ResourceIdKind.Id);
}
/// <summary>
/// The resource identifier for the resource.
/// </summary>
[YamlIgnore()]
public ResourceId Id { get; }
@ -376,6 +441,9 @@ namespace PSRule.Definitions
[YamlIgnore()]
public SourceFile Source { get; }
/// <summary>
/// Information about the resource.
/// </summary>
[YamlIgnore()]
public IResourceHelpInfo Info { get; }

Просмотреть файл

@ -14,17 +14,36 @@ namespace PSRule.Definitions.Rules
/// </summary>
public enum SeverityLevel
{
/// <summary>
/// Severity is unset.
/// </summary>
None = 0,
/// <summary>
/// A failure generates an error.
/// </summary>
Error = 1,
/// <summary>
/// A fiailure generates a warning.
/// </summary>
Warning = 2,
/// <summary>
/// A failure generate an informational message.
/// </summary>
Information = 3
}
/// <summary>
/// A rule resource V1.
/// </summary>
public interface IRuleV1 : IResource, IDependencyTarget
{
/// <summary>
/// Obsolete. The name of the rule.
/// Replaced by <see cref="IResource.Name"/>.
/// </summary>
[Obsolete("Use Name instead.")]
string RuleName { get; }
@ -33,16 +52,32 @@ namespace PSRule.Definitions.Rules
/// </summary>
SeverityLevel Level { get; }
/// <summary>
/// A short description of the rule.
/// </summary>
string Synopsis { get; }
/// <summary>
/// Obsolete. A short description of the rule.
/// Replaced by <see cref="Synopsis"/>.
/// </summary>
[Obsolete("Use Synopsis instead.")]
string Description { get; }
/// <summary>
/// Any additional tags assigned to the rule.
/// </summary>
ResourceTags Tag { get; }
}
/// <summary>
/// A specification for a rule resource.
/// </summary>
internal interface IRuleSpec
{
/// <summary>
/// The of the rule condition that will be evaluated.
/// </summary>
LanguageIf Condition { get; }
/// <summary>
@ -50,9 +85,20 @@ namespace PSRule.Definitions.Rules
/// </summary>
SeverityLevel? Level { get; }
/// <summary>
/// An optional type pre-condition before the rule is evaluated.
/// </summary>
string[] Type { get; }
/// <summary>
/// An optional selector pre-condition before the rule is evaluated.
/// </summary>
string[] With { get; }
/// <summary>
/// An optional sub-selector pre-condition before the rule is evaluated.
/// </summary>
LanguageIf Where { get; }
}
[Spec(Specs.V1, Specs.Rule)]
@ -68,10 +114,12 @@ namespace PSRule.Definitions.Rules
Level = ResourceHelper.GetLevel(spec.Level);
}
/// <inheritdoc/>
[JsonIgnore]
[YamlIgnore]
public ResourceId? Ref { get; }
/// <inheritdoc/>
[JsonIgnore]
[YamlIgnore]
public ResourceId[] Alias { get; }
@ -90,43 +138,52 @@ namespace PSRule.Definitions.Rules
[YamlIgnore]
public string Synopsis => Info.Synopsis.Text;
/// <inheritdoc/>
ResourceId? IDependencyTarget.Ref => Ref;
/// <inheritdoc/>
ResourceId[] IDependencyTarget.Alias => Alias;
// Not supported with resource rules.
ResourceId[] IDependencyTarget.DependsOn => Array.Empty<ResourceId>();
/// <inheritdoc/>
bool IDependencyTarget.Dependency => Source.IsDependency();
/// <inheritdoc/>
ResourceId? IResource.Ref => Ref;
/// <inheritdoc/>
ResourceId[] IResource.Alias => Alias;
/// <inheritdoc/>
string IRuleV1.RuleName => Name;
/// <inheritdoc/>
ResourceTags IRuleV1.Tag => Metadata.Tags;
/// <inheritdoc/>
string IRuleV1.Description => Info.Synopsis.Text;
}
/// <summary>
/// A specification for a V1 rule resource.
/// </summary>
internal sealed class RuleV1Spec : Spec, IRuleSpec
{
/// <inheritdoc/>
public LanguageIf Condition { get; set; }
/// <summary>
/// If the rule fails, how serious is the result.
/// </summary>
/// <inheritdoc/>
public SeverityLevel? Level { get; set; }
/// <summary>
/// An optional type precondition before the rule is evaluated.
/// </summary>
/// <inheritdoc/>
public string[] Type { get; set; }
/// <summary>
/// An optional selector precondition before the rule is evaluated.
/// </summary>
/// <inheritdoc/>
public string[] With { get; set; }
/// <inheritdoc/>
public LanguageIf Where { get; set; }
}
}

Просмотреть файл

@ -11,13 +11,18 @@ using PSRule.Runtime;
namespace PSRule.Definitions.Rules
{
/// <summary>
/// A rule visitor.
/// </summary>
[DebuggerDisplay("Id: {Id}")]
internal sealed class RuleVisitor : ICondition
{
private readonly LanguageExpressionOuterFn _Condition;
private readonly RunspaceContext _Context;
public RuleVisitor(ResourceId id, SourceFile source, IRuleSpec spec)
public RuleVisitor(RunspaceContext context, ResourceId id, SourceFile source, IRuleSpec spec)
{
_Context = context;
ErrorAction = ActionPreference.Stop;
Id = id;
Source = source;
@ -26,6 +31,7 @@ namespace PSRule.Definitions.Rules
_Condition = builder
.WithSelector(spec.With)
.WithType(spec.Type)
.WithSubselector(spec.Where)
.Build(spec.Condition);
}
@ -50,14 +56,14 @@ namespace PSRule.Definitions.Rules
public IConditionResult If()
{
var context = new ExpressionContext(Source, ResourceKind.Rule, RunspaceContext.CurrentThread.TargetObject.Value);
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, RunspaceContext.CurrentThread.TargetObject.Value);
var result = _Condition(context, _Context.TargetObject.Value);
if (result.HasValue && !result.Value)
RunspaceContext.CurrentThread.WriteReason(context.GetReasons());
_Context.WriteReason(context.GetReasons());
return result.HasValue ? new RuleConditionResult(result.Value ? 1 : 0, 1, false) : null;
}

Просмотреть файл

@ -6,6 +6,7 @@ using System.Diagnostics;
using PSRule.Definitions.Expressions;
using PSRule.Pipeline;
using PSRule.Resources;
using PSRule.Runtime;
namespace PSRule.Definitions.Selectors
{
@ -18,9 +19,11 @@ namespace PSRule.Definitions.Selectors
internal sealed class SelectorVisitor : ISelector
{
private readonly LanguageExpressionOuterFn _Fn;
private readonly RunspaceContext _Context;
public SelectorVisitor(ResourceId id, SourceFile source, LanguageIf expression)
public SelectorVisitor(RunspaceContext context, ResourceId id, SourceFile source, LanguageIf expression)
{
_Context = context;
Id = id;
Source = source;
InstanceId = Guid.NewGuid();
@ -42,7 +45,7 @@ namespace PSRule.Definitions.Selectors
public bool Match(object o)
{
var context = new ExpressionContext(Source, ResourceKind.Selector, o);
var context = new ExpressionContext(_Context, Source, ResourceKind.Selector, o);
context.Debug(PSRuleResources.SelectorMatchTrace, Id);
return _Fn(context, o).GetValueOrDefault(false);
}

Просмотреть файл

@ -5,6 +5,7 @@ using System;
using PSRule.Definitions.Expressions;
using PSRule.Pipeline;
using PSRule.Resources;
using PSRule.Runtime;
namespace PSRule.Definitions.SuppressionGroups
{
@ -12,9 +13,11 @@ namespace PSRule.Definitions.SuppressionGroups
{
private readonly LanguageExpressionOuterFn _Fn;
private readonly SuppressionInfo _Info;
private readonly RunspaceContext _Context;
public SuppressionGroupVisitor(ResourceId id, SourceFile source, ISuppressionGroupSpec spec, IResourceHelpInfo info)
public SuppressionGroupVisitor(RunspaceContext context, ResourceId id, SourceFile source, ISuppressionGroupSpec spec, IResourceHelpInfo info)
{
_Context = context;
Id = id;
Source = source;
InstanceId = Guid.NewGuid();
@ -77,7 +80,7 @@ namespace PSRule.Definitions.SuppressionGroups
public bool TryMatch(object o, out ISuppressionInfo suppression)
{
suppression = null;
var context = new ExpressionContext(Source, ResourceKind.SuppressionGroup, o);
var context = new ExpressionContext(_Context, Source, ResourceKind.SuppressionGroup, o);
context.Debug(PSRuleResources.SelectorMatchTrace, Id);
if (_Fn(context, o).GetValueOrDefault(false))
{

Просмотреть файл

@ -56,11 +56,6 @@ namespace PSRule.Host
return builder.Build();
}
internal static IEnumerable<RuleBlock> GetRuleYamlBlocks(Source[] source, RunspaceContext context)
{
return ToRuleBlockV1(GetYamlLanguageBlocks(source, context), context, skipDuplicateName: true).GetAll();
}
private static IEnumerable<ILanguageBlock> GetYamlJsonLanguageBlocks(Source[] source, RunspaceContext context)
{
var results = new List<ILanguageBlock>();
@ -530,7 +525,7 @@ namespace PSRule.Host
@ref: block.Ref,
level: block.Level,
info: info,
condition: new RuleVisitor(block.Id, block.Source, block.Spec),
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
@ -706,11 +701,11 @@ namespace PSRule.Host
// Process module configurations first
foreach (var resource in resources.Where(r => r.Kind == ResourceKind.ModuleConfig).ToArray())
context.Pipeline.Import(resource);
context.Pipeline.Import(context, resource);
// Process other resources
foreach (var resource in resources.Where(r => r.Kind != ResourceKind.ModuleConfig).ToArray())
context.Pipeline.Import(resource);
context.Pipeline.Import(context, resource);
}
private static void Import(IConvention[] blocks, RunspaceContext context)

Просмотреть файл

@ -476,7 +476,7 @@ namespace PSRule.Pipeline
{
// 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, PSObject targetObject, out string path) =>
return (string[] propertyNames, bool caseSensitive, bool preferTargetInfo, object targetObject, out string path) =>
{
return action(propertyNames, caseSensitive, preferTargetInfo, targetObject, previous, out path);
};
@ -484,7 +484,7 @@ namespace PSRule.Pipeline
private static BindTargetMethod AddBindTargetAction(BindTargetName action, BindTargetMethod previous)
{
return AddBindTargetAction((string[] propertyNames, bool caseSensitive, bool preferTargetInfo, PSObject targetObject, BindTargetMethod next, out string path) =>
return AddBindTargetAction((string[] propertyNames, bool caseSensitive, bool preferTargetInfo, object targetObject, BindTargetMethod next, out string path) =>
{
path = null;
var targetType = action(targetObject);

Просмотреть файл

@ -161,7 +161,7 @@ namespace PSRule.Pipeline
return _Runspace;
}
internal void Import(IResource resource)
internal void Import(RunspaceContext context, IResource resource)
{
TrackIssue(resource);
if (TryBaseline(resource, out var baseline) && TryBaselineRef(resource.Id, out var baselineRef))
@ -170,7 +170,7 @@ namespace PSRule.Pipeline
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)
Selector[selector.Id.Value] = new SelectorVisitor(selector.Id, selector.Source, selector.Spec.If);
Selector[selector.Id.Value] = new SelectorVisitor(context, selector.Id, selector.Source, selector.Spec.If);
else if (TryModuleConfig(resource, out var moduleConfig))
{
if (!string.IsNullOrEmpty(moduleConfig?.Spec?.Rule?.Baseline))
@ -184,6 +184,7 @@ namespace PSRule.Pipeline
else if (resource.Kind == ResourceKind.SuppressionGroup && resource is SuppressionGroupV1 suppressionGroup)
{
SuppressionGroup.Add(new SuppressionGroupVisitor(
context: context,
id: suppressionGroup.Id,
source: suppressionGroup.Source,
spec: suppressionGroup.Spec,

Просмотреть файл

@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Globalization;
using System.Linq;
using System.Management.Automation;
@ -19,9 +20,12 @@ namespace PSRule.Pipeline
private const string Property_TargetName = "TargetName";
private const string Property_Name = "Name";
public static string BindTargetName(string[] propertyNames, bool caseSensitive, bool preferTargetInfo, PSObject targetObject, out string path)
public static string BindTargetName(string[] propertyNames, bool caseSensitive, bool preferTargetInfo, object targetObject, out string path)
{
path = null;
if (targetObject == null)
return null;
if (preferTargetInfo && TryGetInfoTargetName(targetObject, out var targetName))
return targetName;
@ -33,9 +37,12 @@ namespace PSRule.Pipeline
return DefaultTargetNameBinding(targetObject);
}
public static string BindTargetType(string[] propertyNames, bool caseSensitive, bool preferTargetInfo, PSObject targetObject, out string path)
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;
@ -47,9 +54,12 @@ namespace PSRule.Pipeline
return DefaultTargetTypeBinding(targetObject);
}
public static string BindField(string[] propertyNames, bool caseSensitive, bool preferTargetInfo, PSObject targetObject, out string path)
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)
@ -63,7 +73,7 @@ namespace PSRule.Pipeline
/// </summary>
/// <param name="targetObject">A PSObject to bind.</param>
/// <returns>The TargetName of the object.</returns>
private static string DefaultTargetNameBinding(PSObject targetObject)
private static string DefaultTargetNameBinding(object targetObject)
{
return TryGetInfoTargetName(targetObject, out var targetName) ||
TryGetTargetName(targetObject, propertyName: Property_TargetName, targetName: out targetName) ||
@ -76,16 +86,17 @@ namespace PSRule.Pipeline
/// Get the TargetName of the object by using any of the specified property names.
/// </summary>
/// <param name="propertyNames">One or more property names to use to bind TargetName.</param>
/// <param name="caseSensitive">Determines if binding properties are case-sensitive.</param>
/// <param name="targetObject">A PSObject to bind.</param>
/// <param name="next">The next delegate function to check if all of the property names can not be found.</param>
/// <returns>The TargetName of the object.</returns>
private static string CustomTargetPropertyBinding(string[] propertyNames, bool caseSensitive, PSObject targetObject, BindTargetName next, out string path)
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++)
{
targetName = targetObject.ValueAsString(propertyName: propertyNames[i], caseSensitive: caseSensitive);
targetName = ValueAsString(targetObject, propertyName: propertyNames[i], caseSensitive: caseSensitive);
if (targetName != null)
path = propertyNames[i];
}
@ -97,10 +108,11 @@ namespace PSRule.Pipeline
/// Get the TargetName of the object by using any of the specified property names.
/// </summary>
/// <param name="propertyNames">One or more property names to use to bind TargetName.</param>
/// <param name="caseSensitive">Determines if binding properties are case-sensitive.</param>
/// <param name="targetObject">A PSObject to bind.</param>
/// <param name="next">The next delegate function to check if all of the property names can not be found.</param>
/// <returns>The TargetName of the object.</returns>
private static string NestedTargetPropertyBinding(string[] propertyNames, bool caseSensitive, PSObject targetObject, BindTargetName next, out string path)
private static string NestedTargetPropertyBinding(string[] propertyNames, bool caseSensitive, object targetObject, BindTargetName next, out string path)
{
path = null;
string targetName = null;
@ -128,7 +140,7 @@ namespace PSRule.Pipeline
/// </summary>
/// <param name="targetObject">A PSObject to hash.</param>
/// <returns>The TargetName of the object.</returns>
private static string GetUnboundObjectTargetName(PSObject targetObject)
private static string GetUnboundObjectTargetName(object targetObject)
{
var settings = new JsonSerializerSettings
{
@ -146,9 +158,9 @@ namespace PSRule.Pipeline
/// <summary>
/// Try to get TargetName from specified property.
/// </summary>
private static bool TryGetTargetName(PSObject targetObject, string propertyName, out string targetName)
private static bool TryGetTargetName(object targetObject, string propertyName, out string targetName)
{
targetName = targetObject.ValueAsString(propertyName, false);
targetName = ValueAsString(targetObject, propertyName, false);
return targetName != null;
}
@ -157,34 +169,49 @@ namespace PSRule.Pipeline
/// </summary>
/// <param name="targetObject">A PSObject to bind.</param>
/// <returns>The TargetObject of the object.</returns>
private static string DefaultTargetTypeBinding(PSObject targetObject)
private static string DefaultTargetTypeBinding(object targetObject)
{
return TryGetInfoTargetType(targetObject, out var targetType) ? targetType : targetObject.TypeNames[0];
return TryGetInfoTargetType(targetObject, out var targetType) ? targetType : GetTypeNames(targetObject);
}
private static string DefaultFieldBinding(PSObject targetObject)
private static string GetTypeNames(object targetObject)
{
if (targetObject == null)
return null;
return targetObject is PSObject pso ? pso.TypeNames[0] : targetObject.GetType().FullName;
}
private static string DefaultFieldBinding(object targetObject)
{
return null;
}
private static bool TryGetInfoTargetName(PSObject targetObject, out string targetName)
private static bool TryGetInfoTargetName(object targetObject, out string targetName)
{
targetName = null;
if (!(targetObject.BaseObject is ITargetInfo info))
var baseObject = ExpressionHelpers.GetBaseObject(targetObject);
if (baseObject is not ITargetInfo info)
return false;
targetName = info.TargetName;
return true;
}
private static bool TryGetInfoTargetType(PSObject targetObject, out string targetType)
private static bool TryGetInfoTargetType(object targetObject, out string targetType)
{
targetType = null;
if (!(targetObject.BaseObject is ITargetInfo info))
var baseObject = ExpressionHelpers.GetBaseObject(targetObject);
if (baseObject is not ITargetInfo info)
return false;
targetType = info.TargetType;
return true;
}
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;
}
}
}

Просмотреть файл

@ -4,8 +4,8 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Management.Automation;
using PSRule.Configuration;
using static PSRule.Pipeline.TargetBinder;
namespace PSRule.Pipeline
{
@ -17,6 +17,8 @@ namespace PSRule.Pipeline
void Bind(TargetObject targetObject);
ITargetBindingContext Using(string languageScope);
ITargetBindingResult Result(string languageScope);
}
/// <summary>
@ -26,6 +28,11 @@ namespace PSRule.Pipeline
{
string LanguageScope { get; }
ITargetBindingResult Bind(object o);
}
internal interface ITargetBindingResult
{
/// <summary>
/// The bound TargetName of the target object.
/// </summary>
@ -46,8 +53,6 @@ namespace PSRule.Pipeline
Hashtable Field { get; }
bool ShouldFilter { get; }
void Bind(TargetObject targetObject, BindTargetMethod bindTargetName, BindTargetMethod bindTargetType, BindTargetMethod bindField, HashSet<string> typeFilter);
}
/// <summary>
@ -56,7 +61,7 @@ namespace PSRule.Pipeline
internal sealed class TargetBinderBuilder
{
private readonly List<ITargetBindingContext> _BindingContext;
private readonly string[] _TypeFilter;
private readonly HashSet<string> _TypeFilter;
private readonly BindTargetMethod _BindTargetName;
private readonly BindTargetMethod _BindTargetType;
private readonly BindTargetMethod _BindField;
@ -67,7 +72,8 @@ namespace PSRule.Pipeline
_BindTargetType = bindTargetType;
_BindField = bindField;
_BindingContext = new List<ITargetBindingContext>();
_TypeFilter = typeFilter;
if (typeFilter != null && typeFilter.Length > 0)
_TypeFilter = new HashSet<string>(typeFilter, StringComparer.OrdinalIgnoreCase);
}
/// <summary>
@ -75,7 +81,7 @@ namespace PSRule.Pipeline
/// </summary>
public ITargetBinder Build()
{
return new TargetBinder(_BindingContext.ToArray(), _BindTargetName, _BindTargetType, _BindField, _TypeFilter);
return new TargetBinder(_BindingContext.ToArray());
}
/// <summary>
@ -85,6 +91,14 @@ namespace PSRule.Pipeline
{
_BindingContext.Add(bindingContext);
}
/// <summary>
/// Add a target binding context.
/// </summary>
public void With(string languageScope, IBindingOption bindingOption)
{
_BindingContext.Add(new TargetBindingContext(languageScope, bindingOption, _BindTargetName, _BindTargetType, _BindField, _TypeFilter));
}
}
/// <summary>
@ -94,23 +108,13 @@ namespace PSRule.Pipeline
{
private const string STANDALONE_SCOPE = ".";
private readonly BindTargetMethod _BindTargetName;
private readonly BindTargetMethod _BindTargetType;
private readonly BindTargetMethod _BindField;
private readonly HashSet<string> _TypeFilter;
private readonly Dictionary<string, ITargetBindingContext> _BindingContext;
private readonly Dictionary<string, ITargetBindingResult> _BindingResult;
internal TargetBinder(ITargetBindingContext[] bindingContext, BindTargetMethod bindTargetName, BindTargetMethod bindTargetType, BindTargetMethod bindField, string[] typeFilter)
internal TargetBinder(ITargetBindingContext[] bindingContext)
{
_BindingContext = new Dictionary<string, ITargetBindingContext>();
_BindTargetName = bindTargetName;
_BindTargetType = bindTargetType;
_BindField = bindField;
if (typeFilter != null && typeFilter.Length > 0)
_TypeFilter = new HashSet<string>(typeFilter, StringComparer.OrdinalIgnoreCase);
_BindingResult = new Dictionary<string, ITargetBindingResult>();
for (var i = 0; bindingContext != null && i < bindingContext.Length; i++)
_BindingContext.Add(bindingContext[i].LanguageScope ?? STANDALONE_SCOPE, bindingContext[i]);
}
@ -166,56 +170,79 @@ namespace PSRule.Pipeline
}
}
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;
}
/// <inheritdoc/>
public string TargetName { get; }
/// <inheritdoc/>
public string TargetNamePath { get; }
/// <inheritdoc/>
public string TargetType { get; }
/// <inheritdoc/>
public string TargetTypePath { get; }
/// <inheritdoc/>
public bool ShouldFilter { get; }
/// <inheritdoc/>
public Hashtable Field { get; }
}
internal sealed class TargetBindingContext : ITargetBindingContext
{
private readonly IBindingOption _BindingOption;
private readonly BindTargetMethod _BindTargetName;
private readonly BindTargetMethod _BindTargetType;
private readonly BindTargetMethod _BindField;
private readonly HashSet<string> _TypeFilter;
public TargetBindingContext(string languageScope, IBindingOption bindingOption)
public TargetBindingContext(string languageScope, IBindingOption bindingOption, BindTargetMethod bindTargetName, BindTargetMethod bindTargetType, BindTargetMethod bindField, HashSet<string> typeFilter)
{
LanguageScope = languageScope;
_BindingOption = bindingOption;
_BindTargetName = bindTargetName;
_BindTargetType = bindTargetType;
_BindField = bindField;
_TypeFilter = typeFilter;
}
public string LanguageScope { get; }
/// <summary>
/// The bound TargetName of the target object.
/// </summary>
public string TargetName { get; private set; }
public string TargetNamePath { get; private set; }
/// <summary>
/// The bound TargetType of the target object.
/// </summary>
public string TargetType { get; private set; }
public string TargetTypePath { get; private set; }
/// <summary>
/// Additional bound fields of the target object.
/// </summary>
public Hashtable Field { get; private set; }
/// <summary>
/// Determines if the target object should be filtered.
/// </summary>
public bool ShouldFilter { get; private set; }
public void Bind(TargetObject targetObject, BindTargetMethod bindTargetName, BindTargetMethod bindTargetType, BindTargetMethod bindField, HashSet<string> typeFilter)
public ITargetBindingResult Bind(object o)
{
TargetName = bindTargetName(_BindingOption.TargetName, !_BindingOption.IgnoreCase, _BindingOption.PreferTargetInfo, targetObject.Value, out var targetNamePath);
TargetNamePath = targetNamePath;
TargetType = bindTargetType(_BindingOption.TargetType, !_BindingOption.IgnoreCase, _BindingOption.PreferTargetInfo, targetObject.Value, out var targetTypePath);
TargetTypePath = targetTypePath;
ShouldFilter = !(typeFilter == null || typeFilter.Contains(TargetType));
var targetName = _BindTargetName(_BindingOption.TargetName, !_BindingOption.IgnoreCase, _BindingOption.PreferTargetInfo, o, out var targetNamePath);
var targetType = _BindTargetType(_BindingOption.TargetType, !_BindingOption.IgnoreCase, _BindingOption.PreferTargetInfo, o, out var targetTypePath);
var shouldFilter = !(_TypeFilter == null || _TypeFilter.Contains(targetType));
// Use qualified name
if (_BindingOption.UseQualifiedName)
TargetName = string.Concat(TargetType, _BindingOption.NameSeparator, TargetName);
targetName = string.Concat(targetType, _BindingOption.NameSeparator, targetName);
// Bind custom fields
Field = BindField(bindField, _BindingOption.Field, !_BindingOption.IgnoreCase, targetObject.Value);
var field = BindField(_BindField, _BindingOption.Field, !_BindingOption.IgnoreCase, o);
return new TargetBindingResult
(
targetName: targetName,
targetNamePath: targetNamePath,
targetType: targetType,
targetTypePath: targetTypePath,
shouldFilter: shouldFilter,
field: field
);
}
}
@ -227,7 +254,7 @@ namespace PSRule.Pipeline
public void Bind(TargetObject targetObject)
{
foreach (var bindingContext in _BindingContext.Values)
bindingContext.Bind(targetObject, _BindTargetName, _BindTargetType, _BindField, _TypeFilter);
_BindingResult[bindingContext.LanguageScope] = bindingContext.Bind(targetObject.Value);
}
public ITargetBindingContext Using(string languageScope)
@ -235,10 +262,15 @@ namespace PSRule.Pipeline
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;
}
/// <summary>
/// Bind additional fields.
/// </summary>
private static Hashtable BindField(BindTargetMethod bindField, FieldMap[] map, bool caseSensitive, PSObject targetObject)
private static Hashtable BindField(BindTargetMethod bindField, FieldMap[] map, bool caseSensitive, object o)
{
if (map == null || map.Length == 0)
return null;
@ -254,7 +286,7 @@ namespace PSRule.Pipeline
if (hashtable.ContainsKey(field.Key))
continue;
hashtable.Add(field.Key, bindField(field.Value, caseSensitive, false, targetObject, out _));
hashtable.Add(field.Key, bindField(field.Value, caseSensitive, false, o, out _));
}
}
hashtable.Protect();

816
src/PSRule/Resources/PSRuleResources.Designer.cs сгенерированный

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -382,4 +382,7 @@
<data name="ReadJsonFailedExpectedToken" xml:space="preserve">
<value>Read JSON failed because the token ({0}) was not expected.</value>
</data>
<data name="DebugTargetSubselectorMismatch" xml:space="preserve">
<value>Target failed sub-selector pre-condition.</value>
</data>
</root>

Просмотреть файл

@ -12,6 +12,9 @@ namespace PSRule.Runtime
/// </summary>
internal interface ILanguageScope : IDisposable
{
/// <summary>
/// The name of the scope.
/// </summary>
string Name { get; }
/// <summary>
@ -28,29 +31,42 @@ namespace PSRule.Runtime
IResourceFilter GetFilter(ResourceKind kind);
/// <summary>
/// Add a service to the scope.
/// </summary>
void AddService(string name, object service);
/// <summary>
/// Get a previously added service.
/// </summary>
object GetService(string name);
bool TryGetType(object o, out string type, out string path);
bool TryGetName(object o, out string name, out string path);
}
internal sealed class LanguageScope : ILanguageScope
{
private const string STANDALONE_SCOPENAME = ".";
private readonly RunspaceContext _Context;
private readonly Dictionary<string, object> _Configuration;
private readonly Dictionary<string, object> _Service;
private readonly Dictionary<ResourceKind, IResourceFilter> _Filter;
private bool _Disposed;
public LanguageScope(string name)
public LanguageScope(RunspaceContext context, string name)
{
_Context = context;
Name = Normalize(name);
_Configuration = new Dictionary<string, object>();
_Filter = new Dictionary<ResourceKind, IResourceFilter>();
_Service = new Dictionary<string, object>();
}
/// <inheritdoc/>
public string Name { get; }
public void Configure(Dictionary<string, object> configuration)
@ -74,6 +90,7 @@ namespace PSRule.Runtime
return _Filter.TryGetValue(kind, out var filter) ? filter : null;
}
/// <inheritdoc/>
public void AddService(string name, object service)
{
if (_Service.ContainsKey(name))
@ -82,11 +99,54 @@ namespace PSRule.Runtime
_Service.Add(name, service);
}
/// <inheritdoc/>
public object GetService(string name)
{
return _Service.TryGetValue(name, out var service) ? service : null;
}
public bool TryGetType(object o, out string type, out string path)
{
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;
}
public bool TryGetName(object o, out string name, out string path)
{
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;
}
internal static string Normalize(string scope)
{
return string.IsNullOrEmpty(scope) ? STANDALONE_SCOPENAME : scope;
@ -123,13 +183,15 @@ namespace PSRule.Runtime
internal sealed class LanguageScopeSet : IDisposable
{
private readonly RunspaceContext _Context;
private readonly Dictionary<string, ILanguageScope> _Scopes;
private ILanguageScope _Current;
private bool _Disposed;
public LanguageScopeSet()
public LanguageScopeSet(RunspaceContext context)
{
_Context = context;
_Scopes = new Dictionary<string, ILanguageScope>(StringComparer.OrdinalIgnoreCase);
Import(null, out _Current);
}
@ -168,7 +230,7 @@ namespace PSRule.Runtime
if (_Scopes.TryGetValue(GetScopeName(name), out scope))
return false;
scope = new LanguageScope(name);
scope = new LanguageScope(_Context, name);
Add(scope);
return true;
}

Просмотреть файл

@ -10,7 +10,7 @@ namespace PSRule.Runtime.ObjectPath
/// <summary>
/// An expression function that returns one or more values when successful.
/// </summary>
internal delegate bool PathExpressionFn(IPathExpressionContext context, object input, out IEnumerable<object> value);
internal delegate bool PathExpressionFn(IPathExpressionContext context, object input, out IEnumerable<object> value, out bool enumerable);
/// <summary>
/// A function for filter objects that simply returns true or false.
@ -92,7 +92,7 @@ namespace PSRule.Runtime.ObjectPath
public bool TryGet(object o, bool caseSensitive, out object[] value)
{
value = null;
if (!TryGet(o, caseSensitive, out IEnumerable<object> result))
if (!TryGet(o, caseSensitive, out var result, out var enumerable))
return false;
value = result.ToArray();
@ -106,10 +106,11 @@ namespace PSRule.Runtime.ObjectPath
public bool TryGet(object o, bool caseSensitive, out object value)
{
value = null;
if (!TryGet(o, caseSensitive, out object[] result))
if (!TryGet(o, caseSensitive, out var result, out var enumerable))
return false;
value = IsArray ? result : result[0];
var items = result.ToArray();
value = IsArray || enumerable ? items : items[0];
return true;
}
@ -120,10 +121,10 @@ namespace PSRule.Runtime.ObjectPath
/// <param name="caseSensitive">Determines if member name matching is case-sensitive.</param>
/// <param name="value">The values selected from the object.</param>
/// <returns>Returns true when the path exists within the object. Returns false if the path does not exist.</returns>
private bool TryGet(object o, bool caseSensitive, out IEnumerable<object> value)
private bool TryGet(object o, bool caseSensitive, out IEnumerable<object> value, out bool enumerable)
{
var context = new PathExpressionContext(o, caseSensitive);
return _Expression.Invoke(context, o, out value);
return _Expression.Invoke(context, o, out value, out enumerable);
}
}
}

Просмотреть файл

@ -95,7 +95,7 @@ namespace PSRule.Runtime.ObjectPath
UseArray();
var filter = BuildExpression(reader, PathTokenType.EndFilter);
var next = BuildSelector(reader);
return (IPathExpressionContext context, object input, out IEnumerable<object> value) =>
return (IPathExpressionContext context, object input, out IEnumerable<object> value, out bool enumerable) =>
{
var result = new List<object>();
var success = 0;
@ -104,13 +104,14 @@ namespace PSRule.Runtime.ObjectPath
if (!filter(context, i))
continue;
if (!next(context, i, out var items))
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;
};
}
@ -118,10 +119,11 @@ namespace PSRule.Runtime.ObjectPath
private PathExpressionFn IndexSelector(ITokenReader reader, int index)
{
var next = BuildSelector(reader);
return (IPathExpressionContext context, object input, out IEnumerable<object> value) =>
return (IPathExpressionContext context, object input, out IEnumerable<object> value, out bool enumerable) =>
{
value = null;
return TryGetIndex(input, index, out var item) && next(context, item, out value);
enumerable = false;
return TryGetIndex(input, index, out var item) && next(context, item, out value, out enumerable);
};
}
@ -129,19 +131,20 @@ namespace PSRule.Runtime.ObjectPath
{
UseArray();
var next = BuildSelector(reader);
return (IPathExpressionContext context, object input, out IEnumerable<object> value) =>
return (IPathExpressionContext context, object input, out IEnumerable<object> value, out bool enumerable) =>
{
var result = new List<object>();
var success = 0;
foreach (var i in GetAll(input))
{
if (!next(context, i, out var items))
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;
};
}
@ -153,18 +156,19 @@ namespace PSRule.Runtime.ObjectPath
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<object> value) =>
return (IPathExpressionContext context, object input, out IEnumerable<object> value, out bool enumerable) =>
{
var result = new List<object>();
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))
if (!next(context, slice, out var items, out _))
continue;
result.AddRange(items);
}
enumerable = true;
value = result.ToArray();
return true;
};
@ -174,11 +178,12 @@ namespace PSRule.Runtime.ObjectPath
{
var caseSensitiveFlag = option == PathTokenOption.CaseSensitive;
var next = BuildSelector(reader);
return (IPathExpressionContext context, object input, out IEnumerable<object> value) =>
return (IPathExpressionContext context, object input, out IEnumerable<object> 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);
return TryGetField(input, memberName, caseSensitive, out var item) && next(context, item, out value, out enumerable);
};
}
@ -186,20 +191,21 @@ namespace PSRule.Runtime.ObjectPath
{
var caseSensitiveFlag = option == PathTokenOption.CaseSensitive;
var next = BuildSelector(reader);
return (IPathExpressionContext context, object input, out IEnumerable<object> value) =>
return (IPathExpressionContext context, object input, out IEnumerable<object> value, out bool enumerable) =>
{
var caseSensitive = context.CaseSensitive != caseSensitiveFlag;
var result = new List<object>();
var success = 0;
foreach (var i in GetAllRecurse(input, memberName, caseSensitive))
{
if (!next(context, i, out var items))
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;
};
}
@ -207,13 +213,13 @@ namespace PSRule.Runtime.ObjectPath
private PathExpressionFn CurrentRef(ITokenReader reader)
{
var next = BuildSelector(reader);
return (IPathExpressionContext context, object input, out IEnumerable<object> value) => next(context, input, out value);
return (IPathExpressionContext context, object input, out IEnumerable<object> 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<object> value) => next(context, context.Input, out value);
return (IPathExpressionContext context, object input, out IEnumerable<object> value, out bool enumerable) => next(context, context.Input, out value, out enumerable);
}
private PathExpressionFilterFn BuildExpression(ITokenReader reader, PathTokenType stop)
@ -282,7 +288,7 @@ namespace PSRule.Runtime.ObjectPath
private static PathExpressionFilterFn ExistCondition(PathExpressionFn next)
{
return (IPathExpressionContext context, object input) => next(context, input, out _);
return (IPathExpressionContext context, object input) => next(context, input, out _, out _);
}
private static PathExpressionFilterFn NotCondition(PathExpressionFilterFn next)
@ -294,7 +300,7 @@ namespace PSRule.Runtime.ObjectPath
{
return (IPathExpressionContext context, object input) =>
{
if (!left(context, input, out var leftValue) || !right(context, input, out var rightValue))
if (!left(context, input, out var leftValue, out _) || !right(context, input, out var rightValue, out _))
return false;
var operand1 = leftValue.FirstOrDefault();
@ -328,22 +334,32 @@ namespace PSRule.Runtime.ObjectPath
};
}
private static bool Return(IPathExpressionContext context, object input, out IEnumerable<object> value)
private static bool Return(IPathExpressionContext context, object input, out IEnumerable<object> 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;
value = new object[] { input };
enumerable = false;
if (input is object[] eo)
{
enumerable = true;
value = eo;
}
else
value = new object[] { input };
return true;
}
private static PathExpressionFn Literal(object arg)
{
var result = new object[] { arg };
return (IPathExpressionContext context, object input, out IEnumerable<object> value) =>
var isEnumerable = arg is object[];
var result = isEnumerable ? arg as object[] : new object[] { arg };
return (IPathExpressionContext context, object input, out IEnumerable<object> value, out bool enumerable) =>
{
value = result;
enumerable = isEnumerable;
return true;
};
}

Просмотреть файл

@ -70,7 +70,7 @@ namespace PSRule.Runtime
// Fields exposed to engine
internal RuleRecord RuleRecord;
internal RuleBlock RuleBlock;
internal ITargetBindingContext Binding;
internal ITargetBindingResult Binding;
private readonly bool _InconclusiveWarning;
private readonly bool _NotProcessedWarning;
@ -118,7 +118,7 @@ namespace PSRule.Runtime
_RuleTimer = new Stopwatch();
_Reason = new List<ResultReason>();
_Conventions = new List<IConvention>();
_LanguageScopes = new LanguageScopeSet();
_LanguageScopes = new LanguageScopeSet(this);
_Scope = new Stack<RunspaceScope>();
}
@ -643,7 +643,7 @@ namespace PSRule.Runtime
/// </summary>
public RuleRecord EnterRuleBlock(RuleBlock ruleBlock)
{
Binding = TargetBinder.Using(ruleBlock.Info.ModuleName);
Binding = TargetBinder.Result(ruleBlock.Info.ModuleName);
_RuleErrors = 0;
RuleBlock = ruleBlock;
@ -791,10 +791,14 @@ namespace PSRule.Runtime
Pipeline.BindField,
Pipeline.Option.Input.TargetType);
HashSet<string> _TypeFilter = null;
if (Pipeline.Option.Input.TargetType != null && Pipeline.Option.Input.TargetType.Length > 0)
_TypeFilter = new HashSet<string>(Pipeline.Option.Input.TargetType, StringComparer.OrdinalIgnoreCase);
foreach (var languageScope in _LanguageScopes.Get())
{
var targetBinding = Pipeline.Baseline.GetTargetBinding();
builder.With(new TargetBinder.TargetBindingContext(languageScope.Name, targetBinding));
builder.With(new TargetBinder.TargetBindingContext(languageScope.Name, targetBinding, Pipeline.BindTargetName, Pipeline.BindTargetType, Pipeline.BindField, _TypeFilter));
}
TargetBinder = builder.Build();
RunConventionInitialize();

Просмотреть файл

@ -0,0 +1,66 @@
[
{
// 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 filter.
"apiVersion": "github.com/microsoft/PSRule/v1",
"kind": "Rule",
"metadata": {
"name": "JsonRuleWithSubselector"
},
"spec": {
"condition": {
"field": "resources",
"where": {
"field": ".",
"isString": true
},
"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"
}
}
}
}
]

Просмотреть файл

@ -0,0 +1,52 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
#
# YAML-based rules for unit testing
#
---
# Synopsis: A rule with sub-selector pre-condition.
apiVersion: github.com/microsoft/PSRule/v1
kind: Rule
metadata:
name: YamlRuleWithPrecondition
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: YamlRuleWithSubselector
spec:
condition:
field: resources
where:
field: '.'
isString: true
allOf:
- field: '.'
equals: abc
---
# Synopsis: A rule with sub-selector filter.
apiVersion: github.com/microsoft/PSRule/v1
kind: Rule
metadata:
name: YamlRuleWithSubselectorReordered
spec:
condition:
allOf:
- field: '.'
equals: abc
field: resources
where:
field: '.'
equals: 'abc'

Просмотреть файл

@ -52,7 +52,7 @@ namespace PSRule
context.Init(source);
context.Begin();
var selector = HostHelper.GetSelector(source, context).ToArray().FirstOrDefault(s => s.Name == name);
return new SelectorVisitor(selector.Id, selector.Source, selector.Spec.If);
return new SelectorVisitor(context, selector.Id, selector.Source, selector.Spec.If);
}
private static string GetSourcePath(string fileName)

Просмотреть файл

@ -264,7 +264,7 @@ namespace PSRule
targetObject.Properties.Add(new PSNoteProperty("name", "TestObject1"));
var context = new Runtime.RunspaceContext(PipelineContext.New(GetOption(), null, null, PipelineHookActions.BindTargetName, PipelineHookActions.BindTargetType, PipelineHookActions.BindField, new OptionContextBuilder(GetOption(), null, null, null).Build(), null), null);
var s = GetSource();
var result = new ExpressionContext(s[0].File[0], Definitions.ResourceKind.Rule, targetObject);
var result = new ExpressionContext(context, s[0].File[0], Definitions.ResourceKind.Rule, targetObject);
context.Init(s);
context.Begin();
context.PushScope(Runtime.RunspaceScope.Precondition);

Просмотреть файл

@ -70,6 +70,12 @@
<None Update="FromFileName.Rule.yaml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="FromFileSubSelector.Rule.jsonc">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="FromFileSubSelector.Rule.yaml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Functions.Rule.jsonc">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>

Просмотреть файл

@ -20,6 +20,9 @@ namespace PSRule
{
#region Yaml rules
/// <summary>
/// Test that a YAML-based rule can be parsed.
/// </summary>
[Fact]
public void ReadYamlRule()
{
@ -44,6 +47,61 @@ namespace PSRule
Assert.Equal("tag", hashtable["feature"]);
}
/// <summary>
/// Test that a YAML-based rule with sub-selectors can be parsed.
/// </summary>
[Fact]
public void ReadYamlSubSelectorRule()
{
var context = new RunspaceContext(PipelineContext.New(GetOption(), null, null, PipelineHookActions.BindTargetName, PipelineHookActions.BindTargetType, PipelineHookActions.BindField, new OptionContext(), null), new TestWriter(GetOption()));
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);
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"));
context.EnterSourceScope(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());
}
[Fact]
public void EvaluateYamlRule()
{
@ -136,6 +194,9 @@ namespace PSRule
#region Json rules
/// <summary>
/// Test that a JSON-based rule can be parsed.
/// </summary>
[Fact]
public void ReadJsonRule()
{
@ -160,6 +221,61 @@ namespace PSRule
Assert.Equal("tag", hashtable["feature"]);
}
/// <summary>
/// Test that a JSON-based rule with sub-selectors can be parsed.
/// </summary>
[Fact]
public void ReadJsonSubSelectorRule()
{
var context = new RunspaceContext(PipelineContext.New(GetOption(), null, null, PipelineHookActions.BindTargetName, PipelineHookActions.BindTargetType, PipelineHookActions.BindField, new OptionContext(), null), new TestWriter(GetOption()));
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);
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"));
context.EnterSourceScope(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());
}
#endregion Json rules
#region Helper methods
@ -190,17 +306,17 @@ namespace PSRule
return JsonConvert.DeserializeObject<object[]>(File.ReadAllText(path));
}
private static RuleBlock GetRuleVisitor(RunspaceContext context, string name)
private static RuleBlock GetRuleVisitor(RunspaceContext context, string name, Source[] source = null)
{
var block = HostHelper.GetRuleYamlBlocks(GetSource(), context);
var block = HostHelper.GetRuleBlockGraph(source ?? GetSource(), context).GetAll();
return block.FirstOrDefault(s => s.Name == name);
}
private static void ImportSelectors(RunspaceContext context)
private static void ImportSelectors(RunspaceContext context, Source[] source = null)
{
var selectors = HostHelper.GetSelector(GetSource(), context).ToArray();
var selectors = HostHelper.GetSelector(source ?? GetSource(), context).ToArray();
foreach (var selector in selectors)
context.Pipeline.Import(selector);
context.Pipeline.Import(context, selector);
}
private static string GetSourcePath(string path)

Просмотреть файл

@ -1712,7 +1712,7 @@ namespace PSRule
context.Init(source);
context.Begin();
var selector = HostHelper.GetSelector(source, context).ToArray().FirstOrDefault(s => s.Name == name);
return new SelectorVisitor(selector.Id, selector.Source, selector.Spec.If);
return new SelectorVisitor(context, selector.Id, selector.Source, selector.Spec.If);
}
private static string GetSourcePath(string fileName)

Просмотреть файл

@ -7,7 +7,6 @@ using PSRule.Configuration;
using PSRule.Definitions.Baselines;
using PSRule.Pipeline;
using Xunit;
using static PSRule.Pipeline.TargetBinder;
namespace PSRule
{
@ -20,15 +19,15 @@ namespace PSRule
var targetObject = GetTargetObject();
binder.Bind(targetObject);
var m1 = binder.Using("Module1");
var m1 = binder.Result("Module1");
Assert.Equal("Name1", m1.TargetName);
Assert.Equal("Type1", m1.TargetType);
var m2 = binder.Using("Module2");
var m2 = binder.Result("Module2");
Assert.Equal("Name2", m2.TargetName);
Assert.Equal("Type1", m2.TargetType);
var m0 = binder.Using(".");
var m0 = binder.Result(".");
Assert.Equal("Name1", m0.TargetName);
Assert.Equal("System.Management.Automation.PSCustomObject", m0.TargetType);
}
@ -40,15 +39,15 @@ namespace PSRule
var targetObject = new TargetObject(PSObject.AsPSObject(JToken.Parse("{ \"name\": \"Name1\", \"type\": \"Type1\", \"AlternativeName\": \"Name2\", \"AlternativeType\": \"Type2\" }")));
binder.Bind(targetObject);
var m1 = binder.Using("Module1");
var m1 = binder.Result("Module1");
Assert.Equal("Name1", m1.TargetName);
Assert.Equal("Type1", m1.TargetType);
var m2 = binder.Using("Module2");
var m2 = binder.Result("Module2");
Assert.Equal("Name2", m2.TargetName);
Assert.Equal("Type1", m2.TargetType);
var m0 = binder.Using(".");
var m0 = binder.Result(".");
Assert.Equal("Name1", m0.TargetName);
Assert.Equal("System.Management.Automation.PSCustomObject", m0.TargetType);
}
@ -93,13 +92,13 @@ namespace PSRule
));
option.UseScope("Module1");
builder.With(new TargetBindingContext("Module1", option.GetTargetBinding()));
builder.With("Module1", option.GetTargetBinding());
option.UseScope("Module2");
builder.With(new TargetBindingContext("Module2", option.GetTargetBinding()));
builder.With("Module2", option.GetTargetBinding());
option.UseScope(null);
builder.With(new TargetBindingContext(".", option.GetTargetBinding()));
builder.With(".", option.GetTargetBinding());
return builder.Build();
}