Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
58 changes: 58 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,63 @@ public void SetText(string text)
return;
}

Match matches;

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this logic perhaps go into BuildControl.xaml.cs -> DisplayText() on line 2371?

Also maybe extract method so that this logic is a separate method.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the other hand perhaps keep it here, maybe in the future we can do smart highlighting to highlight substrings using the textEditor

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

still, I'd extract this logic to a separate method

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved to a separate function. I wanted to add Outlining to help visualize the position, but it might be too visually busy. Maybe in the future, it could have toggle button.

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);
sb.AppendLine();

Action<ConditionNode> nodeFormat = null;

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

if (!string.IsNullOrEmpty(node.Text))
{
sb.Append('\u274C'); // X marker

for (int i = 0; i < node.Level; i++)
{
sb.Append('\t');
}

sb.AppendLine(node.Text);
}

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

nodeFormat(nodeResult);

textEditor.Text = sb.ToString();
}
catch { }

// Show why this task/target skipped.
return;
}

bool looksLikeXml = Utilities.LooksLikeXml(text);
if (looksLikeXml && !IsXml)
{
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