Esempio n. 1
0
        /// <summary>
        /// Returns existing matching comments from the pull request for a list of issues.
        /// </summary>
        /// <param name="reportIssuesToPullRequestSettings">Settings to use.</param>
        /// <param name="issues">Issues for which matching comments should be found.</param>
        /// <param name="existingThreads">Existing discussion threads on the pull request.</param>
        /// <returns>Dictionary containing issues and its associated matching comments on the pull request.</returns>
        private IDictionary <ICodeAnalysisIssue, IEnumerable <IPrcaDiscussionComment> > BuildIssueToCommentDictonary(
            ReportIssuesToPullRequestSettings reportIssuesToPullRequestSettings,
            IList <ICodeAnalysisIssue> issues,
            IList <IPrcaDiscussionThread> existingThreads)
        {
            issues.NotNull(nameof(issues));
            existingThreads.NotNull(nameof(existingThreads));

            var stopwatch = new Stopwatch();

            stopwatch.Start();

            var result = new Dictionary <ICodeAnalysisIssue, IEnumerable <IPrcaDiscussionComment> >();

            foreach (var issue in issues)
            {
                var matchingComments =
                    this.GetMatchingComments(
                        reportIssuesToPullRequestSettings,
                        issue,
                        existingThreads).ToList();

                if (matchingComments.Any())
                {
                    result.Add(issue, matchingComments);
                }
            }

            this.log.Verbose("Built a issue to comment dictionary in {0} ms", stopwatch.ElapsedMilliseconds);

            return(result);
        }
Esempio n. 2
0
        /// <summary>
        /// Posts new issues, ignoring duplicate comments and resolves comments that were open in an old iteration
        /// of the pull request.
        /// </summary>
        /// <param name="reportIssuesToPullRequestSettings">Settings for posting the issues.</param>
        /// <param name="issues">Issues to post.</param>
        /// <returns>Issues reported to the pull request.</returns>
        private IEnumerable <ICodeAnalysisIssue> PostAndResolveComments(
            ReportIssuesToPullRequestSettings reportIssuesToPullRequestSettings,
            IList <ICodeAnalysisIssue> issues)
        {
            issues.NotNull(nameof(issues));

            this.log.Information("Fetching existing threads and comments...");

            var existingThreads =
                this.pullRequestSystem.FetchActiveDiscussionThreads(
                    reportIssuesToPullRequestSettings.CommentSource).ToList();

            var issueComments =
                this.BuildIssueToCommentDictonary(
                    reportIssuesToPullRequestSettings,
                    issues,
                    existingThreads);

            // Comments that were created by this logic but do not have corresponding issues can be marked as 'Resolved'
            this.ResolveExistingComments(existingThreads, issueComments);

            if (!issues.Any())
            {
                this.log.Information("No new issues were posted");
                return(new List <ICodeAnalysisIssue>());
            }

            // Remove issues that cannot be posted
            var issueFilterer =
                new IssueFilterer(this.log, this.pullRequestSystem, reportIssuesToPullRequestSettings);
            var remainingIssues = issueFilterer.FilterIssues(issues, issueComments).ToList();

            if (remainingIssues.Any())
            {
                var formattedMessages =
                    from issue in remainingIssues
                    select
                    string.Format(
                        CultureInfo.InvariantCulture,
                        "  Rule: {0} Line: {1} File: {2}",
                        issue.Rule,
                        issue.Line,
                        issue.AffectedFileRelativePath);

                this.log.Verbose(
                    "Posting {0} issue(s):\n{1}",
                    remainingIssues.Count,
                    string.Join(Environment.NewLine, formattedMessages));

                this.pullRequestSystem.PostDiscussionThreads(
                    remainingIssues,
                    reportIssuesToPullRequestSettings.CommentSource);
            }
            else
            {
                this.log.Information("All issues were filtered. Nothing new to post.");
            }

            return(remainingIssues);
        }
Esempio n. 3
0
        /// <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);
        }
Esempio n. 4
0
        /// <summary>
        /// Initializes a new instance of the <see cref="IssueFilterer"/> class.
        /// </summary>
        /// <param name="log">The Cake log instance.</param>
        /// <param name="pullRequestSystem">Pull request system to use.</param>
        /// <param name="settings">Settings to use.</param>
        public IssueFilterer(
            ICakeLog log,
            IPullRequestSystem pullRequestSystem,
            ReportIssuesToPullRequestSettings settings)
        {
            log.NotNull(nameof(log));
            pullRequestSystem.NotNull(nameof(pullRequestSystem));
            settings.NotNull(nameof(settings));

            this.log = log;
            this.pullRequestSystem = pullRequestSystem;
            this.settings          = settings;
        }
Esempio n. 5
0
        public static PrcaResult ReportIssuesToPullRequest(
            this ICakeContext context,
            ICodeAnalysisProvider codeAnalysisProvider,
            IPullRequestSystem pullRequestSystem,
            ReportIssuesToPullRequestSettings settings)
        {
            context.NotNull(nameof(context));
            codeAnalysisProvider.NotNull(nameof(codeAnalysisProvider));
            pullRequestSystem.NotNull(nameof(pullRequestSystem));
            settings.NotNull(nameof(settings));

            return
                (context.ReportIssuesToPullRequest(
                     new List <ICodeAnalysisProvider> {
                codeAnalysisProvider
            },
                     pullRequestSystem,
                     settings));
        }
Esempio n. 6
0
        /// <summary>
        /// Initializes a new instance of the <see cref="Orchestrator"/> class.
        /// </summary>
        /// <param name="log">Cake log instance.</param>
        /// <param name="codeAnalysisProviders">List of code analysis issue providers to use.</param>
        /// <param name="pullRequestSystem">Object for accessing pull request system.
        /// <c>null</c> if only issues should be read.</param>
        /// <param name="settings">Settings.</param>
        public Orchestrator(
            ICakeLog log,
            IEnumerable <ICodeAnalysisProvider> codeAnalysisProviders,
            IPullRequestSystem pullRequestSystem,
            ReportIssuesToPullRequestSettings settings)
        {
            log.NotNull(nameof(log));
            pullRequestSystem.NotNull(nameof(pullRequestSystem));
            settings.NotNull(nameof(settings));

            // ReSharper disable once PossibleMultipleEnumeration
            codeAnalysisProviders.NotNullOrEmptyOrEmptyElement(nameof(codeAnalysisProviders));

            this.log = log;
            this.pullRequestSystem = pullRequestSystem;
            this.settings          = settings;

            // ReSharper disable once PossibleMultipleEnumeration
            this.codeAnalysisProviders.AddRange(codeAnalysisProviders);
        }
Esempio n. 7
0
        public static PrcaResult ReportIssuesToPullRequest(
            this ICakeContext context,
            IEnumerable <ICodeAnalysisProvider> codeAnalysisProviders,
            IPullRequestSystem pullRequestSystem,
            ReportIssuesToPullRequestSettings settings)
        {
            context.NotNull(nameof(context));
            pullRequestSystem.NotNull(nameof(pullRequestSystem));
            settings.NotNull(nameof(settings));

            // ReSharper disable once PossibleMultipleEnumeration
            codeAnalysisProviders.NotNullOrEmptyOrEmptyElement(nameof(codeAnalysisProviders));

            // ReSharper disable once PossibleMultipleEnumeration
            var orchestrator =
                new Orchestrator(
                    context.Log,
                    codeAnalysisProviders,
                    pullRequestSystem,
                    settings);

            return(orchestrator.Run());
        }