Skip to content

Commit

Permalink
Add source location of pipeline objects #729 (#730)
Browse files Browse the repository at this point in the history
  • Loading branch information
BernieWhite committed May 27, 2021
1 parent 7215960 commit 89e2eb2
Show file tree
Hide file tree
Showing 8 changed files with 116 additions and 5 deletions.
5 changes: 3 additions & 2 deletions docs/CHANGELOG-v1.md
Expand Up @@ -22,8 +22,9 @@ What's changed since pre-release v1.4.0-B2105019:
What's changed since pre-release v1.4.0-B2105004:

- General improvements:
- Source location of object from input files are included in rule records. [#624](https://github.com/microsoft/PSRule/issues/624)
- Currently only JSON and YAML files support source locations.
- Source location of objects are included in results.
- Source location of objects from JSON and YAML input files are read automatically. [#624](https://github.com/microsoft/PSRule/issues/624)
- Source location of objects from the pipeline are read from properties. [#729](https://github.com/microsoft/PSRule/issues/729)
- Improved support for version constraints by:
- Constraints can include prerelease versions of other matching versions. [#714](https://github.com/microsoft/PSRule/issues/714)
- Constraints support using a `@prerelease` or `@pre` to include prerelease versions. [#717](https://github.com/microsoft/PSRule/issues/717)
Expand Down
30 changes: 30 additions & 0 deletions src/PSRule/Common/PSObjectExtensions.cs
Expand Up @@ -14,6 +14,8 @@ namespace PSRule
{
internal static class PSObjectExtensions
{
private const string PROPERTY_SOURCE = "source";

public static T PropertyValue<T>(this PSObject o, string propertyName)
{
if (o.BaseObject is Hashtable hashtable)
Expand Down Expand Up @@ -53,6 +55,18 @@ public static bool HasNoteProperty(this PSObject o)
return false;
}

public static bool TryProperty<T>(this PSObject o, string name, out T value)
{
value = default;
var pValue = ConvertValue<T>(o.Properties[name]?.Value);
if (pValue is T tValue)
{
value = tValue;
return true;
}
return false;
}

public static string ToJson(this PSObject o)
{
var settings = new JsonSerializerSettings { Formatting = Formatting.None, TypeNameHandling = TypeNameHandling.None, MaxDepth = 1024, Culture = CultureInfo.InvariantCulture };
Expand Down Expand Up @@ -93,6 +107,22 @@ public static TargetSourceInfo[] GetSourceInfo(this PSObject o)
return targetInfo.Source.ToArray();
}

public static void ConvertTargetInfoProperty(this PSObject o)
{
if (o == null || !TryProperty(o, PSRuleTargetInfo.PropertyName, out PSObject value))
return;

UseTargetInfo(o, out PSRuleTargetInfo targetInfo);
if (TryProperty(value, PROPERTY_SOURCE, out Array sources))
{
for (var i = 0; i < sources.Length; i++)
{
var source = TargetSourceInfo.Create(sources.GetValue(i));
targetInfo.WithSource(source);
}
}
}

private static T ConvertValue<T>(object value)
{
if (value == null)
Expand Down
34 changes: 31 additions & 3 deletions src/PSRule/Data/TargetSourceInfo.cs
Expand Up @@ -4,11 +4,16 @@
using Newtonsoft.Json;
using System;
using System.IO;
using System.Management.Automation;

namespace PSRule.Data
{
public sealed class TargetSourceInfo
{
private const string PROPERTY_FILE = "file";
private const string PROPERTY_LINE = "line";
private const string PROPERTY_POSITION = "position";

public TargetSourceInfo()
{
// Do nothing
Expand All @@ -29,13 +34,13 @@ internal TargetSourceInfo(Uri uri)
File = uri.AbsoluteUri;
}

[JsonProperty(PropertyName = "file")]
[JsonProperty(PropertyName = PROPERTY_FILE)]
public string File { get; internal set; }

[JsonProperty(PropertyName = "line")]
[JsonProperty(PropertyName = PROPERTY_LINE)]
public int? Line { get; internal set; }

[JsonProperty(PropertyName = "position")]
[JsonProperty(PropertyName = PROPERTY_POSITION)]
public int? Position { get; internal set; }

public bool Equals(TargetSourceInfo other)
Expand All @@ -57,5 +62,28 @@ public override int GetHashCode()
return hash;
}
}

public static TargetSourceInfo Create(object o)
{
if (o is PSObject pso)
return Create(pso);

return null;
}

public static TargetSourceInfo Create(PSObject o)
{
var result = new TargetSourceInfo();
if (o.TryProperty(PROPERTY_FILE, out string file))
result.File = file;

if (o.TryProperty(PROPERTY_LINE, out int line))
result.Line = line;

if (o.TryProperty(PROPERTY_POSITION, out int position))
result.Position = position;

return result;
}
}
}
4 changes: 4 additions & 0 deletions src/PSRule/Pipeline/PipelineReader.cs
Expand Up @@ -32,6 +32,7 @@ public void Enqueue(PSObject sourceObject, bool skipExpansion = false)

if (_Input == null || skipExpansion)
{
sourceObject.ConvertTargetInfoProperty();
_Queue.Enqueue(sourceObject);
return;
}
Expand All @@ -42,7 +43,10 @@ public void Enqueue(PSObject sourceObject, bool skipExpansion = false)
return;

foreach (var item in input)
{
sourceObject.ConvertTargetInfoProperty();
_Queue.Enqueue(item);
}
}

public bool TryDequeue(out PSObject sourceObject)
Expand Down
3 changes: 3 additions & 0 deletions src/PSRule/Runtime/PSRuleMemberInfo.cs
Expand Up @@ -70,6 +70,9 @@ internal void Combine(PSRuleTargetInfo targetInfo)

internal void WithSource(TargetSourceInfo source)
{
if (source == null)
return;

if (Source.Count == 0)
{
Source.Add(source);
Expand Down
13 changes: 13 additions & 0 deletions tests/PSRule.Tests/PSRule.Common.Tests.ps1
Expand Up @@ -36,6 +36,15 @@ Describe 'Invoke-PSRule' -Tag 'Invoke-PSRule','Common' {
$testObject = [PSCustomObject]@{
Name = 'TestObject1'
Value = 1
'_PSRule' = [PSCustomObject]@{
source = @(
[PSCustomObject]@{
file = 'source.json'
Line = 100
Position = 1000
}
)
}
}
$testObject.PSObject.TypeNames.Insert(0, 'TestType');

Expand All @@ -48,6 +57,10 @@ Describe 'Invoke-PSRule' -Tag 'Invoke-PSRule','Common' {
$result.TargetName | Should -Be 'TestObject1';
$result.Info.Annotations.culture | Should -Be 'en-ZZ';
$result.Recommendation | Should -Be 'This is a recommendation.';
$result.Source | Should -Not -BeNullOrEmpty;
$result.Source[0].File | Should -Be 'source.json';
$result.Source[0].Line | Should -Be 100;
$result.Source[0].Position | Should -Be 1000;
Assert-VerifiableMock;
}
finally {
Expand Down
1 change: 1 addition & 0 deletions tests/PSRule.Tests/PipelineTests.cs
Expand Up @@ -95,6 +95,7 @@ public void PipelineWithSource()
var builder = PipelineBuilder.Invoke(GetSource(), option, null, null);
builder.InputPath(new string[] { "./**/ObjectFromFile.json" });
var pipeline = builder.Build();
Assert.NotNull(pipeline);
pipeline.Begin();
pipeline.End();
}
Expand Down
31 changes: 31 additions & 0 deletions tests/PSRule.Tests/SourceTests.cs
@@ -0,0 +1,31 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Management.Automation;
using Xunit;

namespace PSRule
{
public sealed class SourceTests
{
[Fact]
public void TargetSourceInfo()
{
var source = new PSObject();
source.Properties.Add(new PSNoteProperty("file", "file.json"));
source.Properties.Add(new PSNoteProperty("line", 100));
source.Properties.Add(new PSNoteProperty("position", 1000));
var info = new PSObject();
info.Properties.Add(new PSNoteProperty("source", new PSObject[] { source }));
var o = new PSObject();
o.Properties.Add(new PSNoteProperty("_PSRule", info));
o.ConvertTargetInfoProperty();

var actual = o.GetSourceInfo();
Assert.NotNull(actual);
Assert.Equal("file.json", actual[0].File);
Assert.Equal(100, actual[0].Line);
Assert.Equal(1000, actual[0].Position);
}
}
}

0 comments on commit 89e2eb2

Please sign in to comment.