diff --git a/nuspec/nuget/Cake.Issues.Testing.nuspec b/nuspec/nuget/Cake.Issues.Testing.nuspec
index 6a653fa18..9201296c7 100644
--- a/nuspec/nuget/Cake.Issues.Testing.nuspec
+++ b/nuspec/nuget/Cake.Issues.Testing.nuspec
@@ -17,7 +17,7 @@ Common helpers for testing add-ins based on Cake.Issues
Copyright © BBT Software AG and contributors
Cake Script Cake-Issues Issues Testing
- https://github.com/cake-contrib/Cake.Issues/releases/tag/0.6.0
+ https://github.com/cake-contrib/Cake.Issues/releases/tag/0.6.1
diff --git a/nuspec/nuget/Cake.Issues.nuspec b/nuspec/nuget/Cake.Issues.nuspec
index 23f76af80..c07df87ae 100644
--- a/nuspec/nuget/Cake.Issues.nuspec
+++ b/nuspec/nuget/Cake.Issues.nuspec
@@ -24,7 +24,7 @@ See the Project Site for an overview of the whole ecosystem of addins for workin
Copyright © BBT Software AG and contributors
Cake Script Cake-Issues CodeAnalysis Linting Issues
- https://github.com/cake-contrib/Cake.Issues/releases/tag/0.6.0
+ https://github.com/cake-contrib/Cake.Issues/releases/tag/0.6.1
diff --git a/src/Cake.Issues.Tests/IIssueExtensionsTests.cs b/src/Cake.Issues.Tests/IIssueExtensionsTests.cs
new file mode 100644
index 000000000..500e9bb95
--- /dev/null
+++ b/src/Cake.Issues.Tests/IIssueExtensionsTests.cs
@@ -0,0 +1,332 @@
+namespace Cake.Issues.Tests
+{
+ using System;
+ using Cake.Issues.Testing;
+ using Shouldly;
+ using Xunit;
+
+ public sealed class IIssueExtensionsTests
+ {
+ public sealed class TheProjectPathExtension
+ {
+ [Fact]
+ public void Should_Throw_If_Issue_Is_Null()
+ {
+ // Given
+ IIssue issue = null;
+
+ // When
+ var result = Record.Exception(() => issue.ProjectPath());
+
+ // Then
+ result.IsArgumentNullException("issue");
+ }
+
+ [Fact]
+ public void Should_Return_Full_Path()
+ {
+ // Given
+ var projectPath = @"src\Cake.Issues\Cake.Issues.csproj";
+ var issue =
+ IssueBuilder
+ .NewIssue("Message Foo", "ProviderType Foo", "ProviderName Foo")
+ .InProjectFile(projectPath)
+ .Create();
+
+ // When
+ var result = issue.ProjectPath();
+
+ // Then
+ result.ShouldBe(@"src/Cake.Issues/Cake.Issues.csproj");
+ }
+
+ [Fact]
+ public void Should_Return_Null_If_Project_Is_Not_Set()
+ {
+ // Given
+ var issue =
+ IssueBuilder
+ .NewIssue("Message Foo", "ProviderType Foo", "ProviderName Foo")
+ .Create();
+
+ // When
+ var result = issue.ProjectPath();
+
+ // Then
+ result.ShouldBeNull();
+ }
+ }
+
+ public sealed class TheProjectDirectoryExtension
+ {
+ [Fact]
+ public void Should_Throw_If_Issue_Is_Null()
+ {
+ // Given
+ IIssue issue = null;
+
+ // When
+ var result = Record.Exception(() => issue.ProjectDirectory());
+
+ // Then
+ result.IsArgumentNullException("issue");
+ }
+
+ [Fact]
+ public void Should_Return_Full_Path()
+ {
+ // Given
+ var filePath = @"src\Cake.Issues\Foo.cs";
+ var issue =
+ IssueBuilder
+ .NewIssue("Message Foo", "ProviderType Foo", "ProviderName Foo")
+ .InProjectFile(filePath)
+ .Create();
+
+ // When
+ var result = issue.ProjectDirectory();
+
+ // Then
+ result.ShouldBe(@"src/Cake.Issues");
+ }
+
+ [Fact]
+ public void Should_Return_Null_If_File_Is_Not_Set()
+ {
+ // Given
+ var issue =
+ IssueBuilder
+ .NewIssue("Message Foo", "ProviderType Foo", "ProviderName Foo")
+ .Create();
+
+ // When
+ var result = issue.ProjectDirectory();
+
+ // Then
+ result.ShouldBeNull();
+ }
+ }
+
+ public sealed class TheFilePathExtension
+ {
+ [Fact]
+ public void Should_Throw_If_Issue_Is_Null()
+ {
+ // Given
+ IIssue issue = null;
+
+ // When
+ var result = Record.Exception(() => issue.FilePath());
+
+ // Then
+ result.IsArgumentNullException("issue");
+ }
+
+ [Fact]
+ public void Should_Return_Full_Path()
+ {
+ // Given
+ var filePath = @"src\Cake.Issues\Foo.cs";
+ var issue =
+ IssueBuilder
+ .NewIssue("Message Foo", "ProviderType Foo", "ProviderName Foo")
+ .InFile(filePath)
+ .Create();
+
+ // When
+ var result = issue.FilePath();
+
+ // Then
+ result.ShouldBe(@"src/Cake.Issues/Foo.cs");
+ }
+
+ [Fact]
+ public void Should_Return_Null_If_File_Is_Not_Set()
+ {
+ // Given
+ var issue =
+ IssueBuilder
+ .NewIssue("Message Foo", "ProviderType Foo", "ProviderName Foo")
+ .Create();
+
+ // When
+ var result = issue.FilePath();
+
+ // Then
+ result.ShouldBeNull();
+ }
+ }
+
+ public sealed class TheFileDirectoryExtension
+ {
+ [Fact]
+ public void Should_Throw_If_Issue_Is_Null()
+ {
+ // Given
+ IIssue issue = null;
+
+ // When
+ var result = Record.Exception(() => issue.FileDirectory());
+
+ // Then
+ result.IsArgumentNullException("issue");
+ }
+
+ [Fact]
+ public void Should_Return_Full_Path()
+ {
+ // Given
+ var filePath = @"src\Cake.Issues\Foo.cs";
+ var issue =
+ IssueBuilder
+ .NewIssue("Message Foo", "ProviderType Foo", "ProviderName Foo")
+ .InFile(filePath)
+ .Create();
+
+ // When
+ var result = issue.FileDirectory();
+
+ // Then
+ result.ShouldBe(@"src/Cake.Issues");
+ }
+
+ [Fact]
+ public void Should_Return_Null_If_File_Is_Not_Set()
+ {
+ // Given
+ var issue =
+ IssueBuilder
+ .NewIssue("Message Foo", "ProviderType Foo", "ProviderName Foo")
+ .Create();
+
+ // When
+ var result = issue.FileDirectory();
+
+ // Then
+ result.ShouldBeNull();
+ }
+ }
+
+ public sealed class TheFileNameExtension
+ {
+ [Fact]
+ public void Should_Throw_If_Issue_Is_Null()
+ {
+ // Given
+ IIssue issue = null;
+
+ // When
+ var result = Record.Exception(() => issue.FileName());
+
+ // Then
+ result.IsArgumentNullException("issue");
+ }
+
+ [Fact]
+ public void Should_Return_Full_Path()
+ {
+ // Given
+ var filePath = @"src\Cake.Issues\Foo.cs";
+ var issue =
+ IssueBuilder
+ .NewIssue("Message Foo", "ProviderType Foo", "ProviderName Foo")
+ .InFile(filePath)
+ .Create();
+
+ // When
+ var result = issue.FileName();
+
+ // Then
+ result.ShouldBe("Foo.cs");
+ }
+
+ [Fact]
+ public void Should_Return_Null_If_File_Is_Not_Set()
+ {
+ // Given
+ var issue =
+ IssueBuilder
+ .NewIssue("Message Foo", "ProviderType Foo", "ProviderName Foo")
+ .Create();
+
+ // When
+ var result = issue.FileName();
+
+ // Then
+ result.ShouldBeNull();
+ }
+ }
+
+ public sealed class TheReplaceIssuePatternExtension
+ {
+ [Fact]
+ public void Should_Throw_If_Pattern_Is_Null()
+ {
+ // Given
+ string pattern = null;
+ var issue =
+ IssueBuilder
+ .NewIssue("Message Foo", "ProviderType Foo", "ProviderName Foo")
+ .Create();
+
+ // When
+ var result = Record.Exception(() => pattern.ReplaceIssuePattern(issue));
+
+ // Then
+ result.IsArgumentNullException("pattern");
+ }
+
+ [Fact]
+ public void Should_Throw_If_Issue_Is_Null()
+ {
+ // Given
+ var pattern = "foo";
+ IIssue issue = null;
+
+ // When
+ var result = Record.Exception(() => pattern.ReplaceIssuePattern(issue));
+
+ // Then
+ result.IsArgumentNullException("issue");
+ }
+
+ [Theory]
+ [InlineData("", "")]
+ [InlineData(" ", " ")]
+ [InlineData("foo", "foo")]
+ [InlineData("{foo}", "{foo}")]
+ [InlineData("foo {ProviderType} bar", "foo ProviderType Foo bar")]
+ [InlineData("foo {ProviderName} bar", "foo ProviderName Foo bar")]
+ [InlineData("foo {Priority} bar", "foo 400 bar")]
+ [InlineData("foo {PriorityName} bar", "foo Error bar")]
+ [InlineData("foo {ProjectPath} bar", "foo src/Cake.Issues/Cake.Issues.csproj bar")]
+ [InlineData("foo {ProjectDirectory} bar", "foo src/Cake.Issues bar")]
+ [InlineData("foo {ProjectName} bar", "foo Cake.Issues bar")]
+ [InlineData("foo {FilePath} bar", "foo src/Cake.Issues/foo.cs bar")]
+ [InlineData("foo {FileDirectory} bar", "foo src/Cake.Issues bar")]
+ [InlineData("foo {FileName} bar", "foo foo.cs bar")]
+ [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")]
+ public void Should_Replace_Tokens(string pattern, string expectedResult)
+ {
+ // Given
+ var issue =
+ IssueBuilder
+ .NewIssue("Message Foo", "ProviderType Foo", "ProviderName 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"))
+ .WithPriority(IssuePriority.Error)
+ .Create();
+
+ // When
+ var result = pattern.ReplaceIssuePattern(issue);
+
+ // Then
+ result.ShouldBe(expectedResult);
+ }
+ }
+ }
+}
diff --git a/src/Cake.Issues/IIssueExtensions.cs b/src/Cake.Issues/IIssueExtensions.cs
new file mode 100644
index 000000000..eccddde38
--- /dev/null
+++ b/src/Cake.Issues/IIssueExtensions.cs
@@ -0,0 +1,161 @@
+namespace Cake.Issues
+{
+ ///
+ /// Extensions for .
+ ///
+ public static class IIssueExtensions
+ {
+ ///
+ /// Returns the full path of or null.
+ ///
+ /// Issue for which the path should be returned.
+ /// Full path to the project to which the file affected by the issue belongs.
+ public static string ProjectPath(this IIssue issue)
+ {
+ issue.NotNull(nameof(issue));
+
+ return issue.ProjectFileRelativePath?.FullPath;
+ }
+
+ ///
+ /// Returns the directory of the .
+ ///
+ /// Issue for which the project directory should be returned.
+ /// Directory of the project to which the file affected by the issue belongs.
+ public static string ProjectDirectory(this IIssue issue)
+ {
+ issue.NotNull(nameof(issue));
+
+ return issue.ProjectFileRelativePath?.GetDirectory().FullPath;
+ }
+
+ ///
+ /// Returns the full path of the .
+ ///
+ /// Issue for which the path should be returned.
+ /// Full path of the file affected by the issue.
+ public static string FilePath(this IIssue issue)
+ {
+ issue.NotNull(nameof(issue));
+
+ return issue.AffectedFileRelativePath?.FullPath;
+ }
+
+ ///
+ /// Returns the directory of the .
+ ///
+ /// Issue for which the directory should be returned.
+ /// Directory of the file affected by the issue.
+ public static string FileDirectory(this IIssue issue)
+ {
+ issue.NotNull(nameof(issue));
+
+ return issue.AffectedFileRelativePath?.GetDirectory().FullPath;
+ }
+
+ ///
+ /// Returns the name of the file of the .
+ ///
+ /// Issue for which the file name should be returned.
+ /// Name of the file affected by the issue.
+ public static string FileName(this IIssue issue)
+ {
+ issue.NotNull(nameof(issue));
+
+ return issue.AffectedFileRelativePath?.GetFilename().ToString();
+ }
+
+ ///
+ /// Returns a string with all patterns replaced by the values of .
+ ///
+ /// Pattern whose values should be replaced.
+ /// The following patterns are supported:
+ ///
+ ///
+ /// Pattern
+ /// Description
+ ///
+ /// -
+ /// {ProviderType}
+ /// The value of .
+ ///
+ /// -
+ /// {ProviderName}
+ /// The value of .
+ ///
+ /// -
+ /// {Priority}
+ /// The value of .
+ ///
+ /// -
+ /// {PriorityName}
+ /// The value of .
+ ///
+ /// -
+ /// {ProjectPath}
+ /// The value of .
+ ///
+ /// -
+ /// {ProjectDirectory}
+ /// The value of .
+ ///
+ /// -
+ /// {ProjectName}
+ /// The value of .
+ ///
+ /// -
+ /// {FilePath}
+ /// The value of .
+ ///
+ /// -
+ /// {FileDirectory}
+ /// The value of .
+ ///
+ /// -
+ /// {FileName}
+ /// The value of .
+ ///
+ /// -
+ /// {Line}
+ /// The value of .
+ ///
+ /// -
+ /// {Rule}
+ /// The value of .
+ ///
+ /// -
+ /// {RuleUrl}
+ /// The value of .
+ ///
+ /// -
+ /// {Message}
+ /// The value of .
+ ///
+ ///
+ ///
+ /// Issue whose values should be used to replace the patterns.
+ /// Value with all patterns replaced.
+ public static string ReplaceIssuePattern(this string pattern, IIssue issue)
+ {
+ pattern.NotNull(nameof(pattern));
+ issue.NotNull(nameof(issue));
+
+ return
+ pattern
+ .Replace("{ProviderType}", issue.ProviderType)
+ .Replace("{ProviderName}", issue.ProviderName)
+ .Replace("{Priority}", issue.Priority?.ToString())
+ .Replace("{PriorityName}", issue.PriorityName)
+ .Replace("{ProjectPath}", issue.ProjectPath())
+ .Replace("{ProjectDirectory}", issue.ProjectDirectory())
+ .Replace("{ProjectName}", issue.ProjectName)
+ .Replace("{FilePath}", issue.FilePath())
+ .Replace("{FileDirectory}", issue.FileDirectory())
+ .Replace("{FileName}", issue.FileName())
+ .Replace("{Line}", issue.Line?.ToString())
+ .Replace("{Rule}", issue.Rule)
+ .Replace("{RuleUrl}", issue.RuleUrl?.ToString())
+ .Replace("{Message}", issue.Message);
+ }
+ }
+}
\ No newline at end of file