private static void CheckIssue( ICodeAnalysisIssue issue, string affectedFileRelativePath, int?line, string rule, string ruleUrl, int priority, string message) { if (issue.AffectedFileRelativePath == null) { affectedFileRelativePath.ShouldBeNull(); } else { issue.AffectedFileRelativePath.ToString().ShouldBe(new FilePath(affectedFileRelativePath).ToString()); issue.AffectedFileRelativePath.IsRelative.ShouldBe(true, "Issue path is not relative"); } issue.Line.ShouldBe(line); issue.Rule.ShouldBe(rule); if (issue.RuleUrl == null) { ruleUrl.ShouldBeNull(); } else { issue.RuleUrl.ToString().ShouldBe(ruleUrl); } issue.Priority.ShouldBe(priority); issue.Message.ShouldBe(message); }
/// <summary> /// Checks if there's already a comment for an issue. /// </summary> /// <param name="issue">Issue to check.</param> /// <param name="issueComments">List of existing comments.</param> /// <returns>True if there are already comments for an issue.</returns> private static bool IssueHasMatchingComments( ICodeAnalysisIssue issue, IDictionary <ICodeAnalysisIssue, IEnumerable <IPrcaDiscussionComment> > issueComments) { return (issueComments.ContainsKey(issue) && issueComments[issue].Any()); }
/// <summary> /// Checks if file path from an <see cref="ICodeAnalysisIssue"/> and <see cref="IPrcaDiscussionThread"/> /// are matching. /// </summary> /// <param name="issue">Issue to check.</param> /// <param name="thread">Comment thread to check.</param> /// <returns><c>true</c> if both paths are matching or if both paths are set to <c>null</c>.</returns> private static bool FilePathsAreMatching(ICodeAnalysisIssue issue, IPrcaDiscussionThread thread) { return ((issue.AffectedFileRelativePath == null && thread.AffectedFileRelativePath == null) || ( issue.AffectedFileRelativePath != null && thread.AffectedFileRelativePath != null && thread.AffectedFileRelativePath.ToString() == issue.AffectedFileRelativePath.ToString())); }
/// <summary> /// Returns all matching comments from discussion threads for an issue. /// Comments are considered matching if they fulfill all of the following conditions: /// * The thread is active. /// * The thread is for the same file. /// * The thread was created by the same logic, i.e. the same <code>commentSource</code>. /// * The comment contains the same content. /// </summary> /// <remarks> /// The line cannot be used since comments can move around. /// </remarks> /// <param name="reportIssuesToPullRequestSettings">Settings to use.</param> /// <param name="issue">Issue for which the comments should be returned.</param> /// <param name="existingThreads">Existing discussion threads on the pull request.</param> /// <returns>Active comments for the issue.</returns> private IEnumerable <IPrcaDiscussionComment> GetMatchingComments( ReportIssuesToPullRequestSettings reportIssuesToPullRequestSettings, ICodeAnalysisIssue issue, IList <IPrcaDiscussionThread> existingThreads) { issue.NotNull(nameof(issue)); existingThreads.NotNull(nameof(existingThreads)); // Select threads that are active, that point to the same file and have been marked with the given comment source. var matchingThreads = (from thread in existingThreads where thread != null && thread.Status == PrcaDiscussionStatus.Active && FilePathsAreMatching(issue, thread) && thread.CommentSource == reportIssuesToPullRequestSettings.CommentSource select thread).ToList(); if (matchingThreads.Any()) { this.log.Verbose( "Found {0} matching thread(s) for the issue at {1} line {2}", matchingThreads.Count, issue.AffectedFileRelativePath, issue.Line); } var result = new List <IPrcaDiscussionComment>(); foreach (var thread in matchingThreads) { // Select comments from this thread that are not deleted and that match the given message. var matchingComments = (from comment in thread.Comments where comment != null && !comment.IsDeleted && comment.Content == issue.Message select comment).ToList(); if (matchingComments.Any()) { this.log.Verbose( "Found {0} matching comment(s) for the issue at {1} line {2}", matchingComments.Count, issue.AffectedFileRelativePath, issue.Line); } result.AddRange(matchingComments); } return(result); }
private static void AddCodeFlowProperties( ICodeAnalysisIssue issue, int iterationId, int changeTrackingId, PropertiesCollection properties) { issue.NotNull(nameof(issue)); properties.NotNull(nameof(properties)); properties.Add("Microsoft.VisualStudio.Services.CodeReview.ItemPath", "/" + issue.AffectedFileRelativePath); properties.Add("Microsoft.VisualStudio.Services.CodeReview.Right.StartLine", issue.Line); properties.Add("Microsoft.VisualStudio.Services.CodeReview.Right.EndLine", issue.Line); properties.Add("Microsoft.VisualStudio.Services.CodeReview.Right.StartOffset", 0); properties.Add("Microsoft.VisualStudio.Services.CodeReview.Right.EndOffset", 1); properties.Add("Microsoft.VisualStudio.Services.CodeReview.FirstComparingIteration", iterationId); properties.Add("Microsoft.VisualStudio.Services.CodeReview.SecondComparingIteration", iterationId); properties.Add("Microsoft.VisualStudio.Services.CodeReview.ChangeTrackingId", changeTrackingId); }
private bool AddThreadProperties( GitPullRequestCommentThread thread, GitPullRequestIterationChanges changes, ICodeAnalysisIssue issue, int iterationId, string commentSource) { thread.NotNull(nameof(thread)); changes.NotNull(nameof(changes)); issue.NotNull(nameof(issue)); var properties = new PropertiesCollection(); if (this.pullRequest.CodeReviewId > 0) { var changeTrackingId = this.TryGetCodeFlowChangeTrackingId(changes, issue.AffectedFileRelativePath); if (changeTrackingId < 0) { // Don't post comment if we couldn't determine the change. return(false); } AddCodeFlowProperties(issue, iterationId, changeTrackingId, properties); } else { throw new NotSupportedException("Legacy code reviews are not supported."); } // A VSTS UI extension will recognize this and format the comments differently. properties.Add("CodeAnalysisThreadType", "CodeAnalysisIssue"); thread.Properties = properties; // Add a custom property to be able to distinguish all comments created this way. thread.SetCommentSource(commentSource); // Add a custom property to be able to return issue message from existing threads, // without any formatting done by this addin, back to Cake.Prca. thread.SetIssueMessage(issue.Message); return(true); }
/// <summary> /// Returns the content for an issue. /// </summary> /// <param name="issue">Issue for which the content should be returned.</param> /// <returns>Comment content for the issue.</returns> public static string GetContent(ICodeAnalysisIssue issue) { var result = issue.Message; if (string.IsNullOrWhiteSpace(issue.Rule)) { return(result); } var ruleContent = issue.Rule; if (issue.RuleUrl != null) { ruleContent = $"[{issue.Rule}]({issue.RuleUrl})"; } result = $"{ruleContent}: {result}"; return(result); }