1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
|
# frozen_string_literal: true
module Gitlab
module Graphql
module Deprecations
class Deprecation
REASON_RENAMED = :renamed
REASON_ALPHA = :alpha # TODO remove support in this class
REASONS = {
REASON_RENAMED => 'This was renamed.',
REASON_ALPHA => 'This feature is in Alpha. It can be changed or removed at any time.'
}.freeze
include ActiveModel::Validations
validates :milestone, presence: true, format: { with: /\A\d+\.\d+\z/, message: 'must be milestone-ish' }
validates :reason, presence: true
validates :reason,
format: { with: /.*[^.]\z/, message: 'must not end with a period' },
if: :reason_is_string?
validate :milestone_is_string
validate :reason_known_or_string
def self.parse(alpha: nil, deprecated: nil)
options = alpha || deprecated
return unless options
if alpha
raise ArgumentError, '`alpha` and `deprecated` arguments cannot be passed at the same time' \
if deprecated
options[:reason] = :alpha
end
new(**options)
end
def initialize(reason: nil, milestone: nil, replacement: nil)
@reason = reason.presence
@milestone = milestone.presence
@replacement = replacement.presence
end
def ==(other)
return false unless other.is_a?(self.class)
[reason_text, milestone, replacement] == [:reason_text, :milestone, :replacement].map do |attr|
other.send(attr) # rubocop: disable GitlabSecurity/PublicSend
end
end
alias_method :eql, :==
def markdown(context: :inline)
parts = [
"#{changed_in_milestone(format: :markdown)}.",
reason_text,
replacement_markdown.then { |r| "Use: #{r}." if r }
].compact
case context
when :block
['WARNING:', *parts].join("\n")
when :inline
parts.join(' ')
end
end
def replacement_markdown
return unless replacement.present?
return "`#{replacement}`" unless replacement.include?('.') # only fully qualified references can be linked
"[`#{replacement}`](##{replacement.downcase.tr('.', '')})"
end
def edit_description(original_description)
@original_description = original_description
return unless original_description
original_description + description_suffix
end
def original_description
return unless @original_description
return @original_description if @original_description.ends_with?('.')
"#{@original_description}."
end
def deprecation_reason
[
reason_text,
replacement && "Please use `#{replacement}`.",
"#{changed_in_milestone}."
].compact.join(' ')
end
def alpha?
reason == REASON_ALPHA
end
private
attr_reader :reason, :milestone, :replacement
def milestone_is_string
return if milestone.is_a?(String)
errors.add(:milestone, 'must be a string')
end
def reason_known_or_string
return if REASONS.key?(reason)
return if reason_is_string?
errors.add(:reason, 'must be a known reason or a string')
end
def reason_is_string?
reason.is_a?(String)
end
def reason_text
@reason_text ||= REASONS[reason] || "#{reason.to_s.strip}."
end
def description_suffix
" #{changed_in_milestone}: #{reason_text}"
end
# Returns 'Deprecated in <milestone>' for proper deprecations.
# Retruns 'Introduced in <milestone>' for :alpha deprecations.
# Formatted to markdown or plain format.
def changed_in_milestone(format: :plain)
verb = if alpha?
'Introduced'
else
'Deprecated'
end
case format
when :plain
"#{verb} in #{milestone}"
when :markdown
"**#{verb}** in #{milestone}"
end
end
end
end
end
end
|