Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions src/StructuredLogViewer/Controls/TextViewerControl.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
using ICSharpCode.AvalonEdit.Highlighting;
using ICSharpCode.AvalonEdit.Highlighting.Xshd;
using ICSharpCode.AvalonEdit.Search;
using Microsoft.Build.Logging.StructuredLogger;
using Microsoft.Win32;

namespace StructuredLogViewer.Controls
Expand Down Expand Up @@ -159,6 +160,12 @@ public void SetText(string text)
return;
}

if (TryParseCondition(text, out string newText))
{
textEditor.Text = newText;
return;
}

bool looksLikeXml = Utilities.LooksLikeXml(text);
if (looksLikeXml && !IsXml)
{
Expand Down Expand Up @@ -216,6 +223,68 @@ void SetColor(string name, string hex)
}
}

private bool TryParseCondition(string text, out string newText)
{
Match matches = Strings.TargetSkippedFalseConditionRegex.Match(text);
if (!matches.Success)
{
matches = Strings.TaskSkippedFalseConditionRegex.Match(text);
}

if (matches.Success)
{
string unevaluated = matches.Groups[2].Value;
string evaluated = matches.Groups[3].Value;

try
{
var nodeResult = ConditionNode.ParseAndProcess(unevaluated, evaluated);
StringBuilder sb = new StringBuilder();
sb.AppendLine(text);

bool firstPrint = true;

Action<ConditionNode> nodeFormat = null;

nodeFormat = (ConditionNode node) =>
{
if (node.Result)
{
// sb.Append('\u2714'); // check mark
return;
}

if (!string.IsNullOrEmpty(node.Text))
{
if (firstPrint)
{
sb.AppendLine();
sb.AppendLine("Condition Analyzer:");
firstPrint = false;
}

sb.Append("\u274C "); // X marker
sb.AppendLine(node.Text);
}

foreach (var child in node.Children)
{
nodeFormat(child);
}
};

nodeFormat(nodeResult);

newText = sb.ToString();
return true;
}
catch { }
}

newText = null;
return false;
}

public void SetPathDisplay(bool displayPath)
{
var visibility = displayPath ? Visibility.Visible : Visibility.Collapsed;
Expand Down
221 changes: 221 additions & 0 deletions src/StructuredLogger.Tests/ConditionParserTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
using System.Linq;
using System.Threading;
using StructuredLogViewer;
using Xunit;

namespace StructuredLogger.Tests
{
public class ConditionParserTests
{
[Fact]
public void Empty_Test()
{
ParseAndAssert(@"", 1, evaluate: false);
ParseAndAssert(@"( )", 2, evaluate: false);
ParseAndAssert(@"(() )", 3, evaluate: false);
}

[Fact]
public void EvaluatedNotEqual()
{
ParseAndAssert(@"'statement2' != 'statement2'", 2, expectedResult: false);
}

[Fact]
public void EvaluatedEqual()
{
ParseAndAssert(@"'statement2' == 'statement2'", 2, expectedResult: true);
ParseAndAssert(@"'statement2' == ''", 2, expectedResult: false);
ParseAndAssert(@"'' == 'statement2'", 2, expectedResult: false);
}

[Fact]
public void EvaluatedAnd()
{
ParseAndAssert(@"( 'statement1' != '' And 'statement2' != 'statement2' )", 4, expectedResult: false);
}

[Fact]
public void EvaluatedOr()
{
ParseAndAssert(@"( 'statement1' != '' Or 'statement2' != 'statement2' )", 4, expectedResult: true);
}

[Fact]
public void EvaluatedNestedStatements()
{
string evaluated = @"('statement1' != '' And ('statement2' != 'statement2' or 'statement3' != 'statement3') And 'statement4' != '')";

var node = ConditionNode.Parse(evaluated, true);
Assert.Equal(7, node.Count());
Assert.False(node.Result);
Assert.Equal(2, node.Max(p => p.Level));

string evaluatedTrue = @"('statement1' != '' And ('statement2' == 'statement2' or 'statement3' != 'statement3') And 'statement4' != '')";

var node2 = ConditionNode.Parse(evaluatedTrue, true);
Assert.Equal(7, node2.Count());
Assert.True(node2.Result);
Assert.Equal(2, node2.Max(p => p.Level));
}

[Fact]
public void Properties()
{
string unevaluated = @"('$(Property1)' != '' And '$(Property3)' != '$(Property3)' )";
string evaluated = @"( 'statement1' != '' And 'statement2' != 'statement2' )";

var node = ConditionNode.ParseAndProcess(unevaluated, evaluated);
Assert.Equal(4, node.Count());
Assert.False(node.Result);
}

[Fact]
public void Items()
{
string unevaluated = @"('@(Item1)' != '' And '@(Item2)' != '@(Item2)' )";
string evaluated = @"( 'statement1' != '' And 'statement2' != 'statement2' )";

var node = ConditionNode.ParseAndProcess(unevaluated, evaluated);
Assert.Equal(4, node.Count());
Assert.False(node.Result);
}

[Fact]
public void ItemMetadata()
{
string unevaluated = @"('%(Item.Data1)' != '' And '%(Item.Data2)' != '%(Item.Data2)' )";
string evaluated = @"( 'statement1' != '' And 'statement2' != 'statement2' )";

var node = ConditionNode.ParseAndProcess(unevaluated, evaluated);
Assert.Equal(4, node.Count());
Assert.False(node.Result);
}

[Fact]
public void ItemTransformation()
{
string unevaluated = @"'%(Filename)%(Extension)' != '@(Items->'%(Filename)%(Extension)')'";
string evaluated = @"'file.cs' != 'file.cs'";

var node = ConditionNode.ParseAndProcess(unevaluated, evaluated);
Assert.Equal(2, node.Count());
Assert.False(node.Result);
}

[Fact]
public void ConjunctionAnds()
{
string unevaluated = @" '$(EnableBaseIntermediateOutputPathMismatchWarning)' == 'true' And '$(_InitialBaseIntermediateOutputPath)' != '$(BaseIntermediateOutputPath)' And '$(BaseIntermediateOutputPath)' != '$(MSBuildProjectExtensionsPath)' ";
string evaluatedFalse = @"'' == 'true' And '' != 'obj\' And 'obj\' != 'project\obj\'";

var node = ConditionNode.ParseAndProcess(unevaluated, evaluatedFalse);
Assert.Equal(4, node.Count());
Assert.False(node.Result);

string evaluatedTrue = @"'true' == 'true' And '' != 'obj\' And '' != 'project\obj\'";

var node2 = ConditionNode.ParseAndProcess(unevaluated, evaluatedTrue);
Assert.Equal(4, node2.Count());
Assert.True(node2.Result);
}

[Fact]
public void ConjunctionOrs()
{
string unevaluated = @" '$(EnableBaseIntermediateOutputPathMismatchWarning)' == 'true' Or '$(_InitialBaseIntermediateOutputPath)' != '$(BaseIntermediateOutputPath)' Or '$(BaseIntermediateOutputPath)' != '$(MSBuildProjectExtensionsPath)' ";
string evaluatedTrue = @"'' == 'true' Or 'obj\' != 'obj\' Or 'obj\' != 'project\obj\'";

var node = ConditionNode.ParseAndProcess(unevaluated, evaluatedTrue);
Assert.Equal(4, node.Count());
Assert.True(node.Result);

string evaluatedFalse = @"'' == 'true' Or 'obj\' != 'obj\' Or 'project\obj\' != 'project\obj\'";

var node2 = ConditionNode.ParseAndProcess(unevaluated, evaluatedFalse);
Assert.Equal(4, node2.Count());
Assert.False(node2.Result);
}

[Fact]
public void ExistsNot()
{
var node = ParseAndAssert(@"!Exists($(File))", 2, evaluate: false);
Assert.Equal("!Exists($(File))", node.Children[0].Text);
}

[Fact]
public void Exists()
{
var node = ParseAndAssert(@"Exists($(File))", 2, evaluate: false);
Assert.Equal("Exists($(File))", node.Children[0].Text);
}

[Fact]
public void NoQuotesProperty()
{
var node = ParseAndAssert(@"$(file) == ''", 2, evaluate: false);
Assert.Equal("$(file)==''", node.Children[0].Text);
}

[Fact]
public void NumericCompareDouble()
{
ParseAndAssert(@"'123.456' < '567.123'", 2, expectedResult: true);
ParseAndAssert(@"'123.456' <= '567.123'", 2, expectedResult: true);
ParseAndAssert(@"'123.456' > '567.123'", 2, expectedResult: false);
ParseAndAssert(@"'123.456' >= '567.123'", 2, expectedResult: false);
}

[Fact]
public void NumericCompareVersion()
{
ParseAndAssert(@"'123.456.789' < '567.123.456'", 2, expectedResult: true);
ParseAndAssert(@"'123.456.789' <= '567.123.456'", 2, expectedResult: true);
ParseAndAssert(@"'123.456.789' > '567.123.456'", 2, expectedResult: false);
ParseAndAssert(@"'123.456.789' >= '567.123.456'", 2, expectedResult: false);
}

[Fact]
public void Boolean()
{
// test with whitespace
var node = ParseAndAssert(@" false ", 2, expectedResult: false);
Assert.Equal("false", node.Children[0].Text);

var node2 = ParseAndAssert(@"true", 2, expectedResult: true);
Assert.Equal("true", node2.Children[0].Text);

var node3 = ParseAndAssert(@"!false", 2, expectedResult: true);
Assert.Equal("!false", node3.Children[0].Text);
}

private static ConditionNode ParseAndAssert(string text, int expectedCount, bool evaluate = true, bool expectedResult = true)
{
var node = ConditionNode.Parse(text, evaluate);

if (expectedCount == 1)
{
Assert.Single(node);
}
else
{
Assert.Equal(expectedCount, node.Count());
}

if (evaluate)
{
if (expectedResult)
{
Assert.True(node.Result);
}
else
{
Assert.False(node.Result);
}
}

return node;
}
}
}
24 changes: 12 additions & 12 deletions src/StructuredLogger.Tests/ItemGroupParserTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,11 +99,11 @@ public void ParseThereWasAConflict()
[Fact]
public void ParseThereWasAConflictMultiline()
{
var message = @" References which depend on ""System.IO.Compression.FileSystem, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"" [C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\3.1.0\ref\netcoreapp3.1\System.IO.Compression.FileSystem.dll].
C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\3.1.0\ref\netcoreapp3.1\System.IO.Compression.FileSystem.dll
Project file item includes which caused reference ""C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\3.1.0\ref\netcoreapp3.1\System.IO.Compression.FileSystem.dll"".
C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\3.1.0\ref\netcoreapp3.1\System.IO.Compression.FileSystem.dll
References which depend on ""System.IO.Compression.FileSystem"" [].
var message = @" References which depend on ""System.IO.Compression.FileSystem, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"" [C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\3.1.0\ref\netcoreapp3.1\System.IO.Compression.FileSystem.dll].
C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\3.1.0\ref\netcoreapp3.1\System.IO.Compression.FileSystem.dll
Project file item includes which caused reference ""C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\3.1.0\ref\netcoreapp3.1\System.IO.Compression.FileSystem.dll"".
C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\3.1.0\ref\netcoreapp3.1\System.IO.Compression.FileSystem.dll
References which depend on ""System.IO.Compression.FileSystem"" [].
Unresolved primary reference with an item include of ""System.IO.Compression.FileSystem"".".NormalizeLineBreaks();
var stringCache = new StringCache();
var parameter = new Parameter();
Expand All @@ -123,19 +123,19 @@ Unresolved primary reference with an item include of ""System.IO.Compression.Fil
[Fact]
public void ParseMultilineMetadata()
{
var parameter = ItemGroupParser.ParsePropertyOrItemList(@"Added Item(s):
_ProjectsFiles=
Project1
var parameter = ItemGroupParser.ParsePropertyOrItemList(@"Added Item(s):
_ProjectsFiles=
Project1
AdditionalProperties=
AutoParameterizationWebConfigConnectionStrings=false;
_PackageTempDir=Out\Dir;

Project2
Project2
AdditionalProperties=
AutoParameterizationWebConfigConnectionStrings=false;
_PackageTempDir=Out\Dir;

Project3
Project3
AdditionalProperties=
AutoParameterizationWebConfigConnectionStrings=false;
_PackageTempDir=Out\Dir;
Expand Down
Loading