1# frozen_string_literal: true
2
3# This class replaces Github markdown suggestion tag with
4# a Gitlab suggestion tag. The difference between
5# Github's and Gitlab's suggestion tags is that Gitlab
6# includes the range of the suggestion in the tag, while Github
7# uses other note attributes to position the suggestion.
8module Gitlab
9  module GithubImport
10    module Representation
11      module DiffNotes
12        class SuggestionFormatter
13          include Gitlab::Utils::StrongMemoize
14
15          # A github suggestion:
16          # - the ```suggestion tag must be the first text of the line
17          #   - it might have up to 3 spaces before the ```suggestion tag
18          # - extra text on the ```suggestion tag line will be ignored
19          GITHUB_SUGGESTION = /^\ {,3}(?<suggestion>```suggestion\b).*(?<eol>\R)/.freeze
20
21          def initialize(note:, start_line: nil, end_line: nil)
22            @note = note
23            @start_line = start_line
24            @end_line = end_line
25          end
26
27          # Returns a tuple with:
28          #   - a boolean indicating if the note has suggestions
29          #   - the note with the suggestion formatted for Gitlab
30          def formatted_note
31            @formatted_note ||=
32              if contains_suggestion?
33                note.gsub(
34                  GITHUB_SUGGESTION,
35                  "\\k<suggestion>:#{suggestion_range}\\k<eol>"
36                )
37              else
38                note
39              end
40          end
41
42          def contains_suggestion?
43            strong_memoize(:contain_suggestion) do
44              note.to_s.match?(GITHUB_SUGGESTION)
45            end
46          end
47
48          private
49
50          attr_reader :note, :start_line, :end_line
51
52          # Github always saves the comment on the _last_ line of the range.
53          # Therefore, the diff hunk will always be related to lines before
54          # the comment itself.
55          def suggestion_range
56            "-#{line_count}+0"
57          end
58
59          def line_count
60            if start_line.present?
61              end_line - start_line
62            else
63              0
64            end
65          end
66        end
67      end
68    end
69  end
70end
71