diff --git a/.appveyor.yml b/.appveyor.yml index 1dbceb116..c180aecce 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -6,6 +6,10 @@ image: Visual Studio 2017 #---------------------------------# # Build Script # #---------------------------------# +install: + # Update to latest NuGet version since we require 5.3.0 for embedded icon + - ps: nuget update -self + build_script: - ps: .\build.ps1 -Target AppVeyor @@ -27,4 +31,4 @@ branches: # Build Cache # #---------------------------------# cache: -- tools -> setup.cake +- tools -> recipe.cake diff --git a/3rd-Party-License.md b/3rd-Party-License.md new file mode 100644 index 000000000..9487ddc3d --- /dev/null +++ b/3rd-Party-License.md @@ -0,0 +1,10 @@ +# License overview of included 3rd party code + +Cake.Issues is licensed under the terms of the [MIT License](LICENSE). + +Cake.Issues includes third-party code which is licensed under its own respective license. + +## LitJSON + +License: Unlicense +https://github.com/LitJSON/litjson/blob/develop/COPYING diff --git a/README.md b/README.md index 8417d5d26..a8574d888 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ This addin for the Cake build automation system allows you to read issues from any code analyzer or linter. For more information about this addin see the [Cake.Issues website](https://cakeissues.net) -and for general information about the Cake build automation system see the [Cake website](http://cakebuild.net) +and for general information about the Cake build automation system see the [Cake website](http://cakebuild.net). [![License](http://img.shields.io/:license-mit-blue.svg)](https://github.com/cake-contrib/Cake.Issues/blob/develop/LICENSE) @@ -31,7 +31,7 @@ and for general information about the Cake build automation system see the [Cake ## Chat Room -Come join in the conversation about this addin in our Gitter Chat Room +Come join in the conversation about this addin in our Gitter Chat Room. [![Join the chat at https://gitter.im/cake-contrib/Lobby](https://badges.gitter.im/cake-contrib/Lobby.svg)](https://gitter.im/cake-contrib/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 8f395df29..d7198be65 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -6,6 +6,8 @@ trigger: pr: - develop +- release/* +- hotfix/* jobs: - job: Windows diff --git a/build.ps1 b/build.ps1 index bdfb32b4f..a6d30c4e8 100644 --- a/build.ps1 +++ b/build.ps1 @@ -35,7 +35,7 @@ http://cakebuild.net [CmdletBinding()] Param( - [string]$Script = "setup.cake", + [string]$Script = "recipe.cake", [string]$Target = "Default", [ValidateSet("Release", "Debug")] [string]$Configuration = "Release", @@ -181,4 +181,4 @@ if (!(Test-Path $CAKE_EXE)) { # Start Cake Write-Host "Running build script..." Invoke-Expression "& `"$CAKE_EXE`" `"$Script`" -target=`"$Target`" -configuration=`"$Configuration`" -verbosity=`"$Verbosity`" $UseMono $UseDryRun $UseExperimental $ScriptArgs" -exit $LASTEXITCODE \ No newline at end of file +exit $LASTEXITCODE diff --git a/build.sh b/build.sh old mode 100644 new mode 100755 index a541ec165..d35bcf4ef --- a/build.sh +++ b/build.sh @@ -11,7 +11,7 @@ NUGET_EXE=$TOOLS_DIR/nuget.exe CAKE_EXE=$TOOLS_DIR/Cake/Cake.exe # Define default arguments. -SCRIPT="setup.cake" +SCRIPT="recipe.cake" TARGET="Default" CONFIGURATION="Release" VERBOSITY="verbose" diff --git a/nuspec/nuget/Cake.Issues.Testing.nuspec b/nuspec/nuget/Cake.Issues.Testing.nuspec index 2b702c31f..c536efe0e 100644 --- a/nuspec/nuget/Cake.Issues.Testing.nuspec +++ b/nuspec/nuget/Cake.Issues.Testing.nuspec @@ -12,14 +12,15 @@ Common helpers for testing add-ins based on Cake.Issues MIT https://cakeissues.net - https://cdn.jsdelivr.net/gh/cake-contrib/graphics@a5cf0f881c390650144b2243ae551d5b9f836196/png/cake-contrib-medium.png + icon.png false Copyright © BBT Software AG and contributors Cake Script Cake-Issues Issues Testing - https://github.com/cake-contrib/Cake.Issues/releases/tag/0.7.1 + https://github.com/cake-contrib/Cake.Issues/releases/tag/0.8.0 + diff --git a/nuspec/nuget/Cake.Issues.nuspec b/nuspec/nuget/Cake.Issues.nuspec index 1a698374e..a8e8c08e6 100644 --- a/nuspec/nuget/Cake.Issues.nuspec +++ b/nuspec/nuget/Cake.Issues.nuspec @@ -19,14 +19,15 @@ See the Project Site for an overview of the whole ecosystem of addins for workin MIT https://cakeissues.net - https://cdn.jsdelivr.net/gh/cake-contrib/graphics@a5cf0f881c390650144b2243ae551d5b9f836196/png/cake-contrib-medium.png + icon.png false Copyright © BBT Software AG and contributors Cake Script Cake-Issues CodeAnalysis Linting Issues - https://github.com/cake-contrib/Cake.Issues/releases/tag/0.7.1 + https://github.com/cake-contrib/Cake.Issues/releases/tag/0.8.0 + diff --git a/nuspec/nuget/icon.png b/nuspec/nuget/icon.png new file mode 100644 index 000000000..9881edc4e Binary files /dev/null and b/nuspec/nuget/icon.png differ diff --git a/setup.cake b/recipe.cake similarity index 58% rename from setup.cake rename to recipe.cake index c22d5dd85..5836671e3 100644 --- a/setup.cake +++ b/recipe.cake @@ -3,21 +3,27 @@ Environment.SetVariableNames(); BuildParameters.SetParameters( - context: Context, + context: Context, buildSystem: BuildSystem, sourceDirectoryPath: "./src", title: "Cake.Issues", repositoryOwner: "cake-contrib", repositoryName: "Cake.Issues", appVeyorAccountName: "cakecontrib", - shouldRunCodecov: false); + shouldRunCodecov: false, + shouldRunGitVersion: true); BuildParameters.PrintParameters(Context); ToolSettings.SetToolSettings( context: Context, - dupFinderExcludePattern: new string[] { BuildParameters.RootDirectoryPath + "/src/Cake.Issues.Tests/*.cs", BuildParameters.RootDirectoryPath + "/src/Cake.Issues*/**/*.AssemblyInfo.cs" }, - testCoverageFilter: "+[*]* -[xunit.*]* -[Cake.Core]* -[Cake.Testing]* -[*.Tests]* -[Shouldly]*", + dupFinderExcludePattern: new string[] + { + BuildParameters.RootDirectoryPath + "/src/Cake.Issues*/**/*.AssemblyInfo.cs", + BuildParameters.RootDirectoryPath + "/src/Cake.Issues*/Serialization/LitJson/*.cs", + BuildParameters.RootDirectoryPath + "/src/Cake.Issues.Tests/**/*.cs" + }, + testCoverageFilter: "+[*]* -[xunit.*]* -[Cake.Core]* -[Cake.Testing]* -[*.Tests]* -[Cake.Issues]LitJson.* -[Shouldly]*", testCoverageExcludeByAttribute: "*.ExcludeFromCodeCoverage*", testCoverageExcludeByFile: "*/*Designer.cs;*/*.g.cs;*/*.g.i.cs"); diff --git a/src/Cake.Issues.Testing/BaseIssueProviderFixture.cs b/src/Cake.Issues.Testing/BaseIssueProviderFixture.cs index 8016a45d2..c71f7f198 100644 --- a/src/Cake.Issues.Testing/BaseIssueProviderFixture.cs +++ b/src/Cake.Issues.Testing/BaseIssueProviderFixture.cs @@ -33,13 +33,13 @@ protected BaseIssueProviderFixture() public RepositorySettings RepositorySettings { get; set; } /// - /// Calls . + /// Calls . /// /// Issues returned from issue provider. public IEnumerable ReadIssues() { var issueProvider = this.CreateIssueProvider(); - return issueProvider.ReadIssues(IssueCommentFormat.PlainText); + return issueProvider.ReadIssues(); } /// diff --git a/src/Cake.Issues.Testing/Cake.Issues.Testing.csproj b/src/Cake.Issues.Testing/Cake.Issues.Testing.csproj index d628f20db..e91a7e15a 100644 --- a/src/Cake.Issues.Testing/Cake.Issues.Testing.csproj +++ b/src/Cake.Issues.Testing/Cake.Issues.Testing.csproj @@ -26,7 +26,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Cake.Issues.Testing/FakeConfigurableIssueProvider.cs b/src/Cake.Issues.Testing/FakeConfigurableIssueProvider.cs index 3bff27240..5a7c9f061 100644 --- a/src/Cake.Issues.Testing/FakeConfigurableIssueProvider.cs +++ b/src/Cake.Issues.Testing/FakeConfigurableIssueProvider.cs @@ -55,18 +55,12 @@ public FakeConfigurableIssueProvider( /// public new IssueProviderSettings IssueProviderSettings => base.IssueProviderSettings; - /// - /// Gets the format in which issues should be returned. - /// - public IssueCommentFormat Format { get; private set; } - /// public override string ProviderName => "Fake Issue Provider"; /// - protected override IEnumerable InternalReadIssues(IssueCommentFormat format) + protected override IEnumerable InternalReadIssues() { - this.Format = format; return this.issues; } } diff --git a/src/Cake.Issues.Testing/FakeIssueProvider.cs b/src/Cake.Issues.Testing/FakeIssueProvider.cs index 7d84a558f..8dcef5169 100644 --- a/src/Cake.Issues.Testing/FakeIssueProvider.cs +++ b/src/Cake.Issues.Testing/FakeIssueProvider.cs @@ -44,18 +44,12 @@ public FakeIssueProvider(ICakeLog log, IEnumerable issues) /// public new RepositorySettings Settings => base.Settings; - /// - /// Gets the format in which issues should be returned. - /// - public IssueCommentFormat Format { get; private set; } - /// public override string ProviderName => "Fake Issue Provider"; /// - protected override IEnumerable InternalReadIssues(IssueCommentFormat format) + protected override IEnumerable InternalReadIssues() { - this.Format = format; return this.issues; } } diff --git a/src/Cake.Issues.Testing/FakeLogFileFormat.cs b/src/Cake.Issues.Testing/FakeLogFileFormat.cs index 4245b5c1e..8e39bfd73 100644 --- a/src/Cake.Issues.Testing/FakeLogFileFormat.cs +++ b/src/Cake.Issues.Testing/FakeLogFileFormat.cs @@ -42,7 +42,6 @@ public FakeLogFileFormat(ICakeLog log, IEnumerable issues) /// public override IEnumerable ReadIssues( FakeMultiFormatIssueProvider issueProvider, - IssueCommentFormat format, RepositorySettings repositorySettings, FakeMultiFormatIssueProviderSettings issueProviderSettings) { diff --git a/src/Cake.Issues.Testing/IssueChecker.cs b/src/Cake.Issues.Testing/IssueChecker.cs index a784d8970..d68a51799 100644 --- a/src/Cake.Issues.Testing/IssueChecker.cs +++ b/src/Cake.Issues.Testing/IssueChecker.cs @@ -45,7 +45,9 @@ public static void Check( expectedIssue.ProjectName, expectedIssue.AffectedFileRelativePath?.ToString(), expectedIssue.Line, - expectedIssue.Message, + expectedIssue.MessageText, + expectedIssue.MessageHtml, + expectedIssue.MessageMarkdown, expectedIssue.Priority, expectedIssue.PriorityName, expectedIssue.Rule, @@ -66,7 +68,9 @@ public static void Check( /// null if the issue is not expected to be related to a change in a file. /// Expected line number. /// null if the issue is not expected to be related to a file or specific line. - /// Expected message. + /// Expected message in plain text format. + /// Expected message in HTML format. + /// Expected message in Markdown format. /// Expected priority. /// null if no priority is expected. /// Expected priority name. @@ -83,7 +87,9 @@ public static void Check( string projectName, string affectedFileRelativePath, int? line, - string message, + string messageText, + string messageHtml, + string messageMarkdown, int? priority, string priorityName, string rule, @@ -161,10 +167,22 @@ public static void Check( $"Expected issue.Line to be '{line}' but was '{issue.Line}'."); } - if (issue.Message != message) + if (issue.MessageText != messageText) { throw new Exception( - $"Expected issue.Message to be '{message}' but was '{issue.Message}'."); + $"Expected issue.MessageText to be '{messageText}' but was '{issue.MessageText}'."); + } + + if (issue.MessageHtml != messageHtml) + { + throw new Exception( + $"Expected issue.MessageHtml to be '{messageHtml}' but was '{issue.MessageHtml}'."); + } + + if (issue.MessageMarkdown != messageMarkdown) + { + throw new Exception( + $"Expected issue.MessageMarkdown to be '{messageMarkdown}' but was '{issue.MessageMarkdown}'."); } if (issue.Priority != priority) diff --git a/src/Cake.Issues.Tests/BaseIssueProviderTests.cs b/src/Cake.Issues.Tests/BaseIssueProviderTests.cs index 05e9d0735..f943a5aa0 100644 --- a/src/Cake.Issues.Tests/BaseIssueProviderTests.cs +++ b/src/Cake.Issues.Tests/BaseIssueProviderTests.cs @@ -42,7 +42,7 @@ public void Should_Throw_If_Settings_Is_Null() var provider = new FakeIssueProvider(new FakeLog()); // When - var result = Record.Exception(() => provider.ReadIssues(IssueCommentFormat.PlainText)); + var result = Record.Exception(() => provider.ReadIssues()); // Then result.IsInvalidOperationException("Initialize needs to be called first."); diff --git a/src/Cake.Issues.Tests/BaseMultiFormatIssueProviderTests.cs b/src/Cake.Issues.Tests/BaseMultiFormatIssueProviderTests.cs index 114c6fec0..f446e83d8 100644 --- a/src/Cake.Issues.Tests/BaseMultiFormatIssueProviderTests.cs +++ b/src/Cake.Issues.Tests/BaseMultiFormatIssueProviderTests.cs @@ -113,7 +113,7 @@ public void Should_Read_Issues_From_Format() provider.Initialize(new RepositorySettings(@"c:\repo")); // When - var result = provider.ReadIssues(IssueCommentFormat.PlainText); + var result = provider.ReadIssues(); // Then result.Count().ShouldBe(2); diff --git a/src/Cake.Issues.Tests/Cake.Issues.Tests.csproj b/src/Cake.Issues.Tests/Cake.Issues.Tests.csproj index e373cbaa5..63f8064b5 100644 --- a/src/Cake.Issues.Tests/Cake.Issues.Tests.csproj +++ b/src/Cake.Issues.Tests/Cake.Issues.Tests.csproj @@ -1,7 +1,7 @@  - netcoreapp2.0 + netcoreapp2.1 false Cake.Issues Copyright © BBT Software AG and contributors @@ -25,7 +25,7 @@ - + @@ -43,6 +43,12 @@ + + Always + + + Always + Always diff --git a/src/Cake.Issues.Tests/IIssueComparerTests.cs b/src/Cake.Issues.Tests/IIssueComparerTests.cs new file mode 100644 index 000000000..169003a5a --- /dev/null +++ b/src/Cake.Issues.Tests/IIssueComparerTests.cs @@ -0,0 +1,1425 @@ +namespace Cake.Issues.Tests +{ + using System; + using System.Collections.Generic; + using System.Linq; + using Shouldly; + using Xunit; + + public sealed class IIssueComparerTests + { + public sealed class TheCtorWithCompareOnlyPersistentPropertiesSetToFalse + { + [Fact] + public void Should_Return_False_If_First_Issue_Is_Null() + { + // Given + IIssue issue1 = null; + var issue2 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .Create(); + + // When / Then + CompareIssues(issue1, issue2, false); + } + + [Fact] + public void Should_Return_False_If_Second_Issue_Is_Null() + { + // Given + var issue1 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .Create(); + IIssue issue2 = null; + + // When / Then + CompareIssues(issue1, issue2, false); + } + + [Theory] + [InlineData("foo", "bar")] + [InlineData("foo", "Foo")] + [InlineData("foo", null)] + [InlineData(null, "foo")] + public void Should_Return_False_If_ProjectFileRelativePath_Is_Different(string path1, string path2) + { + // Given + var issue1 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .InProjectFile(path1) + .Create(); + var issue2 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .InProjectFile(path2) + .Create(); + + // When / Then + CompareIssues(issue1, issue2, false); + } + + [Theory] + [InlineData("foo", "bar")] + [InlineData("foo", "Foo")] + [InlineData("foo", null)] + [InlineData(null, "foo")] + public void Should_Return_False_If_ProjectName_Is_Different(string projectName1, string projectName2) + { + // Given + var issue1 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .InProjectOfName(projectName1) + .Create(); + var issue2 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .InProjectOfName(projectName2) + .Create(); + + // When / Then + CompareIssues(issue1, issue2, false); + } + + [Theory] + [InlineData("foo", "bar")] + [InlineData("foo", "Foo")] + [InlineData("foo", null)] + [InlineData(null, "foo")] + public void Should_Return_False_If_AffectedFileRelativePath_Is_Different(string path1, string path2) + { + // Given + var issue1 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .InFile(path1) + .Create(); + var issue2 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .InFile(path2) + .Create(); + + // When / Then + CompareIssues(issue1, issue2, false); + } + + [Theory] + [InlineData(1, 2)] + [InlineData(1, null)] + [InlineData(null, 1)] + [InlineData(int.MaxValue, 1)] + [InlineData(1, int.MaxValue)] + public void Should_Return_False_If_Line_Is_Different(int? line1, int? line2) + { + // Given + var issue1 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .InFile("foo", line1) + .Create(); + var issue2 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .InFile("foo", line2) + .Create(); + + // When / Then + CompareIssues(issue1, issue2, false); + } + + [Fact] + public void Should_Return_False_If_MessageText_Is_Different() + { + // Given + var issue1 = + IssueBuilder + .NewIssue("message1", "providerType", "providerName") + .Create(); + var issue2 = + IssueBuilder + .NewIssue("message2", "providerType", "providerName") + .Create(); + + // When / Then + CompareIssues(issue1, issue2, false); + } + + [Theory] + [InlineData("foo", "bar")] + [InlineData("foo", "Foo")] + [InlineData("foo", null)] + [InlineData(null, "foo")] + public void Should_Return_False_If_MessageHtml_Is_Different(string message1, string message2) + { + // Given + var issue1 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .WithMessageInHtmlFormat(message1) + .Create(); + var issue2 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .WithMessageInHtmlFormat(message2) + .Create(); + + // When / Then + CompareIssues(issue1, issue2, false); + } + + [Theory] + [InlineData("foo", "bar")] + [InlineData("foo", "Foo")] + [InlineData("foo", null)] + [InlineData(null, "foo")] + public void Should_Return_False_If_MessageMarkdown_Is_Different(string message1, string message2) + { + // Given + var issue1 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .WithMessageInMarkdownFormat(message1) + .Create(); + var issue2 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .WithMessageInMarkdownFormat(message2) + .Create(); + + // When / Then + CompareIssues(issue1, issue2, false); + } + + [Theory] + [InlineData(1, 2)] + [InlineData(1, null)] + [InlineData(null, 1)] + [InlineData(int.MinValue, 0)] + [InlineData(int.MaxValue, 0)] + public void Should_Return_False_If_Priority_Is_Different(int? priority1, int? priority2) + { + // Given + var issue1 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .WithPriority(priority1, "Foo") + .Create(); + var issue2 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .WithPriority(priority2, "Foo") + .Create(); + + // When / Then + CompareIssues(issue1, issue2, false); + } + + [Theory] + [InlineData("foo", "bar")] + [InlineData("foo", "Foo")] + [InlineData("foo", null)] + [InlineData(null, "foo")] + public void Should_Return_False_If_PriorityName_Is_Different(string priorityName1, string priorityName2) + { + // Given + var issue1 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .WithPriority(42, priorityName1) + .Create(); + var issue2 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .WithPriority(42, priorityName2) + .Create(); + + // When / Then + CompareIssues(issue1, issue2, false); + } + + [Theory] + [InlineData("foo", "bar")] + [InlineData("foo", "Foo")] + [InlineData("foo", null)] + [InlineData(null, "foo")] + public void Should_Return_False_If_Rule_Is_Different(string rule1, string rule2) + { + // Given + var issue1 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .OfRule(rule1) + .Create(); + var issue2 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .OfRule(rule2) + .Create(); + + // When / Then + CompareIssues(issue1, issue2, false); + } + + [Theory] + [InlineData("http://foo", "http://bar")] + [InlineData("http://foo", null)] + [InlineData(null, "http://foo")] + public void Should_Return_False_If_RuleUlr_Is_Different(string ruleUrl1, string ruleUrl2) + { + // Given + var issueBuilder = + IssueBuilder + .NewIssue("message", "providerType", "providerName"); + if (!string.IsNullOrEmpty(ruleUrl1)) + { + issueBuilder = + issueBuilder + .OfRule("foo", new Uri(ruleUrl1)); + } + + var issue1 = issueBuilder.Create(); + + issueBuilder = + IssueBuilder + .NewIssue("message", "providerType", "providerName"); + if (!string.IsNullOrEmpty(ruleUrl2)) + { + issueBuilder = + issueBuilder + .OfRule("foo", new Uri(ruleUrl2)); + } + + var issue2 = issueBuilder.Create(); + + // When / Then + CompareIssues(issue1, issue2, false); + } + + [Fact] + public void Should_Return_False_If_ProviderType_Is_Different() + { + // Given + var issue1 = + IssueBuilder + .NewIssue("message", "providerType1", "providerName") + .Create(); + var issue2 = + IssueBuilder + .NewIssue("message", "providerType2", "providerName") + .Create(); + + // When / Then + CompareIssues(issue1, issue2, false); + } + + [Fact] + public void Should_Return_False_If_ProviderName_Is_Different() + { + // Given + var issue1 = + IssueBuilder + .NewIssue("message", "providerType", "providerName1") + .Create(); + var issue2 = + IssueBuilder + .NewIssue("message", "providerType", "providerName2") + .Create(); + + // When / Then + CompareIssues(issue1, issue2, false); + } + + [Fact] + public void Should_Return_True_If_Same_Reference() + { + // Given + var issue1 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .Create(); + var issue2 = issue1; + + // When / Then + CompareIssues(issue1, issue2, true); + } + + [Fact] + public void Should_Return_True_If_Both_Are_Null() + { + // Given + IIssue issue1 = null; + IIssue issue2 = null; + + // When / Then + CompareIssues(issue1, issue2, true); + } + + [Fact] + public void Should_Return_True_If_Properties_Are_The_Same() + { + // Given + var issue1 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .Create(); + var issue2 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .Create(); + + // When / Then + CompareIssues(issue1, issue2, true); + } + + [Theory] + [InlineData("foo", "foo")] + [InlineData("foo", "foo/")] + [InlineData("foo/", "foo")] + [InlineData("", "")] + [InlineData(null, null)] + public void Should_Return_True_If_ProjectFileRelativePath_Is_Same(string path1, string path2) + { + // Given + var issue1 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .InProjectFile(path1) + .Create(); + var issue2 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .InProjectFile(path2) + .Create(); + + // When / Then + CompareIssues(issue1, issue2, true); + } + + [Theory] + [InlineData("foo", "foo")] + [InlineData("", "")] + [InlineData(null, null)] + public void Should_Return_True_If_ProjectName_Is_Same(string projectName1, string projectName2) + { + // Given + var issue1 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .InProjectOfName(projectName1) + .Create(); + var issue2 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .InProjectOfName(projectName2) + .Create(); + + // When / Then + CompareIssues(issue1, issue2, true); + } + + [Theory] + [InlineData("foo", "foo")] + [InlineData("foo", "foo/")] + [InlineData("foo/", "foo")] + [InlineData("", "")] + [InlineData(null, null)] + public void Should_Return_True_If_AffectedFileRelativePath_Is_Same(string path1, string path2) + { + // Given + var issue1 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .InFile(path1) + .Create(); + var issue2 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .InFile(path2) + .Create(); + + // When / Then + CompareIssues(issue1, issue2, true); + } + + [Theory] + [InlineData(1, 1)] + [InlineData(null, null)] + [InlineData(int.MaxValue, int.MaxValue)] + public void Should_Return_True_If_Line_Is_Same(int? line1, int? line2) + { + // Given + var issue1 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .InFile("foo", line1) + .Create(); + var issue2 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .InFile("foo", line2) + .Create(); + + // When / Then + CompareIssues(issue1, issue2, true); + } + + [Fact] + public void Should_Return_True_If_MessageText_Is_Same() + { + // Given + var issue1 = + IssueBuilder + .NewIssue("messageText", "providerType", "providerName") + .Create(); + var issue2 = + IssueBuilder + .NewIssue("messageText", "providerType", "providerName") + .Create(); + + // When / Then + CompareIssues(issue1, issue2, true); + } + + [Theory] + [InlineData("foo", "foo")] + [InlineData("", "")] + [InlineData(null, null)] + public void Should_Return_True_If_MessageHtml_Is_Same(string message1, string message2) + { + // Given + var issue1 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .WithMessageInHtmlFormat(message1) + .Create(); + var issue2 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .WithMessageInHtmlFormat(message2) + .Create(); + + // When / Then + CompareIssues(issue1, issue2, true); + } + + [Theory] + [InlineData("foo", "foo")] + [InlineData("", "")] + [InlineData(null, null)] + public void Should_Return_True_If_MessageMarkdown_Is_Same(string message1, string message2) + { + // Given + var issue1 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .WithMessageInMarkdownFormat(message1) + .Create(); + var issue2 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .WithMessageInMarkdownFormat(message2) + .Create(); + + // When / Then + CompareIssues(issue1, issue2, true); + } + + [Theory] + [InlineData(1, 1)] + [InlineData(null, null)] + [InlineData(0, 0)] + [InlineData(int.MinValue, int.MinValue)] + [InlineData(int.MaxValue, int.MaxValue)] + public void Should_Return_True_If_Priority_Is_Same(int? priority1, int? priority2) + { + // Given + var issue1 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .WithPriority(priority1, "Foo") + .Create(); + var issue2 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .WithPriority(priority2, "Foo") + .Create(); + + // When / Then + CompareIssues(issue1, issue2, true); + } + + [Theory] + [InlineData("foo", "foo")] + [InlineData("", "")] + [InlineData(null, null)] + public void Should_Return_True_If_PriorityName_Is_Same(string priorityName1, string priorityName2) + { + // Given + var issue1 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .WithPriority(42, priorityName1) + .Create(); + var issue2 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .WithPriority(42, priorityName2) + .Create(); + + // When / Then + CompareIssues(issue1, issue2, true); + } + + [Theory] + [InlineData("foo", "foo")] + [InlineData("", "")] + [InlineData(null, null)] + public void Should_Return_True_If_Rule_Is_Same(string rule1, string rule2) + { + // Given + var issue1 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .OfRule(rule1) + .Create(); + var issue2 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .OfRule(rule2) + .Create(); + + // When / Then + CompareIssues(issue1, issue2, true); + } + + [Theory] + [InlineData("http://foo", "http://foo")] + [InlineData("http://foo", "http://Foo")] + [InlineData(null, null)] + public void Should_Return_True_If_RuleUlr_Is_Same(string ruleUrl1, string ruleUrl2) + { + // Given + var issueBuilder = + IssueBuilder + .NewIssue("message", "providerType", "providerName"); + if (!string.IsNullOrEmpty(ruleUrl1)) + { + issueBuilder = + issueBuilder + .OfRule("foo", new Uri(ruleUrl1)); + } + + var issue1 = issueBuilder.Create(); + + issueBuilder = + IssueBuilder + .NewIssue("message", "providerType", "providerName"); + if (!string.IsNullOrEmpty(ruleUrl2)) + { + issueBuilder = + issueBuilder + .OfRule("foo", new Uri(ruleUrl2)); + } + + var issue2 = issueBuilder.Create(); + + // When / Then + CompareIssues(issue1, issue2, true); + } + + [Fact] + public void Should_Return_True_If_ProviderType_Is_Same() + { + // Given + var issue1 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .Create(); + var issue2 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .Create(); + + // When / Then + CompareIssues(issue1, issue2, true); + } + + [Fact] + public void Should_Return_True_If_ProviderName_Is_Same() + { + // Given + var issue1 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .Create(); + var issue2 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .Create(); + + // When / Then + CompareIssues(issue1, issue2, true); + } + + [Fact] + public void Should_Remove_Identical_Issues_From_List_Of_Issues() + { + // Given + var issue1_1 = + IssueBuilder + .NewIssue("message1", "providerType1", "providerName1") + .Create(); + var issue1_2 = + IssueBuilder + .NewIssue("message1", "providerType1", "providerName1") + .Create(); + var issue2 = + IssueBuilder + .NewIssue("message2", "providerType2", "providerName2") + .Create(); + var issue3 = + IssueBuilder + .NewIssue("message3", "providerType3", "providerName3") + .Create(); + var issues1 = new List { issue1_1, issue2 }; + var issues2 = new List { issue1_2, issue3 }; + var comparer = new IIssueComparer(); + + // When + var result = issues1.Except(issues2, comparer); + + // Then + result.Count().ShouldBe(1); + result.ShouldContain(issue2); + } + + private static void CompareIssues(IIssue issue1, IIssue issue2, bool expectedToBeEqual) + { + var comparer = new IIssueComparer(false); + + comparer.Equals(issue1, issue2).ShouldBe(expectedToBeEqual); + + if (expectedToBeEqual) + { + comparer.GetHashCode(issue1).ShouldBe(comparer.GetHashCode(issue2)); + } + else + { + comparer.GetHashCode(issue1).ShouldNotBe(comparer.GetHashCode(issue2)); + } + } + } + + public sealed class TheCtorWithCompareOnlyPersistentPropertiesSetToTrue + { + [Fact] + public void Should_Return_False_If_First_Issue_Is_Null() + { + // Given + IIssue issue1 = null; + var issue2 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .Create(); + + // When / Then + CompareIssues(issue1, issue2, false); + } + + [Fact] + public void Should_Return_False_If_Second_Issue_Is_Null() + { + // Given + var issue1 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .Create(); + IIssue issue2 = null; + + // When / Then + CompareIssues(issue1, issue2, false); + } + + [Theory] + [InlineData("foo", "bar")] + [InlineData("foo", "Foo")] + [InlineData("foo", null)] + [InlineData(null, "foo")] + public void Should_Return_False_If_ProjectName_Is_Different(string projectName1, string projectName2) + { + // Given + var issue1 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .InProjectOfName(projectName1) + .Create(); + var issue2 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .InProjectOfName(projectName2) + .Create(); + + // When / Then + CompareIssues(issue1, issue2, false); + } + + [Fact] + public void Should_Return_False_If_MessageText_Is_Different() + { + // Given + var issue1 = + IssueBuilder + .NewIssue("message1", "providerType", "providerName") + .Create(); + var issue2 = + IssueBuilder + .NewIssue("message2", "providerType", "providerName") + .Create(); + + // When / Then + CompareIssues(issue1, issue2, false); + } + + [Theory] + [InlineData("foo", "bar")] + [InlineData("foo", "Foo")] + [InlineData("foo", null)] + [InlineData(null, "foo")] + public void Should_Return_False_If_MessageHtml_Is_Different(string message1, string message2) + { + // Given + var issue1 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .WithMessageInHtmlFormat(message1) + .Create(); + var issue2 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .WithMessageInHtmlFormat(message2) + .Create(); + + // When / Then + CompareIssues(issue1, issue2, false); + } + + [Theory] + [InlineData("foo", "bar")] + [InlineData("foo", "Foo")] + [InlineData("foo", null)] + [InlineData(null, "foo")] + public void Should_Return_False_If_MessageMarkdown_Is_Different(string message1, string message2) + { + // Given + var issue1 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .WithMessageInMarkdownFormat(message1) + .Create(); + var issue2 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .WithMessageInMarkdownFormat(message2) + .Create(); + + // When / Then + CompareIssues(issue1, issue2, false); + } + + [Theory] + [InlineData(1, 2)] + [InlineData(1, null)] + [InlineData(null, 1)] + [InlineData(int.MinValue, 0)] + [InlineData(int.MaxValue, 0)] + public void Should_Return_False_If_Priority_Is_Different(int? priority1, int? priority2) + { + // Given + var issue1 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .WithPriority(priority1, "Foo") + .Create(); + var issue2 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .WithPriority(priority2, "Foo") + .Create(); + + // When / Then + CompareIssues(issue1, issue2, false); + } + + [Theory] + [InlineData("foo", "bar")] + [InlineData("foo", "Foo")] + [InlineData("foo", null)] + [InlineData(null, "foo")] + public void Should_Return_False_If_PriorityName_Is_Different(string priorityName1, string priorityName2) + { + // Given + var issue1 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .WithPriority(42, priorityName1) + .Create(); + var issue2 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .WithPriority(42, priorityName2) + .Create(); + + // When / Then + CompareIssues(issue1, issue2, false); + } + + [Theory] + [InlineData("foo", "bar")] + [InlineData("foo", "Foo")] + [InlineData("foo", null)] + [InlineData(null, "foo")] + public void Should_Return_False_If_Rule_Is_Different(string rule1, string rule2) + { + // Given + var issue1 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .OfRule(rule1) + .Create(); + var issue2 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .OfRule(rule2) + .Create(); + + // When / Then + CompareIssues(issue1, issue2, false); + } + + [Theory] + [InlineData("http://foo", "http://bar")] + [InlineData("http://foo", null)] + [InlineData(null, "http://foo")] + public void Should_Return_False_If_RuleUlr_Is_Different(string ruleUrl1, string ruleUrl2) + { + // Given + var issueBuilder = + IssueBuilder + .NewIssue("message", "providerType", "providerName"); + if (!string.IsNullOrEmpty(ruleUrl1)) + { + issueBuilder = + issueBuilder + .OfRule("foo", new Uri(ruleUrl1)); + } + + var issue1 = issueBuilder.Create(); + + issueBuilder = + IssueBuilder + .NewIssue("message", "providerType", "providerName"); + if (!string.IsNullOrEmpty(ruleUrl2)) + { + issueBuilder = + issueBuilder + .OfRule("foo", new Uri(ruleUrl2)); + } + + var issue2 = issueBuilder.Create(); + + // When / Then + CompareIssues(issue1, issue2, false); + } + + [Fact] + public void Should_Return_False_If_ProviderType_Is_Different() + { + // Given + var issue1 = + IssueBuilder + .NewIssue("message", "providerType1", "providerName") + .Create(); + var issue2 = + IssueBuilder + .NewIssue("message", "providerType2", "providerName") + .Create(); + + // When / Then + CompareIssues(issue1, issue2, false); + } + + [Fact] + public void Should_Return_False_If_ProviderName_Is_Different() + { + // Given + var issue1 = + IssueBuilder + .NewIssue("message", "providerType", "providerName1") + .Create(); + var issue2 = + IssueBuilder + .NewIssue("message", "providerType", "providerName2") + .Create(); + + // When / Then + CompareIssues(issue1, issue2, false); + } + + [Fact] + public void Should_Return_True_If_Same_Reference() + { + // Given + var issue1 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .Create(); + var issue2 = issue1; + + // When / Then + CompareIssues(issue1, issue2, true); + } + + [Fact] + public void Should_Return_True_If_Both_Are_Null() + { + // Given + IIssue issue1 = null; + IIssue issue2 = null; + + // When / Then + CompareIssues(issue1, issue2, true); + } + + [Fact] + public void Should_Return_True_If_Properties_Are_The_Same() + { + // Given + var issue1 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .Create(); + var issue2 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .Create(); + + // When / Then + CompareIssues(issue1, issue2, true); + } + + [Theory] + [InlineData("foo", "bar")] + [InlineData("foo", "Foo")] + [InlineData("foo", null)] + [InlineData(null, "foo")] + public void Should_Return_True_If_ProjectFileRelativePath_Is_Different(string path1, string path2) + { + // Given + var issue1 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .InProjectFile(path1) + .Create(); + var issue2 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .InProjectFile(path2) + .Create(); + + // When / Then + CompareIssues(issue1, issue2, true); + } + + [Theory] + [InlineData("foo", "foo")] + [InlineData("foo", "foo/")] + [InlineData("foo/", "foo")] + [InlineData("", "")] + [InlineData(null, null)] + public void Should_Return_True_If_ProjectFileRelativePath_Is_Same(string path1, string path2) + { + // Given + var issue1 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .InProjectFile(path1) + .Create(); + var issue2 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .InProjectFile(path2) + .Create(); + + // When / Then + CompareIssues(issue1, issue2, true); + } + + [Theory] + [InlineData("foo", "foo")] + [InlineData("", "")] + [InlineData(null, null)] + public void Should_Return_True_If_ProjectName_Is_Same(string projectName1, string projectName2) + { + // Given + var issue1 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .InProjectOfName(projectName1) + .Create(); + var issue2 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .InProjectOfName(projectName2) + .Create(); + + // When / Then + CompareIssues(issue1, issue2, true); + } + + [Theory] + [InlineData("foo", "bar")] + [InlineData("foo", "Foo")] + [InlineData("foo", null)] + [InlineData(null, "foo")] + public void Should_Return_True_If_AffectedFileRelativePath_Is_Different(string path1, string path2) + { + // Given + var issue1 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .InFile(path1) + .Create(); + var issue2 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .InFile(path2) + .Create(); + + // When / Then + CompareIssues(issue1, issue2, true); + } + + [Theory] + [InlineData("foo", "foo")] + [InlineData("foo", "foo/")] + [InlineData("foo/", "foo")] + [InlineData("", "")] + [InlineData(null, null)] + public void Should_Return_True_If_AffectedFileRelativePath_Is_Same(string path1, string path2) + { + // Given + var issue1 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .InFile(path1) + .Create(); + var issue2 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .InFile(path2) + .Create(); + + // When / Then + CompareIssues(issue1, issue2, true); + } + + [Theory] + [InlineData(1, 2)] + [InlineData(1, null)] + [InlineData(null, 1)] + [InlineData(int.MaxValue, 1)] + [InlineData(1, int.MaxValue)] + public void Should_Return_True_If_Line_Is_Different(int? line1, int? line2) + { + // Given + var issue1 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .InFile("foo", line1) + .Create(); + var issue2 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .InFile("foo", line2) + .Create(); + + // When / Then + CompareIssues(issue1, issue2, true); + } + + [Theory] + [InlineData(1, 1)] + [InlineData(null, null)] + [InlineData(int.MaxValue, int.MaxValue)] + public void Should_Return_True_If_Line_Is_Same(int? line1, int? line2) + { + // Given + var issue1 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .InFile("foo", line1) + .Create(); + var issue2 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .InFile("foo", line2) + .Create(); + + // When / Then + CompareIssues(issue1, issue2, true); + } + + [Fact] + public void Should_Return_True_If_MessageText_Is_Same() + { + // Given + var issue1 = + IssueBuilder + .NewIssue("messageText", "providerType", "providerName") + .Create(); + var issue2 = + IssueBuilder + .NewIssue("messageText", "providerType", "providerName") + .Create(); + + // When / Then + CompareIssues(issue1, issue2, true); + } + + [Theory] + [InlineData("foo", "foo")] + [InlineData("", "")] + [InlineData(null, null)] + public void Should_Return_True_If_MessageHtml_Is_Same(string message1, string message2) + { + // Given + var issue1 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .WithMessageInHtmlFormat(message1) + .Create(); + var issue2 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .WithMessageInHtmlFormat(message2) + .Create(); + + // When / Then + CompareIssues(issue1, issue2, true); + } + + [Theory] + [InlineData("foo", "foo")] + [InlineData("", "")] + [InlineData(null, null)] + public void Should_Return_True_If_MessageMarkdown_Is_Same(string message1, string message2) + { + // Given + var issue1 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .WithMessageInMarkdownFormat(message1) + .Create(); + var issue2 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .WithMessageInMarkdownFormat(message2) + .Create(); + + // When / Then + CompareIssues(issue1, issue2, true); + } + + [Theory] + [InlineData(1, 1)] + [InlineData(null, null)] + [InlineData(0, 0)] + [InlineData(int.MinValue, int.MinValue)] + [InlineData(int.MaxValue, int.MaxValue)] + public void Should_Return_True_If_Priority_Is_Same(int? priority1, int? priority2) + { + // Given + var issue1 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .WithPriority(priority1, "Foo") + .Create(); + var issue2 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .WithPriority(priority2, "Foo") + .Create(); + + // When / Then + CompareIssues(issue1, issue2, true); + } + + [Theory] + [InlineData("foo", "foo")] + [InlineData("", "")] + [InlineData(null, null)] + public void Should_Return_True_If_PriorityName_Is_Same(string priorityName1, string priorityName2) + { + // Given + var issue1 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .WithPriority(42, priorityName1) + .Create(); + var issue2 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .WithPriority(42, priorityName2) + .Create(); + + // When / Then + CompareIssues(issue1, issue2, true); + } + + [Theory] + [InlineData("foo", "foo")] + [InlineData("", "")] + [InlineData(null, null)] + public void Should_Return_True_If_Rule_Is_Same(string rule1, string rule2) + { + // Given + var issue1 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .OfRule(rule1) + .Create(); + var issue2 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .OfRule(rule2) + .Create(); + + // When / Then + CompareIssues(issue1, issue2, true); + } + + [Theory] + [InlineData("http://foo", "http://foo")] + [InlineData("http://foo", "http://Foo")] + [InlineData(null, null)] + public void Should_Return_True_If_RuleUlr_Is_Same(string ruleUrl1, string ruleUrl2) + { + // Given + var issueBuilder = + IssueBuilder + .NewIssue("message", "providerType", "providerName"); + if (!string.IsNullOrEmpty(ruleUrl1)) + { + issueBuilder = + issueBuilder + .OfRule("foo", new Uri(ruleUrl1)); + } + + var issue1 = issueBuilder.Create(); + + issueBuilder = + IssueBuilder + .NewIssue("message", "providerType", "providerName"); + if (!string.IsNullOrEmpty(ruleUrl2)) + { + issueBuilder = + issueBuilder + .OfRule("foo", new Uri(ruleUrl2)); + } + + var issue2 = issueBuilder.Create(); + + // When / Then + CompareIssues(issue1, issue2, true); + } + + [Fact] + public void Should_Return_True_If_ProviderType_Is_Same() + { + // Given + var issue1 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .Create(); + var issue2 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .Create(); + + // When / Then + CompareIssues(issue1, issue2, true); + } + + [Fact] + public void Should_Return_True_If_ProviderName_Is_Same() + { + // Given + var issue1 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .Create(); + var issue2 = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .Create(); + + // When / Then + CompareIssues(issue1, issue2, true); + } + + [Fact] + public void Should_Remove_Identical_Issues_From_List_Of_Issues() + { + // Given + var issue1_1 = + IssueBuilder + .NewIssue("message1", "providerType1", "providerName1") + .InFile("foo.cs", 10) + .Create(); + var issue1_2 = + IssueBuilder + .NewIssue("message1", "providerType1", "providerName1") + .InFile("foo.cs", 20) + .Create(); + var issue2 = + IssueBuilder + .NewIssue("message2", "providerType2", "providerName2") + .Create(); + var issue3 = + IssueBuilder + .NewIssue("message3", "providerType3", "providerName3") + .Create(); + var issues1 = new List { issue1_1, issue2 }; + var issues2 = new List { issue1_2, issue3 }; + var comparer = new IIssueComparer(true); + + // When + var result = issues1.Except(issues2, comparer); + + // Then + result.Count().ShouldBe(1); + result.ShouldContain(issue2); + } + + private static void CompareIssues(IIssue issue1, IIssue issue2, bool expectedToBeEqual) + { + var comparer = new IIssueComparer(true); + + comparer.Equals(issue1, issue2).ShouldBe(expectedToBeEqual); + + if (expectedToBeEqual) + { + comparer.GetHashCode(issue1).ShouldBe(comparer.GetHashCode(issue2)); + } + else + { + comparer.GetHashCode(issue1).ShouldNotBe(comparer.GetHashCode(issue2)); + } + } + } + } +} diff --git a/src/Cake.Issues.Tests/IIssueExtensionsTests.cs b/src/Cake.Issues.Tests/IIssueExtensionsTests.cs index 500e9bb95..c444ac375 100644 --- a/src/Cake.Issues.Tests/IIssueExtensionsTests.cs +++ b/src/Cake.Issues.Tests/IIssueExtensionsTests.cs @@ -308,13 +308,17 @@ public void Should_Throw_If_Issue_Is_Null() [InlineData("foo {Line} bar", "foo 42 bar")] [InlineData("foo {Rule} bar", "foo Rule Foo bar")] [InlineData("foo {RuleUrl} bar", "foo https://google.com/ bar")] - [InlineData("foo {Message} bar", "foo Message Foo bar")] + [InlineData("foo {MessageText} bar", "foo MessageText Foo bar")] + [InlineData("foo {MessageHtml} bar", "foo MessageHtml Foo bar")] + [InlineData("foo {MessageMarkdown} bar", "foo MessageMarkdown Foo bar")] public void Should_Replace_Tokens(string pattern, string expectedResult) { // Given var issue = IssueBuilder - .NewIssue("Message Foo", "ProviderType Foo", "ProviderName Foo") + .NewIssue("MessageText Foo", "ProviderType Foo", "ProviderName Foo") + .WithMessageInHtmlFormat("MessageHtml Foo") + .WithMessageInMarkdownFormat("MessageMarkdown Foo") .InFile(@"src/Cake.Issues/foo.cs", 42) .InProject(@"src/Cake.Issues/Cake.Issues.csproj", "Cake.Issues") .OfRule("Rule Foo", new Uri("https://google.com")) diff --git a/src/Cake.Issues.Tests/IssueBuilderFixture.cs b/src/Cake.Issues.Tests/IssueBuilderFixture.cs index d734d2da9..7b5e1cbc0 100644 --- a/src/Cake.Issues.Tests/IssueBuilderFixture.cs +++ b/src/Cake.Issues.Tests/IssueBuilderFixture.cs @@ -7,10 +7,10 @@ public IssueBuilderFixture() { } - public IssueBuilderFixture(string message, string providerType, string providerName) + public IssueBuilderFixture(string messageText, string providerType, string providerName) { this.IssueBuilder = - IssueBuilder.NewIssue(message, providerType, providerName); + IssueBuilder.NewIssue(messageText, providerType, providerName); } public IssueBuilder IssueBuilder { get; private set; } diff --git a/src/Cake.Issues.Tests/IssueReaderTests.cs b/src/Cake.Issues.Tests/IssueReaderTests.cs index 61efe99b5..6a6696d62 100644 --- a/src/Cake.Issues.Tests/IssueReaderTests.cs +++ b/src/Cake.Issues.Tests/IssueReaderTests.cs @@ -20,7 +20,7 @@ public void Should_Throw_If_Log_Is_Null() }; // When - var result = Record.Exception(() => fixture.ReadIssues(IssueCommentFormat.Undefined)); + var result = Record.Exception(() => fixture.ReadIssues()); // Then result.IsArgumentNullException("log"); @@ -36,7 +36,7 @@ public void Should_Throw_If_Issue_Provider_List_Is_Null() }; // When - var result = Record.Exception(() => fixture.ReadIssues(IssueCommentFormat.Undefined)); + var result = Record.Exception(() => fixture.ReadIssues()); // Then result.IsArgumentNullException("issueProviders"); @@ -50,7 +50,7 @@ public void Should_Throw_If_Issue_Provider_List_Is_Empty() fixture.IssueProviders.Clear(); // When - var result = Record.Exception(() => fixture.ReadIssues(IssueCommentFormat.Undefined)); + var result = Record.Exception(() => fixture.ReadIssues()); // Then result.IsArgumentException("issueProviders"); @@ -65,7 +65,7 @@ public void Should_Throw_If_Issue_Provider_Is_Null() fixture.IssueProviders.Add(null); // When - var result = Record.Exception(() => fixture.ReadIssues(IssueCommentFormat.Undefined)); + var result = Record.Exception(() => fixture.ReadIssues()); // Then result.IsArgumentOutOfRangeException("issueProviders"); @@ -81,7 +81,7 @@ public void Should_Throw_If_Settings_Are_Null() }; // When - var result = Record.Exception(() => fixture.ReadIssues(IssueCommentFormat.Undefined)); + var result = Record.Exception(() => fixture.ReadIssues()); // Then result.IsArgumentNullException("settings"); @@ -97,7 +97,7 @@ public void Should_Initialize_Issue_Provider() var fixture = new IssuesFixture(); // When - fixture.ReadIssues(IssueCommentFormat.Undefined); + fixture.ReadIssues(); // Then fixture.IssueProviders.ShouldAllBe(x => x.Settings == fixture.Settings); @@ -147,7 +147,7 @@ public void Should_Initialize_All_Issue_Provider() })); // When - fixture.ReadIssues(IssueCommentFormat.Undefined); + fixture.ReadIssues(); // Then fixture.IssueProviders.ShouldAllBe(x => x.Settings == fixture.Settings); @@ -183,7 +183,7 @@ public void Should_Read_Correct_Number_Of_Issues() })); // When - var issues = fixture.ReadIssues(IssueCommentFormat.Undefined).ToList(); + var issues = fixture.ReadIssues().ToList(); // Then issues.Count.ShouldBe(2); @@ -219,7 +219,7 @@ public void Should_Read_Correct_Number_Of_Issues_Not_Related_To_A_File() })); // When - var issues = fixture.ReadIssues(IssueCommentFormat.Undefined).ToList(); + var issues = fixture.ReadIssues().ToList(); // Then issues.Count.ShouldBe(2); @@ -279,7 +279,7 @@ public void Should_Read_Correct_Number_Of_Issues_From_Multiple_Providers() })); // When - var issues = fixture.ReadIssues(IssueCommentFormat.Undefined).ToList(); + var issues = fixture.ReadIssues().ToList(); // Then issues.Count.ShouldBe(4); diff --git a/src/Cake.Issues.Tests/IssueSerializationExtensionsTests.cs b/src/Cake.Issues.Tests/IssueSerializationExtensionsTests.cs deleted file mode 100644 index dd18d166d..000000000 --- a/src/Cake.Issues.Tests/IssueSerializationExtensionsTests.cs +++ /dev/null @@ -1,827 +0,0 @@ -namespace Cake.Issues.Tests -{ - using System; - using System.Collections.Generic; - using System.Linq; - using Cake.Core.IO; - using Cake.Issues.Testing; - using Shouldly; - using Xunit; - - public sealed class IssueSerializationExtensionsTests - { - public sealed class TheSerializeToJsonStringExtensionForASingleIssue - { - [Fact] - public void Should_Throw_If_Issue_Is_Null() - { - // Given - IIssue issue = null; - - // When - var result = Record.Exception(() => issue.SerializeToJsonString()); - - // Then - result.IsArgumentNullException("issue"); - } - - [Fact] - public void Should_Give_Correct_Result_For_Message_After_Roundtrip() - { - // Given - var message = "message"; - var issue = - IssueBuilder - .NewIssue(message, "providerType", "providerName") - .Create(); - - // When - var result = issue.SerializeToJsonString().DeserializeToIssue(); - - // Then - result.Message.ShouldBe(message); - } - - [Fact] - public void Should_Give_Correct_Result_For_ProviderType_After_Roundtrip() - { - // Given - var providerType = "providerType"; - var issue = - IssueBuilder - .NewIssue("message", providerType, "providerName") - .Create(); - - // When - var result = issue.SerializeToJsonString().DeserializeToIssue(); - - // Then - result.ProviderType.ShouldBe(providerType); - } - - [Fact] - public void Should_Give_Correct_Result_For_ProviderName_After_Roundtrip() - { - // Given - var providerName = "providerName"; - var issue = - IssueBuilder - .NewIssue("message", "providerType", providerName) - .Create(); - - // When - var result = issue.SerializeToJsonString().DeserializeToIssue(); - - // Then - result.ProviderName.ShouldBe(providerName); - } - - [Fact] - public void Should_Give_Correct_Result_For_ProjectFileRelativePath_After_Roundtrip() - { - // Given - var projectFileRelativePath = @"src/myproj.file"; - var issue = - IssueBuilder - .NewIssue("message", "providerType", "providerName") - .InProjectFile(projectFileRelativePath) - .Create(); - - // When - var result = issue.SerializeToJsonString().DeserializeToIssue(); - - // Then - result.ProjectFileRelativePath.FullPath.ShouldBe(projectFileRelativePath); - } - - [Fact] - public void Should_Give_Correct_Result_For_ProjectName_After_Roundtrip() - { - // Given - var projectName = "projectName"; - var issue = - IssueBuilder - .NewIssue("message", "providerType", "providerName") - .InProjectOfName(projectName) - .Create(); - - // When - var result = issue.SerializeToJsonString().DeserializeToIssue(); - - // Then - result.ProjectName.ShouldBe(projectName); - } - - [Fact] - public void Should_Give_Correct_Result_For_AffectedFileRelativePath_After_Roundtrip() - { - // Given - var affectedFileRelativePath = @"src/foo.bar"; - var issue = - IssueBuilder - .NewIssue("message", "providerType", "providerName") - .InFile(affectedFileRelativePath) - .Create(); - - // When - var result = issue.SerializeToJsonString().DeserializeToIssue(); - - // Then - result.AffectedFileRelativePath.FullPath.ShouldBe(affectedFileRelativePath); - } - - [Fact] - public void Should_Give_Correct_Result_For_Line_After_Roundtrip() - { - // Given - var line = 42; - var issue = - IssueBuilder - .NewIssue("message", "providerType", "providerName") - .InFile(@"src/foo.bar", line) - .Create(); - - // When - var result = issue.SerializeToJsonString().DeserializeToIssue(); - - // Then - result.Line.ShouldBe(line); - } - - [Fact] - public void Should_Give_Correct_Result_For_Priority_After_Roundtrip() - { - // Given - var priority = 42; - var issue = - IssueBuilder - .NewIssue("message", "providerType", "providerName") - .WithPriority(priority, "priorityName") - .Create(); - - // When - var result = issue.SerializeToJsonString().DeserializeToIssue(); - - // Then - result.Priority.ShouldBe(priority); - } - - [Fact] - public void Should_Give_Correct_Result_For_PriorityName_After_Roundtrip() - { - // Given - var priorityName = "priorityName"; - var issue = - IssueBuilder - .NewIssue("message", "providerType", "providerName") - .WithPriority(42, priorityName) - .Create(); - - // When - var result = issue.SerializeToJsonString().DeserializeToIssue(); - - // Then - result.PriorityName.ShouldBe(priorityName); - } - - [Fact] - public void Should_Give_Correct_Result_For_Rule_After_Roundtrip() - { - // Given - var rule = "rule"; - var issue = - IssueBuilder - .NewIssue("message", "providerType", "providerName") - .OfRule(rule) - .Create(); - - // When - var result = issue.SerializeToJsonString().DeserializeToIssue(); - - // Then - result.Rule.ShouldBe(rule); - } - - [Fact] - public void Should_Give_Correct_Result_For_RuleUrl_After_Roundtrip() - { - // Given - var ruleUrl = new Uri("https://rule.url"); - var issue = - IssueBuilder - .NewIssue("message", "providerType", "providerName") - .OfRule("rule", ruleUrl) - .Create(); - - // When - var result = issue.SerializeToJsonString().DeserializeToIssue(); - - // Then - result.RuleUrl.ToString().ShouldBe(ruleUrl.ToString()); - } - } - - public sealed class TheSerializeToJsonStringExtensionForAnEnumerableOfIssues - { - [Fact] - public void Should_Throw_If_Issue_Is_Null() - { - // Given - IEnumerable issues = null; - - // When - var result = Record.Exception(() => issues.SerializeToJsonString()); - - // Then - result.IsArgumentNullException("issues"); - } - - [Fact] - public void Should_Serialize_Issues() - { - // Given - var issues = - new List - { - IssueBuilder - .NewIssue("message1", "providerType1", "providerName1") - .Create(), - IssueBuilder - .NewIssue("message2", "providerType2", "providerName2") - .Create(), - }; - - // When - var result = issues.SerializeToJsonString(); - - // Then - result.ShouldNotBeNull(); - } - } - - public sealed class TheSerializeToJsonFileExtensionForASingleIssue - { - [Fact] - public void Should_Throw_If_Issue_Is_Null() - { - // Given - IIssue issue = null; - var filePath = @"c:\issue.json"; - - // When - var result = Record.Exception(() => issue.SerializeToJsonFile(filePath)); - - // Then - result.IsArgumentNullException("issue"); - } - - [Fact] - public void Should_Throw_If_FilePath_Is_Null() - { - // Given - var issue = - IssueBuilder - .NewIssue("message", "providerType", "providerName") - .Create(); - FilePath filePath = null; - - // When - var result = Record.Exception(() => issue.SerializeToJsonFile(filePath)); - - // Then - result.IsArgumentNullException("filePath"); - } - - [Fact] - public void Should_Give_Correct_Result_For_Message_After_Roundtrip() - { - // Given - var message = "message"; - var issue = - IssueBuilder - .NewIssue(message, "providerType", "providerName") - .Create(); - var filePath = new FilePath(System.IO.Path.GetTempPath() + Guid.NewGuid().ToString() + ".json"); - - try - { - // When - issue.SerializeToJsonFile(filePath); - var result = filePath.DeserializeToIssue(); - - // Then - result.Message.ShouldBe(message); - } - finally - { - System.IO.File.Delete(filePath.FullPath); - } - } - - [Fact] - public void Should_Give_Correct_Result_For_ProviderType_After_Roundtrip() - { - // Given - var providerType = "providerType"; - var issue = - IssueBuilder - .NewIssue("message", providerType, "providerName") - .Create(); - var filePath = new FilePath(System.IO.Path.GetTempPath() + Guid.NewGuid().ToString() + ".json"); - - try - { - // When - issue.SerializeToJsonFile(filePath); - var result = filePath.DeserializeToIssue(); - - // Then - result.ProviderType.ShouldBe(providerType); - } - finally - { - System.IO.File.Delete(filePath.FullPath); - } - } - - [Fact] - public void Should_Give_Correct_Result_For_ProviderName_After_Roundtrip() - { - // Given - var providerName = "providerName"; - var issue = - IssueBuilder - .NewIssue("message", "providerType", providerName) - .Create(); - var filePath = new FilePath(System.IO.Path.GetTempPath() + Guid.NewGuid().ToString() + ".json"); - - try - { - // When - issue.SerializeToJsonFile(filePath); - var result = filePath.DeserializeToIssue(); - - // Then - result.ProviderName.ShouldBe(providerName); - } - finally - { - System.IO.File.Delete(filePath.FullPath); - } - } - - [Fact] - public void Should_Give_Correct_Result_For_ProjectFileRelativePath_After_Roundtrip() - { - // Given - var projectFileRelativePath = @"src/myproj.file"; - var issue = - IssueBuilder - .NewIssue("message", "providerType", "providerName") - .InProjectFile(projectFileRelativePath) - .Create(); - var filePath = new FilePath(System.IO.Path.GetTempPath() + Guid.NewGuid().ToString() + ".json"); - - try - { - // When - issue.SerializeToJsonFile(filePath); - var result = filePath.DeserializeToIssue(); - - // Then - result.ProjectFileRelativePath.FullPath.ShouldBe(projectFileRelativePath); - } - finally - { - System.IO.File.Delete(filePath.FullPath); - } - } - - [Fact] - public void Should_Give_Correct_Result_For_ProjectName_After_Roundtrip() - { - // Given - var projectName = "projectName"; - var issue = - IssueBuilder - .NewIssue("message", "providerType", "providerName") - .InProjectOfName(projectName) - .Create(); - var filePath = new FilePath(System.IO.Path.GetTempPath() + Guid.NewGuid().ToString() + ".json"); - - try - { - // When - issue.SerializeToJsonFile(filePath); - var result = filePath.DeserializeToIssue(); - - // Then - result.ProjectName.ShouldBe(projectName); - } - finally - { - System.IO.File.Delete(filePath.FullPath); - } - } - - [Fact] - public void Should_Give_Correct_Result_For_AffectedFileRelativePath_After_Roundtrip() - { - // Given - var affectedFileRelativePath = @"src/foo.bar"; - var issue = - IssueBuilder - .NewIssue("message", "providerType", "providerName") - .InFile(affectedFileRelativePath) - .Create(); - var filePath = new FilePath(System.IO.Path.GetTempPath() + Guid.NewGuid().ToString() + ".json"); - - try - { - // When - issue.SerializeToJsonFile(filePath); - var result = filePath.DeserializeToIssue(); - - // Then - result.AffectedFileRelativePath.FullPath.ShouldBe(affectedFileRelativePath); - } - finally - { - System.IO.File.Delete(filePath.FullPath); - } - } - - [Fact] - public void Should_Give_Correct_Result_For_Line_After_Roundtrip() - { - // Given - var line = 42; - var issue = - IssueBuilder - .NewIssue("message", "providerType", "providerName") - .InFile(@"src/foo.bar", line) - .Create(); - var filePath = new FilePath(System.IO.Path.GetTempPath() + Guid.NewGuid().ToString() + ".json"); - - try - { - // When - issue.SerializeToJsonFile(filePath); - var result = filePath.DeserializeToIssue(); - - // Then - result.Line.ShouldBe(line); - } - finally - { - System.IO.File.Delete(filePath.FullPath); - } - } - - [Fact] - public void Should_Give_Correct_Result_For_Priority_After_Roundtrip() - { - // Given - var priority = 42; - var issue = - IssueBuilder - .NewIssue("message", "providerType", "providerName") - .WithPriority(priority, "priorityName") - .Create(); - var filePath = new FilePath(System.IO.Path.GetTempPath() + Guid.NewGuid().ToString() + ".json"); - - try - { - // When - issue.SerializeToJsonFile(filePath); - var result = filePath.DeserializeToIssue(); - - // Then - result.Priority.ShouldBe(priority); - } - finally - { - System.IO.File.Delete(filePath.FullPath); - } - } - - [Fact] - public void Should_Give_Correct_Result_For_PriorityName_After_Roundtrip() - { - // Given - var priorityName = "priorityName"; - var issue = - IssueBuilder - .NewIssue("message", "providerType", "providerName") - .WithPriority(42, priorityName) - .Create(); - var filePath = new FilePath(System.IO.Path.GetTempPath() + Guid.NewGuid().ToString() + ".json"); - - try - { - // When - issue.SerializeToJsonFile(filePath); - var result = filePath.DeserializeToIssue(); - - // Then - result.PriorityName.ShouldBe(priorityName); - } - finally - { - System.IO.File.Delete(filePath.FullPath); - } - } - - [Fact] - public void Should_Give_Correct_Result_For_Rule_After_Roundtrip() - { - // Given - var rule = "rule"; - var issue = - IssueBuilder - .NewIssue("message", "providerType", "providerName") - .OfRule(rule) - .Create(); - var filePath = new FilePath(System.IO.Path.GetTempPath() + Guid.NewGuid().ToString() + ".json"); - - try - { - // When - issue.SerializeToJsonFile(filePath); - var result = filePath.DeserializeToIssue(); - - // Then - result.Rule.ShouldBe(rule); - } - finally - { - System.IO.File.Delete(filePath.FullPath); - } - } - - [Fact] - public void Should_Give_Correct_Result_For_RuleUrl_After_Roundtrip() - { - // Given - var ruleUrl = new Uri("https://rule.url"); - var issue = - IssueBuilder - .NewIssue("message", "providerType", "providerName") - .OfRule("rule", ruleUrl) - .Create(); - var filePath = new FilePath(System.IO.Path.GetTempPath() + Guid.NewGuid().ToString() + ".json"); - - try - { - // When - issue.SerializeToJsonFile(filePath); - var result = filePath.DeserializeToIssue(); - - // Then - result.RuleUrl.ToString().ShouldBe(ruleUrl.ToString()); - } - finally - { - System.IO.File.Delete(filePath.FullPath); - } - } - } - - public sealed class TheSerializeToJsonFileExtensionForAnEnumerableOfIssues - { - [Fact] - public void Should_Throw_If_Issue_Is_Null() - { - // Given - IEnumerable issues = null; - var filePath = @"c:\issues.json"; - - // When - var result = Record.Exception(() => issues.SerializeToJsonFile(filePath)); - - // Then - result.IsArgumentNullException("issues"); - } - - [Fact] - public void Should_Throw_If_FilePath_Is_Null() - { - // Given - var issues = new List(); - FilePath filePath = null; - - // When - var result = Record.Exception(() => issues.SerializeToJsonFile(filePath)); - - // Then - result.IsArgumentNullException("filePath"); - } - - [Fact] - public void Should_Serialize_Issues() - { - // Given - var issues = - new List - { - IssueBuilder - .NewIssue("message1", "providerType1", "providerName1") - .Create(), - IssueBuilder - .NewIssue("message2", "providerType2", "providerName2") - .Create(), - }; - var filePath = new FilePath(System.IO.Path.GetTempPath() + Guid.NewGuid().ToString() + ".json"); - - try - { - // When - issues.SerializeToJsonFile(filePath); - - // Then - System.IO.File.Exists(filePath.FullPath).ShouldBeTrue(); - } - finally - { - System.IO.File.Delete(filePath.FullPath); - } - } - } - - public sealed class TheDeserializeToIssueExtensionForAJsonString - { - [Fact] - public void Should_Throw_If_JsonString_Is_Null() - { - // Given - string jsonString = null; - - // When - var result = Record.Exception(() => jsonString.DeserializeToIssue()); - - // Then - result.IsArgumentNullException("jsonString"); - } - } - - public sealed class TheDeserializeToIssuesExtensionForAJsonString - { - [Fact] - public void Should_Throw_If_JsonString_Is_Null() - { - // Given - string jsonString = null; - - // When - var result = Record.Exception(() => jsonString.DeserializeToIssues()); - - // Then - result.IsArgumentNullException("jsonString"); - } - - [Fact] - public void Should_Return_An_Empty_List_For_An_Empty_Array() - { - // Given - string jsonString = "[]"; - - // When - var result = jsonString.DeserializeToIssues(); - - // Then - result.ShouldBeEmpty(); - } - } - - public sealed class TheDeserializeToIssueExtensionForAJsonFile - { - [Fact] - public void Should_Throw_If_FilePath_Is_Null() - { - // Given - FilePath filePath = null; - - // When - var result = Record.Exception(() => filePath.DeserializeToIssue()); - - // Then - result.IsArgumentNullException("filePath"); - } - - [Fact] - public void Should_Return_Issue() - { - // Given - var filePath = new FilePath("Testfiles/issue.json"); - - // When - var result = filePath.DeserializeToIssue(); - - // Then - IssueChecker.Check( - result, - IssueBuilder.NewIssue( - "Something went wrong.", - "TestProvider", - "Test Provider") - .InProject(@"src\Foo\Bar.csproj", "Bar") - .InFile(@"src\Foo\Bar.cs", 42) - .OfRule("Rule", new Uri("https://google.com")) - .WithPriority(IssuePriority.Warning)); - } - } - - public sealed class TheDeserializeToIssuesExtensionForAJsonFile - { - [Fact] - public void Should_Throw_If_FilePath_Is_Null() - { - // Given - FilePath filePath = null; - - // When - var result = Record.Exception(() => filePath.DeserializeToIssues()); - - // Then - result.IsArgumentNullException("filePath"); - } - - [Fact] - public void Should_Return_An_Empty_List_For_An_Empty_Array() - { - // Given - var filePath = new FilePath("Testfiles/empty-array.json"); - - // When - var result = filePath.DeserializeToIssues(); - - // Then - result.ShouldBeEmpty(); - } - - [Fact] - public void Should_Return_List_Of_Issues() - { - // Given - var filePath = new FilePath("Testfiles/issues.json"); - - // When - var result = filePath.DeserializeToIssues().ToList(); - - // Then - result.Count.ShouldBe(2); - IssueChecker.Check( - result[0], - IssueBuilder.NewIssue( - "Something went wrong.", - "TestProvider", - "Test Provider") - .InProject(@"src\Foo\Bar.csproj", "Bar") - .InFile(@"src\Foo\Bar.cs", 42) - .OfRule("Rule", new Uri("https://google.com")) - .WithPriority(IssuePriority.Warning)); - IssueChecker.Check( - result[1], - IssueBuilder.NewIssue( - "Something went wrong again.", - "TestProvider", - "Test Provider") - .InProject(@"src\Foo\Bar.csproj", "Bar") - .InFile(@"src\Foo\Bar2.cs") - .WithPriority(IssuePriority.Warning)); - } - } - - public sealed class TheToSerializableIssueExtension - { - [Fact] - public void Should_Throw_If_Issue_Is_Null() - { - // Given - IIssue issue = null; - - // When - var result = Record.Exception(() => issue.ToSerializableIssue()); - - // Then - result.IsArgumentNullException("issue"); - } - } - - public sealed class TheToIssueExtension - { - [Fact] - public void Should_Throw_If_SerializableIssue_Is_Null() - { - // Given - SerializableIssue serializableIssue = null; - - // When - var result = Record.Exception(() => serializableIssue.ToIssue()); - - // Then - result.IsArgumentNullException("serializableIssue"); - } - } - } -} \ No newline at end of file diff --git a/src/Cake.Issues.Tests/IssueTests.cs b/src/Cake.Issues.Tests/IssueTests.cs index 3df1205d5..5a5d9b7ea 100644 --- a/src/Cake.Issues.Tests/IssueTests.cs +++ b/src/Cake.Issues.Tests/IssueTests.cs @@ -19,7 +19,9 @@ public void Should_Throw_If_Project_Path_Is_Invalid(string projectPath) var projectName = "foo"; var filePath = @"src\foo.cs"; var line = 100; - var message = "Message"; + var messageText = "MessageText"; + var messageHtml = "MessageHtml"; + var messageMarkdown = "MessageMarkdown"; var priority = 1; var priorityName = "Warning"; var rule = "Rule"; @@ -34,7 +36,9 @@ public void Should_Throw_If_Project_Path_Is_Invalid(string projectPath) projectName, filePath, line, - message, + messageText, + messageHtml, + messageMarkdown, priority, priorityName, rule, @@ -56,7 +60,9 @@ public void Should_Throw_If_File_Path_Is_Absolute(string projectPath) var projectName = "foo"; var filePath = @"src\foo.cs"; var line = 100; - var message = "Message"; + var messageText = "MessageText"; + var messageHtml = "MessageHtml"; + var messageMarkdown = "MessageMarkdown"; var priority = 1; var priorityName = "Warning"; var rule = "Rule"; @@ -71,7 +77,9 @@ public void Should_Throw_If_File_Path_Is_Absolute(string projectPath) projectName, filePath, line, - message, + messageText, + messageHtml, + messageMarkdown, priority, priorityName, rule, @@ -91,7 +99,9 @@ public void Should_Handle_Project_Paths_Which_Are_Null() var projectName = "foo"; var filePath = @"src\foo.cs"; var line = 10; - var message = "Message"; + var messageText = "MessageText"; + var messageHtml = "MessageHtml"; + var messageMarkdown = "MessageMarkdown"; var priority = 1; var priorityName = "Warning"; var rule = "Rule"; @@ -106,7 +116,9 @@ public void Should_Handle_Project_Paths_Which_Are_Null() projectName, filePath, line, - message, + messageText, + messageHtml, + messageMarkdown, priority, priorityName, rule, @@ -126,7 +138,9 @@ public void Should_Handle_Project_Paths_Which_Are_Empty() var projectName = "foo"; var filePath = @"src\foo.cs"; var line = 10; - var message = "Message"; + var messageText = "MessageText"; + var messageHtml = "MessageHtml"; + var messageMarkdown = "MessageMarkdown"; var priority = 1; var priorityName = "Warning"; var rule = "Rule"; @@ -141,7 +155,9 @@ public void Should_Handle_Project_Paths_Which_Are_Empty() projectName, filePath, line, - message, + messageText, + messageHtml, + messageMarkdown, priority, priorityName, rule, @@ -161,7 +177,9 @@ public void Should_Handle_Project_Paths_Which_Are_WhiteSpace() var projectName = "foo"; var filePath = @"src\foo.cs"; var line = 10; - var message = "Message"; + var messageText = "MessageText"; + var messageHtml = "MessageHtml"; + var messageMarkdown = "MessageMarkdown"; var priority = 1; var priorityName = "Warning"; var rule = "Rule"; @@ -176,7 +194,9 @@ public void Should_Handle_Project_Paths_Which_Are_WhiteSpace() projectName, filePath, line, - message, + messageText, + messageHtml, + messageMarkdown, priority, priorityName, rule, @@ -196,7 +216,9 @@ public void Should_Set_ProjectFileRelativePath(string projectPath) var projectName = "foo"; var filePath = @"src\foo.cs"; var line = 10; - var message = "Message"; + var messageText = "MessageText"; + var messageHtml = "MessageHtml"; + var messageMarkdown = "MessageMarkdown"; var priority = 1; var priorityName = "Warning"; var rule = "Rule"; @@ -211,7 +233,9 @@ public void Should_Set_ProjectFileRelativePath(string projectPath) projectName, filePath, line, - message, + messageText, + messageHtml, + messageMarkdown, priority, priorityName, rule, @@ -234,7 +258,9 @@ public void Should_Handle_Projects_Which_Are_Null() string projectName = null; var filePath = @"src\foo.cs"; var line = 10; - var message = "Message"; + var messageText = "MessageText"; + var messageHtml = "MessageHtml"; + var messageMarkdown = "MessageMarkdown"; var priority = 1; var priorityName = "Warning"; var rule = "Rule"; @@ -249,7 +275,9 @@ public void Should_Handle_Projects_Which_Are_Null() projectName, filePath, line, - message, + messageText, + messageHtml, + messageMarkdown, priority, priorityName, rule, @@ -269,7 +297,9 @@ public void Should_Handle_Projects_Which_Are_Empty() var projectName = string.Empty; var filePath = @"src\foo.cs"; var line = 10; - var message = "Message"; + var messageText = "MessageText"; + var messageHtml = "MessageHtml"; + var messageMarkdown = "MessageMarkdown"; var priority = 1; var priorityName = "Warning"; var rule = "Rule"; @@ -284,7 +314,9 @@ public void Should_Handle_Projects_Which_Are_Empty() projectName, filePath, line, - message, + messageText, + messageHtml, + messageMarkdown, priority, priorityName, rule, @@ -304,7 +336,9 @@ public void Should_Handle_Projects_Which_Are_WhiteSpace() var projectName = " "; var filePath = @"src\foo.cs"; var line = 10; - var message = "Message"; + var messageText = "MessageText"; + var messageHtml = "MessageHtml"; + var messageMarkdown = "MessageMarkdown"; var priority = 1; var priorityName = "Warning"; var rule = "Rule"; @@ -319,7 +353,9 @@ public void Should_Handle_Projects_Which_Are_WhiteSpace() projectName, filePath, line, - message, + messageText, + messageHtml, + messageMarkdown, priority, priorityName, rule, @@ -339,7 +375,9 @@ public void Should_Set_ProjectName(string projectName) var projectPath = @"src\foo.csproj"; var filePath = @"src\foo.cs"; var line = 10; - var message = "Message"; + var messageText = "MessageText"; + var messageHtml = "MessageHtml"; + var messageMarkdown = "MessageMarkdown"; var priority = 1; var priorityName = "Warning"; var rule = "Rule"; @@ -354,7 +392,9 @@ public void Should_Set_ProjectName(string projectName) projectName, filePath, line, - message, + messageText, + messageHtml, + messageMarkdown, priority, priorityName, rule, @@ -377,7 +417,9 @@ public void Should_Throw_If_File_Path_Is_Invalid(string filePath) var projectPath = @"src\foo.csproj"; var projectName = "foo"; var line = 100; - var message = "Message"; + var messageText = "MessageText"; + var messageHtml = "MessageHtml"; + var messageMarkdown = "MessageMarkdown"; var priority = 1; var priorityName = "Warning"; var rule = "Rule"; @@ -392,7 +434,9 @@ public void Should_Throw_If_File_Path_Is_Invalid(string filePath) projectName, filePath, line, - message, + messageText, + messageHtml, + messageMarkdown, priority, priorityName, rule, @@ -414,7 +458,9 @@ public void Should_Throw_If_File_Path_Is_Absolute(string filePath) var projectPath = @"src\foo.csproj"; var projectName = "foo"; var line = 100; - var message = "Message"; + var messageText = "MessageText"; + var messageHtml = "MessageHtml"; + var messageMarkdown = "MessageMarkdown"; var priority = 1; var priorityName = "Warning"; var rule = "Rule"; @@ -429,7 +475,9 @@ public void Should_Throw_If_File_Path_Is_Absolute(string filePath) projectName, filePath, line, - message, + messageText, + messageHtml, + messageMarkdown, priority, priorityName, rule, @@ -449,7 +497,9 @@ public void Should_Handle_File_Paths_Which_Are_Null() var projectName = "foo"; string filePath = null; int? line = null; - var message = "Message"; + var messageText = "MessageText"; + var messageHtml = "MessageHtml"; + var messageMarkdown = "MessageMarkdown"; var priority = 1; var priorityName = "Warning"; var rule = "Rule"; @@ -464,7 +514,9 @@ public void Should_Handle_File_Paths_Which_Are_Null() projectName, filePath, line, - message, + messageText, + messageHtml, + messageMarkdown, priority, priorityName, rule, @@ -484,7 +536,9 @@ public void Should_Handle_File_Paths_Which_Are_Empty() var projectName = "foo"; var filePath = string.Empty; int? line = null; - var message = "Message"; + var messageText = "MessageText"; + var messageHtml = "MessageHtml"; + var messageMarkdown = "MessageMarkdown"; var priority = 1; var priorityName = "Warning"; var rule = "Rule"; @@ -499,7 +553,9 @@ public void Should_Handle_File_Paths_Which_Are_Empty() projectName, filePath, line, - message, + messageText, + messageHtml, + messageMarkdown, priority, priorityName, rule, @@ -519,7 +575,9 @@ public void Should_Handle_File_Paths_Which_Are_WhiteSpace() var projectName = "foo"; var filePath = " "; int? line = null; - var message = "Message"; + var messageText = "MessageText"; + var messageHtml = "MessageHtml"; + var messageMarkdown = "MessageMarkdown"; var priority = 1; var priorityName = "Warning"; var rule = "Rule"; @@ -534,7 +592,9 @@ public void Should_Handle_File_Paths_Which_Are_WhiteSpace() projectName, filePath, line, - message, + messageText, + messageHtml, + messageMarkdown, priority, priorityName, rule, @@ -562,7 +622,9 @@ public void Should_Set_File_Path(string filePath, string expectedFilePath) var projectPath = @"src\foo.csproj"; var projectName = "foo"; var line = 10; - var message = "Message"; + var messageText = "MessageText"; + var messageHtml = "MessageHtml"; + var messageMarkdown = "MessageMarkdown"; var priority = 1; var priorityName = "Warning"; var rule = "Rule"; @@ -577,7 +639,9 @@ public void Should_Set_File_Path(string filePath, string expectedFilePath) projectName, filePath, line, - message, + messageText, + messageHtml, + messageMarkdown, priority, priorityName, rule, @@ -601,7 +665,9 @@ public void Should_Throw_If_Line_Is_Negative() var projectName = "foo"; var filePath = @"src\foo.cs"; var line = -1; - var message = "Message"; + var messageText = "MessageText"; + var messageHtml = "MessageHtml"; + var messageMarkdown = "MessageMarkdown"; var priority = 1; var priorityName = "Warning"; var rule = "Rule"; @@ -616,7 +682,9 @@ public void Should_Throw_If_Line_Is_Negative() projectName, filePath, line, - message, + messageText, + messageHtml, + messageMarkdown, priority, priorityName, rule, @@ -636,7 +704,9 @@ public void Should_Throw_If_Line_Is_Zero() var projectName = "foo"; var filePath = @"src\foo.cs"; var line = 0; - var message = "Message"; + var messageText = "MessageText"; + var messageHtml = "MessageHtml"; + var messageMarkdown = "MessageMarkdown"; var priority = 1; var priorityName = "Warning"; var rule = "Rule"; @@ -651,7 +721,9 @@ public void Should_Throw_If_Line_Is_Zero() projectName, filePath, line, - message, + messageText, + messageHtml, + messageMarkdown, priority, priorityName, rule, @@ -671,7 +743,9 @@ public void Should_Throw_If_Line_Is_Set_But_No_File() var projectName = "foo"; string filePath = null; var line = 10; - var message = "Message"; + var messageText = "MessageText"; + var messageHtml = "MessageHtml"; + var messageMarkdown = "MessageMarkdown"; var priority = 1; var priorityName = "Warning"; var rule = "Rule"; @@ -686,7 +760,9 @@ public void Should_Throw_If_Line_Is_Set_But_No_File() projectName, filePath, line, - message, + messageText, + messageHtml, + messageMarkdown, priority, priorityName, rule, @@ -708,7 +784,9 @@ public void Should_Set_Line(int? line) var projectPath = @"src\foo.csproj"; var projectName = "foo"; var filePath = @"src\foo.cs"; - var message = "Message"; + var messageText = "MessageText"; + var messageHtml = "MessageHtml"; + var messageMarkdown = "MessageMarkdown"; var priority = 1; var priorityName = "Warning"; var rule = "Rule"; @@ -723,7 +801,9 @@ public void Should_Set_Line(int? line) projectName, filePath, line, - message, + messageText, + messageHtml, + messageMarkdown, priority, priorityName, rule, @@ -736,17 +816,19 @@ public void Should_Set_Line(int? line) } } - public sealed class TheMessageArgument + public sealed class TheMessageTextArgument { [Fact] - public void Should_Throw_If_Message_Is_Null() + public void Should_Throw_If_MessageText_Is_Null() { // Given var projectPath = @"src\foo.csproj"; var projectName = "foo"; var filePath = @"src\foo.cs"; var line = 10; - string message = null; + string messageText = null; + var messageHtml = "MessageHtml"; + var messageMarkdown = "MessageMarkdown"; var priority = 1; var priorityName = "Warning"; var rule = "Rule"; @@ -761,7 +843,9 @@ public void Should_Throw_If_Message_Is_Null() projectName, filePath, line, - message, + messageText, + messageHtml, + messageMarkdown, priority, priorityName, rule, @@ -770,18 +854,20 @@ public void Should_Throw_If_Message_Is_Null() providerName)); // Then - result.IsArgumentNullException("message"); + result.IsArgumentNullException("messageText"); } [Fact] - public void Should_Throw_If_Message_Is_Empty() + public void Should_Throw_If_MessageText_Is_Empty() { // Given var projectPath = @"src\foo.csproj"; var projectName = "foo"; var filePath = @"src\foo.cs"; var line = 10; - var message = string.Empty; + var messageText = string.Empty; + var messageHtml = "MessageHtml"; + var messageMarkdown = "MessageMarkdown"; var priority = 1; var priorityName = "Warning"; var rule = "Rule"; @@ -796,7 +882,9 @@ public void Should_Throw_If_Message_Is_Empty() projectName, filePath, line, - message, + messageText, + messageHtml, + messageMarkdown, priority, priorityName, rule, @@ -805,18 +893,20 @@ public void Should_Throw_If_Message_Is_Empty() providerName)); // Then - result.IsArgumentOutOfRangeException("message"); + result.IsArgumentOutOfRangeException("messageText"); } [Fact] - public void Should_Throw_If_Message_Is_WhiteSpace() + public void Should_Throw_If_MessageText_Is_WhiteSpace() { // Given var projectPath = @"src\foo.csproj"; var projectName = "foo"; var filePath = @"src\foo.cs"; var line = 10; - var message = " "; + var messageText = " "; + var messageHtml = "MessageHtml"; + var messageMarkdown = "MessageMarkdown"; var priority = 1; var priorityName = "Warning"; var rule = "Rule"; @@ -831,7 +921,9 @@ public void Should_Throw_If_Message_Is_WhiteSpace() projectName, filePath, line, - message, + messageText, + messageHtml, + messageMarkdown, priority, priorityName, rule, @@ -840,18 +932,20 @@ public void Should_Throw_If_Message_Is_WhiteSpace() providerName)); // Then - result.IsArgumentOutOfRangeException("message"); + result.IsArgumentOutOfRangeException("messageText"); } [Theory] - [InlineData("message")] - public void Should_Set_Message(string message) + [InlineData("messageText")] + public void Should_Set_MessageText(string messageText) { // Given var projectPath = @"src\foo.csproj"; var projectName = "foo"; var filePath = @"src\foo.cs"; var line = 10; + var messageHtml = "MessageHtml"; + var messageMarkdown = "MessageMarkdown"; var priority = 1; var priorityName = "Warning"; var rule = "Rule"; @@ -866,7 +960,9 @@ public void Should_Set_Message(string message) projectName, filePath, line, - message, + messageText, + messageHtml, + messageMarkdown, priority, priorityName, rule, @@ -875,7 +971,97 @@ public void Should_Set_Message(string message) providerName); // Then - issue.Message.ShouldBe(message); + issue.MessageText.ShouldBe(messageText); + } + } + + public sealed class TheMessageHtmlArgument + { + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + [InlineData("messageHtml")] + public void Should_Set_MessageHtml(string messageHtml) + { + // Given + var projectPath = @"src\foo.csproj"; + var projectName = "foo"; + var filePath = @"src\foo.cs"; + var line = 10; + var messageText = "MessageText"; + var messageMarkdown = "MessageMarkdown"; + var priority = 1; + var priorityName = "Warning"; + var rule = "Rule"; + var ruleUri = new Uri("https://google.com"); + var providerType = "ProviderType"; + var providerName = "ProviderName"; + + // When + var issue = + new Issue( + projectPath, + projectName, + filePath, + line, + messageText, + messageHtml, + messageMarkdown, + priority, + priorityName, + rule, + ruleUri, + providerType, + providerName); + + // Then + issue.MessageHtml.ShouldBe(messageHtml); + } + } + + public sealed class TheMessageMarkdownArgument + { + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + [InlineData("messageMarkdown")] + public void Should_Set_MessageHtml(string messageMarkdown) + { + // Given + var projectPath = @"src\foo.csproj"; + var projectName = "foo"; + var filePath = @"src\foo.cs"; + var line = 10; + var messageText = "MessageText"; + var messageHtml = "MessageHtml"; + var priority = 1; + var priorityName = "Warning"; + var rule = "Rule"; + var ruleUri = new Uri("https://google.com"); + var providerType = "ProviderType"; + var providerName = "ProviderName"; + + // When + var issue = + new Issue( + projectPath, + projectName, + filePath, + line, + messageText, + messageHtml, + messageMarkdown, + priority, + priorityName, + rule, + ruleUri, + providerType, + providerName); + + // Then + issue.MessageMarkdown.ShouldBe(messageMarkdown); } } @@ -895,7 +1081,9 @@ public void Should_Set_Priority(int? priority) var projectName = "foo"; var filePath = @"src\foo.cs"; var line = 10; - var message = "Message"; + var messageText = "MessageText"; + var messageHtml = "MessageHtml"; + var messageMarkdown = "MessageMarkdown"; var priorityName = "Warning"; var rule = "Rule"; var ruleUri = new Uri("https://google.com"); @@ -909,7 +1097,9 @@ public void Should_Set_Priority(int? priority) projectName, filePath, line, - message, + messageText, + messageHtml, + messageMarkdown, priority, priorityName, rule, @@ -932,7 +1122,9 @@ public void Should_Handle_PriorityNames_Which_Are_Null() var projectName = "foo"; var filePath = @"src\foo.cs"; var line = 10; - var message = "Message"; + var messageText = "MessageText"; + var messageHtml = "MessageHtml"; + var messageMarkdown = "MessageMarkdown"; var priority = 1; string priorityName = null; var rule = "Rule"; @@ -947,7 +1139,9 @@ public void Should_Handle_PriorityNames_Which_Are_Null() projectName, filePath, line, - message, + messageText, + messageHtml, + messageMarkdown, priority, priorityName, rule, @@ -967,7 +1161,9 @@ public void Should_Handle_PriorityNames_Which_Are_Empty() var projectName = "foo"; var filePath = @"src\foo.cs"; var line = 10; - var message = "Message"; + var messageText = "MessageText"; + var messageHtml = "MessageHtml"; + var messageMarkdown = "MessageMarkdown"; var priority = 1; var priorityName = string.Empty; var rule = "Rule"; @@ -982,7 +1178,9 @@ public void Should_Handle_PriorityNames_Which_Are_Empty() projectName, filePath, line, - message, + messageText, + messageHtml, + messageMarkdown, priority, priorityName, rule, @@ -1002,7 +1200,9 @@ public void Should_Handle_PriorityNames_Which_Are_WhiteSpace() var projectName = "foo"; var filePath = @"src\foo.cs"; var line = 10; - var message = "Message"; + var messageText = "MessageText"; + var messageHtml = "MessageHtml"; + var messageMarkdown = "MessageMarkdown"; var priority = 1; var priorityName = " "; var rule = "Rule"; @@ -1017,7 +1217,9 @@ public void Should_Handle_PriorityNames_Which_Are_WhiteSpace() projectName, filePath, line, - message, + messageText, + messageHtml, + messageMarkdown, priority, priorityName, rule, @@ -1038,7 +1240,9 @@ public void Should_Set_Priority_Name(string priorityName) var projectName = "foo"; var filePath = @"src\foo.cs"; var line = 10; - var message = "Message"; + var messageText = "MessageText"; + var messageHtml = "MessageHtml"; + var messageMarkdown = "MessageMarkdown"; var priority = 1; var rule = "Rule"; var ruleUri = new Uri("https://google.com"); @@ -1052,7 +1256,9 @@ public void Should_Set_Priority_Name(string priorityName) projectName, filePath, line, - message, + messageText, + messageHtml, + messageMarkdown, priority, priorityName, rule, @@ -1078,7 +1284,9 @@ public void Should_Set_Rule(string rule) var projectName = "foo"; var filePath = @"src\foo.cs"; var line = 10; - var message = "Message"; + var messageText = "MessageText"; + var messageHtml = "MessageHtml"; + var messageMarkdown = "MessageMarkdown"; var priority = 1; var priorityName = "Warning"; var ruleUri = new Uri("https://google.com"); @@ -1092,7 +1300,9 @@ public void Should_Set_Rule(string rule) projectName, filePath, line, - message, + messageText, + messageHtml, + messageMarkdown, priority, priorityName, rule, @@ -1115,7 +1325,9 @@ public void Should_Set_Rule_Url() var projectName = "foo"; var filePath = @"src\foo.cs"; var line = 10; - var message = "Message"; + var messageText = "MessageText"; + var messageHtml = "MessageHtml"; + var messageMarkdown = "MessageMarkdown"; var priority = 1; var priorityName = "Warning"; var rule = "Rule"; @@ -1130,7 +1342,9 @@ public void Should_Set_Rule_Url() projectName, filePath, line, - message, + messageText, + messageHtml, + messageMarkdown, priority, priorityName, rule, @@ -1150,7 +1364,9 @@ public void Should_Set_Rule_Url_If_Null() var projectName = "foo"; var filePath = @"src\foo.cs"; var line = 10; - var message = "Message"; + var messageText = "MessageText"; + var messageHtml = "MessageHtml"; + var messageMarkdown = "MessageMarkdown"; var priority = 1; var priorityName = "Warning"; var rule = "Rule"; @@ -1165,7 +1381,9 @@ public void Should_Set_Rule_Url_If_Null() projectName, filePath, line, - message, + messageText, + messageHtml, + messageMarkdown, priority, priorityName, rule, @@ -1188,7 +1406,9 @@ public void Should_Throw_If_Provider_Type_Is_Null() var projectName = "foo"; var filePath = @"src\foo.cs"; var line = 10; - var message = "Message"; + var messageText = "MessageText"; + var messageHtml = "MessageHtml"; + var messageMarkdown = "MessageMarkdown"; var priority = 1; var priorityName = "Warning"; var rule = "Rule"; @@ -1203,7 +1423,9 @@ public void Should_Throw_If_Provider_Type_Is_Null() projectName, filePath, line, - message, + messageText, + messageHtml, + messageMarkdown, priority, priorityName, rule, @@ -1223,7 +1445,9 @@ public void Should_Throw_If_Provider_Type_Is_Empty() var projectName = "foo"; var filePath = @"src\foo.cs"; var line = 10; - var message = "Message"; + var messageText = "MessageText"; + var messageHtml = "MessageHtml"; + var messageMarkdown = "MessageMarkdown"; var priority = 1; var priorityName = "Warning"; var rule = "Rule"; @@ -1238,7 +1462,9 @@ public void Should_Throw_If_Provider_Type_Is_Empty() projectName, filePath, line, - message, + messageText, + messageHtml, + messageMarkdown, priority, priorityName, rule, @@ -1258,7 +1484,9 @@ public void Should_Throw_If_Provider_Type_Is_WhiteSpace() var projectName = "foo"; var filePath = @"src\foo.cs"; var line = 10; - var message = "Message"; + var messageText = "MessageText"; + var messageHtml = "MessageHtml"; + var messageMarkdown = "MessageMarkdown"; var priority = 1; var priorityName = "Warning"; var rule = "Rule"; @@ -1273,7 +1501,9 @@ public void Should_Throw_If_Provider_Type_Is_WhiteSpace() projectName, filePath, line, - message, + messageText, + messageHtml, + messageMarkdown, priority, priorityName, rule, @@ -1294,7 +1524,9 @@ public void Should_Set_ProviderType(string providerType) var projectName = "foo"; var filePath = @"src\foo.cs"; var line = 10; - var message = "Message"; + var messageText = "MessageText"; + var messageHtml = "MessageHtml"; + var messageMarkdown = "MessageMarkdown"; var priority = 1; var priorityName = "Warning"; var rule = "Rule"; @@ -1308,7 +1540,9 @@ public void Should_Set_ProviderType(string providerType) projectName, filePath, line, - message, + messageText, + messageHtml, + messageMarkdown, priority, priorityName, rule, @@ -1331,7 +1565,9 @@ public void Should_Throw_If_Provider_Name_Is_Null() var projectName = "foo"; var filePath = @"src\foo.cs"; var line = 10; - var message = "Message"; + var messageText = "MessageText"; + var messageHtml = "MessageHtml"; + var messageMarkdown = "MessageMarkdown"; var priority = 1; var priorityName = "Warning"; var rule = "Rule"; @@ -1346,7 +1582,9 @@ public void Should_Throw_If_Provider_Name_Is_Null() projectName, filePath, line, - message, + messageText, + messageHtml, + messageMarkdown, priority, priorityName, rule, @@ -1366,7 +1604,9 @@ public void Should_Throw_If_Provider_Name_Is_Empty() var projectName = "foo"; var filePath = @"src\foo.cs"; var line = 10; - var message = "Message"; + var messageText = "MessageText"; + var messageHtml = "MessageHtml"; + var messageMarkdown = "MessageMarkdown"; var priority = 1; var priorityName = "Warning"; var rule = "Rule"; @@ -1381,7 +1621,9 @@ public void Should_Throw_If_Provider_Name_Is_Empty() projectName, filePath, line, - message, + messageText, + messageHtml, + messageMarkdown, priority, priorityName, rule, @@ -1401,7 +1643,9 @@ public void Should_Throw_If_Provider_Name_Is_WhiteSpace() var projectName = "foo"; var filePath = @"src\foo.cs"; var line = 10; - var message = "Message"; + var messageText = "MessageText"; + var messageHtml = "MessageHtml"; + var messageMarkdown = "MessageMarkdown"; var priority = 1; var priorityName = "Warning"; var rule = "Rule"; @@ -1416,7 +1660,9 @@ public void Should_Throw_If_Provider_Name_Is_WhiteSpace() projectName, filePath, line, - message, + messageText, + messageHtml, + messageMarkdown, priority, priorityName, rule, @@ -1437,7 +1683,9 @@ public void Should_Set_ProviderName(string providerName) var projectName = "foo"; var filePath = @"src\foo.cs"; var line = 10; - var message = "Message"; + var messageText = "MessageText"; + var messageHtml = "MessageHtml"; + var messageMarkdown = "MessageMarkdown"; var priority = 1; var priorityName = "Warning"; var rule = "Rule"; @@ -1451,7 +1699,9 @@ public void Should_Set_ProviderName(string providerName) projectName, filePath, line, - message, + messageText, + messageHtml, + messageMarkdown, priority, priorityName, rule, diff --git a/src/Cake.Issues.Tests/IssuesFixture.cs b/src/Cake.Issues.Tests/IssuesFixture.cs index 9c6baf706..eecba2799 100644 --- a/src/Cake.Issues.Tests/IssuesFixture.cs +++ b/src/Cake.Issues.Tests/IssuesFixture.cs @@ -22,10 +22,10 @@ public IssuesFixture() public RepositorySettings Settings { get; set; } - public IEnumerable ReadIssues(IssueCommentFormat format) + public IEnumerable ReadIssues() { var issueReader = new IssuesReader(this.Log, this.IssueProviders, this.Settings); - return issueReader.ReadIssues(format); + return issueReader.ReadIssues(); } } } diff --git a/src/Cake.Issues.Tests/Serialization/IssueDeserializationExtensionsTests.cs b/src/Cake.Issues.Tests/Serialization/IssueDeserializationExtensionsTests.cs new file mode 100644 index 000000000..f2ad8facb --- /dev/null +++ b/src/Cake.Issues.Tests/Serialization/IssueDeserializationExtensionsTests.cs @@ -0,0 +1,230 @@ +namespace Cake.Issues.Tests.Serialization +{ + using System; + using System.Linq; + using Cake.Core.IO; + using Cake.Issues.Serialization; + using Cake.Issues.Testing; + using Shouldly; + using Xunit; + + public sealed class IssueDeserializationExtensionsTests + { + public sealed class TheDeserializeToIssueExtensionForAJsonString + { + [Fact] + public void Should_Throw_If_JsonString_Is_Null() + { + // Given + string jsonString = null; + + // When + var result = Record.Exception(() => jsonString.DeserializeToIssue()); + + // Then + result.IsArgumentNullException("jsonString"); + } + + [Fact] + public void Should_Throw_If_JsonString_Has_Unknown_Version() + { + // Given + var jsonString = "{\"Version\": -1}"; + + // When + var result = Record.Exception(() => jsonString.DeserializeToIssue()); + + // Then + result.Message.ShouldBe("Not supported issue serialization format -1"); + } + } + + public sealed class TheDeserializeToIssuesExtensionForAJsonString + { + [Fact] + public void Should_Throw_If_JsonString_Is_Null() + { + // Given + string jsonString = null; + + // When + var result = Record.Exception(() => jsonString.DeserializeToIssues()); + + // Then + result.IsArgumentNullException("jsonString"); + } + + [Fact] + public void Should_Return_An_Empty_List_For_An_Empty_Array() + { + // Given + string jsonString = "[]"; + + // When + var result = jsonString.DeserializeToIssues(); + + // Then + result.ShouldBeEmpty(); + } + } + + public sealed class TheDeserializeToIssueExtensionForAJsonFile + { + [Fact] + public void Should_Throw_If_FilePath_Is_Null() + { + // Given + FilePath filePath = null; + + // When + var result = Record.Exception(() => filePath.DeserializeToIssue()); + + // Then + result.IsArgumentNullException("filePath"); + } + + [Fact] + public void Should_Return_Issue() + { + // Given + var filePath = new FilePath("Testfiles/issue.json"); + + // When + var result = filePath.DeserializeToIssue(); + + // Then + IssueChecker.Check( + result, + IssueBuilder.NewIssue( + "Something went wrong.", + "TestProvider", + "Test Provider") + .InProject(@"src\Foo\Bar.csproj", "Bar") + .InFile(@"src\Foo\Bar.cs", 42) + .OfRule("Rule", new Uri("https://google.com")) + .WithPriority(IssuePriority.Warning)); + } + + [Fact] + public void Should_Return_IssueV2() + { + // Given + var filePath = new FilePath("Testfiles/issueV2.json"); + + // When + var result = filePath.DeserializeToIssue(); + + // Then + IssueChecker.Check( + result, + IssueBuilder.NewIssue( + "Something went wrong.", + "TestProvider", + "Test Provider") + .WithMessageInHtmlFormat("Something went wrong.") + .WithMessageInMarkdownFormat("Something went **wrong**.") + .InProject(@"src\Foo\Bar.csproj", "Bar") + .InFile(@"src\Foo\Bar.cs", 42) + .OfRule("Rule", new Uri("https://google.com")) + .WithPriority(IssuePriority.Warning)); + } + } + + public sealed class TheDeserializeToIssuesExtensionForAJsonFile + { + [Fact] + public void Should_Throw_If_FilePath_Is_Null() + { + // Given + FilePath filePath = null; + + // When + var result = Record.Exception(() => filePath.DeserializeToIssues()); + + // Then + result.IsArgumentNullException("filePath"); + } + + [Fact] + public void Should_Return_An_Empty_List_For_An_Empty_Array() + { + // Given + var filePath = new FilePath("Testfiles/empty-array.json"); + + // When + var result = filePath.DeserializeToIssues(); + + // Then + result.ShouldBeEmpty(); + } + + [Fact] + public void Should_Return_List_Of_Issues() + { + // Given + var filePath = new FilePath("Testfiles/issues.json"); + + // When + var result = filePath.DeserializeToIssues().ToList(); + + // Then + result.Count.ShouldBe(2); + IssueChecker.Check( + result[0], + IssueBuilder.NewIssue( + "Something went wrong.", + "TestProvider", + "Test Provider") + .InProject(@"src\Foo\Bar.csproj", "Bar") + .InFile(@"src\Foo\Bar.cs", 42) + .OfRule("Rule", new Uri("https://google.com")) + .WithPriority(IssuePriority.Warning)); + IssueChecker.Check( + result[1], + IssueBuilder.NewIssue( + "Something went wrong again.", + "TestProvider", + "Test Provider") + .InProject(@"src\Foo\Bar.csproj", "Bar") + .InFile(@"src\Foo\Bar2.cs") + .WithPriority(IssuePriority.Warning)); + } + + [Fact] + public void Should_Return_List_Of_IssuesV2() + { + // Given + var filePath = new FilePath("Testfiles/issuesV2.json"); + + // When + var result = filePath.DeserializeToIssues().ToList(); + + // Then + result.Count.ShouldBe(2); + IssueChecker.Check( + result[0], + IssueBuilder.NewIssue( + "Something went wrong.", + "TestProvider", + "Test Provider") + .WithMessageInHtmlFormat("Something went wrong.") + .WithMessageInMarkdownFormat("Something went **wrong**.") + .InProject(@"src\Foo\Bar.csproj", "Bar") + .InFile(@"src\Foo\Bar.cs", 42) + .OfRule("Rule", new Uri("https://google.com")) + .WithPriority(IssuePriority.Warning)); + IssueChecker.Check( + result[1], + IssueBuilder.NewIssue( + "Something went wrong again.", + "TestProvider", + "Test Provider") + .WithMessageInHtmlFormat("Something went wrong again.") + .WithMessageInMarkdownFormat("Something went **wrong** again.") + .InProject(@"src\Foo\Bar.csproj", "Bar") + .InFile(@"src\Foo\Bar2.cs") + .WithPriority(IssuePriority.Warning)); + } + } + } +} \ No newline at end of file diff --git a/src/Cake.Issues.Tests/Serialization/IssueSerializationExtensionsTests.cs b/src/Cake.Issues.Tests/Serialization/IssueSerializationExtensionsTests.cs new file mode 100644 index 000000000..a280ff2af --- /dev/null +++ b/src/Cake.Issues.Tests/Serialization/IssueSerializationExtensionsTests.cs @@ -0,0 +1,1617 @@ +namespace Cake.Issues.Tests.Serialization +{ + using System; + using System.Collections.Generic; + using System.Linq; + using Cake.Core.IO; + using Cake.Issues.Serialization; + using Cake.Issues.Testing; + using Shouldly; + using Xunit; + + public sealed class IssueSerializationExtensionsTests + { + public sealed class TheSerializeToJsonStringExtensionForASingleIssue + { + [Fact] + public void Should_Throw_If_Issue_Is_Null() + { + // Given + IIssue issue = null; + + // When + var result = Record.Exception(() => issue.SerializeToJsonString()); + + // Then + result.IsArgumentNullException("issue"); + } + + [Fact] + public void Should_Give_Correct_Result_For_MessageText_After_Roundtrip() + { + // Given + var message = "message"; + var issue = + IssueBuilder + .NewIssue(message, "providerType", "providerName") + .Create(); + + // When + var result = issue.SerializeToJsonString().DeserializeToIssue(); + + // Then + result.MessageText.ShouldBe(message); + } + + [Fact] + public void Should_Give_Correct_Result_For_MessageMarkdown_After_Roundtrip() + { + // Given + var messageMarkdown = "messageMarkdown"; + var issue = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .WithMessageInMarkdownFormat(messageMarkdown) + .Create(); + + // When + var result = issue.SerializeToJsonString().DeserializeToIssue(); + + // Then + result.MessageMarkdown.ShouldBe(messageMarkdown); + } + + [Fact] + public void Should_Give_Correct_Result_For_MessageHtml_After_Roundtrip() + { + // Given + var messageHtml = "messageHtml"; + var issue = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .WithMessageInHtmlFormat(messageHtml) + .Create(); + + // When + var result = issue.SerializeToJsonString().DeserializeToIssue(); + + // Then + result.MessageHtml.ShouldBe(messageHtml); + } + + [Fact] + public void Should_Give_Correct_Result_For_ProviderType_After_Roundtrip() + { + // Given + var providerType = "providerType"; + var issue = + IssueBuilder + .NewIssue("message", providerType, "providerName") + .Create(); + + // When + var result = issue.SerializeToJsonString().DeserializeToIssue(); + + // Then + result.ProviderType.ShouldBe(providerType); + } + + [Fact] + public void Should_Give_Correct_Result_For_ProviderName_After_Roundtrip() + { + // Given + var providerName = "providerName"; + var issue = + IssueBuilder + .NewIssue("message", "providerType", providerName) + .Create(); + + // When + var result = issue.SerializeToJsonString().DeserializeToIssue(); + + // Then + result.ProviderName.ShouldBe(providerName); + } + + [Fact] + public void Should_Give_Correct_Result_For_ProjectFileRelativePath_After_Roundtrip() + { + // Given + var projectFileRelativePath = @"src/myproj.file"; + var issue = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .InProjectFile(projectFileRelativePath) + .Create(); + + // When + var result = issue.SerializeToJsonString().DeserializeToIssue(); + + // Then + result.ProjectFileRelativePath.FullPath.ShouldBe(projectFileRelativePath); + } + + [Fact] + public void Should_Give_Correct_Result_For_ProjectName_After_Roundtrip() + { + // Given + var projectName = "projectName"; + var issue = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .InProjectOfName(projectName) + .Create(); + + // When + var result = issue.SerializeToJsonString().DeserializeToIssue(); + + // Then + result.ProjectName.ShouldBe(projectName); + } + + [Fact] + public void Should_Give_Correct_Result_For_AffectedFileRelativePath_After_Roundtrip() + { + // Given + var affectedFileRelativePath = @"src/foo.bar"; + var issue = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .InFile(affectedFileRelativePath) + .Create(); + + // When + var result = issue.SerializeToJsonString().DeserializeToIssue(); + + // Then + result.AffectedFileRelativePath.FullPath.ShouldBe(affectedFileRelativePath); + } + + [Fact] + public void Should_Give_Correct_Result_For_Line_After_Roundtrip() + { + // Given + var line = 42; + var issue = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .InFile(@"src/foo.bar", line) + .Create(); + + // When + var result = issue.SerializeToJsonString().DeserializeToIssue(); + + // Then + result.Line.ShouldBe(line); + } + + [Fact] + public void Should_Give_Correct_Result_For_Priority_After_Roundtrip() + { + // Given + var priority = 42; + var issue = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .WithPriority(priority, "priorityName") + .Create(); + + // When + var result = issue.SerializeToJsonString().DeserializeToIssue(); + + // Then + result.Priority.ShouldBe(priority); + } + + [Fact] + public void Should_Give_Correct_Result_For_PriorityName_After_Roundtrip() + { + // Given + var priorityName = "priorityName"; + var issue = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .WithPriority(42, priorityName) + .Create(); + + // When + var result = issue.SerializeToJsonString().DeserializeToIssue(); + + // Then + result.PriorityName.ShouldBe(priorityName); + } + + [Fact] + public void Should_Give_Correct_Result_For_Rule_After_Roundtrip() + { + // Given + var rule = "rule"; + var issue = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .OfRule(rule) + .Create(); + + // When + var result = issue.SerializeToJsonString().DeserializeToIssue(); + + // Then + result.Rule.ShouldBe(rule); + } + + [Fact] + public void Should_Give_Correct_Result_For_RuleUrl_After_Roundtrip() + { + // Given + var ruleUrl = new Uri("https://rule.url"); + var issue = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .OfRule("rule", ruleUrl) + .Create(); + + // When + var result = issue.SerializeToJsonString().DeserializeToIssue(); + + // Then + result.RuleUrl.ToString().ShouldBe(ruleUrl.ToString()); + } + } + + public sealed class TheSerializeToJsonStringExtensionForAnEnumerableOfIssues + { + [Fact] + public void Should_Throw_If_Issue_Is_Null() + { + // Given + IEnumerable issues = null; + + // When + var result = Record.Exception(() => issues.SerializeToJsonString()); + + // Then + result.IsArgumentNullException("issues"); + } + + [Fact] + public void Should_Give_Correct_Result_For_MessageText_After_Roundtrip() + { + // Given + var messageText1 = "messageText1"; + var messageText2 = "messageText2"; + var issues = + new List + { + IssueBuilder + .NewIssue(messageText1, "providerType1", "providerName1") + .Create(), + IssueBuilder + .NewIssue(messageText2, "providerType2", "providerName2") + .Create(), + }; + + // When + var result = issues.SerializeToJsonString().DeserializeToIssues(); + + // Then + result.Count().ShouldBe(2); + result.First().MessageText.ShouldBe(messageText1); + result.Last().MessageText.ShouldBe(messageText2); + } + + [Fact] + public void Should_Give_Correct_Result_For_MessageMarkdown_After_Roundtrip() + { + // Given + var messageMarkdown1 = "messageMarkdown1"; + var messageMarkdown2 = "messageMarkdown2"; + var issues = + new List + { + IssueBuilder + .NewIssue("messageText1", "providerType1", "providerName1") + .WithMessageInMarkdownFormat(messageMarkdown1) + .Create(), + IssueBuilder + .NewIssue("messageText2", "providerType2", "providerName2") + .WithMessageInMarkdownFormat(messageMarkdown2) + .Create(), + }; + + // When + var result = issues.SerializeToJsonString().DeserializeToIssues(); + + // Then + result.Count().ShouldBe(2); + result.First().MessageMarkdown.ShouldBe(messageMarkdown1); + result.Last().MessageMarkdown.ShouldBe(messageMarkdown2); + } + + [Fact] + public void Should_Give_Correct_Result_For_MessageHtml_After_Roundtrip() + { + // Given + var messageHtml1 = "messageHtml1"; + var messageHtml2 = "messageHtml2"; + var issues = + new List + { + IssueBuilder + .NewIssue("messageText1", "providerType1", "providerName1") + .WithMessageInHtmlFormat(messageHtml1) + .Create(), + IssueBuilder + .NewIssue("messageText2", "providerType2", "providerName2") + .WithMessageInHtmlFormat(messageHtml2) + .Create(), + }; + + // When + var result = issues.SerializeToJsonString().DeserializeToIssues(); + + // Then + result.Count().ShouldBe(2); + result.First().MessageHtml.ShouldBe(messageHtml1); + result.Last().MessageHtml.ShouldBe(messageHtml2); + } + + [Fact] + public void Should_Give_Correct_Result_For_ProviderType_After_Roundtrip() + { + // Given + var providerType1 = "providerType1"; + var providerType2 = "providerType2"; + var issues = + new List + { + IssueBuilder + .NewIssue("message1", providerType1, "providerName1") + .Create(), + IssueBuilder + .NewIssue("message2", providerType2, "providerName2") + .Create(), + }; + + // When + var result = issues.SerializeToJsonString().DeserializeToIssues(); + + // Then + result.Count().ShouldBe(2); + result.First().ProviderType.ShouldBe(providerType1); + result.Last().ProviderType.ShouldBe(providerType2); + } + + [Fact] + public void Should_Give_Correct_Result_For_ProviderName_After_Roundtrip() + { + // Given + var providerName1 = "providerName1"; + var providerName2 = "providerName2"; + var issues = + new List + { + IssueBuilder + .NewIssue("message1", "providerType1", providerName1) + .Create(), + IssueBuilder + .NewIssue("message2", "providerType2", providerName2) + .Create(), + }; + + // When + var result = issues.SerializeToJsonString().DeserializeToIssues(); + + // Then + result.Count().ShouldBe(2); + result.First().ProviderName.ShouldBe(providerName1); + result.Last().ProviderName.ShouldBe(providerName2); + } + + [Fact] + public void Should_Give_Correct_Result_For_ProjectFileRelativePath_After_Roundtrip() + { + // Given + var projectFileRelativePath1 = @"src/myproj1.file"; + var projectFileRelativePath2 = @"src/myproj2.file"; + var issues = + new List + { + IssueBuilder + .NewIssue("message1", "providerType1", "providerName1") + .InProjectFile(projectFileRelativePath1) + .Create(), + IssueBuilder + .NewIssue("message2", "providerType2", "providerName2") + .InProjectFile(projectFileRelativePath2) + .Create(), + }; + + // When + var result = issues.SerializeToJsonString().DeserializeToIssues(); + + // Then + result.Count().ShouldBe(2); + result.First().ProjectFileRelativePath.FullPath.ShouldBe(projectFileRelativePath1); + result.Last().ProjectFileRelativePath.FullPath.ShouldBe(projectFileRelativePath2); + } + + [Fact] + public void Should_Give_Correct_Result_For_ProjectName_After_Roundtrip() + { + // Given + var projectName1 = "projectName1"; + var projectName2 = "projectName2"; + var issues = + new List + { + IssueBuilder + .NewIssue("message1", "providerType1", "providerName1") + .InProjectOfName(projectName1) + .Create(), + IssueBuilder + .NewIssue("message2", "providerType2", "providerName2") + .InProjectOfName(projectName2) + .Create(), + }; + + // When + var result = issues.SerializeToJsonString().DeserializeToIssues(); + + // Then + result.Count().ShouldBe(2); + result.First().ProjectName.ShouldBe(projectName1); + result.Last().ProjectName.ShouldBe(projectName2); + } + + [Fact] + public void Should_Give_Correct_Result_For_AffectedFileRelativePath_After_Roundtrip() + { + // Given + var affectedFileRelativePath1 = @"src/foo1.bar"; + var affectedFileRelativePath2 = @"src/foo2.bar"; + var issues = + new List + { + IssueBuilder + .NewIssue("message1", "providerType1", "providerName1") + .InFile(affectedFileRelativePath1) + .Create(), + IssueBuilder + .NewIssue("message2", "providerType2", "providerName2") + .InFile(affectedFileRelativePath2) + .Create(), + }; + + // When + var result = issues.SerializeToJsonString().DeserializeToIssues(); + + // Then + result.Count().ShouldBe(2); + result.First().AffectedFileRelativePath.FullPath.ShouldBe(affectedFileRelativePath1); + result.Last().AffectedFileRelativePath.FullPath.ShouldBe(affectedFileRelativePath2); + } + + [Fact] + public void Should_Give_Correct_Result_For_Line_After_Roundtrip() + { + // Given + var line1 = 23; + var line2 = 42; + var issues = + new List + { + IssueBuilder + .NewIssue("message1", "providerType1", "providerName1") + .InFile(@"src/foo.bar", line1) + .Create(), + IssueBuilder + .NewIssue("message2", "providerType2", "providerName2") + .InFile(@"src/foo.bar", line2) + .Create(), + }; + + // When + var result = issues.SerializeToJsonString().DeserializeToIssues(); + + // Then + result.Count().ShouldBe(2); + result.First().Line.ShouldBe(line1); + result.Last().Line.ShouldBe(line2); + } + + [Fact] + public void Should_Give_Correct_Result_For_Priority_After_Roundtrip() + { + // Given + var priority1 = 23; + var priority2 = 42; + var issues = + new List + { + IssueBuilder + .NewIssue("message1", "providerType1", "providerName1") + .WithPriority(priority1, "priorityName") + .Create(), + IssueBuilder + .NewIssue("message2", "providerType2", "providerName2") + .WithPriority(priority2, "priorityName") + .Create(), + }; + + // When + var result = issues.SerializeToJsonString().DeserializeToIssues(); + + // Then + result.Count().ShouldBe(2); + result.First().Priority.ShouldBe(priority1); + result.Last().Priority.ShouldBe(priority2); + } + + [Fact] + public void Should_Give_Correct_Result_For_PriorityName_After_Roundtrip() + { + // Given + var priorityName1 = "priorityName1"; + var priorityName2 = "priorityName2"; + var issues = + new List + { + IssueBuilder + .NewIssue("message1", "providerType1", "providerName1") + .WithPriority(42, priorityName1) + .Create(), + IssueBuilder + .NewIssue("message2", "providerType2", "providerName2") + .WithPriority(42, priorityName2) + .Create(), + }; + + // When + var result = issues.SerializeToJsonString().DeserializeToIssues(); + + // Then + result.Count().ShouldBe(2); + result.First().PriorityName.ShouldBe(priorityName1); + result.Last().PriorityName.ShouldBe(priorityName2); + } + + [Fact] + public void Should_Give_Correct_Result_For_Rule_After_Roundtrip() + { + // Given + var rule1 = "rule1"; + var rule2 = "rule2"; + var issues = + new List + { + IssueBuilder + .NewIssue("message1", "providerType1", "providerName1") + .OfRule(rule1) + .Create(), + IssueBuilder + .NewIssue("message2", "providerType2", "providerName2") + .OfRule(rule2) + .Create(), + }; + + // When + var result = issues.SerializeToJsonString().DeserializeToIssues(); + + // Then + result.Count().ShouldBe(2); + result.First().Rule.ShouldBe(rule1); + result.Last().Rule.ShouldBe(rule2); + } + + [Fact] + public void Should_Give_Correct_Result_For_RuleUrl_After_Roundtrip() + { + // Given + var ruleUrl1 = new Uri("https://rule1.url"); + var ruleUrl2 = new Uri("https://rule2.url"); + var issues = + new List + { + IssueBuilder + .NewIssue("message1", "providerType1", "providerName1") + .OfRule("rule", ruleUrl1) + .Create(), + IssueBuilder + .NewIssue("message2", "providerType2", "providerName2") + .OfRule("rule", ruleUrl2) + .Create(), + }; + + // When + var result = issues.SerializeToJsonString().DeserializeToIssues(); + + // Then + result.Count().ShouldBe(2); + result.First().RuleUrl.ToString().ShouldBe(ruleUrl1.ToString()); + result.Last().RuleUrl.ToString().ShouldBe(ruleUrl2.ToString()); + } + } + + public sealed class TheSerializeToJsonFileExtensionForASingleIssue + { + [Fact] + public void Should_Throw_If_Issue_Is_Null() + { + // Given + IIssue issue = null; + var filePath = @"c:\issue.json"; + + // When + var result = Record.Exception(() => issue.SerializeToJsonFile(filePath)); + + // Then + result.IsArgumentNullException("issue"); + } + + [Fact] + public void Should_Throw_If_FilePath_Is_Null() + { + // Given + var issue = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .Create(); + FilePath filePath = null; + + // When + var result = Record.Exception(() => issue.SerializeToJsonFile(filePath)); + + // Then + result.IsArgumentNullException("filePath"); + } + + [Fact] + public void Should_Give_Correct_Result_For_MessageText_After_Roundtrip() + { + // Given + var messageText = "messageText"; + var issue = + IssueBuilder + .NewIssue(messageText, "providerType", "providerName") + .Create(); + var filePath = new FilePath(System.IO.Path.GetTempPath() + Guid.NewGuid().ToString() + ".json"); + + try + { + // When + issue.SerializeToJsonFile(filePath); + var result = filePath.DeserializeToIssue(); + + // Then + result.MessageText.ShouldBe(messageText); + } + finally + { + if (System.IO.File.Exists(filePath.FullPath)) + { + System.IO.File.Delete(filePath.FullPath); + } + } + } + + [Fact] + public void Should_Give_Correct_Result_For_MessageMarkdown_After_Roundtrip() + { + // Given + var messageMarkdown = "messageMarkdown"; + var issue = + IssueBuilder + .NewIssue("messageText", "providerType", "providerName") + .WithMessageInMarkdownFormat(messageMarkdown) + .Create(); + var filePath = new FilePath(System.IO.Path.GetTempPath() + Guid.NewGuid().ToString() + ".json"); + + try + { + // When + issue.SerializeToJsonFile(filePath); + var result = filePath.DeserializeToIssue(); + + // Then + result.MessageMarkdown.ShouldBe(messageMarkdown); + } + finally + { + if (System.IO.File.Exists(filePath.FullPath)) + { + System.IO.File.Delete(filePath.FullPath); + } + } + } + + [Fact] + public void Should_Give_Correct_Result_For_MessageHtml_After_Roundtrip() + { + // Given + var messageHtml = "messageHtml"; + var issue = + IssueBuilder + .NewIssue("messageText", "providerType", "providerName") + .WithMessageInHtmlFormat(messageHtml) + .Create(); + var filePath = new FilePath(System.IO.Path.GetTempPath() + Guid.NewGuid().ToString() + ".json"); + + try + { + // When + issue.SerializeToJsonFile(filePath); + var result = filePath.DeserializeToIssue(); + + // Then + result.MessageHtml.ShouldBe(messageHtml); + } + finally + { + if (System.IO.File.Exists(filePath.FullPath)) + { + System.IO.File.Delete(filePath.FullPath); + } + } + } + + [Fact] + public void Should_Give_Correct_Result_For_ProviderType_After_Roundtrip() + { + // Given + var providerType = "providerType"; + var issue = + IssueBuilder + .NewIssue("message", providerType, "providerName") + .Create(); + var filePath = new FilePath(System.IO.Path.GetTempPath() + Guid.NewGuid().ToString() + ".json"); + + try + { + // When + issue.SerializeToJsonFile(filePath); + var result = filePath.DeserializeToIssue(); + + // Then + result.ProviderType.ShouldBe(providerType); + } + finally + { + if (System.IO.File.Exists(filePath.FullPath)) + { + System.IO.File.Delete(filePath.FullPath); + } + } + } + + [Fact] + public void Should_Give_Correct_Result_For_ProviderName_After_Roundtrip() + { + // Given + var providerName = "providerName"; + var issue = + IssueBuilder + .NewIssue("message", "providerType", providerName) + .Create(); + var filePath = new FilePath(System.IO.Path.GetTempPath() + Guid.NewGuid().ToString() + ".json"); + + try + { + // When + issue.SerializeToJsonFile(filePath); + var result = filePath.DeserializeToIssue(); + + // Then + result.ProviderName.ShouldBe(providerName); + } + finally + { + if (System.IO.File.Exists(filePath.FullPath)) + { + System.IO.File.Delete(filePath.FullPath); + } + } + } + + [Fact] + public void Should_Give_Correct_Result_For_ProjectFileRelativePath_After_Roundtrip() + { + // Given + var projectFileRelativePath = @"src/myproj.file"; + var issue = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .InProjectFile(projectFileRelativePath) + .Create(); + var filePath = new FilePath(System.IO.Path.GetTempPath() + Guid.NewGuid().ToString() + ".json"); + + try + { + // When + issue.SerializeToJsonFile(filePath); + var result = filePath.DeserializeToIssue(); + + // Then + result.ProjectFileRelativePath.FullPath.ShouldBe(projectFileRelativePath); + } + finally + { + if (System.IO.File.Exists(filePath.FullPath)) + { + System.IO.File.Delete(filePath.FullPath); + } + } + } + + [Fact] + public void Should_Give_Correct_Result_For_ProjectName_After_Roundtrip() + { + // Given + var projectName = "projectName"; + var issue = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .InProjectOfName(projectName) + .Create(); + var filePath = new FilePath(System.IO.Path.GetTempPath() + Guid.NewGuid().ToString() + ".json"); + + try + { + // When + issue.SerializeToJsonFile(filePath); + var result = filePath.DeserializeToIssue(); + + // Then + result.ProjectName.ShouldBe(projectName); + } + finally + { + if (System.IO.File.Exists(filePath.FullPath)) + { + System.IO.File.Delete(filePath.FullPath); + } + } + } + + [Fact] + public void Should_Give_Correct_Result_For_AffectedFileRelativePath_After_Roundtrip() + { + // Given + var affectedFileRelativePath = @"src/foo.bar"; + var issue = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .InFile(affectedFileRelativePath) + .Create(); + var filePath = new FilePath(System.IO.Path.GetTempPath() + Guid.NewGuid().ToString() + ".json"); + + try + { + // When + issue.SerializeToJsonFile(filePath); + var result = filePath.DeserializeToIssue(); + + // Then + result.AffectedFileRelativePath.FullPath.ShouldBe(affectedFileRelativePath); + } + finally + { + if (System.IO.File.Exists(filePath.FullPath)) + { + System.IO.File.Delete(filePath.FullPath); + } + } + } + + [Fact] + public void Should_Give_Correct_Result_For_Line_After_Roundtrip() + { + // Given + var line = 42; + var issue = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .InFile(@"src/foo.bar", line) + .Create(); + var filePath = new FilePath(System.IO.Path.GetTempPath() + Guid.NewGuid().ToString() + ".json"); + + try + { + // When + issue.SerializeToJsonFile(filePath); + var result = filePath.DeserializeToIssue(); + + // Then + result.Line.ShouldBe(line); + } + finally + { + if (System.IO.File.Exists(filePath.FullPath)) + { + System.IO.File.Delete(filePath.FullPath); + } + } + } + + [Fact] + public void Should_Give_Correct_Result_For_Priority_After_Roundtrip() + { + // Given + var priority = 42; + var issue = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .WithPriority(priority, "priorityName") + .Create(); + var filePath = new FilePath(System.IO.Path.GetTempPath() + Guid.NewGuid().ToString() + ".json"); + + try + { + // When + issue.SerializeToJsonFile(filePath); + var result = filePath.DeserializeToIssue(); + + // Then + result.Priority.ShouldBe(priority); + } + finally + { + if (System.IO.File.Exists(filePath.FullPath)) + { + System.IO.File.Delete(filePath.FullPath); + } + } + } + + [Fact] + public void Should_Give_Correct_Result_For_PriorityName_After_Roundtrip() + { + // Given + var priorityName = "priorityName"; + var issue = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .WithPriority(42, priorityName) + .Create(); + var filePath = new FilePath(System.IO.Path.GetTempPath() + Guid.NewGuid().ToString() + ".json"); + + try + { + // When + issue.SerializeToJsonFile(filePath); + var result = filePath.DeserializeToIssue(); + + // Then + result.PriorityName.ShouldBe(priorityName); + } + finally + { + if (System.IO.File.Exists(filePath.FullPath)) + { + System.IO.File.Delete(filePath.FullPath); + } + } + } + + [Fact] + public void Should_Give_Correct_Result_For_Rule_After_Roundtrip() + { + // Given + var rule = "rule"; + var issue = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .OfRule(rule) + .Create(); + var filePath = new FilePath(System.IO.Path.GetTempPath() + Guid.NewGuid().ToString() + ".json"); + + try + { + // When + issue.SerializeToJsonFile(filePath); + var result = filePath.DeserializeToIssue(); + + // Then + result.Rule.ShouldBe(rule); + } + finally + { + if (System.IO.File.Exists(filePath.FullPath)) + { + System.IO.File.Delete(filePath.FullPath); + } + } + } + + [Fact] + public void Should_Give_Correct_Result_For_RuleUrl_After_Roundtrip() + { + // Given + var ruleUrl = new Uri("https://rule.url"); + var issue = + IssueBuilder + .NewIssue("message", "providerType", "providerName") + .OfRule("rule", ruleUrl) + .Create(); + var filePath = new FilePath(System.IO.Path.GetTempPath() + Guid.NewGuid().ToString() + ".json"); + + try + { + // When + issue.SerializeToJsonFile(filePath); + var result = filePath.DeserializeToIssue(); + + // Then + result.RuleUrl.ToString().ShouldBe(ruleUrl.ToString()); + } + finally + { + if (System.IO.File.Exists(filePath.FullPath)) + { + System.IO.File.Delete(filePath.FullPath); + } + } + } + } + + public sealed class TheSerializeToJsonFileExtensionForAnEnumerableOfIssues + { + [Fact] + public void Should_Throw_If_Issue_Is_Null() + { + // Given + IEnumerable issues = null; + var filePath = @"c:\issues.json"; + + // When + var result = Record.Exception(() => issues.SerializeToJsonFile(filePath)); + + // Then + result.IsArgumentNullException("issues"); + } + + [Fact] + public void Should_Throw_If_FilePath_Is_Null() + { + // Given + var issues = new List(); + FilePath filePath = null; + + // When + var result = Record.Exception(() => issues.SerializeToJsonFile(filePath)); + + // Then + result.IsArgumentNullException("filePath"); + } + + [Fact] + public void Should_Give_Correct_Result_For_MessageText_After_Roundtrip() + { + // Given + var messageText1 = "messageText1"; + var messageText2 = "messageText2"; + var issues = + new List + { + IssueBuilder + .NewIssue(messageText1, "providerType1", "providerName1") + .Create(), + IssueBuilder + .NewIssue(messageText2, "providerType2", "providerName2") + .Create(), + }; + var filePath = new FilePath(System.IO.Path.GetTempPath() + Guid.NewGuid().ToString() + ".json"); + + try + { + // When + issues.SerializeToJsonFile(filePath); + var result = filePath.DeserializeToIssues(); + + // Then + result.Count().ShouldBe(2); + result.First().MessageText.ShouldBe(messageText1); + result.Last().MessageText.ShouldBe(messageText2); + } + finally + { + if (System.IO.File.Exists(filePath.FullPath)) + { + System.IO.File.Delete(filePath.FullPath); + } + } + } + + [Fact] + public void Should_Give_Correct_Result_For_MessageMarkdown_After_Roundtrip() + { + // Given + var messageMarkdown1 = "messageMarkdown1"; + var messageMarkdown2 = "messageMarkdown2"; + var issues = + new List + { + IssueBuilder + .NewIssue("messageText1", "providerType1", "providerName1") + .WithMessageInMarkdownFormat(messageMarkdown1) + .Create(), + IssueBuilder + .NewIssue("messageText2", "providerType2", "providerName2") + .WithMessageInMarkdownFormat(messageMarkdown2) + .Create(), + }; + var filePath = new FilePath(System.IO.Path.GetTempPath() + Guid.NewGuid().ToString() + ".json"); + + try + { + // When + issues.SerializeToJsonFile(filePath); + var result = filePath.DeserializeToIssues(); + + // Then + result.Count().ShouldBe(2); + result.First().MessageMarkdown.ShouldBe(messageMarkdown1); + result.Last().MessageMarkdown.ShouldBe(messageMarkdown2); + } + finally + { + if (System.IO.File.Exists(filePath.FullPath)) + { + System.IO.File.Delete(filePath.FullPath); + } + } + } + + [Fact] + public void Should_Give_Correct_Result_For_MessageHtml_After_Roundtrip() + { + // Given + var messageHtml1 = "messageHtml1"; + var messageHtml2 = "messageHtml2"; + var issues = + new List + { + IssueBuilder + .NewIssue("messageText1", "providerType1", "providerName1") + .WithMessageInHtmlFormat(messageHtml1) + .Create(), + IssueBuilder + .NewIssue("messageText2", "providerType2", "providerName2") + .WithMessageInHtmlFormat(messageHtml2) + .Create(), + }; + var filePath = new FilePath(System.IO.Path.GetTempPath() + Guid.NewGuid().ToString() + ".json"); + + try + { + // When + issues.SerializeToJsonFile(filePath); + var result = filePath.DeserializeToIssues(); + + // Then + result.Count().ShouldBe(2); + result.First().MessageHtml.ShouldBe(messageHtml1); + result.Last().MessageHtml.ShouldBe(messageHtml2); + } + finally + { + if (System.IO.File.Exists(filePath.FullPath)) + { + System.IO.File.Delete(filePath.FullPath); + } + } + } + + [Fact] + public void Should_Give_Correct_Result_For_ProviderType_After_Roundtrip() + { + // Given + var providerType1 = "providerType1"; + var providerType2 = "providerType2"; + var issues = + new List + { + IssueBuilder + .NewIssue("message1", providerType1, "providerName1") + .Create(), + IssueBuilder + .NewIssue("message2", providerType2, "providerName2") + .Create(), + }; + var filePath = new FilePath(System.IO.Path.GetTempPath() + Guid.NewGuid().ToString() + ".json"); + + try + { + // When + issues.SerializeToJsonFile(filePath); + var result = filePath.DeserializeToIssues(); + + // Then + result.Count().ShouldBe(2); + result.First().ProviderType.ShouldBe(providerType1); + result.Last().ProviderType.ShouldBe(providerType2); + } + finally + { + if (System.IO.File.Exists(filePath.FullPath)) + { + System.IO.File.Delete(filePath.FullPath); + } + } + } + + [Fact] + public void Should_Give_Correct_Result_For_ProviderName_After_Roundtrip() + { + // Given + var providerName1 = "providerName1"; + var providerName2 = "providerName2"; + var issues = + new List + { + IssueBuilder + .NewIssue("message1", "providerType1", providerName1) + .Create(), + IssueBuilder + .NewIssue("message2", "providerType2", providerName2) + .Create(), + }; + var filePath = new FilePath(System.IO.Path.GetTempPath() + Guid.NewGuid().ToString() + ".json"); + + try + { + // When + issues.SerializeToJsonFile(filePath); + var result = filePath.DeserializeToIssues(); + + // Then + result.Count().ShouldBe(2); + result.First().ProviderName.ShouldBe(providerName1); + result.Last().ProviderName.ShouldBe(providerName2); + } + finally + { + if (System.IO.File.Exists(filePath.FullPath)) + { + System.IO.File.Delete(filePath.FullPath); + } + } + } + + [Fact] + public void Should_Give_Correct_Result_For_ProjectFileRelativePath_After_Roundtrip() + { + // Given + var projectFileRelativePath1 = @"src/myproj1.file"; + var projectFileRelativePath2 = @"src/myproj2.file"; + var issues = + new List + { + IssueBuilder + .NewIssue("message1", "providerType1", "providerName1") + .InProjectFile(projectFileRelativePath1) + .Create(), + IssueBuilder + .NewIssue("message2", "providerType2", "providerName2") + .InProjectFile(projectFileRelativePath2) + .Create(), + }; + var filePath = new FilePath(System.IO.Path.GetTempPath() + Guid.NewGuid().ToString() + ".json"); + + try + { + // When + issues.SerializeToJsonFile(filePath); + var result = filePath.DeserializeToIssues(); + + // Then + result.Count().ShouldBe(2); + result.First().ProjectFileRelativePath.FullPath.ShouldBe(projectFileRelativePath1); + result.Last().ProjectFileRelativePath.FullPath.ShouldBe(projectFileRelativePath2); + } + finally + { + if (System.IO.File.Exists(filePath.FullPath)) + { + System.IO.File.Delete(filePath.FullPath); + } + } + } + + [Fact] + public void Should_Give_Correct_Result_For_ProjectName_After_Roundtrip() + { + // Given + var projectName1 = "projectName1"; + var projectName2 = "projectName2"; + var issues = + new List + { + IssueBuilder + .NewIssue("message1", "providerType1", "providerName1") + .InProjectOfName(projectName1) + .Create(), + IssueBuilder + .NewIssue("message2", "providerType2", "providerName2") + .InProjectOfName(projectName2) + .Create(), + }; + var filePath = new FilePath(System.IO.Path.GetTempPath() + Guid.NewGuid().ToString() + ".json"); + + try + { + // When + issues.SerializeToJsonFile(filePath); + var result = filePath.DeserializeToIssues(); + + // Then + result.Count().ShouldBe(2); + result.First().ProjectName.ShouldBe(projectName1); + result.Last().ProjectName.ShouldBe(projectName2); + } + finally + { + if (System.IO.File.Exists(filePath.FullPath)) + { + System.IO.File.Delete(filePath.FullPath); + } + } + } + + [Fact] + public void Should_Give_Correct_Result_For_AffectedFileRelativePath_After_Roundtrip() + { + // Given + var affectedFileRelativePath1 = @"src/foo1.bar"; + var affectedFileRelativePath2 = @"src/foo2.bar"; + var issues = + new List + { + IssueBuilder + .NewIssue("message1", "providerType1", "providerName1") + .InFile(affectedFileRelativePath1) + .Create(), + IssueBuilder + .NewIssue("message2", "providerType2", "providerName2") + .InFile(affectedFileRelativePath2) + .Create(), + }; + var filePath = new FilePath(System.IO.Path.GetTempPath() + Guid.NewGuid().ToString() + ".json"); + + try + { + // When + issues.SerializeToJsonFile(filePath); + var result = filePath.DeserializeToIssues(); + + // Then + result.Count().ShouldBe(2); + result.First().AffectedFileRelativePath.FullPath.ShouldBe(affectedFileRelativePath1); + result.Last().AffectedFileRelativePath.FullPath.ShouldBe(affectedFileRelativePath2); + } + finally + { + if (System.IO.File.Exists(filePath.FullPath)) + { + System.IO.File.Delete(filePath.FullPath); + } + } + } + + [Fact] + public void Should_Give_Correct_Result_For_Line_After_Roundtrip() + { + // Given + var line1 = 23; + var line2 = 42; + var issues = + new List + { + IssueBuilder + .NewIssue("message1", "providerType1", "providerName1") + .InFile(@"src/foo.bar", line1) + .Create(), + IssueBuilder + .NewIssue("message2", "providerType2", "providerName2") + .InFile(@"src/foo.bar", line2) + .Create(), + }; + var filePath = new FilePath(System.IO.Path.GetTempPath() + Guid.NewGuid().ToString() + ".json"); + + try + { + // When + issues.SerializeToJsonFile(filePath); + var result = filePath.DeserializeToIssues(); + + // Then + result.Count().ShouldBe(2); + result.First().Line.ShouldBe(line1); + result.Last().Line.ShouldBe(line2); + } + finally + { + if (System.IO.File.Exists(filePath.FullPath)) + { + System.IO.File.Delete(filePath.FullPath); + } + } + } + + [Fact] + public void Should_Give_Correct_Result_For_Priority_After_Roundtrip() + { + // Given + var priority1 = 23; + var priority2 = 42; + var issues = + new List + { + IssueBuilder + .NewIssue("message1", "providerType1", "providerName1") + .WithPriority(priority1, "priorityName") + .Create(), + IssueBuilder + .NewIssue("message2", "providerType2", "providerName2") + .WithPriority(priority2, "priorityName") + .Create(), + }; + var filePath = new FilePath(System.IO.Path.GetTempPath() + Guid.NewGuid().ToString() + ".json"); + + try + { + // When + issues.SerializeToJsonFile(filePath); + var result = filePath.DeserializeToIssues(); + + // Then + result.Count().ShouldBe(2); + result.First().Priority.ShouldBe(priority1); + result.Last().Priority.ShouldBe(priority2); + } + finally + { + if (System.IO.File.Exists(filePath.FullPath)) + { + System.IO.File.Delete(filePath.FullPath); + } + } + } + + [Fact] + public void Should_Give_Correct_Result_For_PriorityName_After_Roundtrip() + { + // Given + var priorityName1 = "priorityName1"; + var priorityName2 = "priorityName2"; + var issues = + new List + { + IssueBuilder + .NewIssue("message1", "providerType1", "providerName1") + .WithPriority(42, priorityName1) + .Create(), + IssueBuilder + .NewIssue("message2", "providerType2", "providerName2") + .WithPriority(42, priorityName2) + .Create(), + }; + var filePath = new FilePath(System.IO.Path.GetTempPath() + Guid.NewGuid().ToString() + ".json"); + + try + { + // When + issues.SerializeToJsonFile(filePath); + var result = filePath.DeserializeToIssues(); + + // Then + result.Count().ShouldBe(2); + result.First().PriorityName.ShouldBe(priorityName1); + result.Last().PriorityName.ShouldBe(priorityName2); + } + finally + { + if (System.IO.File.Exists(filePath.FullPath)) + { + System.IO.File.Delete(filePath.FullPath); + } + } + } + + [Fact] + public void Should_Give_Correct_Result_For_Rule_After_Roundtrip() + { + // Given + var rule1 = "rule1"; + var rule2 = "rule2"; + var issues = + new List + { + IssueBuilder + .NewIssue("message1", "providerType1", "providerName1") + .OfRule(rule1) + .Create(), + IssueBuilder + .NewIssue("message2", "providerType2", "providerName2") + .OfRule(rule2) + .Create(), + }; + var filePath = new FilePath(System.IO.Path.GetTempPath() + Guid.NewGuid().ToString() + ".json"); + + try + { + // When + issues.SerializeToJsonFile(filePath); + var result = filePath.DeserializeToIssues(); + + // Then + result.Count().ShouldBe(2); + result.First().Rule.ShouldBe(rule1); + result.Last().Rule.ShouldBe(rule2); + } + finally + { + if (System.IO.File.Exists(filePath.FullPath)) + { + System.IO.File.Delete(filePath.FullPath); + } + } + } + + [Fact] + public void Should_Give_Correct_Result_For_RuleUrl_After_Roundtrip() + { + // Given + var ruleUrl1 = new Uri("https://rule1.url"); + var ruleUrl2 = new Uri("https://rule2.url"); + var issues = + new List + { + IssueBuilder + .NewIssue("message1", "providerType1", "providerName1") + .OfRule("rule", ruleUrl1) + .Create(), + IssueBuilder + .NewIssue("message2", "providerType2", "providerName2") + .OfRule("rule", ruleUrl2) + .Create(), + }; + var filePath = new FilePath(System.IO.Path.GetTempPath() + Guid.NewGuid().ToString() + ".json"); + + try + { + // When + issues.SerializeToJsonFile(filePath); + var result = filePath.DeserializeToIssues(); + + // Then + result.Count().ShouldBe(2); + result.First().RuleUrl.ToString().ShouldBe(ruleUrl1.ToString()); + result.Last().RuleUrl.ToString().ShouldBe(ruleUrl2.ToString()); + } + finally + { + if (System.IO.File.Exists(filePath.FullPath)) + { + System.IO.File.Delete(filePath.FullPath); + } + } + } + } + + public sealed class TheToSerializableIssueExtension + { + [Fact] + public void Should_Throw_If_Issue_Is_Null() + { + // Given + IIssue issue = null; + + // When + var result = Record.Exception(() => issue.ToSerializableIssue()); + + // Then + result.IsArgumentNullException("issue"); + } + } + } +} \ No newline at end of file diff --git a/src/Cake.Issues.Tests/Serialization/SerializableIssueExtensionsTests.cs b/src/Cake.Issues.Tests/Serialization/SerializableIssueExtensionsTests.cs new file mode 100644 index 000000000..79e174bae --- /dev/null +++ b/src/Cake.Issues.Tests/Serialization/SerializableIssueExtensionsTests.cs @@ -0,0 +1,25 @@ +namespace Cake.Issues.Tests.Serialization +{ + using Cake.Issues.Serialization; + using Cake.Issues.Testing; + using Xunit; + + public sealed class SerializableIssueExtensionsTests + { + public sealed class TheToIssueExtension + { + [Fact] + public void Should_Throw_If_SerializableIssue_Is_Null() + { + // Given + SerializableIssue serializableIssue = null; + + // When + var result = Record.Exception(() => serializableIssue.ToIssue()); + + // Then + result.IsArgumentNullException("serializableIssue"); + } + } + } +} \ No newline at end of file diff --git a/src/Cake.Issues.Tests/Serialization/SerializableIssueV2ExtensionsTests.cs b/src/Cake.Issues.Tests/Serialization/SerializableIssueV2ExtensionsTests.cs new file mode 100644 index 000000000..4ef0f43c0 --- /dev/null +++ b/src/Cake.Issues.Tests/Serialization/SerializableIssueV2ExtensionsTests.cs @@ -0,0 +1,25 @@ +namespace Cake.Issues.Tests.Serialization +{ + using Cake.Issues.Serialization; + using Cake.Issues.Testing; + using Xunit; + + public sealed class SerializableIssueV2ExtensionsTests + { + public sealed class TheToIssueExtension + { + [Fact] + public void Should_Throw_If_SerializableIssue_Is_Null() + { + // Given + SerializableIssueV2 serializableIssue = null; + + // When + var result = Record.Exception(() => serializableIssue.ToIssue()); + + // Then + result.IsArgumentNullException("serializableIssue"); + } + } + } +} \ No newline at end of file diff --git a/src/Cake.Issues.Tests/Testfiles/issueV2.json b/src/Cake.Issues.Tests/Testfiles/issueV2.json new file mode 100644 index 000000000..88edfa346 --- /dev/null +++ b/src/Cake.Issues.Tests/Testfiles/issueV2.json @@ -0,0 +1,16 @@ +{ + "Version": 2, + "AffectedFileRelativePath": "src\/Foo\/Bar.cs", + "Line": 42, + "MessageText": "Something went wrong.", + "MessageHtml": "Something went wrong.", + "MessageMarkdown": "Something went **wrong**.", + "Priority": 300, + "PriorityName": "Warning", + "ProjectFileRelativePath": "src\/Foo\/Bar.csproj", + "ProjectName": "Bar", + "ProviderName": "Test Provider", + "ProviderType": "TestProvider", + "Rule": "Rule", + "RuleUrl": "https://google.com" +} \ No newline at end of file diff --git a/src/Cake.Issues.Tests/Testfiles/issuesV2.json b/src/Cake.Issues.Tests/Testfiles/issuesV2.json new file mode 100644 index 000000000..9b42ecc13 --- /dev/null +++ b/src/Cake.Issues.Tests/Testfiles/issuesV2.json @@ -0,0 +1,34 @@ +[ + { + "Version": 2, + "AffectedFileRelativePath": "src\/Foo\/Bar.cs", + "Line": 42, + "MessageText": "Something went wrong.", + "MessageHtml": "Something went wrong.", + "MessageMarkdown": "Something went **wrong**.", + "Priority": 300, + "PriorityName": "Warning", + "ProjectFileRelativePath": "src\/Foo\/Bar.csproj", + "ProjectName": "Bar", + "ProviderName": "Test Provider", + "ProviderType": "TestProvider", + "Rule": "Rule", + "RuleUrl": "https://google.com" + }, + { + "Version": 2, + "AffectedFileRelativePath": "src\/Foo\/Bar2.cs", + "Line": null, + "MessageText": "Something went wrong again.", + "MessageHtml": "Something went wrong again.", + "MessageMarkdown": "Something went **wrong** again.", + "Priority": 300, + "PriorityName": "Warning", + "ProjectFileRelativePath": "src\/Foo\/Bar.csproj", + "ProjectName": "Bar", + "ProviderName": "Test Provider", + "ProviderType": "TestProvider", + "Rule": null, + "RuleUrl": null + } +] \ No newline at end of file diff --git a/src/Cake.Issues.Tests/Testing/IssueCheckerFixture.cs b/src/Cake.Issues.Tests/Testing/IssueCheckerFixture.cs index e6102eeb4..918571a1d 100644 --- a/src/Cake.Issues.Tests/Testing/IssueCheckerFixture.cs +++ b/src/Cake.Issues.Tests/Testing/IssueCheckerFixture.cs @@ -9,8 +9,8 @@ public IssueCheckerFixture() { } - public IssueCheckerFixture(string message, string providerType, string providerName) - : base(message, providerType, providerName) + public IssueCheckerFixture(string messageText, string providerType, string providerName) + : base(messageText, providerType, providerName) { this.ProviderType = providerType; this.ProviderName = providerName; @@ -18,13 +18,17 @@ public IssueCheckerFixture(string message, string providerType, string providerN this.ProjectName = "ProjectName"; this.AffectedFileRelativePath = @"src\source.file"; this.Line = 42; - this.Message = message; + this.MessageText = messageText; + this.MessageHtml = "messageHtml"; + this.MessageMarkdown = "messageMarkdown"; this.Priority = 100; this.PriorityName = "PriorityName"; this.Rule = "Rule"; this.RuleUrl = new Uri("https://google.com"); this.IssueBuilder + .WithMessageInHtmlFormat(this.MessageHtml) + .WithMessageInMarkdownFormat(this.MessageMarkdown) .InProject(this.ProjectFileRelativePath, this.ProjectName) .InFile(this.AffectedFileRelativePath, this.Line) .OfRule(this.Rule, this.RuleUrl) @@ -48,7 +52,11 @@ public IssueCheckerFixture(string message, string providerType, string providerN public int Line { get; private set; } - public string Message { get; private set; } + public string MessageText { get; private set; } + + public string MessageHtml { get; private set; } + + public string MessageMarkdown { get; private set; } public int Priority { get; private set; } diff --git a/src/Cake.Issues.Tests/Testing/IssueCheckerTests.cs b/src/Cake.Issues.Tests/Testing/IssueCheckerTests.cs index 310fdd7ca..2b8c8fddf 100644 --- a/src/Cake.Issues.Tests/Testing/IssueCheckerTests.cs +++ b/src/Cake.Issues.Tests/Testing/IssueCheckerTests.cs @@ -148,7 +148,9 @@ public void Should_Throw_If_Issue_Is_Null() fixture.ProjectName, fixture.AffectedFileRelativePath, fixture.Line, - fixture.Message, + fixture.MessageText, + fixture.MessageHtml, + fixture.MessageMarkdown, fixture.Priority, fixture.PriorityName, fixture.Rule, @@ -173,7 +175,9 @@ public void Should_Not_Throw_If_All_Values_Are_The_Same() fixture.ProjectName, fixture.AffectedFileRelativePath, fixture.Line, - fixture.Message, + fixture.MessageText, + fixture.MessageHtml, + fixture.MessageMarkdown, fixture.Priority, fixture.PriorityName, fixture.Rule, @@ -202,7 +206,9 @@ public void Should_Throw_If_ProviderType_Is_Different(string expectedValue, stri fixture.ProjectName, fixture.AffectedFileRelativePath, fixture.Line, - fixture.Message, + fixture.MessageText, + fixture.MessageHtml, + fixture.MessageMarkdown, fixture.Priority, fixture.PriorityName, fixture.Rule, @@ -233,7 +239,9 @@ public void Should_Throw_If_ProviderName_Is_Different(string expectedValue, stri fixture.ProjectName, fixture.AffectedFileRelativePath, fixture.Line, - fixture.Message, + fixture.MessageText, + fixture.MessageHtml, + fixture.MessageMarkdown, fixture.Priority, fixture.PriorityName, fixture.Rule, @@ -265,7 +273,9 @@ public void Should_Throw_If_ProjectFileRelativePath_Is_Different(string expected fixture.ProjectName, fixture.AffectedFileRelativePath, fixture.Line, - fixture.Message, + fixture.MessageText, + fixture.MessageHtml, + fixture.MessageMarkdown, fixture.Priority, fixture.PriorityName, fixture.Rule, @@ -300,7 +310,9 @@ public void Should_Throw_If_ProjectName_Is_Different(string expectedValue, strin expectedValue, fixture.AffectedFileRelativePath, fixture.Line, - fixture.Message, + fixture.MessageText, + fixture.MessageHtml, + fixture.MessageMarkdown, fixture.Priority, fixture.PriorityName, fixture.Rule, @@ -332,7 +344,9 @@ public void Should_Throw_If_AffectedFileRelativePath_Is_Different(string expecte fixture.ProjectName, expectedValue, fixture.Line, - fixture.Message, + fixture.MessageText, + fixture.MessageHtml, + fixture.MessageMarkdown, fixture.Priority, fixture.PriorityName, fixture.Rule, @@ -366,7 +380,9 @@ public void Should_Throw_If_Line_Is_Different(int? expectedValue, int? actualVal fixture.ProjectName, fixture.AffectedFileRelativePath, expectedValue, - fixture.Message, + fixture.MessageText, + fixture.MessageHtml, + fixture.MessageMarkdown, fixture.Priority, fixture.PriorityName, fixture.Rule, @@ -382,7 +398,7 @@ public void Should_Throw_If_Line_Is_Different(int? expectedValue, int? actualVal [InlineData(null, "Foo")] [InlineData("", "Foo")] [InlineData(" ", "Foo")] - public void Should_Throw_If_Message_Is_Different(string expectedValue, string actualValue) + public void Should_Throw_If_MessageText_Is_Different(string expectedValue, string actualValue) { // Given var fixture = new IssueCheckerFixture(actualValue, "ProviderType", "ProviderName"); @@ -398,6 +414,8 @@ public void Should_Throw_If_Message_Is_Different(string expectedValue, string ac fixture.AffectedFileRelativePath, fixture.Line, expectedValue, + fixture.MessageHtml, + fixture.MessageMarkdown, fixture.Priority, fixture.PriorityName, fixture.Rule, @@ -405,7 +423,81 @@ public void Should_Throw_If_Message_Is_Different(string expectedValue, string ac // Then result.ShouldBeOfType(); - result.Message.ShouldStartWith("Expected issue.Message"); + result.Message.ShouldStartWith("Expected issue.MessageText"); + } + + [Theory] + [InlineData("Message", "Foo")] + [InlineData(null, "Foo")] + [InlineData("", "Foo")] + [InlineData(" ", "Foo")] + public void Should_Throw_If_MessageHtml_Is_Different(string expectedValue, string actualValue) + { + // Given + var fixture = new IssueCheckerFixture(); + var issue = + fixture.IssueBuilder + .WithMessageInHtmlFormat(actualValue) + .Create(); + + // When + var result = Record.Exception(() => + IssueChecker.Check( + fixture.Issue, + fixture.ProviderType, + fixture.ProviderName, + fixture.ProjectFileRelativePath, + fixture.ProjectName, + fixture.AffectedFileRelativePath, + fixture.Line, + fixture.MessageText, + expectedValue, + fixture.MessageMarkdown, + fixture.Priority, + fixture.PriorityName, + fixture.Rule, + fixture.RuleUrl)); + + // Then + result.ShouldBeOfType(); + result.Message.ShouldStartWith("Expected issue.MessageHtml"); + } + + [Theory] + [InlineData("Message", "Foo")] + [InlineData(null, "Foo")] + [InlineData("", "Foo")] + [InlineData(" ", "Foo")] + public void Should_Throw_If_MessageMarkdown_Is_Different(string expectedValue, string actualValue) + { + // Given + var fixture = new IssueCheckerFixture(); + var issue = + fixture.IssueBuilder + .WithMessageInMarkdownFormat(actualValue) + .Create(); + + // When + var result = Record.Exception(() => + IssueChecker.Check( + fixture.Issue, + fixture.ProviderType, + fixture.ProviderName, + fixture.ProjectFileRelativePath, + fixture.ProjectName, + fixture.AffectedFileRelativePath, + fixture.Line, + fixture.MessageText, + fixture.MessageHtml, + expectedValue, + fixture.Priority, + fixture.PriorityName, + fixture.Rule, + fixture.RuleUrl)); + + // Then + result.ShouldBeOfType(); + result.Message.ShouldStartWith("Expected issue.MessageMarkdown"); } [Theory] @@ -429,7 +521,9 @@ public void Should_Throw_If_Priority_Is_Different(IssuePriority expectedValue, I fixture.ProjectName, fixture.AffectedFileRelativePath, fixture.Line, - fixture.Message, + fixture.MessageText, + fixture.MessageHtml, + fixture.MessageMarkdown, (int)expectedValue, fixture.PriorityName, fixture.Rule, @@ -464,7 +558,9 @@ public void Should_Throw_If_PriorityName_Is_Different(string expectedValue, stri fixture.ProjectName, fixture.AffectedFileRelativePath, fixture.Line, - fixture.Message, + fixture.MessageText, + fixture.MessageHtml, + fixture.MessageMarkdown, fixture.Priority, expectedValue, fixture.Rule, @@ -499,7 +595,9 @@ public void Should_Throw_If_Rule_Is_Different(string expectedValue, string actua fixture.ProjectName, fixture.AffectedFileRelativePath, fixture.Line, - fixture.Message, + fixture.MessageText, + fixture.MessageHtml, + fixture.MessageMarkdown, fixture.Priority, fixture.PriorityName, expectedValue, @@ -531,7 +629,9 @@ public void Should_Throw_If_RuleUrl_Is_Different(string expectedValue, string ac fixture.ProjectName, fixture.AffectedFileRelativePath, fixture.Line, - fixture.Message, + fixture.MessageText, + fixture.MessageHtml, + fixture.MessageMarkdown, fixture.Priority, fixture.PriorityName, fixture.Rule, diff --git a/src/Cake.Issues.sln b/src/Cake.Issues.sln index 8b6a67117..12fdc0efa 100644 --- a/src/Cake.Issues.sln +++ b/src/Cake.Issues.sln @@ -9,7 +9,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Cake.Issues.Tests", "Cake.I EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{12963AB7-D907-4D27-B1F0-8C77A55BD2F2}" ProjectSection(SolutionItems) = preProject - ..\setup.cake = ..\setup.cake + ..\recipe.cake = ..\recipe.cake EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "nuspec", "nuspec", "{C9E29268-658F-4887-95C3-99C48F9A2DAD}" diff --git a/src/Cake.Issues/Aliases.IssueSerialization.cs b/src/Cake.Issues/Aliases.IssueSerialization.cs index 56959cff3..c1422b39d 100644 --- a/src/Cake.Issues/Aliases.IssueSerialization.cs +++ b/src/Cake.Issues/Aliases.IssueSerialization.cs @@ -4,6 +4,7 @@ using Cake.Core; using Cake.Core.Annotations; using Cake.Core.IO; + using Cake.Issues.Serialization; /// /// Contains functionality related to serializing and deserializing issues. diff --git a/src/Cake.Issues/Aliases.ReadIssues.cs b/src/Cake.Issues/Aliases.ReadIssues.cs index cb302213c..1abb5a8d6 100644 --- a/src/Cake.Issues/Aliases.ReadIssues.cs +++ b/src/Cake.Issues/Aliases.ReadIssues.cs @@ -182,7 +182,7 @@ public static IEnumerable ReadIssues( var issuesReader = new IssuesReader(context.Log, issueProviders, settings); - return issuesReader.ReadIssues(settings.Format); + return issuesReader.ReadIssues(); } } } diff --git a/src/Cake.Issues/BaseIssueProvider.cs b/src/Cake.Issues/BaseIssueProvider.cs index 198821f40..e3eeb928b 100644 --- a/src/Cake.Issues/BaseIssueProvider.cs +++ b/src/Cake.Issues/BaseIssueProvider.cs @@ -21,19 +21,18 @@ protected BaseIssueProvider(ICakeLog log) public abstract string ProviderName { get; } /// - public IEnumerable ReadIssues(IssueCommentFormat format) + public IEnumerable ReadIssues() { this.AssertInitialized(); - return this.InternalReadIssues(format); + return this.InternalReadIssues(); } /// /// Gets all issues. /// Compared to it is safe to access Settings from this method. /// - /// Preferred format of the comments. /// List of issues. - protected abstract IEnumerable InternalReadIssues(IssueCommentFormat format); + protected abstract IEnumerable InternalReadIssues(); } } diff --git a/src/Cake.Issues/BaseLogFileFormat.cs b/src/Cake.Issues/BaseLogFileFormat.cs index 0e51dbc6a..facf44245 100644 --- a/src/Cake.Issues/BaseLogFileFormat.cs +++ b/src/Cake.Issues/BaseLogFileFormat.cs @@ -31,7 +31,6 @@ protected BaseLogFileFormat(ICakeLog log) /// public abstract IEnumerable ReadIssues( TIssueProvider issueProvider, - IssueCommentFormat format, RepositorySettings repositorySettings, TSettings issueProviderSettings); } diff --git a/src/Cake.Issues/BaseMultiFormatIssueProvider.cs b/src/Cake.Issues/BaseMultiFormatIssueProvider.cs index 4b40c25d8..92ebb7042 100644 --- a/src/Cake.Issues/BaseMultiFormatIssueProvider.cs +++ b/src/Cake.Issues/BaseMultiFormatIssueProvider.cs @@ -23,12 +23,11 @@ protected BaseMultiFormatIssueProvider(ICakeLog log, TSettings settings) } /// - protected override IEnumerable InternalReadIssues(IssueCommentFormat format) + protected override IEnumerable InternalReadIssues() { return this.IssueProviderSettings.Format.ReadIssues( (TIssueProvider)this, - format, this.Settings, this.IssueProviderSettings); } diff --git a/src/Cake.Issues/Cake.Issues.csproj b/src/Cake.Issues/Cake.Issues.csproj index d4003de5f..294cf68fc 100644 --- a/src/Cake.Issues/Cake.Issues.csproj +++ b/src/Cake.Issues/Cake.Issues.csproj @@ -24,7 +24,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Cake.Issues/IIssue.cs b/src/Cake.Issues/IIssue.cs index 2394f725b..0a90ff793 100644 --- a/src/Cake.Issues/IIssue.cs +++ b/src/Cake.Issues/IIssue.cs @@ -35,9 +35,19 @@ public interface IIssue int? Line { get; } /// - /// Gets the message of the issue. + /// Gets the message of the issue in text format. /// - string Message { get; } + string MessageText { get; } + + /// + /// Gets the message of the issue in HTML format. + /// + string MessageHtml { get; } + + /// + /// Gets the message of the issue in Markdown format. + /// + string MessageMarkdown { get; } /// /// Gets the priority of the message. A higher value indicates a higher priority. diff --git a/src/Cake.Issues/IIssueComparer.cs b/src/Cake.Issues/IIssueComparer.cs new file mode 100644 index 000000000..d7367fba3 --- /dev/null +++ b/src/Cake.Issues/IIssueComparer.cs @@ -0,0 +1,135 @@ +namespace Cake.Issues +{ + using System.Collections.Generic; + + /// + /// Comparer to compare if two issues are identical. + /// + public class IIssueComparer : IEqualityComparer + { + private readonly bool compareOnlyPersistentProperties; + + /// + /// Initializes a new instance of the class. + /// Two issues are seen as identical if all properties have identical values. + /// + public IIssueComparer() + : this(false) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Flag indicating whether properties which + /// are affected by changes in files should be considered while comparing issues. + /// If set to true, the comparer can be used to compare issues from different + /// build runs, where files might have been changed or renamed. + /// + /// If is set to true the following + /// properties will be ignored while comparing the issue: + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public IIssueComparer(bool compareOnlyPersistentProperties) + { + this.compareOnlyPersistentProperties = compareOnlyPersistentProperties; + } + + /// + public bool Equals(IIssue x, IIssue y) + { + if (object.ReferenceEquals(x, y)) + { + return true; + } + + if (x is null || y is null) + { + return false; + } + + return + (this.compareOnlyPersistentProperties || x.ProjectFileRelativePath?.FullPath == y.ProjectFileRelativePath?.FullPath) && + (x.ProjectName == y.ProjectName) && + (this.compareOnlyPersistentProperties || x.AffectedFileRelativePath?.FullPath == y.AffectedFileRelativePath?.FullPath) && + (this.compareOnlyPersistentProperties || x.Line == y.Line) && + (x.MessageText == y.MessageText) && + (x.MessageHtml == y.MessageHtml) && + (x.MessageMarkdown == y.MessageMarkdown) && + (x.Priority == y.Priority) && + (x.PriorityName == y.PriorityName) && + (x.Rule == y.Rule) && + (x.RuleUrl?.ToString() == y.RuleUrl?.ToString()) && + (x.ProviderType == y.ProviderType) && + (x.ProviderName == y.ProviderName); + } + + /// + public int GetHashCode(IIssue obj) + { + if (obj is null) + { + return 0; + } + + if (this.compareOnlyPersistentProperties) + { + return + GetHashCode( + obj.ProjectName, + obj.MessageText, + obj.MessageHtml, + obj.MessageMarkdown, + obj.Priority, + obj.PriorityName, + obj.Rule, + obj.RuleUrl, + obj.ProviderType, + obj.ProviderName); + } + else + { + return + GetHashCode( + obj.ProjectFileRelativePath?.ToString(), + obj.ProjectName, + obj.AffectedFileRelativePath?.ToString(), + obj.Line, + obj.MessageText, + obj.MessageHtml, + obj.MessageMarkdown, + obj.Priority, + obj.PriorityName, + obj.Rule, + obj.RuleUrl, + obj.ProviderType, + obj.ProviderName); + } + } + + private static int GetHashCode(params object[] objects) + { + unchecked + { + int hash = 17; + + foreach (var obj in objects) + { + hash = (23 * hash) + (obj is object ? obj.GetHashCode() : 0); + } + + return hash; + } + } + } +} diff --git a/src/Cake.Issues/IIssueExtensions.cs b/src/Cake.Issues/IIssueExtensions.cs index eccddde38..4c3c604bc 100644 --- a/src/Cake.Issues/IIssueExtensions.cs +++ b/src/Cake.Issues/IIssueExtensions.cs @@ -1,10 +1,38 @@ namespace Cake.Issues { + using System; + /// /// Extensions for . /// public static class IIssueExtensions { + /// + /// Gets the message of the issue in a specific format. + /// If the message is not available in the specific format, the message in + /// text format will be returned. + /// + /// Issue for which the message should be returned. + /// Format in which the message should be returned. + /// Message in the format specified by or message in text + /// format if it is not available in the desired format. + public static string Message(this IIssue issue, IssueCommentFormat format) + { + issue.NotNull(nameof(issue)); + + switch (format) + { + case IssueCommentFormat.PlainText: + return issue.MessageText; + case IssueCommentFormat.Html: + return !string.IsNullOrEmpty(issue.MessageHtml) ? issue.MessageHtml : issue.MessageText; + case IssueCommentFormat.Markdown: + return !string.IsNullOrEmpty(issue.MessageMarkdown) ? issue.MessageMarkdown : issue.MessageText; + default: + throw new ArgumentOutOfRangeException(nameof(format)); + } + } + /// /// Returns the full path of or null. /// @@ -128,8 +156,18 @@ public static string FileName(this IIssue issue) /// The value of . /// /// - /// {Message} - /// The value of . + /// {MessageText} + /// The value of . + /// + /// + /// {MessageHtml} + /// The value of or + /// if message in HTML format is not available. + /// + /// + /// {MessageMarkdown} + /// The value of or + /// if message in Markdown format is not available. /// /// /// @@ -155,7 +193,9 @@ public static string ReplaceIssuePattern(this string pattern, IIssue issue) .Replace("{Line}", issue.Line?.ToString()) .Replace("{Rule}", issue.Rule) .Replace("{RuleUrl}", issue.RuleUrl?.ToString()) - .Replace("{Message}", issue.Message); + .Replace("{MessageText}", issue.Message(IssueCommentFormat.PlainText)) + .Replace("{MessageHtml}", issue.Message(IssueCommentFormat.Html)) + .Replace("{MessageMarkdown}", issue.Message(IssueCommentFormat.Markdown)); } } } \ No newline at end of file diff --git a/src/Cake.Issues/IIssueProvider.cs b/src/Cake.Issues/IIssueProvider.cs index 832a1e66b..dd3231b46 100644 --- a/src/Cake.Issues/IIssueProvider.cs +++ b/src/Cake.Issues/IIssueProvider.cs @@ -15,8 +15,7 @@ public interface IIssueProvider : IBaseIssueComponent /// /// Gets all issues. /// - /// Preferred format of the comments. /// List of issues. - IEnumerable ReadIssues(IssueCommentFormat format); + IEnumerable ReadIssues(); } } diff --git a/src/Cake.Issues/ILogFileFormat.cs b/src/Cake.Issues/ILogFileFormat.cs index ca62fdcdd..457bc8f12 100644 --- a/src/Cake.Issues/ILogFileFormat.cs +++ b/src/Cake.Issues/ILogFileFormat.cs @@ -15,13 +15,11 @@ public interface ILogFileFormat /// Gets all issues. /// /// Issue provider instance. - /// Preferred format for comments. /// Repository settings to use. /// Settings for issue provider to use. /// List of issues. IEnumerable ReadIssues( TIssueProvider issueProvider, - IssueCommentFormat format, RepositorySettings repositorySettings, TSettings issueProviderSettings); } diff --git a/src/Cake.Issues/Issue.cs b/src/Cake.Issues/Issue.cs index c33cc31c2..e699c512b 100644 --- a/src/Cake.Issues/Issue.cs +++ b/src/Cake.Issues/Issue.cs @@ -21,7 +21,9 @@ public class Issue : IIssue /// null or if issue is not related to a change in a file. /// The line in the file where the issues has occurred. /// null if the issue affects the whole file or an asssembly. - /// The message of the issue. + /// The message of the issue in plain text format. + /// The message of the issue in Html format. + /// The message of the issue in Markdown format. /// The priority of the message. /// null if no priority was assigned. /// The human friendly name of the priority. @@ -37,7 +39,9 @@ public Issue( string projectName, string affectedFileRelativePath, int? line, - string message, + string messageText, + string messageHtml, + string messageMarkdown, int? priority, string priorityName, string rule, @@ -46,7 +50,7 @@ public Issue( string providerName) { line?.NotNegativeOrZero(nameof(line)); - message.NotNullOrWhiteSpace(nameof(message)); + messageText.NotNullOrWhiteSpace(nameof(messageText)); providerType.NotNullOrWhiteSpace(nameof(providerType)); providerName.NotNullOrWhiteSpace(nameof(providerName)); @@ -81,7 +85,7 @@ public Issue( if (!this.AffectedFileRelativePath.IsRelative) { throw new ArgumentOutOfRangeException( - nameof(affectedFileRelativePath), + nameof(affectedFileRelativePath), $"File path '{this.AffectedFileRelativePath}' needs to be relative to the repository root."); } } @@ -93,7 +97,9 @@ public Issue( this.ProjectName = projectName; this.Line = line; - this.Message = message; + this.MessageText = messageText; + this.MessageHtml = messageHtml; + this.MessageMarkdown = messageMarkdown; this.Priority = priority; this.PriorityName = priorityName; this.Rule = rule; @@ -115,7 +121,13 @@ public Issue( public int? Line { get; } /// - public string Message { get; } + public string MessageText { get; } + + /// + public string MessageHtml { get; } + + /// + public string MessageMarkdown { get; } /// public int? Priority { get; } diff --git a/src/Cake.Issues/IssueBuilder.cs b/src/Cake.Issues/IssueBuilder.cs index 0be1bf428..aee2041d6 100644 --- a/src/Cake.Issues/IssueBuilder.cs +++ b/src/Cake.Issues/IssueBuilder.cs @@ -7,9 +7,11 @@ /// public class IssueBuilder { - private readonly string message; private readonly string providerType; private readonly string providerName; + private readonly string messageText; + private string messageHtml; + private string messageMarkdown; private string projectFileRelativePath; private string projectName; private string filePath; @@ -22,7 +24,7 @@ public class IssueBuilder /// /// Initializes a new instance of the class. /// - /// The message of the issue. + /// The message of the issue in plain text format. /// The type of the issue provider. /// The human friendly name of the issue provider. private IssueBuilder( @@ -34,7 +36,7 @@ private IssueBuilder( providerType.NotNullOrWhiteSpace(nameof(providerType)); providerName.NotNullOrWhiteSpace(nameof(providerName)); - this.message = message; + this.messageText = message; this.providerType = providerType; this.providerName = providerName; } @@ -42,7 +44,7 @@ private IssueBuilder( /// /// Initiates the creation of a new . /// - /// The message of the issue. + /// The message of the issue in plain text format. /// The type of the issue provider. /// The human friendly name of the issue provider. /// Builder class for creating a new issue. @@ -62,7 +64,7 @@ public static IssueBuilder NewIssue( /// Initiates the creation of a new . /// /// Type of the issue provider which has the issue created. - /// The message of the issue. + /// The message of the issue in plain text format. /// Issue provider which has the issue created. /// Builder class for creating a new issue. public static IssueBuilder NewIssue( @@ -80,6 +82,32 @@ public static IssueBuilder NewIssue( return new IssueBuilder(message, typeof(T).FullName, issueProvider.ProviderName); } + /// + /// Sets the message in HTML format. + /// + /// Message in HTML format. + /// Can be null or if issue doesn't have a message in HTML format. + /// Issue Builder instance. + public IssueBuilder WithMessageInHtmlFormat(string message) + { + this.messageHtml = message; + + return this; + } + + /// + /// Sets the message in Markdown format. + /// + /// Message in Markdown format. + /// Can be null or if issue doesn't have a message in Markdown format. + /// Issue Builder instance. + public IssueBuilder WithMessageInMarkdownFormat(string message) + { + this.messageMarkdown = message; + + return this; + } + /// /// Sets the path of the project to which the file affected by the issue belongs. /// @@ -224,7 +252,9 @@ public IIssue Create() this.projectName, this.filePath, this.line, - this.message, + this.messageText, + this.messageHtml, + this.messageMarkdown, this.priority, this.priorityName, this.rule, diff --git a/src/Cake.Issues/IssueSerializationExtensions.cs b/src/Cake.Issues/IssueSerializationExtensions.cs deleted file mode 100644 index 3a6a9e6d5..000000000 --- a/src/Cake.Issues/IssueSerializationExtensions.cs +++ /dev/null @@ -1,226 +0,0 @@ -namespace Cake.Issues -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Runtime.Serialization.Json; - using System.Text; - using Cake.Core.IO; - - /// - /// Extensions for serializing and deserializing an . - /// - internal static class IssueSerializationExtensions - { - /// - /// Serializes an to a JSON string. - /// - /// Issue which should be serialized. - /// Serialized issue. - public static string SerializeToJsonString(this IIssue issue) - { - issue.NotNull(nameof(issue)); - - var serializer = new DataContractJsonSerializer(typeof(SerializableIssue)); - - using (var stream = new MemoryStream()) - { - serializer.WriteObject(stream, issue.ToSerializableIssue()); - return Encoding.UTF8.GetString(stream.ToArray()); - } - } - - /// - /// Serializes an to a JSON string. - /// - /// Issues which should be serialized. - /// Serialized issues. - public static string SerializeToJsonString(this IEnumerable issues) - { - issues.NotNull(nameof(issues)); - - var serializer = new DataContractJsonSerializer(typeof(IEnumerable)); - - using (var stream = new MemoryStream()) - { - serializer.WriteObject(stream, issues.Select(x => x.ToSerializableIssue())); - return Encoding.UTF8.GetString(stream.ToArray()); - } - } - - /// - /// Serializes an to a JSON file. - /// - /// Issue which should be serialized. - /// Path to the file. - public static void SerializeToJsonFile(this IIssue issue, FilePath filePath) - { - issue.NotNull(nameof(issue)); - filePath.NotNull(nameof(filePath)); - - var serializer = new DataContractJsonSerializer(typeof(SerializableIssue)); - - using (var stream = File.Open(filePath.FullPath, FileMode.Create)) - { - serializer.WriteObject(stream, issue.ToSerializableIssue()); - } - } - - /// - /// Serializes an to a JSON file. - /// - /// Issues which should be serialized. - /// Path to the file. - public static void SerializeToJsonFile(this IEnumerable issues, FilePath filePath) - { - issues.NotNull(nameof(issues)); - filePath.NotNull(nameof(filePath)); - - var serializer = new DataContractJsonSerializer(typeof(IEnumerable)); - - using (var stream = File.Open(filePath.FullPath, FileMode.Create)) - { - serializer.WriteObject(stream, issues.Select(x => x.ToSerializableIssue())); - } - } - - /// - /// Deserializes an from a JSON string. - /// - /// JSON representation of the issue. - /// Instance of the issue. - public static Issue DeserializeToIssue(this string jsonString) - { - jsonString.NotNullOrWhiteSpace(nameof(jsonString)); - - using (var stream = new MemoryStream(Encoding.Default.GetBytes(jsonString))) - { - return DeserializeStreamToIssue(stream); - } - } - - /// - /// Deserializes an from a JSON string. - /// - /// JSON representation of the issues. - /// List of issues. - public static IEnumerable DeserializeToIssues(this string jsonString) - { - jsonString.NotNullOrWhiteSpace(nameof(jsonString)); - - using (var stream = new MemoryStream(Encoding.Default.GetBytes(jsonString))) - { - return DeserializeStreamToIssues(stream); - } - } - - /// - /// Deserializes an from a JSON file. - /// - /// Path to the JSON file. - /// Instance of the issue. - public static Issue DeserializeToIssue(this FilePath filePath) - { - filePath.NotNull(nameof(filePath)); - - using (var stream = File.Open(filePath.FullPath, FileMode.Open)) - { - return DeserializeStreamToIssue(stream); - } - } - - /// - /// Deserializes an from a JSON file. - /// - /// Path to the JSON file. - /// List of issues. - public static IEnumerable DeserializeToIssues(this FilePath filePath) - { - filePath.NotNull(nameof(filePath)); - - using (var stream = File.Open(filePath.FullPath, FileMode.Open)) - { - return DeserializeStreamToIssues(stream); - } - } - - /// - /// Converts an to a . - /// - /// Issue which should be converted. - /// Converted issue. - internal static SerializableIssue ToSerializableIssue(this IIssue issue) - { - issue.NotNull(nameof(issue)); - - return new SerializableIssue - { - ProjectFileRelativePath = issue.ProjectFileRelativePath?.FullPath, - ProjectName = issue.ProjectName, - AffectedFileRelativePath = issue.AffectedFileRelativePath?.FullPath, - Line = issue.Line, - Message = issue.Message, - Priority = issue.Priority, - PriorityName = issue.PriorityName, - Rule = issue.Rule, - RuleUrl = issue.RuleUrl?.ToString(), - ProviderType = issue.ProviderType, - ProviderName = issue.ProviderName, - }; - } - - /// - /// Converts a to an . - /// - /// Issue which should be converted. - /// Converted issue. - internal static Issue ToIssue(this SerializableIssue serializableIssue) - { - serializableIssue.NotNull(nameof(serializableIssue)); - - Uri ruleUrl = null; - if (!string.IsNullOrWhiteSpace(serializableIssue.RuleUrl)) - { - ruleUrl = new Uri(serializableIssue.RuleUrl); - } - - return new Issue( - serializableIssue.ProjectFileRelativePath, - serializableIssue.ProjectName, - serializableIssue.AffectedFileRelativePath, - serializableIssue.Line, - serializableIssue.Message, - serializableIssue.Priority, - serializableIssue.PriorityName, - serializableIssue.Rule, - ruleUrl, - serializableIssue.ProviderType, - serializableIssue.ProviderName); - } - - private static Issue DeserializeStreamToIssue(Stream stream) - { - var serializer = new DataContractJsonSerializer(typeof(SerializableIssue)); - - if (!(serializer.ReadObject(stream) is SerializableIssue deserializedIssue)) - { - return null; - } - - return deserializedIssue.ToIssue(); - } - - private static IEnumerable DeserializeStreamToIssues(Stream stream) - { - var serializer = new DataContractJsonSerializer(typeof(IEnumerable)); - - if (!(serializer.ReadObject(stream) is IEnumerable deserializedIssues)) - { - return new List(); - } - - return deserializedIssues.Select(x => x.ToIssue()); - } - } -} diff --git a/src/Cake.Issues/IssuesReader.cs b/src/Cake.Issues/IssuesReader.cs index 2eacfe2e2..7751cbc34 100644 --- a/src/Cake.Issues/IssuesReader.cs +++ b/src/Cake.Issues/IssuesReader.cs @@ -40,9 +40,8 @@ public IssuesReader( /// /// Read issues from issue providers. /// - /// Preferred format for comments. /// List of issues. - public IEnumerable ReadIssues(IssueCommentFormat format) + public IEnumerable ReadIssues() { // Initialize issue providers and read issues. var issues = new List(); @@ -53,7 +52,7 @@ public IEnumerable ReadIssues(IssueCommentFormat format) if (issueProvider.Initialize(this.settings)) { this.log.Verbose("Reading issues from {0}...", providerName); - var currentIssues = issueProvider.ReadIssues(format).ToList(); + var currentIssues = issueProvider.ReadIssues().ToList(); this.log.Verbose( "Found {0} issues using issue provider {1}...", diff --git a/src/Cake.Issues/ReadIssuesSettings.cs b/src/Cake.Issues/ReadIssuesSettings.cs index d12003994..d3c52dcec 100644 --- a/src/Cake.Issues/ReadIssuesSettings.cs +++ b/src/Cake.Issues/ReadIssuesSettings.cs @@ -15,10 +15,5 @@ public ReadIssuesSettings(DirectoryPath repositoryRoot) : base(repositoryRoot) { } - - /// - /// Gets or sets the preferred format in which issue comments should be returned. - /// - public IssueCommentFormat Format { get; set; } = IssueCommentFormat.Undefined; } } diff --git a/src/Cake.Issues/Serialization/IssueDeserializationExtensions.cs b/src/Cake.Issues/Serialization/IssueDeserializationExtensions.cs new file mode 100644 index 000000000..49b3ef740 --- /dev/null +++ b/src/Cake.Issues/Serialization/IssueDeserializationExtensions.cs @@ -0,0 +1,141 @@ +namespace Cake.Issues.Serialization +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Text; + using Cake.Core.IO; + using LitJson; + + /// + /// Extensions for deserializing an . + /// + internal static class IssueDeserializationExtensions + { + /// + /// Deserializes an from a JSON string. + /// + /// JSON representation of the issue. + /// Instance of the issue. + public static Issue DeserializeToIssue(this string jsonString) + { + jsonString.NotNullOrWhiteSpace(nameof(jsonString)); + + using (var stream = new MemoryStream(Encoding.Default.GetBytes(jsonString))) + { + return DeserializeStreamToIssue(stream); + } + } + + /// + /// Deserializes an from a JSON string. + /// + /// JSON representation of the issues. + /// List of issues. + public static IEnumerable DeserializeToIssues(this string jsonString) + { + jsonString.NotNullOrWhiteSpace(nameof(jsonString)); + + using (var stream = new MemoryStream(Encoding.Default.GetBytes(jsonString))) + { + return DeserializeStreamToIssues(stream); + } + } + + /// + /// Deserializes an from a JSON file. + /// + /// Path to the JSON file. + /// Instance of the issue. + public static Issue DeserializeToIssue(this FilePath filePath) + { + filePath.NotNull(nameof(filePath)); + + using (var stream = File.Open(filePath.FullPath, FileMode.Open)) + { + return DeserializeStreamToIssue(stream); + } + } + + /// + /// Deserializes an from a JSON file. + /// + /// Path to the JSON file. + /// List of issues. + public static IEnumerable DeserializeToIssues(this FilePath filePath) + { + filePath.NotNull(nameof(filePath)); + + using (var stream = File.Open(filePath.FullPath, FileMode.Open)) + { + return DeserializeStreamToIssues(stream); + } + } + + /// + /// Deserializes a stream containing the JSON representation of an issue to an . + /// + /// Stream whose content should be deserialized. + /// Issue instance. + private static Issue DeserializeStreamToIssue(Stream stream) + { + using (var reader = new StreamReader(stream)) + { + var jsonContent = reader.ReadToEnd(); + + var data = JsonMapper.ToObject(jsonContent); + + return DeserializeJsonDataToIssue(data); + } + } + + /// + /// Deserializes a stream containing the JSON representation of an array of issues to an . + /// + /// Stream whose content should be deserialized. + /// List of issues. + private static IEnumerable DeserializeStreamToIssues(Stream stream) + { + using (var reader = new StreamReader(stream)) + { + var jsonContent = reader.ReadToEnd(); + + var data = JsonMapper.ToObject(jsonContent); + var issues = new List(); + foreach (JsonData element in data) + { + issues.Add(DeserializeJsonDataToIssue(element)); + } + + return issues; + } + } + + /// + /// Deserializes JSON repesentation of an issue to an . + /// Supports serialization format of the current version of Cake.Issues as versions + /// written with previous versions of Cake.Issues. + /// + /// JSON representation of the issue. + /// Issue instance. + private static Issue DeserializeJsonDataToIssue(JsonData data) + { + if (data.ContainsKey("Version")) + { + var version = (int)data["Version"]; + switch (version) + { + case 2: + return JsonMapper.ToObject(data.ToJson()).ToIssue(); + default: + throw new Exception($"Not supported issue serialization format {version}"); + } + } + else + { + // If no version is available deserialize to original format. + return JsonMapper.ToObject(data.ToJson()).ToIssue(); + } + } + } +} diff --git a/src/Cake.Issues/Serialization/IssueSerializationExtensions.cs b/src/Cake.Issues/Serialization/IssueSerializationExtensions.cs new file mode 100644 index 000000000..c107779ec --- /dev/null +++ b/src/Cake.Issues/Serialization/IssueSerializationExtensions.cs @@ -0,0 +1,99 @@ +namespace Cake.Issues.Serialization +{ + using System.Collections.Generic; + using System.IO; + using System.Linq; + using Cake.Core.IO; + using LitJson; + + /// + /// Extensions for serializing an to the latest serialization format. + /// + internal static class IssueSerializationExtensions + { + /// + /// Serializes an to a JSON string. + /// + /// Issue which should be serialized. + /// Serialized issue. + public static string SerializeToJsonString(this IIssue issue) + { + issue.NotNull(nameof(issue)); + + return JsonMapper.ToJson(issue.ToSerializableIssue()); + } + + /// + /// Serializes an to a JSON string. + /// + /// Issues which should be serialized. + /// Serialized issues. + public static string SerializeToJsonString(this IEnumerable issues) + { + issues.NotNull(nameof(issues)); + + return JsonMapper.ToJson(issues.Select(x => x.ToSerializableIssue()).ToArray()); + } + + /// + /// Serializes an to a JSON file. + /// + /// Issue which should be serialized. + /// Path to the file. + public static void SerializeToJsonFile(this IIssue issue, FilePath filePath) + { + issue.NotNull(nameof(issue)); + filePath.NotNull(nameof(filePath)); + + using (var stream = File.Open(filePath.FullPath, FileMode.Create)) + using (var writer = new StreamWriter(stream)) + { + JsonMapper.ToJson(issue.ToSerializableIssue(), new JsonWriter(writer)); + } + } + + /// + /// Serializes an to a JSON file. + /// + /// Issues which should be serialized. + /// Path to the file. + public static void SerializeToJsonFile(this IEnumerable issues, FilePath filePath) + { + issues.NotNull(nameof(issues)); + filePath.NotNull(nameof(filePath)); + + using (var stream = File.Open(filePath.FullPath, FileMode.Create)) + using (var writer = new StreamWriter(stream)) + { + JsonMapper.ToJson(issues.Select(x => x.ToSerializableIssue()).ToArray(), new JsonWriter(writer)); + } + } + + /// + /// Converts an to a . + /// + /// Issue which should be converted. + /// Converted issue. + internal static SerializableIssueV2 ToSerializableIssue(this IIssue issue) + { + issue.NotNull(nameof(issue)); + + return new SerializableIssueV2 + { + ProjectFileRelativePath = issue.ProjectFileRelativePath?.FullPath, + ProjectName = issue.ProjectName, + AffectedFileRelativePath = issue.AffectedFileRelativePath?.FullPath, + Line = issue.Line, + MessageText = issue.MessageText, + MessageMarkdown = issue.MessageMarkdown, + MessageHtml = issue.MessageHtml, + Priority = issue.Priority, + PriorityName = issue.PriorityName, + Rule = issue.Rule, + RuleUrl = issue.RuleUrl?.ToString(), + ProviderType = issue.ProviderType, + ProviderName = issue.ProviderName, + }; + } + } +} diff --git a/src/Cake.Issues/Serialization/LitJson/IJsonWrapper.cs b/src/Cake.Issues/Serialization/LitJson/IJsonWrapper.cs new file mode 100644 index 000000000..34b2b820d --- /dev/null +++ b/src/Cake.Issues/Serialization/LitJson/IJsonWrapper.cs @@ -0,0 +1,66 @@ +#pragma warning disable 1587 +#pragma warning disable 1591 + +#region Header +/** + * IJsonWrapper.cs + * Interface that represents a type capable of handling all kinds of JSON + * data. This is mainly used when mapping objects through JsonMapper, and + * it's implemented by JsonData. + * + * The authors disclaim copyright to this source code. For more details, see + * the 3rd-Party-License.md file included with this distribution. + **/ + +// This file isn't generated, but this comment is necessary to exclude it from code analysis. +// +#endregion + + +using System.Collections; +using System.Collections.Specialized; + + +namespace LitJson +{ + internal enum JsonType + { + None, + + Object, + Array, + String, + Int, + Long, + Double, + Boolean + } + + internal interface IJsonWrapper : IList, IOrderedDictionary + { + bool IsArray { get; } + bool IsBoolean { get; } + bool IsDouble { get; } + bool IsInt { get; } + bool IsLong { get; } + bool IsObject { get; } + bool IsString { get; } + + bool GetBoolean (); + double GetDouble (); + int GetInt (); + JsonType GetJsonType (); + long GetLong (); + string GetString (); + + void SetBoolean (bool val); + void SetDouble (double val); + void SetInt (int val); + void SetJsonType (JsonType type); + void SetLong (long val); + void SetString (string val); + + string ToJson (); + void ToJson (JsonWriter writer); + } +} diff --git a/src/Cake.Issues/Serialization/LitJson/JsonData.cs b/src/Cake.Issues/Serialization/LitJson/JsonData.cs new file mode 100644 index 000000000..d595bfc2e --- /dev/null +++ b/src/Cake.Issues/Serialization/LitJson/JsonData.cs @@ -0,0 +1,1065 @@ +#pragma warning disable 1587 +#pragma warning disable 1591 + +#region Header +/** + * JsonData.cs + * Generic type to hold JSON data (objects, arrays, and so on). This is + * the default type returned by JsonMapper.ToObject(). + * + * The authors disclaim copyright to this source code. For more details, see + * the 3rd-Party-License.md file included with this distribution. + **/ + +// This file isn't generated, but this comment is necessary to exclude it from code analysis. +// +#endregion + + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.IO; + + +namespace LitJson +{ + internal class JsonData : IJsonWrapper, IEquatable + { + #region Fields + private IList inst_array; + private bool inst_boolean; + private double inst_double; + private int inst_int; + private long inst_long; + private IDictionary inst_object; + private string inst_string; + private string json; + private JsonType type; + + // Used to implement the IOrderedDictionary interface + private IList> object_list; + #endregion + + + #region Properties + public int Count { + get { return EnsureCollection ().Count; } + } + + public bool IsArray { + get { return type == JsonType.Array; } + } + + public bool IsBoolean { + get { return type == JsonType.Boolean; } + } + + public bool IsDouble { + get { return type == JsonType.Double; } + } + + public bool IsInt { + get { return type == JsonType.Int; } + } + + public bool IsLong { + get { return type == JsonType.Long; } + } + + public bool IsObject { + get { return type == JsonType.Object; } + } + + public bool IsString { + get { return type == JsonType.String; } + } + + public ICollection Keys { + get { EnsureDictionary (); return inst_object.Keys; } + } + + /// + /// Determines whether the json contains an element that has the specified key. + /// + /// The key to locate in the json. + /// true if the json contains an element that has the specified key; otherwise, false. + public Boolean ContainsKey(String key) { + EnsureDictionary(); + return this.inst_object.Keys.Contains(key); + } + #endregion + + + #region ICollection Properties + int ICollection.Count { + get { + return Count; + } + } + + bool ICollection.IsSynchronized { + get { + return EnsureCollection ().IsSynchronized; + } + } + + object ICollection.SyncRoot { + get { + return EnsureCollection ().SyncRoot; + } + } + #endregion + + + #region IDictionary Properties + bool IDictionary.IsFixedSize { + get { + return EnsureDictionary ().IsFixedSize; + } + } + + bool IDictionary.IsReadOnly { + get { + return EnsureDictionary ().IsReadOnly; + } + } + + ICollection IDictionary.Keys { + get { + EnsureDictionary (); + IList keys = new List (); + + foreach (KeyValuePair entry in + object_list) { + keys.Add (entry.Key); + } + + return (ICollection) keys; + } + } + + ICollection IDictionary.Values { + get { + EnsureDictionary (); + IList values = new List (); + + foreach (KeyValuePair entry in + object_list) { + values.Add (entry.Value); + } + + return (ICollection) values; + } + } + #endregion + + + + #region IJsonWrapper Properties + bool IJsonWrapper.IsArray { + get { return IsArray; } + } + + bool IJsonWrapper.IsBoolean { + get { return IsBoolean; } + } + + bool IJsonWrapper.IsDouble { + get { return IsDouble; } + } + + bool IJsonWrapper.IsInt { + get { return IsInt; } + } + + bool IJsonWrapper.IsLong { + get { return IsLong; } + } + + bool IJsonWrapper.IsObject { + get { return IsObject; } + } + + bool IJsonWrapper.IsString { + get { return IsString; } + } + #endregion + + + #region IList Properties + bool IList.IsFixedSize { + get { + return EnsureList ().IsFixedSize; + } + } + + bool IList.IsReadOnly { + get { + return EnsureList ().IsReadOnly; + } + } + #endregion + + + #region IDictionary Indexer + object IDictionary.this[object key] { + get { + return EnsureDictionary ()[key]; + } + + set { + if (! (key is String)) + throw new ArgumentException ( + "The key has to be a string"); + + JsonData data = ToJsonData (value); + + this[(string) key] = data; + } + } + #endregion + + + #region IOrderedDictionary Indexer + object IOrderedDictionary.this[int idx] { + get { + EnsureDictionary (); + return object_list[idx].Value; + } + + set { + EnsureDictionary (); + JsonData data = ToJsonData (value); + + KeyValuePair old_entry = object_list[idx]; + + inst_object[old_entry.Key] = data; + + KeyValuePair entry = + new KeyValuePair (old_entry.Key, data); + + object_list[idx] = entry; + } + } + #endregion + + + #region IList Indexer + object IList.this[int index] { + get { + return EnsureList ()[index]; + } + + set { + EnsureList (); + JsonData data = ToJsonData (value); + + this[index] = data; + } + } + #endregion + + + #region Public Indexers + public JsonData this[string prop_name] { + get { + EnsureDictionary (); + return inst_object[prop_name]; + } + + set { + EnsureDictionary (); + + KeyValuePair entry = + new KeyValuePair (prop_name, value); + + if (inst_object.ContainsKey (prop_name)) { + for (int i = 0; i < object_list.Count; i++) { + if (object_list[i].Key == prop_name) { + object_list[i] = entry; + break; + } + } + } else + object_list.Add (entry); + + inst_object[prop_name] = value; + + json = null; + } + } + + public JsonData this[int index] { + get { + EnsureCollection (); + + if (type == JsonType.Array) + return inst_array[index]; + + return object_list[index].Value; + } + + set { + EnsureCollection (); + + if (type == JsonType.Array) + inst_array[index] = value; + else { + KeyValuePair entry = object_list[index]; + KeyValuePair new_entry = + new KeyValuePair (entry.Key, value); + + object_list[index] = new_entry; + inst_object[entry.Key] = value; + } + + json = null; + } + } + #endregion + + + #region Constructors + public JsonData () + { + } + + public JsonData (bool boolean) + { + type = JsonType.Boolean; + inst_boolean = boolean; + } + + public JsonData (double number) + { + type = JsonType.Double; + inst_double = number; + } + + public JsonData (int number) + { + type = JsonType.Int; + inst_int = number; + } + + public JsonData (long number) + { + type = JsonType.Long; + inst_long = number; + } + + public JsonData (object obj) + { + if (obj is Boolean) { + type = JsonType.Boolean; + inst_boolean = (bool) obj; + return; + } + + if (obj is Double) { + type = JsonType.Double; + inst_double = (double) obj; + return; + } + + if (obj is Int32) { + type = JsonType.Int; + inst_int = (int) obj; + return; + } + + if (obj is Int64) { + type = JsonType.Long; + inst_long = (long) obj; + return; + } + + if (obj is String) { + type = JsonType.String; + inst_string = (string) obj; + return; + } + + throw new ArgumentException ( + "Unable to wrap the given object with JsonData"); + } + + public JsonData (string str) + { + type = JsonType.String; + inst_string = str; + } + #endregion + + + #region Implicit Conversions + public static implicit operator JsonData (Boolean data) + { + return new JsonData (data); + } + + public static implicit operator JsonData (Double data) + { + return new JsonData (data); + } + + public static implicit operator JsonData (Int32 data) + { + return new JsonData (data); + } + + public static implicit operator JsonData (Int64 data) + { + return new JsonData (data); + } + + public static implicit operator JsonData (String data) + { + return new JsonData (data); + } + #endregion + + + #region Explicit Conversions + public static explicit operator Boolean (JsonData data) + { + if (data.type != JsonType.Boolean) + throw new InvalidCastException ( + "Instance of JsonData doesn't hold a double"); + + return data.inst_boolean; + } + + public static explicit operator Double (JsonData data) + { + if (data.type != JsonType.Double) + throw new InvalidCastException ( + "Instance of JsonData doesn't hold a double"); + + return data.inst_double; + } + + public static explicit operator Int32(JsonData data) + { + if (data.type != JsonType.Int && data.type != JsonType.Long) + { + throw new InvalidCastException( + "Instance of JsonData doesn't hold an int"); + } + + // cast may truncate data... but that's up to the user to consider + return data.type == JsonType.Int ? data.inst_int : (int)data.inst_long; + } + + public static explicit operator Int64(JsonData data) + { + if (data.type != JsonType.Long && data.type != JsonType.Int) + { + throw new InvalidCastException( + "Instance of JsonData doesn't hold a long"); + } + + return data.type == JsonType.Long ? data.inst_long : data.inst_int; + } + + public static explicit operator String (JsonData data) + { + if (data.type != JsonType.String) + throw new InvalidCastException ( + "Instance of JsonData doesn't hold a string"); + + return data.inst_string; + } + #endregion + + + #region ICollection Methods + void ICollection.CopyTo (Array array, int index) + { + EnsureCollection ().CopyTo (array, index); + } + #endregion + + + #region IDictionary Methods + void IDictionary.Add (object key, object value) + { + JsonData data = ToJsonData (value); + + EnsureDictionary ().Add (key, data); + + KeyValuePair entry = + new KeyValuePair ((string) key, data); + object_list.Add (entry); + + json = null; + } + + void IDictionary.Clear () + { + EnsureDictionary ().Clear (); + object_list.Clear (); + json = null; + } + + bool IDictionary.Contains (object key) + { + return EnsureDictionary ().Contains (key); + } + + IDictionaryEnumerator IDictionary.GetEnumerator () + { + return ((IOrderedDictionary) this).GetEnumerator (); + } + + void IDictionary.Remove (object key) + { + EnsureDictionary ().Remove (key); + + for (int i = 0; i < object_list.Count; i++) { + if (object_list[i].Key == (string) key) { + object_list.RemoveAt (i); + break; + } + } + + json = null; + } + #endregion + + + #region IEnumerable Methods + IEnumerator IEnumerable.GetEnumerator () + { + return EnsureCollection ().GetEnumerator (); + } + #endregion + + + #region IJsonWrapper Methods + bool IJsonWrapper.GetBoolean () + { + if (type != JsonType.Boolean) + throw new InvalidOperationException ( + "JsonData instance doesn't hold a boolean"); + + return inst_boolean; + } + + double IJsonWrapper.GetDouble () + { + if (type != JsonType.Double) + throw new InvalidOperationException ( + "JsonData instance doesn't hold a double"); + + return inst_double; + } + + int IJsonWrapper.GetInt () + { + if (type != JsonType.Int) + throw new InvalidOperationException ( + "JsonData instance doesn't hold an int"); + + return inst_int; + } + + long IJsonWrapper.GetLong () + { + if (type != JsonType.Long) + throw new InvalidOperationException ( + "JsonData instance doesn't hold a long"); + + return inst_long; + } + + string IJsonWrapper.GetString () + { + if (type != JsonType.String) + throw new InvalidOperationException ( + "JsonData instance doesn't hold a string"); + + return inst_string; + } + + void IJsonWrapper.SetBoolean (bool val) + { + type = JsonType.Boolean; + inst_boolean = val; + json = null; + } + + void IJsonWrapper.SetDouble (double val) + { + type = JsonType.Double; + inst_double = val; + json = null; + } + + void IJsonWrapper.SetInt (int val) + { + type = JsonType.Int; + inst_int = val; + json = null; + } + + void IJsonWrapper.SetLong (long val) + { + type = JsonType.Long; + inst_long = val; + json = null; + } + + void IJsonWrapper.SetString (string val) + { + type = JsonType.String; + inst_string = val; + json = null; + } + + string IJsonWrapper.ToJson () + { + return ToJson (); + } + + void IJsonWrapper.ToJson (JsonWriter writer) + { + ToJson (writer); + } + #endregion + + + #region IList Methods + int IList.Add (object value) + { + return Add (value); + } + + void IList.Clear () + { + EnsureList ().Clear (); + json = null; + } + + bool IList.Contains (object value) + { + return EnsureList ().Contains (value); + } + + int IList.IndexOf (object value) + { + return EnsureList ().IndexOf (value); + } + + void IList.Insert (int index, object value) + { + EnsureList ().Insert (index, value); + json = null; + } + + void IList.Remove (object value) + { + EnsureList ().Remove (value); + json = null; + } + + void IList.RemoveAt (int index) + { + EnsureList ().RemoveAt (index); + json = null; + } + #endregion + + + #region IOrderedDictionary Methods + IDictionaryEnumerator IOrderedDictionary.GetEnumerator () + { + EnsureDictionary (); + + return new OrderedDictionaryEnumerator ( + object_list.GetEnumerator ()); + } + + void IOrderedDictionary.Insert (int idx, object key, object value) + { + string property = (string) key; + JsonData data = ToJsonData (value); + + this[property] = data; + + KeyValuePair entry = + new KeyValuePair (property, data); + + object_list.Insert (idx, entry); + } + + void IOrderedDictionary.RemoveAt (int idx) + { + EnsureDictionary (); + + inst_object.Remove (object_list[idx].Key); + object_list.RemoveAt (idx); + } + #endregion + + + #region Private Methods + private ICollection EnsureCollection () + { + if (type == JsonType.Array) + return (ICollection) inst_array; + + if (type == JsonType.Object) + return (ICollection) inst_object; + + throw new InvalidOperationException ( + "The JsonData instance has to be initialized first"); + } + + private IDictionary EnsureDictionary () + { + if (type == JsonType.Object) + return (IDictionary) inst_object; + + if (type != JsonType.None) + throw new InvalidOperationException ( + "Instance of JsonData is not a dictionary"); + + type = JsonType.Object; + inst_object = new Dictionary (); + object_list = new List> (); + + return (IDictionary) inst_object; + } + + private IList EnsureList () + { + if (type == JsonType.Array) + return (IList) inst_array; + + if (type != JsonType.None) + throw new InvalidOperationException ( + "Instance of JsonData is not a list"); + + type = JsonType.Array; + inst_array = new List (); + + return (IList) inst_array; + } + + private JsonData ToJsonData (object obj) + { + if (obj == null) + return null; + + if (obj is JsonData) + return (JsonData) obj; + + return new JsonData (obj); + } + + private static void WriteJson (IJsonWrapper obj, JsonWriter writer) + { + if (obj == null) { + writer.Write (null); + return; + } + + if (obj.IsString) { + writer.Write (obj.GetString ()); + return; + } + + if (obj.IsBoolean) { + writer.Write (obj.GetBoolean ()); + return; + } + + if (obj.IsDouble) { + writer.Write (obj.GetDouble ()); + return; + } + + if (obj.IsInt) { + writer.Write (obj.GetInt ()); + return; + } + + if (obj.IsLong) { + writer.Write (obj.GetLong ()); + return; + } + + if (obj.IsArray) { + writer.WriteArrayStart (); + foreach (object elem in (IList) obj) + WriteJson ((JsonData) elem, writer); + writer.WriteArrayEnd (); + + return; + } + + if (obj.IsObject) { + writer.WriteObjectStart (); + + foreach (DictionaryEntry entry in ((IDictionary) obj)) { + writer.WritePropertyName ((string) entry.Key); + WriteJson ((JsonData) entry.Value, writer); + } + writer.WriteObjectEnd (); + + return; + } + } + #endregion + + + public int Add (object value) + { + JsonData data = ToJsonData (value); + + json = null; + + return EnsureList ().Add (data); + } + + public bool Remove(object obj) + { + json = null; + if(IsObject) + { + JsonData value = null; + if (inst_object.TryGetValue((string)obj, out value)) + return inst_object.Remove((string)obj) && object_list.Remove(new KeyValuePair((string)obj, value)); + else + throw new KeyNotFoundException("The specified key was not found in the JsonData object."); + } + if(IsArray) + { + return inst_array.Remove(ToJsonData(obj)); + } + throw new InvalidOperationException ( + "Instance of JsonData is not an object or a list."); + } + + public void Clear () + { + if (IsObject) { + ((IDictionary) this).Clear (); + return; + } + + if (IsArray) { + ((IList) this).Clear (); + return; + } + } + + public bool Equals (JsonData x) + { + if (x == null) + return false; + + if (x.type != this.type) + { + // further check to see if this is a long to int comparison + if ((x.type != JsonType.Int && x.type != JsonType.Long) + || (this.type != JsonType.Int && this.type != JsonType.Long)) + { + return false; + } + } + + switch (this.type) { + case JsonType.None: + return true; + + case JsonType.Object: + return this.inst_object.Equals (x.inst_object); + + case JsonType.Array: + return this.inst_array.Equals (x.inst_array); + + case JsonType.String: + return this.inst_string.Equals (x.inst_string); + + case JsonType.Int: + { + if (x.IsLong) + { + if (x.inst_long < Int32.MinValue || x.inst_long > Int32.MaxValue) + return false; + return this.inst_int.Equals((int)x.inst_long); + } + return this.inst_int.Equals(x.inst_int); + } + + case JsonType.Long: + { + if (x.IsInt) + { + if (this.inst_long < Int32.MinValue || this.inst_long > Int32.MaxValue) + return false; + return x.inst_int.Equals((int)this.inst_long); + } + return this.inst_long.Equals(x.inst_long); + } + + case JsonType.Double: + return this.inst_double.Equals (x.inst_double); + + case JsonType.Boolean: + return this.inst_boolean.Equals (x.inst_boolean); + } + + return false; + } + + public JsonType GetJsonType () + { + return type; + } + + public void SetJsonType (JsonType type) + { + if (this.type == type) + return; + + switch (type) { + case JsonType.None: + break; + + case JsonType.Object: + inst_object = new Dictionary (); + object_list = new List> (); + break; + + case JsonType.Array: + inst_array = new List (); + break; + + case JsonType.String: + inst_string = default (String); + break; + + case JsonType.Int: + inst_int = default (Int32); + break; + + case JsonType.Long: + inst_long = default (Int64); + break; + + case JsonType.Double: + inst_double = default (Double); + break; + + case JsonType.Boolean: + inst_boolean = default (Boolean); + break; + } + + this.type = type; + } + + public string ToJson () + { + if (json != null) + return json; + + StringWriter sw = new StringWriter (); + JsonWriter writer = new JsonWriter (sw); + writer.Validate = false; + + WriteJson (this, writer); + json = sw.ToString (); + + return json; + } + + public void ToJson (JsonWriter writer) + { + bool old_validate = writer.Validate; + + writer.Validate = false; + + WriteJson (this, writer); + + writer.Validate = old_validate; + } + + public override string ToString () + { + switch (type) { + case JsonType.Array: + return "JsonData array"; + + case JsonType.Boolean: + return inst_boolean.ToString (); + + case JsonType.Double: + return inst_double.ToString (); + + case JsonType.Int: + return inst_int.ToString (); + + case JsonType.Long: + return inst_long.ToString (); + + case JsonType.Object: + return "JsonData object"; + + case JsonType.String: + return inst_string; + } + + return "Uninitialized JsonData"; + } + } + + + internal class OrderedDictionaryEnumerator : IDictionaryEnumerator + { + IEnumerator> list_enumerator; + + + public object Current { + get { return Entry; } + } + + public DictionaryEntry Entry { + get { + KeyValuePair curr = list_enumerator.Current; + return new DictionaryEntry (curr.Key, curr.Value); + } + } + + public object Key { + get { return list_enumerator.Current.Key; } + } + + public object Value { + get { return list_enumerator.Current.Value; } + } + + + public OrderedDictionaryEnumerator ( + IEnumerator> enumerator) + { + list_enumerator = enumerator; + } + + + public bool MoveNext () + { + return list_enumerator.MoveNext (); + } + + public void Reset () + { + list_enumerator.Reset (); + } + } +} diff --git a/src/Cake.Issues/Serialization/LitJson/JsonException.cs b/src/Cake.Issues/Serialization/LitJson/JsonException.cs new file mode 100644 index 000000000..6353b6995 --- /dev/null +++ b/src/Cake.Issues/Serialization/LitJson/JsonException.cs @@ -0,0 +1,71 @@ +#pragma warning disable 1587 +#pragma warning disable 1591 + +#region Header +/** + * JsonException.cs + * Base class throwed by LitJSON when a parsing error occurs. + * + * The authors disclaim copyright to this source code. For more details, see + * the 3rd-Party-License.md file included with this distribution. + **/ + +// This file isn't generated, but this comment is necessary to exclude it from code analysis. +// +#endregion + + +using System; + + +namespace LitJson +{ + internal class JsonException : +#if NETSTANDARD1_5 + Exception +#else + ApplicationException +#endif + { + public JsonException () : base () + { + } + + internal JsonException (ParserToken token) : + base (String.Format ( + "Invalid token '{0}' in input string", token)) + { + } + + internal JsonException (ParserToken token, + Exception inner_exception) : + base (String.Format ( + "Invalid token '{0}' in input string", token), + inner_exception) + { + } + + internal JsonException (int c) : + base (String.Format ( + "Invalid character '{0}' in input string", (char) c)) + { + } + + internal JsonException (int c, Exception inner_exception) : + base (String.Format ( + "Invalid character '{0}' in input string", (char) c), + inner_exception) + { + } + + + public JsonException (string message) : base (message) + { + } + + public JsonException (string message, Exception inner_exception) : + base (message, inner_exception) + { + } + } +} diff --git a/src/Cake.Issues/Serialization/LitJson/JsonMapper.cs b/src/Cake.Issues/Serialization/LitJson/JsonMapper.cs new file mode 100644 index 000000000..d9dd42f3f --- /dev/null +++ b/src/Cake.Issues/Serialization/LitJson/JsonMapper.cs @@ -0,0 +1,967 @@ +#pragma warning disable 1587 +#pragma warning disable 1591 + +#region Header +/** + * JsonMapper.cs + * JSON to .Net object and object to JSON conversions. + * + * The authors disclaim copyright to this source code. For more details, see + * the 3rd-Party-License.md file included with this distribution. + **/ + +// This file isn't generated, but this comment is necessary to exclude it from code analysis. +// +#endregion + + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Reflection; + + +namespace LitJson +{ + internal struct PropertyMetadata + { + public MemberInfo Info; + public bool IsField; + public Type Type; + } + + + internal struct ArrayMetadata + { + private Type element_type; + private bool is_array; + private bool is_list; + + + public Type ElementType { + get { + if (element_type == null) + return typeof (JsonData); + + return element_type; + } + + set { element_type = value; } + } + + public bool IsArray { + get { return is_array; } + set { is_array = value; } + } + + public bool IsList { + get { return is_list; } + set { is_list = value; } + } + } + + + internal struct ObjectMetadata + { + private Type element_type; + private bool is_dictionary; + + private IDictionary properties; + + + public Type ElementType { + get { + if (element_type == null) + return typeof (JsonData); + + return element_type; + } + + set { element_type = value; } + } + + public bool IsDictionary { + get { return is_dictionary; } + set { is_dictionary = value; } + } + + public IDictionary Properties { + get { return properties; } + set { properties = value; } + } + } + + + internal delegate void ExporterFunc (object obj, JsonWriter writer); + internal delegate void ExporterFunc (T obj, JsonWriter writer); + + internal delegate object ImporterFunc (object input); + internal delegate TValue ImporterFunc (TJson input); + + internal delegate IJsonWrapper WrapperFactory (); + + + internal class JsonMapper + { + #region Fields + private static readonly int max_nesting_depth; + + private static readonly IFormatProvider datetime_format; + + private static readonly IDictionary base_exporters_table; + private static readonly IDictionary custom_exporters_table; + + private static readonly IDictionary> base_importers_table; + private static readonly IDictionary> custom_importers_table; + + private static readonly IDictionary array_metadata; + private static readonly object array_metadata_lock = new Object (); + + private static readonly IDictionary> conv_ops; + private static readonly object conv_ops_lock = new Object (); + + private static readonly IDictionary object_metadata; + private static readonly object object_metadata_lock = new Object (); + + private static readonly IDictionary> type_properties; + private static readonly object type_properties_lock = new Object (); + + private static readonly JsonWriter static_writer; + private static readonly object static_writer_lock = new Object (); + #endregion + + + #region Constructors + static JsonMapper () + { + max_nesting_depth = 100; + + array_metadata = new Dictionary (); + conv_ops = new Dictionary> (); + object_metadata = new Dictionary (); + type_properties = new Dictionary> (); + + static_writer = new JsonWriter (); + + datetime_format = DateTimeFormatInfo.InvariantInfo; + + base_exporters_table = new Dictionary (); + custom_exporters_table = new Dictionary (); + + base_importers_table = new Dictionary> (); + custom_importers_table = new Dictionary> (); + + RegisterBaseExporters (); + RegisterBaseImporters (); + } + #endregion + + + #region Private Methods + private static void AddArrayMetadata (Type type) + { + if (array_metadata.ContainsKey (type)) + return; + + ArrayMetadata data = new ArrayMetadata (); + + data.IsArray = type.IsArray; + + if (type.GetInterface ("System.Collections.IList") != null) + data.IsList = true; + + foreach (PropertyInfo p_info in type.GetProperties ()) { + if (p_info.Name != "Item") + continue; + + ParameterInfo[] parameters = p_info.GetIndexParameters (); + + if (parameters.Length != 1) + continue; + + if (parameters[0].ParameterType == typeof (int)) + data.ElementType = p_info.PropertyType; + } + + lock (array_metadata_lock) { + try { + array_metadata.Add (type, data); + } catch (ArgumentException) { + return; + } + } + } + + private static void AddObjectMetadata (Type type) + { + if (object_metadata.ContainsKey (type)) + return; + + ObjectMetadata data = new ObjectMetadata (); + + if (type.GetInterface ("System.Collections.IDictionary") != null) + data.IsDictionary = true; + + data.Properties = new Dictionary (); + + foreach (PropertyInfo p_info in type.GetProperties ()) { + if (p_info.Name == "Item") { + ParameterInfo[] parameters = p_info.GetIndexParameters (); + + if (parameters.Length != 1) + continue; + + if (parameters[0].ParameterType == typeof (string)) + data.ElementType = p_info.PropertyType; + + continue; + } + + PropertyMetadata p_data = new PropertyMetadata (); + p_data.Info = p_info; + p_data.Type = p_info.PropertyType; + + data.Properties.Add (p_info.Name, p_data); + } + + foreach (FieldInfo f_info in type.GetFields ()) { + PropertyMetadata p_data = new PropertyMetadata (); + p_data.Info = f_info; + p_data.IsField = true; + p_data.Type = f_info.FieldType; + + data.Properties.Add (f_info.Name, p_data); + } + + lock (object_metadata_lock) { + try { + object_metadata.Add (type, data); + } catch (ArgumentException) { + return; + } + } + } + + private static void AddTypeProperties (Type type) + { + if (type_properties.ContainsKey (type)) + return; + + IList props = new List (); + + foreach (PropertyInfo p_info in type.GetProperties ()) { + if (p_info.Name == "Item") + continue; + + PropertyMetadata p_data = new PropertyMetadata (); + p_data.Info = p_info; + p_data.IsField = false; + props.Add (p_data); + } + + foreach (FieldInfo f_info in type.GetFields ()) { + PropertyMetadata p_data = new PropertyMetadata (); + p_data.Info = f_info; + p_data.IsField = true; + + props.Add (p_data); + } + + lock (type_properties_lock) { + try { + type_properties.Add (type, props); + } catch (ArgumentException) { + return; + } + } + } + + private static MethodInfo GetConvOp (Type t1, Type t2) + { + lock (conv_ops_lock) { + if (! conv_ops.ContainsKey (t1)) + conv_ops.Add (t1, new Dictionary ()); + } + + if (conv_ops[t1].ContainsKey (t2)) + return conv_ops[t1][t2]; + + MethodInfo op = t1.GetMethod ( + "op_Implicit", new Type[] { t2 }); + + lock (conv_ops_lock) { + try { + conv_ops[t1].Add (t2, op); + } catch (ArgumentException) { + return conv_ops[t1][t2]; + } + } + + return op; + } + + private static object ReadValue (Type inst_type, JsonReader reader) + { + reader.Read (); + + if (reader.Token == JsonToken.ArrayEnd) + return null; + + Type underlying_type = Nullable.GetUnderlyingType(inst_type); + Type value_type = underlying_type ?? inst_type; + + if (reader.Token == JsonToken.Null) { + #if NETSTANDARD1_5 + if (inst_type.IsClass() || underlying_type != null) { + return null; + } + #else + if (inst_type.IsClass || underlying_type != null) { + return null; + } + #endif + + throw new JsonException (String.Format ( + "Can't assign null to an instance of type {0}", + inst_type)); + } + + if (reader.Token == JsonToken.Double || + reader.Token == JsonToken.Int || + reader.Token == JsonToken.Long || + reader.Token == JsonToken.String || + reader.Token == JsonToken.Boolean) { + + Type json_type = reader.Value.GetType (); + + if (value_type.IsAssignableFrom (json_type)) + return reader.Value; + + // If there's a custom importer that fits, use it + if (custom_importers_table.ContainsKey (json_type) && + custom_importers_table[json_type].ContainsKey ( + value_type)) { + + ImporterFunc importer = + custom_importers_table[json_type][value_type]; + + return importer (reader.Value); + } + + // Maybe there's a base importer that works + if (base_importers_table.ContainsKey (json_type) && + base_importers_table[json_type].ContainsKey ( + value_type)) { + + ImporterFunc importer = + base_importers_table[json_type][value_type]; + + return importer (reader.Value); + } + + // Maybe it's an enum + #if NETSTANDARD1_5 + if (value_type.IsEnum()) + return Enum.ToObject (value_type, reader.Value); + #else + if (value_type.IsEnum) + return Enum.ToObject (value_type, reader.Value); + #endif + // Try using an implicit conversion operator + MethodInfo conv_op = GetConvOp (value_type, json_type); + + if (conv_op != null) + return conv_op.Invoke (null, + new object[] { reader.Value }); + + // No luck + throw new JsonException (String.Format ( + "Can't assign value '{0}' (type {1}) to type {2}", + reader.Value, json_type, inst_type)); + } + + object instance = null; + + if (reader.Token == JsonToken.ArrayStart) { + + AddArrayMetadata (inst_type); + ArrayMetadata t_data = array_metadata[inst_type]; + + if (! t_data.IsArray && ! t_data.IsList) + throw new JsonException (String.Format ( + "Type {0} can't act as an array", + inst_type)); + + IList list; + Type elem_type; + + if (! t_data.IsArray) { + list = (IList) Activator.CreateInstance (inst_type); + elem_type = t_data.ElementType; + } else { + list = new ArrayList (); + elem_type = inst_type.GetElementType (); + } + + while (true) { + object item = ReadValue (elem_type, reader); + if (item == null && reader.Token == JsonToken.ArrayEnd) + break; + + list.Add (item); + } + + if (t_data.IsArray) { + int n = list.Count; + instance = Array.CreateInstance (elem_type, n); + + for (int i = 0; i < n; i++) + ((Array) instance).SetValue (list[i], i); + } else + instance = list; + + } else if (reader.Token == JsonToken.ObjectStart) { + AddObjectMetadata (value_type); + ObjectMetadata t_data = object_metadata[value_type]; + + instance = Activator.CreateInstance (value_type); + + while (true) { + reader.Read (); + + if (reader.Token == JsonToken.ObjectEnd) + break; + + string property = (string) reader.Value; + + if (t_data.Properties.ContainsKey (property)) { + PropertyMetadata prop_data = + t_data.Properties[property]; + + if (prop_data.IsField) { + ((FieldInfo) prop_data.Info).SetValue ( + instance, ReadValue (prop_data.Type, reader)); + } else { + PropertyInfo p_info = + (PropertyInfo) prop_data.Info; + + if (p_info.CanWrite) + p_info.SetValue ( + instance, + ReadValue (prop_data.Type, reader), + null); + else + ReadValue (prop_data.Type, reader); + } + + } else { + if (! t_data.IsDictionary) { + + if (! reader.SkipNonMembers) { + throw new JsonException (String.Format ( + "The type {0} doesn't have the " + + "property '{1}'", + inst_type, property)); + } else { + ReadSkip (reader); + continue; + } + } + + ((IDictionary) instance).Add ( + property, ReadValue ( + t_data.ElementType, reader)); + } + + } + + } + + return instance; + } + + private static IJsonWrapper ReadValue (WrapperFactory factory, + JsonReader reader) + { + reader.Read (); + + if (reader.Token == JsonToken.ArrayEnd || + reader.Token == JsonToken.Null) + return null; + + IJsonWrapper instance = factory (); + + if (reader.Token == JsonToken.String) { + instance.SetString ((string) reader.Value); + return instance; + } + + if (reader.Token == JsonToken.Double) { + instance.SetDouble ((double) reader.Value); + return instance; + } + + if (reader.Token == JsonToken.Int) { + instance.SetInt ((int) reader.Value); + return instance; + } + + if (reader.Token == JsonToken.Long) { + instance.SetLong ((long) reader.Value); + return instance; + } + + if (reader.Token == JsonToken.Boolean) { + instance.SetBoolean ((bool) reader.Value); + return instance; + } + + if (reader.Token == JsonToken.ArrayStart) { + instance.SetJsonType (JsonType.Array); + + while (true) { + IJsonWrapper item = ReadValue (factory, reader); + if (item == null && reader.Token == JsonToken.ArrayEnd) + break; + + ((IList) instance).Add (item); + } + } + else if (reader.Token == JsonToken.ObjectStart) { + instance.SetJsonType (JsonType.Object); + + while (true) { + reader.Read (); + + if (reader.Token == JsonToken.ObjectEnd) + break; + + string property = (string) reader.Value; + + ((IDictionary) instance)[property] = ReadValue ( + factory, reader); + } + + } + + return instance; + } + + private static void ReadSkip (JsonReader reader) + { + ToWrapper ( + delegate { return new JsonMockWrapper (); }, reader); + } + + private static void RegisterBaseExporters () + { + base_exporters_table[typeof (byte)] = + delegate (object obj, JsonWriter writer) { + writer.Write (Convert.ToInt32 ((byte) obj)); + }; + + base_exporters_table[typeof (char)] = + delegate (object obj, JsonWriter writer) { + writer.Write (Convert.ToString ((char) obj)); + }; + + base_exporters_table[typeof (DateTime)] = + delegate (object obj, JsonWriter writer) { + writer.Write (Convert.ToString ((DateTime) obj, + datetime_format)); + }; + + base_exporters_table[typeof (decimal)] = + delegate (object obj, JsonWriter writer) { + writer.Write ((decimal) obj); + }; + + base_exporters_table[typeof (sbyte)] = + delegate (object obj, JsonWriter writer) { + writer.Write (Convert.ToInt32 ((sbyte) obj)); + }; + + base_exporters_table[typeof (short)] = + delegate (object obj, JsonWriter writer) { + writer.Write (Convert.ToInt32 ((short) obj)); + }; + + base_exporters_table[typeof (ushort)] = + delegate (object obj, JsonWriter writer) { + writer.Write (Convert.ToInt32 ((ushort) obj)); + }; + + base_exporters_table[typeof (uint)] = + delegate (object obj, JsonWriter writer) { + writer.Write (Convert.ToUInt64 ((uint) obj)); + }; + + base_exporters_table[typeof (ulong)] = + delegate (object obj, JsonWriter writer) { + writer.Write ((ulong) obj); + }; + + base_exporters_table[typeof(DateTimeOffset)] = + delegate (object obj, JsonWriter writer) { + writer.Write(((DateTimeOffset)obj).ToString("yyyy-MM-ddTHH:mm:ss.fffffffzzz", datetime_format)); + }; + } + + private static void RegisterBaseImporters () + { + ImporterFunc importer; + + importer = delegate (object input) { + return Convert.ToByte ((int) input); + }; + RegisterImporter (base_importers_table, typeof (int), + typeof (byte), importer); + + importer = delegate (object input) { + return Convert.ToUInt64 ((int) input); + }; + RegisterImporter (base_importers_table, typeof (int), + typeof (ulong), importer); + + importer = delegate (object input) { + return Convert.ToInt64((int)input); + }; + RegisterImporter(base_importers_table, typeof(int), + typeof(long), importer); + + importer = delegate (object input) { + return Convert.ToSByte ((int) input); + }; + RegisterImporter (base_importers_table, typeof (int), + typeof (sbyte), importer); + + importer = delegate (object input) { + return Convert.ToInt16 ((int) input); + }; + RegisterImporter (base_importers_table, typeof (int), + typeof (short), importer); + + importer = delegate (object input) { + return Convert.ToUInt16 ((int) input); + }; + RegisterImporter (base_importers_table, typeof (int), + typeof (ushort), importer); + + importer = delegate (object input) { + return Convert.ToUInt32 ((int) input); + }; + RegisterImporter (base_importers_table, typeof (int), + typeof (uint), importer); + + importer = delegate (object input) { + return Convert.ToSingle ((int) input); + }; + RegisterImporter (base_importers_table, typeof (int), + typeof (float), importer); + + importer = delegate (object input) { + return Convert.ToDouble ((int) input); + }; + RegisterImporter (base_importers_table, typeof (int), + typeof (double), importer); + + importer = delegate (object input) { + return Convert.ToDecimal ((double) input); + }; + RegisterImporter (base_importers_table, typeof (double), + typeof (decimal), importer); + + + importer = delegate (object input) { + return Convert.ToUInt32 ((long) input); + }; + RegisterImporter (base_importers_table, typeof (long), + typeof (uint), importer); + + importer = delegate (object input) { + return Convert.ToChar ((string) input); + }; + RegisterImporter (base_importers_table, typeof (string), + typeof (char), importer); + + importer = delegate (object input) { + return Convert.ToDateTime ((string) input, datetime_format); + }; + RegisterImporter (base_importers_table, typeof (string), + typeof (DateTime), importer); + + importer = delegate (object input) { + return DateTimeOffset.Parse((string)input, datetime_format); + }; + RegisterImporter(base_importers_table, typeof(string), + typeof(DateTimeOffset), importer); + } + + private static void RegisterImporter ( + IDictionary> table, + Type json_type, Type value_type, ImporterFunc importer) + { + if (! table.ContainsKey (json_type)) + table.Add (json_type, new Dictionary ()); + + table[json_type][value_type] = importer; + } + + private static void WriteValue (object obj, JsonWriter writer, + bool writer_is_private, + int depth) + { + if (depth > max_nesting_depth) + throw new JsonException ( + String.Format ("Max allowed object depth reached while " + + "trying to export from type {0}", + obj.GetType ())); + + if (obj == null) { + writer.Write (null); + return; + } + + if (obj is IJsonWrapper) { + if (writer_is_private) + writer.TextWriter.Write (((IJsonWrapper) obj).ToJson ()); + else + ((IJsonWrapper) obj).ToJson (writer); + + return; + } + + if (obj is String) { + writer.Write ((string) obj); + return; + } + + if (obj is Double) { + writer.Write ((double) obj); + return; + } + + if (obj is Int32) { + writer.Write ((int) obj); + return; + } + + if (obj is Boolean) { + writer.Write ((bool) obj); + return; + } + + if (obj is Int64) { + writer.Write ((long) obj); + return; + } + + if (obj is Array) { + writer.WriteArrayStart (); + + foreach (object elem in (Array) obj) + WriteValue (elem, writer, writer_is_private, depth + 1); + + writer.WriteArrayEnd (); + + return; + } + + if (obj is IList) { + writer.WriteArrayStart (); + foreach (object elem in (IList) obj) + WriteValue (elem, writer, writer_is_private, depth + 1); + writer.WriteArrayEnd (); + + return; + } + + if (obj is IDictionary) { + writer.WriteObjectStart (); + foreach (DictionaryEntry entry in (IDictionary) obj) { + writer.WritePropertyName ((string) entry.Key); + WriteValue (entry.Value, writer, writer_is_private, + depth + 1); + } + writer.WriteObjectEnd (); + + return; + } + + Type obj_type = obj.GetType (); + + // See if there's a custom exporter for the object + if (custom_exporters_table.ContainsKey (obj_type)) { + ExporterFunc exporter = custom_exporters_table[obj_type]; + exporter (obj, writer); + + return; + } + + // If not, maybe there's a base exporter + if (base_exporters_table.ContainsKey (obj_type)) { + ExporterFunc exporter = base_exporters_table[obj_type]; + exporter (obj, writer); + + return; + } + + // Last option, let's see if it's an enum + if (obj is Enum) { + Type e_type = Enum.GetUnderlyingType (obj_type); + + if (e_type == typeof (long) + || e_type == typeof (uint) + || e_type == typeof (ulong)) + writer.Write ((ulong) obj); + else + writer.Write ((int) obj); + + return; + } + + // Okay, so it looks like the input should be exported as an + // object + AddTypeProperties (obj_type); + IList props = type_properties[obj_type]; + + writer.WriteObjectStart (); + foreach (PropertyMetadata p_data in props) { + if (p_data.IsField) { + writer.WritePropertyName (p_data.Info.Name); + WriteValue (((FieldInfo) p_data.Info).GetValue (obj), + writer, writer_is_private, depth + 1); + } + else { + PropertyInfo p_info = (PropertyInfo) p_data.Info; + + if (p_info.CanRead) { + writer.WritePropertyName (p_data.Info.Name); + WriteValue (p_info.GetValue (obj, null), + writer, writer_is_private, depth + 1); + } + } + } + writer.WriteObjectEnd (); + } + #endregion + + + public static string ToJson (object obj) + { + lock (static_writer_lock) { + static_writer.Reset (); + + WriteValue (obj, static_writer, true, 0); + + return static_writer.ToString (); + } + } + + public static void ToJson (object obj, JsonWriter writer) + { + WriteValue (obj, writer, false, 0); + } + + public static JsonData ToObject (JsonReader reader) + { + return (JsonData) ToWrapper ( + delegate { return new JsonData (); }, reader); + } + + public static JsonData ToObject (TextReader reader) + { + JsonReader json_reader = new JsonReader (reader); + + return (JsonData) ToWrapper ( + delegate { return new JsonData (); }, json_reader); + } + + public static JsonData ToObject (string json) + { + return (JsonData) ToWrapper ( + delegate { return new JsonData (); }, json); + } + + public static T ToObject (JsonReader reader) + { + return (T) ReadValue (typeof (T), reader); + } + + public static T ToObject (TextReader reader) + { + JsonReader json_reader = new JsonReader (reader); + + return (T) ReadValue (typeof (T), json_reader); + } + + public static T ToObject (string json) + { + JsonReader reader = new JsonReader (json); + + return (T) ReadValue (typeof (T), reader); + } + + public static object ToObject(string json, Type ConvertType ) + { + JsonReader reader = new JsonReader(json); + + return ReadValue(ConvertType, reader); + } + + public static IJsonWrapper ToWrapper (WrapperFactory factory, + JsonReader reader) + { + return ReadValue (factory, reader); + } + + public static IJsonWrapper ToWrapper (WrapperFactory factory, + string json) + { + JsonReader reader = new JsonReader (json); + + return ReadValue (factory, reader); + } + + public static void RegisterExporter (ExporterFunc exporter) + { + ExporterFunc exporter_wrapper = + delegate (object obj, JsonWriter writer) { + exporter ((T) obj, writer); + }; + + custom_exporters_table[typeof (T)] = exporter_wrapper; + } + + public static void RegisterImporter ( + ImporterFunc importer) + { + ImporterFunc importer_wrapper = + delegate (object input) { + return importer ((TJson) input); + }; + + RegisterImporter (custom_importers_table, typeof (TJson), + typeof (TValue), importer_wrapper); + } + + public static void UnregisterExporters () + { + custom_exporters_table.Clear (); + } + + public static void UnregisterImporters () + { + custom_importers_table.Clear (); + } + } +} diff --git a/src/Cake.Issues/Serialization/LitJson/JsonMockWrapper.cs b/src/Cake.Issues/Serialization/LitJson/JsonMockWrapper.cs new file mode 100644 index 000000000..2b02d338d --- /dev/null +++ b/src/Cake.Issues/Serialization/LitJson/JsonMockWrapper.cs @@ -0,0 +1,111 @@ +#pragma warning disable 1587 +#pragma warning disable 1591 + +#region Header +/** + * JsonMockWrapper.cs + * Mock object implementing IJsonWrapper, to facilitate actions like + * skipping data more efficiently. + * + * The authors disclaim copyright to this source code. For more details, see + * the 3rd-Party-License.md file included with this distribution. + **/ + +// This file isn't generated, but this comment is necessary to exclude it from code analysis. +// +#endregion + + +using System; +using System.Collections; +using System.Collections.Specialized; + + +namespace LitJson +{ + internal class JsonMockWrapper : IJsonWrapper + { + public bool IsArray { get { return false; } } + public bool IsBoolean { get { return false; } } + public bool IsDouble { get { return false; } } + public bool IsInt { get { return false; } } + public bool IsLong { get { return false; } } + public bool IsObject { get { return false; } } + public bool IsString { get { return false; } } + + public bool GetBoolean () { return false; } + public double GetDouble () { return 0.0; } + public int GetInt () { return 0; } + public JsonType GetJsonType () { return JsonType.None; } + public long GetLong () { return 0L; } + public string GetString () { return ""; } + + public void SetBoolean (bool val) {} + public void SetDouble (double val) {} + public void SetInt (int val) {} + public void SetJsonType (JsonType type) {} + public void SetLong (long val) {} + public void SetString (string val) {} + + public string ToJson () { return ""; } + public void ToJson (JsonWriter writer) {} + + + bool IList.IsFixedSize { get { return true; } } + bool IList.IsReadOnly { get { return true; } } + + object IList.this[int index] { + get { return null; } + set {} + } + + int IList.Add (object value) { return 0; } + void IList.Clear () {} + bool IList.Contains (object value) { return false; } + int IList.IndexOf (object value) { return -1; } + void IList.Insert (int i, object v) {} + void IList.Remove (object value) {} + void IList.RemoveAt (int index) {} + + + int ICollection.Count { get { return 0; } } + bool ICollection.IsSynchronized { get { return false; } } + object ICollection.SyncRoot { get { return null; } } + + void ICollection.CopyTo (Array array, int index) {} + + + IEnumerator IEnumerable.GetEnumerator () { return null; } + + + bool IDictionary.IsFixedSize { get { return true; } } + bool IDictionary.IsReadOnly { get { return true; } } + + ICollection IDictionary.Keys { get { return null; } } + ICollection IDictionary.Values { get { return null; } } + + object IDictionary.this[object key] { + get { return null; } + set {} + } + + void IDictionary.Add (object k, object v) {} + void IDictionary.Clear () {} + bool IDictionary.Contains (object key) { return false; } + void IDictionary.Remove (object key) {} + + IDictionaryEnumerator IDictionary.GetEnumerator () { return null; } + + + object IOrderedDictionary.this[int idx] { + get { return null; } + set {} + } + + IDictionaryEnumerator IOrderedDictionary.GetEnumerator () { + return null; + } + void IOrderedDictionary.Insert (int i, object k, object v) {} + void IOrderedDictionary.RemoveAt (int i) {} + } +} diff --git a/src/Cake.Issues/Serialization/LitJson/JsonReader.cs b/src/Cake.Issues/Serialization/LitJson/JsonReader.cs new file mode 100644 index 000000000..b298d6aad --- /dev/null +++ b/src/Cake.Issues/Serialization/LitJson/JsonReader.cs @@ -0,0 +1,484 @@ +#pragma warning disable 1587 +#pragma warning disable 1591 + +#region Header +/** + * JsonReader.cs + * Stream-like access to JSON text. + * + * The authors disclaim copyright to this source code. For more details, see + * the 3rd-Party-License.md file included with this distribution. + **/ + +// This file isn't generated, but this comment is necessary to exclude it from code analysis. +// +#endregion + + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Text; + + +namespace LitJson +{ + internal enum JsonToken + { + None, + + ObjectStart, + PropertyName, + ObjectEnd, + + ArrayStart, + ArrayEnd, + + Int, + Long, + Double, + + String, + + Boolean, + Null + } + + + internal class JsonReader + { + #region Fields + private static readonly IDictionary> parse_table; + + private Stack automaton_stack; + private int current_input; + private int current_symbol; + private bool end_of_json; + private bool end_of_input; + private Lexer lexer; + private bool parser_in_string; + private bool parser_return; + private bool read_started; + private TextReader reader; + private bool reader_is_owned; + private bool skip_non_members; + private object token_value; + private JsonToken token; + #endregion + + + #region Public Properties + public bool AllowComments { + get { return lexer.AllowComments; } + set { lexer.AllowComments = value; } + } + + public bool AllowSingleQuotedStrings { + get { return lexer.AllowSingleQuotedStrings; } + set { lexer.AllowSingleQuotedStrings = value; } + } + + public bool SkipNonMembers { + get { return skip_non_members; } + set { skip_non_members = value; } + } + + public bool EndOfInput { + get { return end_of_input; } + } + + public bool EndOfJson { + get { return end_of_json; } + } + + public JsonToken Token { + get { return token; } + } + + public object Value { + get { return token_value; } + } + #endregion + + + #region Constructors + static JsonReader () + { + parse_table = PopulateParseTable (); + } + + public JsonReader (string json_text) : + this (new StringReader (json_text), true) + { + } + + public JsonReader (TextReader reader) : + this (reader, false) + { + } + + private JsonReader (TextReader reader, bool owned) + { + if (reader == null) + throw new ArgumentNullException ("reader"); + + parser_in_string = false; + parser_return = false; + + read_started = false; + automaton_stack = new Stack (); + automaton_stack.Push ((int) ParserToken.End); + automaton_stack.Push ((int) ParserToken.Text); + + lexer = new Lexer (reader); + + end_of_input = false; + end_of_json = false; + + skip_non_members = true; + + this.reader = reader; + reader_is_owned = owned; + } + #endregion + + + #region Static Methods + private static IDictionary> PopulateParseTable () + { + // See section A.2. of the manual for details + IDictionary> parse_table = new Dictionary> (); + + TableAddRow (parse_table, ParserToken.Array); + TableAddCol (parse_table, ParserToken.Array, '[', + '[', + (int) ParserToken.ArrayPrime); + + TableAddRow (parse_table, ParserToken.ArrayPrime); + TableAddCol (parse_table, ParserToken.ArrayPrime, '"', + (int) ParserToken.Value, + + (int) ParserToken.ValueRest, + ']'); + TableAddCol (parse_table, ParserToken.ArrayPrime, '[', + (int) ParserToken.Value, + (int) ParserToken.ValueRest, + ']'); + TableAddCol (parse_table, ParserToken.ArrayPrime, ']', + ']'); + TableAddCol (parse_table, ParserToken.ArrayPrime, '{', + (int) ParserToken.Value, + (int) ParserToken.ValueRest, + ']'); + TableAddCol (parse_table, ParserToken.ArrayPrime, (int) ParserToken.Number, + (int) ParserToken.Value, + (int) ParserToken.ValueRest, + ']'); + TableAddCol (parse_table, ParserToken.ArrayPrime, (int) ParserToken.True, + (int) ParserToken.Value, + (int) ParserToken.ValueRest, + ']'); + TableAddCol (parse_table, ParserToken.ArrayPrime, (int) ParserToken.False, + (int) ParserToken.Value, + (int) ParserToken.ValueRest, + ']'); + TableAddCol (parse_table, ParserToken.ArrayPrime, (int) ParserToken.Null, + (int) ParserToken.Value, + (int) ParserToken.ValueRest, + ']'); + + TableAddRow (parse_table, ParserToken.Object); + TableAddCol (parse_table, ParserToken.Object, '{', + '{', + (int) ParserToken.ObjectPrime); + + TableAddRow (parse_table, ParserToken.ObjectPrime); + TableAddCol (parse_table, ParserToken.ObjectPrime, '"', + (int) ParserToken.Pair, + (int) ParserToken.PairRest, + '}'); + TableAddCol (parse_table, ParserToken.ObjectPrime, '}', + '}'); + + TableAddRow (parse_table, ParserToken.Pair); + TableAddCol (parse_table, ParserToken.Pair, '"', + (int) ParserToken.String, + ':', + (int) ParserToken.Value); + + TableAddRow (parse_table, ParserToken.PairRest); + TableAddCol (parse_table, ParserToken.PairRest, ',', + ',', + (int) ParserToken.Pair, + (int) ParserToken.PairRest); + TableAddCol (parse_table, ParserToken.PairRest, '}', + (int) ParserToken.Epsilon); + + TableAddRow (parse_table, ParserToken.String); + TableAddCol (parse_table, ParserToken.String, '"', + '"', + (int) ParserToken.CharSeq, + '"'); + + TableAddRow (parse_table, ParserToken.Text); + TableAddCol (parse_table, ParserToken.Text, '[', + (int) ParserToken.Array); + TableAddCol (parse_table, ParserToken.Text, '{', + (int) ParserToken.Object); + + TableAddRow (parse_table, ParserToken.Value); + TableAddCol (parse_table, ParserToken.Value, '"', + (int) ParserToken.String); + TableAddCol (parse_table, ParserToken.Value, '[', + (int) ParserToken.Array); + TableAddCol (parse_table, ParserToken.Value, '{', + (int) ParserToken.Object); + TableAddCol (parse_table, ParserToken.Value, (int) ParserToken.Number, + (int) ParserToken.Number); + TableAddCol (parse_table, ParserToken.Value, (int) ParserToken.True, + (int) ParserToken.True); + TableAddCol (parse_table, ParserToken.Value, (int) ParserToken.False, + (int) ParserToken.False); + TableAddCol (parse_table, ParserToken.Value, (int) ParserToken.Null, + (int) ParserToken.Null); + + TableAddRow (parse_table, ParserToken.ValueRest); + TableAddCol (parse_table, ParserToken.ValueRest, ',', + ',', + (int) ParserToken.Value, + (int) ParserToken.ValueRest); + TableAddCol (parse_table, ParserToken.ValueRest, ']', + (int) ParserToken.Epsilon); + + return parse_table; + } + + private static void TableAddCol (IDictionary> parse_table, ParserToken row, int col, + params int[] symbols) + { + parse_table[(int) row].Add (col, symbols); + } + + private static void TableAddRow (IDictionary> parse_table, ParserToken rule) + { + parse_table.Add ((int) rule, new Dictionary ()); + } + #endregion + + + #region Private Methods + private void ProcessNumber (string number) + { + if (number.IndexOf ('.') != -1 || + number.IndexOf ('e') != -1 || + number.IndexOf ('E') != -1) { + + double n_double; + if (double.TryParse (number, NumberStyles.Any, CultureInfo.InvariantCulture, out n_double)) { + token = JsonToken.Double; + token_value = n_double; + + return; + } + } + + int n_int32; + if (int.TryParse (number, NumberStyles.Integer, CultureInfo.InvariantCulture, out n_int32)) { + token = JsonToken.Int; + token_value = n_int32; + + return; + } + + long n_int64; + if (long.TryParse (number, NumberStyles.Integer, CultureInfo.InvariantCulture, out n_int64)) { + token = JsonToken.Long; + token_value = n_int64; + + return; + } + + ulong n_uint64; + if (ulong.TryParse(number, NumberStyles.Integer, CultureInfo.InvariantCulture, out n_uint64)) + { + token = JsonToken.Long; + token_value = n_uint64; + + return; + } + + // Shouldn't happen, but just in case, return something + token = JsonToken.Int; + token_value = 0; + } + + private void ProcessSymbol () + { + if (current_symbol == '[') { + token = JsonToken.ArrayStart; + parser_return = true; + + } else if (current_symbol == ']') { + token = JsonToken.ArrayEnd; + parser_return = true; + + } else if (current_symbol == '{') { + token = JsonToken.ObjectStart; + parser_return = true; + + } else if (current_symbol == '}') { + token = JsonToken.ObjectEnd; + parser_return = true; + + } else if (current_symbol == '"') { + if (parser_in_string) { + parser_in_string = false; + + parser_return = true; + + } else { + if (token == JsonToken.None) + token = JsonToken.String; + + parser_in_string = true; + } + + } else if (current_symbol == (int) ParserToken.CharSeq) { + token_value = lexer.StringValue; + + } else if (current_symbol == (int) ParserToken.False) { + token = JsonToken.Boolean; + token_value = false; + parser_return = true; + + } else if (current_symbol == (int) ParserToken.Null) { + token = JsonToken.Null; + parser_return = true; + + } else if (current_symbol == (int) ParserToken.Number) { + ProcessNumber (lexer.StringValue); + + parser_return = true; + + } else if (current_symbol == (int) ParserToken.Pair) { + token = JsonToken.PropertyName; + + } else if (current_symbol == (int) ParserToken.True) { + token = JsonToken.Boolean; + token_value = true; + parser_return = true; + + } + } + + private bool ReadToken () + { + if (end_of_input) + return false; + + lexer.NextToken (); + + if (lexer.EndOfInput) { + Close (); + + return false; + } + + current_input = lexer.Token; + + return true; + } + #endregion + + + public void Close () + { + if (end_of_input) + return; + + end_of_input = true; + end_of_json = true; + + if (reader_is_owned) + { + using(reader){} + } + + reader = null; + } + + public bool Read () + { + if (end_of_input) + return false; + + if (end_of_json) { + end_of_json = false; + automaton_stack.Clear (); + automaton_stack.Push ((int) ParserToken.End); + automaton_stack.Push ((int) ParserToken.Text); + } + + parser_in_string = false; + parser_return = false; + + token = JsonToken.None; + token_value = null; + + if (! read_started) { + read_started = true; + + if (! ReadToken ()) + return false; + } + + + int[] entry_symbols; + + while (true) { + if (parser_return) { + if (automaton_stack.Peek () == (int) ParserToken.End) + end_of_json = true; + + return true; + } + + current_symbol = automaton_stack.Pop (); + + ProcessSymbol (); + + if (current_symbol == current_input) { + if (! ReadToken ()) { + if (automaton_stack.Peek () != (int) ParserToken.End) + throw new JsonException ( + "Input doesn't evaluate to proper JSON text"); + + if (parser_return) + return true; + + return false; + } + + continue; + } + + try { + + entry_symbols = + parse_table[current_symbol][current_input]; + + } catch (KeyNotFoundException e) { + throw new JsonException ((ParserToken) current_input, e); + } + + if (entry_symbols[0] == (int) ParserToken.Epsilon) + continue; + + for (int i = entry_symbols.Length - 1; i >= 0; i--) + automaton_stack.Push (entry_symbols[i]); + } + } + + } +} diff --git a/src/Cake.Issues/Serialization/LitJson/JsonWriter.cs b/src/Cake.Issues/Serialization/LitJson/JsonWriter.cs new file mode 100644 index 000000000..63af4dde8 --- /dev/null +++ b/src/Cake.Issues/Serialization/LitJson/JsonWriter.cs @@ -0,0 +1,479 @@ +#pragma warning disable 1587 +#pragma warning disable 1591 + +#region Header +/** + * JsonWriter.cs + * Stream-like facility to output JSON text. + * + * The authors disclaim copyright to this source code. For more details, see + * the 3rd-Party-License.md file included with this distribution. + **/ + +// This file isn't generated, but this comment is necessary to exclude it from code analysis. +// +#endregion + + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Text; + + +namespace LitJson +{ + internal enum Condition + { + InArray, + InObject, + NotAProperty, + Property, + Value + } + + internal class WriterContext + { + public int Count; + public bool InArray; + public bool InObject; + public bool ExpectingValue; + public int Padding; + } + + internal class JsonWriter + { + #region Fields + private static readonly NumberFormatInfo number_format; + + private WriterContext context; + private Stack ctx_stack; + private bool has_reached_end; + private char[] hex_seq; + private int indentation; + private int indent_value; + private StringBuilder inst_string_builder; + private bool pretty_print; + private bool validate; + private bool lower_case_properties; + private TextWriter writer; + #endregion + + + #region Properties + public int IndentValue { + get { return indent_value; } + set { + indentation = (indentation / indent_value) * value; + indent_value = value; + } + } + + public bool PrettyPrint { + get { return pretty_print; } + set { pretty_print = value; } + } + + public TextWriter TextWriter { + get { return writer; } + } + + public bool Validate { + get { return validate; } + set { validate = value; } + } + + public bool LowerCaseProperties { + get { return lower_case_properties; } + set { lower_case_properties = value; } + } + #endregion + + + #region Constructors + static JsonWriter () + { + number_format = NumberFormatInfo.InvariantInfo; + } + + public JsonWriter () + { + inst_string_builder = new StringBuilder (); + writer = new StringWriter (inst_string_builder); + + Init (); + } + + public JsonWriter (StringBuilder sb) : + this (new StringWriter (sb)) + { + } + + public JsonWriter (TextWriter writer) + { + if (writer == null) + throw new ArgumentNullException ("writer"); + + this.writer = writer; + + Init (); + } + #endregion + + + #region Private Methods + private void DoValidation (Condition cond) + { + if (! context.ExpectingValue) + context.Count++; + + if (! validate) + return; + + if (has_reached_end) + throw new JsonException ( + "A complete JSON symbol has already been written"); + + switch (cond) { + case Condition.InArray: + if (! context.InArray) + throw new JsonException ( + "Can't close an array here"); + break; + + case Condition.InObject: + if (! context.InObject || context.ExpectingValue) + throw new JsonException ( + "Can't close an object here"); + break; + + case Condition.NotAProperty: + if (context.InObject && ! context.ExpectingValue) + throw new JsonException ( + "Expected a property"); + break; + + case Condition.Property: + if (! context.InObject || context.ExpectingValue) + throw new JsonException ( + "Can't add a property here"); + break; + + case Condition.Value: + if (! context.InArray && + (! context.InObject || ! context.ExpectingValue)) + throw new JsonException ( + "Can't add a value here"); + + break; + } + } + + private void Init () + { + has_reached_end = false; + hex_seq = new char[4]; + indentation = 0; + indent_value = 4; + pretty_print = false; + validate = true; + lower_case_properties = false; + + ctx_stack = new Stack (); + context = new WriterContext (); + ctx_stack.Push (context); + } + + private static void IntToHex (int n, char[] hex) + { + int num; + + for (int i = 0; i < 4; i++) { + num = n % 16; + + if (num < 10) + hex[3 - i] = (char) ('0' + num); + else + hex[3 - i] = (char) ('A' + (num - 10)); + + n >>= 4; + } + } + + private void Indent () + { + if (pretty_print) + indentation += indent_value; + } + + + private void Put (string str) + { + if (pretty_print && ! context.ExpectingValue) + for (int i = 0; i < indentation; i++) + writer.Write (' '); + + writer.Write (str); + } + + private void PutNewline () + { + PutNewline (true); + } + + private void PutNewline (bool add_comma) + { + if (add_comma && ! context.ExpectingValue && + context.Count > 1) + writer.Write (','); + + if (pretty_print && ! context.ExpectingValue) + writer.Write (Environment.NewLine); + } + + private void PutString (string str) + { + Put (String.Empty); + + writer.Write ('"'); + + int n = str.Length; + for (int i = 0; i < n; i++) { + switch (str[i]) { + case '\n': + writer.Write ("\\n"); + continue; + + case '\r': + writer.Write ("\\r"); + continue; + + case '\t': + writer.Write ("\\t"); + continue; + + case '"': + case '\\': + writer.Write ('\\'); + writer.Write (str[i]); + continue; + + case '\f': + writer.Write ("\\f"); + continue; + + case '\b': + writer.Write ("\\b"); + continue; + } + + if ((int) str[i] >= 32 && (int) str[i] <= 126) { + writer.Write (str[i]); + continue; + } + + // Default, turn into a \uXXXX sequence + IntToHex ((int) str[i], hex_seq); + writer.Write ("\\u"); + writer.Write (hex_seq); + } + + writer.Write ('"'); + } + + private void Unindent () + { + if (pretty_print) + indentation -= indent_value; + } + #endregion + + + public override string ToString () + { + if (inst_string_builder == null) + return String.Empty; + + return inst_string_builder.ToString (); + } + + public void Reset () + { + has_reached_end = false; + + ctx_stack.Clear (); + context = new WriterContext (); + ctx_stack.Push (context); + + if (inst_string_builder != null) + inst_string_builder.Remove (0, inst_string_builder.Length); + } + + public void Write (bool boolean) + { + DoValidation (Condition.Value); + PutNewline (); + + Put (boolean ? "true" : "false"); + + context.ExpectingValue = false; + } + + public void Write (decimal number) + { + DoValidation (Condition.Value); + PutNewline (); + + Put (Convert.ToString (number, number_format)); + + context.ExpectingValue = false; + } + + public void Write (double number) + { + DoValidation (Condition.Value); + PutNewline (); + + string str = Convert.ToString (number, number_format); + Put (str); + + if (str.IndexOf ('.') == -1 && + str.IndexOf ('E') == -1) + writer.Write (".0"); + + context.ExpectingValue = false; + } + + public void Write (int number) + { + DoValidation (Condition.Value); + PutNewline (); + + Put (Convert.ToString (number, number_format)); + + context.ExpectingValue = false; + } + + public void Write (long number) + { + DoValidation (Condition.Value); + PutNewline (); + + Put (Convert.ToString (number, number_format)); + + context.ExpectingValue = false; + } + + public void Write (string str) + { + DoValidation (Condition.Value); + PutNewline (); + + if (str == null) + Put ("null"); + else + PutString (str); + + context.ExpectingValue = false; + } + + [CLSCompliant(false)] + public void Write (ulong number) + { + DoValidation (Condition.Value); + PutNewline (); + + Put (Convert.ToString (number, number_format)); + + context.ExpectingValue = false; + } + + public void WriteArrayEnd () + { + DoValidation (Condition.InArray); + PutNewline (false); + + ctx_stack.Pop (); + if (ctx_stack.Count == 1) + has_reached_end = true; + else { + context = ctx_stack.Peek (); + context.ExpectingValue = false; + } + + Unindent (); + Put ("]"); + } + + public void WriteArrayStart () + { + DoValidation (Condition.NotAProperty); + PutNewline (); + + Put ("["); + + context = new WriterContext (); + context.InArray = true; + ctx_stack.Push (context); + + Indent (); + } + + public void WriteObjectEnd () + { + DoValidation (Condition.InObject); + PutNewline (false); + + ctx_stack.Pop (); + if (ctx_stack.Count == 1) + has_reached_end = true; + else { + context = ctx_stack.Peek (); + context.ExpectingValue = false; + } + + Unindent (); + Put ("}"); + } + + public void WriteObjectStart () + { + DoValidation (Condition.NotAProperty); + PutNewline (); + + Put ("{"); + + context = new WriterContext (); + context.InObject = true; + ctx_stack.Push (context); + + Indent (); + } + + public void WritePropertyName (string property_name) + { + DoValidation (Condition.Property); + PutNewline (); + string propertyName = (property_name == null || !lower_case_properties) + ? property_name + : property_name.ToLowerInvariant(); + + PutString (propertyName); + + if (pretty_print) { + if (propertyName.Length > context.Padding) + context.Padding = propertyName.Length; + + for (int i = context.Padding - propertyName.Length; + i >= 0; i--) + writer.Write (' '); + + writer.Write (": "); + } else + writer.Write (':'); + + context.ExpectingValue = true; + } + } +} diff --git a/src/Cake.Issues/Serialization/LitJson/Lexer.cs b/src/Cake.Issues/Serialization/LitJson/Lexer.cs new file mode 100644 index 000000000..9f604509d --- /dev/null +++ b/src/Cake.Issues/Serialization/LitJson/Lexer.cs @@ -0,0 +1,918 @@ +#pragma warning disable 1587 +#pragma warning disable 1591 + +#region Header +/** + * Lexer.cs + * JSON lexer implementation based on a finite state machine. + * + * The authors disclaim copyright to this source code. For more details, see + * the 3rd-Party-License.md file included with this distribution. + **/ + +// This file isn't generated, but this comment is necessary to exclude it from code analysis. +// +#endregion + + +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + + +namespace LitJson +{ + internal class FsmContext + { + public bool Return; + public int NextState; + public Lexer L; + public int StateStack; + } + + + internal class Lexer + { + #region Fields + private delegate bool StateHandler (FsmContext ctx); + + private static readonly int[] fsm_return_table; + private static readonly StateHandler[] fsm_handler_table; + + private bool allow_comments; + private bool allow_single_quoted_strings; + private bool end_of_input; + private FsmContext fsm_context; + private int input_buffer; + private int input_char; + private TextReader reader; + private int state; + private StringBuilder string_buffer; + private string string_value; + private int token; + private int unichar; + #endregion + + + #region Properties + public bool AllowComments { + get { return allow_comments; } + set { allow_comments = value; } + } + + public bool AllowSingleQuotedStrings { + get { return allow_single_quoted_strings; } + set { allow_single_quoted_strings = value; } + } + + public bool EndOfInput { + get { return end_of_input; } + } + + public int Token { + get { return token; } + } + + public string StringValue { + get { return string_value; } + } + #endregion + + + #region Constructors + static Lexer () + { + PopulateFsmTables (out fsm_handler_table, out fsm_return_table); + } + + public Lexer (TextReader reader) + { + allow_comments = true; + allow_single_quoted_strings = true; + + input_buffer = 0; + string_buffer = new StringBuilder (128); + state = 1; + end_of_input = false; + this.reader = reader; + + fsm_context = new FsmContext (); + fsm_context.L = this; + } + #endregion + + + #region Static Methods + private static int HexValue (int digit) + { + switch (digit) { + case 'a': + case 'A': + return 10; + + case 'b': + case 'B': + return 11; + + case 'c': + case 'C': + return 12; + + case 'd': + case 'D': + return 13; + + case 'e': + case 'E': + return 14; + + case 'f': + case 'F': + return 15; + + default: + return digit - '0'; + } + } + + private static void PopulateFsmTables (out StateHandler[] fsm_handler_table, out int[] fsm_return_table) + { + // See section A.1. of the manual for details of the finite + // state machine. + fsm_handler_table = new StateHandler[28] { + State1, + State2, + State3, + State4, + State5, + State6, + State7, + State8, + State9, + State10, + State11, + State12, + State13, + State14, + State15, + State16, + State17, + State18, + State19, + State20, + State21, + State22, + State23, + State24, + State25, + State26, + State27, + State28 + }; + + fsm_return_table = new int[28] { + (int) ParserToken.Char, + 0, + (int) ParserToken.Number, + (int) ParserToken.Number, + 0, + (int) ParserToken.Number, + 0, + (int) ParserToken.Number, + 0, + 0, + (int) ParserToken.True, + 0, + 0, + 0, + (int) ParserToken.False, + 0, + 0, + (int) ParserToken.Null, + (int) ParserToken.CharSeq, + (int) ParserToken.Char, + 0, + 0, + (int) ParserToken.CharSeq, + (int) ParserToken.Char, + 0, + 0, + 0, + 0 + }; + } + + private static char ProcessEscChar (int esc_char) + { + switch (esc_char) { + case '"': + case '\'': + case '\\': + case '/': + return Convert.ToChar (esc_char); + + case 'n': + return '\n'; + + case 't': + return '\t'; + + case 'r': + return '\r'; + + case 'b': + return '\b'; + + case 'f': + return '\f'; + + default: + // Unreachable + return '?'; + } + } + + private static bool State1 (FsmContext ctx) + { + while (ctx.L.GetChar ()) { + if (ctx.L.input_char == ' ' || + ctx.L.input_char >= '\t' && ctx.L.input_char <= '\r') + continue; + + if (ctx.L.input_char >= '1' && ctx.L.input_char <= '9') { + ctx.L.string_buffer.Append ((char) ctx.L.input_char); + ctx.NextState = 3; + return true; + } + + switch (ctx.L.input_char) { + case '"': + ctx.NextState = 19; + ctx.Return = true; + return true; + + case ',': + case ':': + case '[': + case ']': + case '{': + case '}': + ctx.NextState = 1; + ctx.Return = true; + return true; + + case '-': + ctx.L.string_buffer.Append ((char) ctx.L.input_char); + ctx.NextState = 2; + return true; + + case '0': + ctx.L.string_buffer.Append ((char) ctx.L.input_char); + ctx.NextState = 4; + return true; + + case 'f': + ctx.NextState = 12; + return true; + + case 'n': + ctx.NextState = 16; + return true; + + case 't': + ctx.NextState = 9; + return true; + + case '\'': + if (! ctx.L.allow_single_quoted_strings) + return false; + + ctx.L.input_char = '"'; + ctx.NextState = 23; + ctx.Return = true; + return true; + + case '/': + if (! ctx.L.allow_comments) + return false; + + ctx.NextState = 25; + return true; + + default: + return false; + } + } + + return true; + } + + private static bool State2 (FsmContext ctx) + { + ctx.L.GetChar (); + + if (ctx.L.input_char >= '1' && ctx.L.input_char<= '9') { + ctx.L.string_buffer.Append ((char) ctx.L.input_char); + ctx.NextState = 3; + return true; + } + + switch (ctx.L.input_char) { + case '0': + ctx.L.string_buffer.Append ((char) ctx.L.input_char); + ctx.NextState = 4; + return true; + + default: + return false; + } + } + + private static bool State3 (FsmContext ctx) + { + while (ctx.L.GetChar ()) { + if (ctx.L.input_char >= '0' && ctx.L.input_char <= '9') { + ctx.L.string_buffer.Append ((char) ctx.L.input_char); + continue; + } + + if (ctx.L.input_char == ' ' || + ctx.L.input_char >= '\t' && ctx.L.input_char <= '\r') { + ctx.Return = true; + ctx.NextState = 1; + return true; + } + + switch (ctx.L.input_char) { + case ',': + case ']': + case '}': + ctx.L.UngetChar (); + ctx.Return = true; + ctx.NextState = 1; + return true; + + case '.': + ctx.L.string_buffer.Append ((char) ctx.L.input_char); + ctx.NextState = 5; + return true; + + case 'e': + case 'E': + ctx.L.string_buffer.Append ((char) ctx.L.input_char); + ctx.NextState = 7; + return true; + + default: + return false; + } + } + return true; + } + + private static bool State4 (FsmContext ctx) + { + ctx.L.GetChar (); + + if (ctx.L.input_char == ' ' || + ctx.L.input_char >= '\t' && ctx.L.input_char <= '\r') { + ctx.Return = true; + ctx.NextState = 1; + return true; + } + + switch (ctx.L.input_char) { + case ',': + case ']': + case '}': + ctx.L.UngetChar (); + ctx.Return = true; + ctx.NextState = 1; + return true; + + case '.': + ctx.L.string_buffer.Append ((char) ctx.L.input_char); + ctx.NextState = 5; + return true; + + case 'e': + case 'E': + ctx.L.string_buffer.Append ((char) ctx.L.input_char); + ctx.NextState = 7; + return true; + + default: + return false; + } + } + + private static bool State5 (FsmContext ctx) + { + ctx.L.GetChar (); + + if (ctx.L.input_char >= '0' && ctx.L.input_char <= '9') { + ctx.L.string_buffer.Append ((char) ctx.L.input_char); + ctx.NextState = 6; + return true; + } + + return false; + } + + private static bool State6 (FsmContext ctx) + { + while (ctx.L.GetChar ()) { + if (ctx.L.input_char >= '0' && ctx.L.input_char <= '9') { + ctx.L.string_buffer.Append ((char) ctx.L.input_char); + continue; + } + + if (ctx.L.input_char == ' ' || + ctx.L.input_char >= '\t' && ctx.L.input_char <= '\r') { + ctx.Return = true; + ctx.NextState = 1; + return true; + } + + switch (ctx.L.input_char) { + case ',': + case ']': + case '}': + ctx.L.UngetChar (); + ctx.Return = true; + ctx.NextState = 1; + return true; + + case 'e': + case 'E': + ctx.L.string_buffer.Append ((char) ctx.L.input_char); + ctx.NextState = 7; + return true; + + default: + return false; + } + } + + return true; + } + + private static bool State7 (FsmContext ctx) + { + ctx.L.GetChar (); + + if (ctx.L.input_char >= '0' && ctx.L.input_char<= '9') { + ctx.L.string_buffer.Append ((char) ctx.L.input_char); + ctx.NextState = 8; + return true; + } + + switch (ctx.L.input_char) { + case '+': + case '-': + ctx.L.string_buffer.Append ((char) ctx.L.input_char); + ctx.NextState = 8; + return true; + + default: + return false; + } + } + + private static bool State8 (FsmContext ctx) + { + while (ctx.L.GetChar ()) { + if (ctx.L.input_char >= '0' && ctx.L.input_char<= '9') { + ctx.L.string_buffer.Append ((char) ctx.L.input_char); + continue; + } + + if (ctx.L.input_char == ' ' || + ctx.L.input_char >= '\t' && ctx.L.input_char<= '\r') { + ctx.Return = true; + ctx.NextState = 1; + return true; + } + + switch (ctx.L.input_char) { + case ',': + case ']': + case '}': + ctx.L.UngetChar (); + ctx.Return = true; + ctx.NextState = 1; + return true; + + default: + return false; + } + } + + return true; + } + + private static bool State9 (FsmContext ctx) + { + ctx.L.GetChar (); + + switch (ctx.L.input_char) { + case 'r': + ctx.NextState = 10; + return true; + + default: + return false; + } + } + + private static bool State10 (FsmContext ctx) + { + ctx.L.GetChar (); + + switch (ctx.L.input_char) { + case 'u': + ctx.NextState = 11; + return true; + + default: + return false; + } + } + + private static bool State11 (FsmContext ctx) + { + ctx.L.GetChar (); + + switch (ctx.L.input_char) { + case 'e': + ctx.Return = true; + ctx.NextState = 1; + return true; + + default: + return false; + } + } + + private static bool State12 (FsmContext ctx) + { + ctx.L.GetChar (); + + switch (ctx.L.input_char) { + case 'a': + ctx.NextState = 13; + return true; + + default: + return false; + } + } + + private static bool State13 (FsmContext ctx) + { + ctx.L.GetChar (); + + switch (ctx.L.input_char) { + case 'l': + ctx.NextState = 14; + return true; + + default: + return false; + } + } + + private static bool State14 (FsmContext ctx) + { + ctx.L.GetChar (); + + switch (ctx.L.input_char) { + case 's': + ctx.NextState = 15; + return true; + + default: + return false; + } + } + + private static bool State15 (FsmContext ctx) + { + ctx.L.GetChar (); + + switch (ctx.L.input_char) { + case 'e': + ctx.Return = true; + ctx.NextState = 1; + return true; + + default: + return false; + } + } + + private static bool State16 (FsmContext ctx) + { + ctx.L.GetChar (); + + switch (ctx.L.input_char) { + case 'u': + ctx.NextState = 17; + return true; + + default: + return false; + } + } + + private static bool State17 (FsmContext ctx) + { + ctx.L.GetChar (); + + switch (ctx.L.input_char) { + case 'l': + ctx.NextState = 18; + return true; + + default: + return false; + } + } + + private static bool State18 (FsmContext ctx) + { + ctx.L.GetChar (); + + switch (ctx.L.input_char) { + case 'l': + ctx.Return = true; + ctx.NextState = 1; + return true; + + default: + return false; + } + } + + private static bool State19 (FsmContext ctx) + { + while (ctx.L.GetChar ()) { + switch (ctx.L.input_char) { + case '"': + ctx.L.UngetChar (); + ctx.Return = true; + ctx.NextState = 20; + return true; + + case '\\': + ctx.StateStack = 19; + ctx.NextState = 21; + return true; + + default: + ctx.L.string_buffer.Append ((char) ctx.L.input_char); + continue; + } + } + + return true; + } + + private static bool State20 (FsmContext ctx) + { + ctx.L.GetChar (); + + switch (ctx.L.input_char) { + case '"': + ctx.Return = true; + ctx.NextState = 1; + return true; + + default: + return false; + } + } + + private static bool State21 (FsmContext ctx) + { + ctx.L.GetChar (); + + switch (ctx.L.input_char) { + case 'u': + ctx.NextState = 22; + return true; + + case '"': + case '\'': + case '/': + case '\\': + case 'b': + case 'f': + case 'n': + case 'r': + case 't': + ctx.L.string_buffer.Append ( + ProcessEscChar (ctx.L.input_char)); + ctx.NextState = ctx.StateStack; + return true; + + default: + return false; + } + } + + private static bool State22 (FsmContext ctx) + { + int counter = 0; + int mult = 4096; + + ctx.L.unichar = 0; + + while (ctx.L.GetChar ()) { + + if (ctx.L.input_char >= '0' && ctx.L.input_char <= '9' || + ctx.L.input_char >= 'A' && ctx.L.input_char <= 'F' || + ctx.L.input_char >= 'a' && ctx.L.input_char <= 'f') { + + ctx.L.unichar += HexValue (ctx.L.input_char) * mult; + + counter++; + mult /= 16; + + if (counter == 4) { + ctx.L.string_buffer.Append ( + Convert.ToChar (ctx.L.unichar)); + ctx.NextState = ctx.StateStack; + return true; + } + + continue; + } + + return false; + } + + return true; + } + + private static bool State23 (FsmContext ctx) + { + while (ctx.L.GetChar ()) { + switch (ctx.L.input_char) { + case '\'': + ctx.L.UngetChar (); + ctx.Return = true; + ctx.NextState = 24; + return true; + + case '\\': + ctx.StateStack = 23; + ctx.NextState = 21; + return true; + + default: + ctx.L.string_buffer.Append ((char) ctx.L.input_char); + continue; + } + } + + return true; + } + + private static bool State24 (FsmContext ctx) + { + ctx.L.GetChar (); + + switch (ctx.L.input_char) { + case '\'': + ctx.L.input_char = '"'; + ctx.Return = true; + ctx.NextState = 1; + return true; + + default: + return false; + } + } + + private static bool State25 (FsmContext ctx) + { + ctx.L.GetChar (); + + switch (ctx.L.input_char) { + case '*': + ctx.NextState = 27; + return true; + + case '/': + ctx.NextState = 26; + return true; + + default: + return false; + } + } + + private static bool State26 (FsmContext ctx) + { + while (ctx.L.GetChar ()) { + if (ctx.L.input_char == '\n') { + ctx.NextState = 1; + return true; + } + } + + return true; + } + + private static bool State27 (FsmContext ctx) + { + while (ctx.L.GetChar ()) { + if (ctx.L.input_char == '*') { + ctx.NextState = 28; + return true; + } + } + + return true; + } + + private static bool State28 (FsmContext ctx) + { + while (ctx.L.GetChar ()) { + if (ctx.L.input_char == '*') + continue; + + if (ctx.L.input_char == '/') { + ctx.NextState = 1; + return true; + } + + ctx.NextState = 27; + return true; + } + + return true; + } + #endregion + + + private bool GetChar () + { + if ((input_char = NextChar ()) != -1) + return true; + + end_of_input = true; + return false; + } + + private int NextChar () + { + if (input_buffer != 0) { + int tmp = input_buffer; + input_buffer = 0; + + return tmp; + } + + return reader.Read (); + } + + public bool NextToken () + { + StateHandler handler; + fsm_context.Return = false; + + while (true) { + handler = fsm_handler_table[state - 1]; + + if (! handler (fsm_context)) + throw new JsonException (input_char); + + if (end_of_input) + return false; + + if (fsm_context.Return) { + string_value = string_buffer.ToString (); + string_buffer.Remove (0, string_buffer.Length); + token = fsm_return_table[state - 1]; + + if (token == (int) ParserToken.Char) + token = input_char; + + state = fsm_context.NextState; + + return true; + } + + state = fsm_context.NextState; + } + } + + private void UngetChar () + { + input_buffer = input_char; + } + } +} diff --git a/src/Cake.Issues/Serialization/LitJson/ParserToken.cs b/src/Cake.Issues/Serialization/LitJson/ParserToken.cs new file mode 100644 index 000000000..85dd10da3 --- /dev/null +++ b/src/Cake.Issues/Serialization/LitJson/ParserToken.cs @@ -0,0 +1,49 @@ +#pragma warning disable 1587 +#pragma warning disable 1591 + +#region Header +/** + * ParserToken.cs + * Internal representation of the tokens used by the lexer and the parser. + * + * The authors disclaim copyright to this source code. For more details, see + * the 3rd-Party-License.md file included with this distribution. + **/ + +// This file isn't generated, but this comment is necessary to exclude it from code analysis. +// +#endregion + +namespace LitJson +{ + internal enum ParserToken + { + // Lexer tokens (see section A.1.1. of the manual) + None = System.Char.MaxValue + 1, + Number, + True, + False, + Null, + CharSeq, + // Single char + Char, + + // Parser Rules (see section A.2.1 of the manual) + Text, + Object, + ObjectPrime, + Pair, + PairRest, + Array, + ArrayPrime, + Value, + ValueRest, + String, + + // End of input + End, + + // The empty rule + Epsilon + } +} diff --git a/src/Cake.Issues/SerializableIssue.cs b/src/Cake.Issues/Serialization/SerializableIssue.cs similarity index 77% rename from src/Cake.Issues/SerializableIssue.cs rename to src/Cake.Issues/Serialization/SerializableIssue.cs index 86c80e743..86579bd1d 100644 --- a/src/Cake.Issues/SerializableIssue.cs +++ b/src/Cake.Issues/Serialization/SerializableIssue.cs @@ -1,55 +1,41 @@ -namespace Cake.Issues +namespace Cake.Issues.Serialization { - using System.Runtime.Serialization; - /// /// Class for serializing and deserializing an instance. /// - [DataContract] internal class SerializableIssue { /// - [DataMember] public string ProjectFileRelativePath { get; set; } /// - [DataMember] public string ProjectName { get; set; } /// - [DataMember] public string AffectedFileRelativePath { get; set; } /// - [DataMember] public int? Line { get; set; } - /// - [DataMember] + /// public string Message { get; set; } /// - [DataMember] public int? Priority { get; set; } /// - [DataMember] public string PriorityName { get; set; } /// - [DataMember] public string Rule { get; set; } /// - [DataMember] public string RuleUrl { get; set; } /// - [DataMember] public string ProviderType { get; set; } /// - [DataMember] public string ProviderName { get; set; } } } diff --git a/src/Cake.Issues/Serialization/SerializableIssueExtensions.cs b/src/Cake.Issues/Serialization/SerializableIssueExtensions.cs new file mode 100644 index 000000000..9b722e83c --- /dev/null +++ b/src/Cake.Issues/Serialization/SerializableIssueExtensions.cs @@ -0,0 +1,47 @@ +namespace Cake.Issues.Serialization +{ + using System; + + /// + /// Extensions for . + /// + internal static class SerializableIssueExtensions + { + /// + /// Converts a to an . + /// + /// Issue which should be converted. + /// Converted issue. + internal static Issue ToIssue(this SerializableIssue serializableIssue) + { +#pragma warning disable SA1123 // Do not place regions within elements + #region DupFinder Exclusion +#pragma warning restore SA1123 // Do not place regions within elements + + serializableIssue.NotNull(nameof(serializableIssue)); + + Uri ruleUrl = null; + if (!string.IsNullOrWhiteSpace(serializableIssue.RuleUrl)) + { + ruleUrl = new Uri(serializableIssue.RuleUrl); + } + + return new Issue( + serializableIssue.ProjectFileRelativePath, + serializableIssue.ProjectName, + serializableIssue.AffectedFileRelativePath, + serializableIssue.Line, + serializableIssue.Message, + null, + null, + serializableIssue.Priority, + serializableIssue.PriorityName, + serializableIssue.Rule, + ruleUrl, + serializableIssue.ProviderType, + serializableIssue.ProviderName); + + #endregion + } + } +} diff --git a/src/Cake.Issues/Serialization/SerializableIssueV2.cs b/src/Cake.Issues/Serialization/SerializableIssueV2.cs new file mode 100644 index 000000000..9afe5e54c --- /dev/null +++ b/src/Cake.Issues/Serialization/SerializableIssueV2.cs @@ -0,0 +1,75 @@ +namespace Cake.Issues.Serialization +{ + using System.Runtime.Serialization; + + /// + /// Class for serializing and deserializing an instance. + /// + [DataContract] + internal class SerializableIssueV2 + { + /// + /// Gets the version of the serialization format. + /// + [DataMember] + public int Version + { + get + { + return 2; + } + } + + /// + [DataMember] + public string ProjectFileRelativePath { get; set; } + + /// + [DataMember] + public string ProjectName { get; set; } + + /// + [DataMember] + public string AffectedFileRelativePath { get; set; } + + /// + [DataMember] + public int? Line { get; set; } + + /// + [DataMember] + public string MessageText { get; set; } + + /// + [DataMember] + public string MessageMarkdown { get; set; } + + /// + [DataMember] + public string MessageHtml { get; set; } + + /// + [DataMember] + public int? Priority { get; set; } + + /// + [DataMember] + public string PriorityName { get; set; } + + /// + [DataMember] + public string Rule { get; set; } + + /// + [DataMember] + public string RuleUrl { get; set; } + + /// + [DataMember] + public string ProviderType { get; set; } + + /// + [DataMember] + public string ProviderName { get; set; } + } +} diff --git a/src/Cake.Issues/Serialization/SerializableIssueV2Extensions.cs b/src/Cake.Issues/Serialization/SerializableIssueV2Extensions.cs new file mode 100644 index 000000000..4ab0491f8 --- /dev/null +++ b/src/Cake.Issues/Serialization/SerializableIssueV2Extensions.cs @@ -0,0 +1,47 @@ +namespace Cake.Issues.Serialization +{ + using System; + + /// + /// Extensions for . + /// + internal static class SerializableIssueV2Extensions + { + /// + /// Converts a to an . + /// + /// Issue which should be converted. + /// Converted issue. + internal static Issue ToIssue(this SerializableIssueV2 serializableIssue) + { +#pragma warning disable SA1123 // Do not place regions within elements + #region DupFinder Exclusion +#pragma warning restore SA1123 // Do not place regions within elements + + serializableIssue.NotNull(nameof(serializableIssue)); + + Uri ruleUrl = null; + if (!string.IsNullOrWhiteSpace(serializableIssue.RuleUrl)) + { + ruleUrl = new Uri(serializableIssue.RuleUrl); + } + + return new Issue( + serializableIssue.ProjectFileRelativePath, + serializableIssue.ProjectName, + serializableIssue.AffectedFileRelativePath, + serializableIssue.Line, + serializableIssue.MessageText, + serializableIssue.MessageHtml, + serializableIssue.MessageMarkdown, + serializableIssue.Priority, + serializableIssue.PriorityName, + serializableIssue.Rule, + ruleUrl, + serializableIssue.ProviderType, + serializableIssue.ProviderName); + + #endregion + } + } +}