semverve 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.github/workflows/ruby.yml +37 -0
- data/.gitignore +3 -0
- data/.ruby-version +1 -0
- data/CONTRIBUTING.md +155 -0
- data/Gemfile +7 -0
- data/Gemfile.lock +93 -0
- data/LICENSE +21 -0
- data/README.md +551 -0
- data/Rakefile +45 -0
- data/lib/semverve/configuration.rb +471 -0
- data/lib/semverve/docs_publisher/task.rb +166 -0
- data/lib/semverve/docs_publisher.rb +370 -0
- data/lib/semverve/error.rb +8 -0
- data/lib/semverve/file_list_resolver.rb +66 -0
- data/lib/semverve/formats/module_constants.rb +140 -0
- data/lib/semverve/formats/simple_string.rb +72 -0
- data/lib/semverve/formats.rb +27 -0
- data/lib/semverve/generator.rb +66 -0
- data/lib/semverve/presets.rb +126 -0
- data/lib/semverve/project_metadata.rb +153 -0
- data/lib/semverve/railtie.rb +16 -0
- data/lib/semverve/semantic_version.rb +116 -0
- data/lib/semverve/task.rb +409 -0
- data/lib/semverve/version.rb +50 -0
- data/lib/semverve/version_code_references.rb +244 -0
- data/lib/semverve/version_file.rb +142 -0
- data/lib/semverve/version_metadata.rb +358 -0
- data/lib/semverve/version_references.rb +427 -0
- data/lib/semverve.rb +30 -0
- data/semverve.gemspec +36 -0
- metadata +156 -0
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rake"
|
|
4
|
+
|
|
5
|
+
require_relative "../semverve"
|
|
6
|
+
require_relative "generator"
|
|
7
|
+
require_relative "semantic_version"
|
|
8
|
+
require_relative "version_file"
|
|
9
|
+
require_relative "version_code_references"
|
|
10
|
+
require_relative "version_metadata"
|
|
11
|
+
require_relative "version_references"
|
|
12
|
+
|
|
13
|
+
module Semverve
|
|
14
|
+
##
|
|
15
|
+
# Defines Semverve's Rake tasks for the current Rake application.
|
|
16
|
+
class Task
|
|
17
|
+
include Rake::DSL
|
|
18
|
+
|
|
19
|
+
class << self
|
|
20
|
+
##
|
|
21
|
+
# Installs Semverve tasks once for the current Rake application.
|
|
22
|
+
#
|
|
23
|
+
# @return [Semverve::Task, nil]
|
|
24
|
+
def install
|
|
25
|
+
return if installed_for_current_application?
|
|
26
|
+
|
|
27
|
+
new
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
##
|
|
33
|
+
# Whether tasks were already installed for the current Rake application.
|
|
34
|
+
#
|
|
35
|
+
# @return [Boolean]
|
|
36
|
+
def installed_for_current_application?
|
|
37
|
+
installed_applications.include?(Rake.application.object_id)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
##
|
|
41
|
+
# Rake application object IDs that already have Semverve tasks.
|
|
42
|
+
#
|
|
43
|
+
# @return [Array<Integer>]
|
|
44
|
+
def installed_applications
|
|
45
|
+
@installed_applications ||= []
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
##
|
|
49
|
+
# Records the current Rake application as installed.
|
|
50
|
+
#
|
|
51
|
+
# @return [Array<Integer>]
|
|
52
|
+
def mark_current_application_installed
|
|
53
|
+
installed_applications << Rake.application.object_id
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
##
|
|
58
|
+
# Configures and defines Semverve tasks if needed.
|
|
59
|
+
#
|
|
60
|
+
# @yieldparam [Semverve::Configuration] configuration
|
|
61
|
+
#
|
|
62
|
+
# @return [Semverve::Task]
|
|
63
|
+
def initialize
|
|
64
|
+
yield Semverve.configuration if block_given?
|
|
65
|
+
|
|
66
|
+
unless self.class.send(:installed_for_current_application?)
|
|
67
|
+
define
|
|
68
|
+
self.class.send(:mark_current_application_installed)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
##
|
|
73
|
+
# Defines the +semverve:*+ Rake tasks.
|
|
74
|
+
#
|
|
75
|
+
# @return [void]
|
|
76
|
+
def define
|
|
77
|
+
namespace :semverve do
|
|
78
|
+
desc "Print the current version from the version.rb file"
|
|
79
|
+
task :current do
|
|
80
|
+
puts VersionFile.new(Semverve.configuration.resolved).current
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
namespace :increment do
|
|
84
|
+
desc "Increment the version's PATCH level"
|
|
85
|
+
task :patch do
|
|
86
|
+
increment(:patch)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
desc "Increment the version's MINOR level"
|
|
90
|
+
task :minor do
|
|
91
|
+
increment(:minor)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
desc "Increment the version's MAJOR level"
|
|
95
|
+
task :major do
|
|
96
|
+
increment(:major)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
desc "Generate a version.rb file"
|
|
101
|
+
task :generate do
|
|
102
|
+
puts "Generated #{Generator.new(Semverve.configuration.resolved).generate}"
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
desc "Set the version.rb file to VERSION"
|
|
106
|
+
task :set do
|
|
107
|
+
set
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
desc "Check version references, code literals, and metadata"
|
|
111
|
+
task :check do
|
|
112
|
+
check
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
namespace :check do
|
|
116
|
+
desc "Check configured files for stale version references"
|
|
117
|
+
task :references do
|
|
118
|
+
check_references
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
desc "Check configured code files for version literals"
|
|
122
|
+
task :code do
|
|
123
|
+
check_code
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
desc "Check gem metadata for version mismatches"
|
|
127
|
+
task :metadata do
|
|
128
|
+
check_metadata
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
desc "Fix version references, code literals, and metadata"
|
|
133
|
+
task :fix do
|
|
134
|
+
fix
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
namespace :fix do
|
|
138
|
+
desc "Replace stale version references in configured files"
|
|
139
|
+
task :references do
|
|
140
|
+
fix_references
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
desc "Replace safe code version literals in configured files"
|
|
144
|
+
task :code do
|
|
145
|
+
fix_code
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
desc "Fix safe gem metadata version mismatches"
|
|
149
|
+
task :metadata do
|
|
150
|
+
fix_metadata
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
private
|
|
157
|
+
|
|
158
|
+
##
|
|
159
|
+
# Increments a version level and reports the update.
|
|
160
|
+
#
|
|
161
|
+
# @param [Symbol] level
|
|
162
|
+
#
|
|
163
|
+
# @return [void]
|
|
164
|
+
def increment(level)
|
|
165
|
+
configuration = Semverve.configuration.resolved
|
|
166
|
+
update = VersionFile.new(configuration).increment(level)
|
|
167
|
+
|
|
168
|
+
report(update, configuration)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
##
|
|
172
|
+
# Sets the version to the value from the +VERSION+ environment variable.
|
|
173
|
+
#
|
|
174
|
+
# @return [void]
|
|
175
|
+
def set
|
|
176
|
+
configuration = Semverve.configuration.resolved
|
|
177
|
+
requested_version = SemanticVersion.parse(
|
|
178
|
+
ENV.fetch("VERSION") { raise Error, "Set VERSION=MAJOR.MINOR.PATCH." }
|
|
179
|
+
)
|
|
180
|
+
update = VersionFile.new(configuration).set(requested_version)
|
|
181
|
+
|
|
182
|
+
report(update, configuration)
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
##
|
|
186
|
+
# Checks all version-maintenance surfaces.
|
|
187
|
+
#
|
|
188
|
+
# @return [void]
|
|
189
|
+
def check
|
|
190
|
+
configuration, current_version = check_context
|
|
191
|
+
report_findings(
|
|
192
|
+
check_groups(configuration, current_version),
|
|
193
|
+
current_version,
|
|
194
|
+
clean_message: "Version checks passed."
|
|
195
|
+
)
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
##
|
|
199
|
+
# Fixes all check surfaces.
|
|
200
|
+
#
|
|
201
|
+
# @return [void]
|
|
202
|
+
def fix
|
|
203
|
+
configuration, current_version = check_context
|
|
204
|
+
report_fix_results(fix_results(configuration, current_version))
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
##
|
|
208
|
+
# Checks configured files for stale version references.
|
|
209
|
+
#
|
|
210
|
+
# @return [void]
|
|
211
|
+
def check_references
|
|
212
|
+
configuration, current_version = check_context
|
|
213
|
+
report_findings(
|
|
214
|
+
[["version reference", VersionReferences.new(configuration, current_version).findings]],
|
|
215
|
+
current_version,
|
|
216
|
+
clean_message: "Version references are current."
|
|
217
|
+
)
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
##
|
|
221
|
+
# Replaces stale version references in configured files.
|
|
222
|
+
#
|
|
223
|
+
# @return [void]
|
|
224
|
+
def fix_references
|
|
225
|
+
configuration, current_version = check_context
|
|
226
|
+
report_fix_results(
|
|
227
|
+
[["version reference", VersionReferences.new(configuration, current_version).fix]],
|
|
228
|
+
clean_message: "Version references are current."
|
|
229
|
+
)
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
##
|
|
233
|
+
# Checks configured code files for version literals.
|
|
234
|
+
#
|
|
235
|
+
# @return [void]
|
|
236
|
+
def check_code
|
|
237
|
+
configuration, current_version = check_context
|
|
238
|
+
report_findings(
|
|
239
|
+
[["code version literal", VersionCodeReferences.new(configuration, current_version).findings]],
|
|
240
|
+
current_version,
|
|
241
|
+
clean_message: "Code version literals are current."
|
|
242
|
+
)
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
##
|
|
246
|
+
# Replaces safe code version literals in configured files.
|
|
247
|
+
#
|
|
248
|
+
# @return [void]
|
|
249
|
+
def fix_code
|
|
250
|
+
configuration, current_version = check_context
|
|
251
|
+
report_fix_results(
|
|
252
|
+
[["code version literal", VersionCodeReferences.new(configuration, current_version).fix]],
|
|
253
|
+
clean_message: "Code version literals are current."
|
|
254
|
+
)
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
##
|
|
258
|
+
# Checks gem metadata for version mismatches.
|
|
259
|
+
#
|
|
260
|
+
# @return [void]
|
|
261
|
+
def check_metadata
|
|
262
|
+
configuration, current_version = check_context
|
|
263
|
+
report_findings(
|
|
264
|
+
[[nil, VersionMetadata.new(configuration, current_version).findings]],
|
|
265
|
+
current_version,
|
|
266
|
+
clean_message: "Version metadata is current."
|
|
267
|
+
)
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
##
|
|
271
|
+
# Fixes safe gem metadata version mismatches.
|
|
272
|
+
#
|
|
273
|
+
# @return [void]
|
|
274
|
+
def fix_metadata
|
|
275
|
+
configuration, current_version = check_context
|
|
276
|
+
report_fix_results(
|
|
277
|
+
[["metadata version", VersionMetadata.new(configuration, current_version).fix]],
|
|
278
|
+
clean_message: "Version metadata is current."
|
|
279
|
+
)
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
##
|
|
283
|
+
# Resolved configuration and current version for check tasks.
|
|
284
|
+
#
|
|
285
|
+
# @return [Array(Semverve::ResolvedConfiguration, Semverve::SemanticVersion)]
|
|
286
|
+
def check_context
|
|
287
|
+
configuration = Semverve.configuration.resolved
|
|
288
|
+
[configuration, VersionFile.new(configuration).current]
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
##
|
|
292
|
+
# Finding groups enabled for the umbrella check task.
|
|
293
|
+
#
|
|
294
|
+
# @param [Semverve::ResolvedConfiguration] configuration
|
|
295
|
+
# @param [Semverve::SemanticVersion] current_version
|
|
296
|
+
#
|
|
297
|
+
# @return [Array<Array(String, Array)>]
|
|
298
|
+
def check_groups(configuration, current_version)
|
|
299
|
+
groups = []
|
|
300
|
+
if configuration.version_checks.include?(:doc_references)
|
|
301
|
+
groups << ["version reference", VersionReferences.new(configuration, current_version).findings]
|
|
302
|
+
end
|
|
303
|
+
if configuration.version_checks.include?(:code_references)
|
|
304
|
+
groups << ["code version literal", VersionCodeReferences.new(configuration, current_version).findings]
|
|
305
|
+
end
|
|
306
|
+
if configuration.version_checks.include?(:metadata)
|
|
307
|
+
groups << [nil, VersionMetadata.new(configuration, current_version).findings]
|
|
308
|
+
end
|
|
309
|
+
groups
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
##
|
|
313
|
+
# Fix results enabled for the umbrella fix task.
|
|
314
|
+
#
|
|
315
|
+
# @param [Semverve::ResolvedConfiguration] configuration
|
|
316
|
+
# @param [Semverve::SemanticVersion] current_version
|
|
317
|
+
#
|
|
318
|
+
# @return [Array<Array(String, #replacement_count, #changed_files)>]
|
|
319
|
+
def fix_results(configuration, current_version)
|
|
320
|
+
results = []
|
|
321
|
+
if configuration.version_checks.include?(:doc_references)
|
|
322
|
+
results << ["version reference", VersionReferences.new(configuration, current_version).fix]
|
|
323
|
+
end
|
|
324
|
+
if configuration.version_checks.include?(:code_references)
|
|
325
|
+
results << ["code version literal", VersionCodeReferences.new(configuration, current_version).fix]
|
|
326
|
+
end
|
|
327
|
+
if configuration.version_checks.include?(:metadata)
|
|
328
|
+
results << ["metadata version", VersionMetadata.new(configuration, current_version).fix]
|
|
329
|
+
end
|
|
330
|
+
results
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
##
|
|
334
|
+
# Prints findings and raises when mismatches exist.
|
|
335
|
+
#
|
|
336
|
+
# @param [Array<Array(String, Array)>] groups
|
|
337
|
+
# @param [Semverve::SemanticVersion] current_version
|
|
338
|
+
# @param [String] clean_message
|
|
339
|
+
#
|
|
340
|
+
# @return [void]
|
|
341
|
+
def report_findings(groups, current_version, clean_message:)
|
|
342
|
+
findings = groups.flat_map do |(label, group_findings)|
|
|
343
|
+
group_findings.map { |finding| [label || finding.label, finding] }
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
if findings.empty?
|
|
347
|
+
puts clean_message
|
|
348
|
+
return
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
findings.each do |(label, finding)|
|
|
352
|
+
puts "#{finding.path}:#{finding.line}:#{finding.column}: #{label} #{finding.version} -> #{current_version}"
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
issue = findings.one? ? "issue" : "issues"
|
|
356
|
+
raise Error, "Found #{findings.count} version check #{issue}."
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
##
|
|
360
|
+
# Prints fix results.
|
|
361
|
+
#
|
|
362
|
+
# @param [Array<Array(String, #replacement_count, #changed_files)>] results
|
|
363
|
+
# @param [String] clean_message
|
|
364
|
+
#
|
|
365
|
+
# @return [void]
|
|
366
|
+
def report_fix_results(results, clean_message: "Version checks passed.")
|
|
367
|
+
replacement_count = results.sum { |(_label, result)| result.replacement_count }
|
|
368
|
+
bundle_lock_ran = results.any? { |(_label, result)| result.respond_to?(:bundle_lock_ran) && result.bundle_lock_ran }
|
|
369
|
+
|
|
370
|
+
if replacement_count.zero? && !bundle_lock_ran
|
|
371
|
+
puts clean_message
|
|
372
|
+
return
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
results.each do |(label, result)|
|
|
376
|
+
result.changed_files.each { |path| puts "Updated #{path}" }
|
|
377
|
+
next if result.replacement_count.zero?
|
|
378
|
+
|
|
379
|
+
noun = (result.replacement_count == 1) ? label : "#{label}s"
|
|
380
|
+
puts "Replaced #{result.replacement_count} #{noun}."
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
puts "Ran bundle lock." if bundle_lock_ran
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
##
|
|
387
|
+
# Reports an update and runs configured follow-up commands.
|
|
388
|
+
#
|
|
389
|
+
# @param [Semverve::VersionFile::UpdateResult] update
|
|
390
|
+
# @param [Semverve::ResolvedConfiguration] configuration
|
|
391
|
+
#
|
|
392
|
+
# @return [void]
|
|
393
|
+
def report(update, configuration)
|
|
394
|
+
unless update.changed?
|
|
395
|
+
puts "Version is already #{update.version}"
|
|
396
|
+
return
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
if update.version < update.previous_version
|
|
400
|
+
warn "Warning: updating to version #{update.version}, which is lower than the current version #{update.previous_version}."
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
puts "Updating to version #{update.version} (was #{update.previous_version})"
|
|
404
|
+
configuration.command_runner.call("bundle lock") if configuration.bundle_lock
|
|
405
|
+
end
|
|
406
|
+
end
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
Semverve::Task.install
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Semverve
|
|
4
|
+
##
|
|
5
|
+
# Module that contains all gem version information. Follows semantic
|
|
6
|
+
# versioning. Read: https://semver.org/
|
|
7
|
+
module Version
|
|
8
|
+
##
|
|
9
|
+
# Major version.
|
|
10
|
+
#
|
|
11
|
+
# @return [Integer]
|
|
12
|
+
MAJOR = 0
|
|
13
|
+
|
|
14
|
+
##
|
|
15
|
+
# Minor version.
|
|
16
|
+
#
|
|
17
|
+
# @return [Integer]
|
|
18
|
+
MINOR = 1
|
|
19
|
+
|
|
20
|
+
##
|
|
21
|
+
# Patch version.
|
|
22
|
+
#
|
|
23
|
+
# @return [Integer]
|
|
24
|
+
PATCH = 0
|
|
25
|
+
|
|
26
|
+
module_function
|
|
27
|
+
|
|
28
|
+
##
|
|
29
|
+
# Version as +[MAJOR, MINOR, PATCH]+
|
|
30
|
+
#
|
|
31
|
+
# @return [Array<Integer>]
|
|
32
|
+
def to_a
|
|
33
|
+
[MAJOR, MINOR, PATCH]
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
##
|
|
37
|
+
# Version as +MAJOR.MINOR.PATCH+
|
|
38
|
+
#
|
|
39
|
+
# @return [String]
|
|
40
|
+
def to_s
|
|
41
|
+
to_a.join(".")
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
##
|
|
46
|
+
# The version, as a string.
|
|
47
|
+
#
|
|
48
|
+
# @return [String]
|
|
49
|
+
VERSION = Version.to_s
|
|
50
|
+
end
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "configuration"
|
|
4
|
+
require_relative "file_list_resolver"
|
|
5
|
+
require_relative "semantic_version"
|
|
6
|
+
|
|
7
|
+
module Semverve
|
|
8
|
+
##
|
|
9
|
+
# Finds and updates safe version literals in configured code files.
|
|
10
|
+
class VersionCodeReferences
|
|
11
|
+
##
|
|
12
|
+
# Ruby assignments that are safe enough to rewrite automatically.
|
|
13
|
+
#
|
|
14
|
+
# @return [Regexp]
|
|
15
|
+
RUBY_ASSIGNMENT_PATTERN = Configuration::DEFAULT_VERSION_CODE_REFERENCE_PATTERN
|
|
16
|
+
|
|
17
|
+
##
|
|
18
|
+
# A code version literal found in a configured file.
|
|
19
|
+
class Finding
|
|
20
|
+
##
|
|
21
|
+
# Path relative to the configured project root.
|
|
22
|
+
#
|
|
23
|
+
# @return [String]
|
|
24
|
+
attr_reader :path
|
|
25
|
+
|
|
26
|
+
##
|
|
27
|
+
# One-based line number.
|
|
28
|
+
#
|
|
29
|
+
# @return [Integer]
|
|
30
|
+
attr_reader :line
|
|
31
|
+
|
|
32
|
+
##
|
|
33
|
+
# One-based column number.
|
|
34
|
+
#
|
|
35
|
+
# @return [Integer]
|
|
36
|
+
attr_reader :column
|
|
37
|
+
|
|
38
|
+
##
|
|
39
|
+
# Referenced semantic version.
|
|
40
|
+
#
|
|
41
|
+
# @return [Semverve::SemanticVersion]
|
|
42
|
+
attr_reader :version
|
|
43
|
+
|
|
44
|
+
##
|
|
45
|
+
# Initializes a finding.
|
|
46
|
+
#
|
|
47
|
+
# @param [String] path
|
|
48
|
+
# @param [Integer] line
|
|
49
|
+
# @param [Integer] column
|
|
50
|
+
# @param [Semverve::SemanticVersion] version
|
|
51
|
+
#
|
|
52
|
+
# @return [Semverve::VersionCodeReferences::Finding]
|
|
53
|
+
def initialize(path:, line:, column:, version:)
|
|
54
|
+
@path = path
|
|
55
|
+
@line = line
|
|
56
|
+
@column = column
|
|
57
|
+
@version = version
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
##
|
|
62
|
+
# Result of fixing code version literals.
|
|
63
|
+
class FixResult
|
|
64
|
+
##
|
|
65
|
+
# Files changed by the fix.
|
|
66
|
+
#
|
|
67
|
+
# @return [Array<String>]
|
|
68
|
+
attr_reader :changed_files
|
|
69
|
+
|
|
70
|
+
##
|
|
71
|
+
# Number of replacements made.
|
|
72
|
+
#
|
|
73
|
+
# @return [Integer]
|
|
74
|
+
attr_reader :replacement_count
|
|
75
|
+
|
|
76
|
+
##
|
|
77
|
+
# Initializes a fix result.
|
|
78
|
+
#
|
|
79
|
+
# @param [Array<String>] changed_files
|
|
80
|
+
# @param [Integer] replacement_count
|
|
81
|
+
#
|
|
82
|
+
# @return [Semverve::VersionCodeReferences::FixResult]
|
|
83
|
+
def initialize(changed_files:, replacement_count:)
|
|
84
|
+
@changed_files = changed_files
|
|
85
|
+
@replacement_count = replacement_count
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
##
|
|
90
|
+
# Initializes code version literal scanning.
|
|
91
|
+
#
|
|
92
|
+
# @param [Semverve::ResolvedConfiguration] configuration
|
|
93
|
+
# @param [Semverve::SemanticVersion] current_version
|
|
94
|
+
#
|
|
95
|
+
# @return [Semverve::VersionCodeReferences]
|
|
96
|
+
def initialize(configuration, current_version)
|
|
97
|
+
@configuration = configuration
|
|
98
|
+
@current_version = current_version
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
##
|
|
102
|
+
# Code version literal findings in configured files.
|
|
103
|
+
#
|
|
104
|
+
# @return [Array<Semverve::VersionCodeReferences::Finding>]
|
|
105
|
+
def findings
|
|
106
|
+
files.flat_map { |path| findings_for_file(path) }
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
##
|
|
110
|
+
# Replaces found code version literals with the current version.
|
|
111
|
+
#
|
|
112
|
+
# @return [Semverve::VersionCodeReferences::FixResult]
|
|
113
|
+
def fix
|
|
114
|
+
changed_files = []
|
|
115
|
+
replacement_count = 0
|
|
116
|
+
|
|
117
|
+
files.each do |path|
|
|
118
|
+
content = File.read(path)
|
|
119
|
+
fixed, count = fixed_content(content)
|
|
120
|
+
|
|
121
|
+
next if count.zero?
|
|
122
|
+
|
|
123
|
+
File.write(path, fixed)
|
|
124
|
+
changed_files << relative_path(path)
|
|
125
|
+
replacement_count += count
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
FixResult.new(changed_files: changed_files, replacement_count: replacement_count)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
private
|
|
132
|
+
|
|
133
|
+
##
|
|
134
|
+
# Resolved Semverve configuration.
|
|
135
|
+
#
|
|
136
|
+
# @return [Semverve::ResolvedConfiguration]
|
|
137
|
+
attr_reader :configuration
|
|
138
|
+
|
|
139
|
+
##
|
|
140
|
+
# Current gem version.
|
|
141
|
+
#
|
|
142
|
+
# @return [Semverve::SemanticVersion]
|
|
143
|
+
attr_reader :current_version
|
|
144
|
+
|
|
145
|
+
##
|
|
146
|
+
# Absolute configured project root.
|
|
147
|
+
#
|
|
148
|
+
# @return [String]
|
|
149
|
+
def root
|
|
150
|
+
configuration.root
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
##
|
|
154
|
+
# Configured pattern used to find code version literals.
|
|
155
|
+
#
|
|
156
|
+
# @return [Regexp]
|
|
157
|
+
def pattern
|
|
158
|
+
configuration.version_code_reference_pattern
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
##
|
|
162
|
+
# Absolute files to scan.
|
|
163
|
+
#
|
|
164
|
+
# @return [Array<String>]
|
|
165
|
+
def files
|
|
166
|
+
@files ||= FileListResolver.new(
|
|
167
|
+
root: root,
|
|
168
|
+
file_list: configuration.version_code_reference_files
|
|
169
|
+
).files.reject { |path| File.expand_path(path) == File.expand_path(configuration.absolute_version_file) }
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
##
|
|
173
|
+
# Findings for a single file.
|
|
174
|
+
#
|
|
175
|
+
# @param [String] path
|
|
176
|
+
#
|
|
177
|
+
# @return [Array<Semverve::VersionCodeReferences::Finding>]
|
|
178
|
+
def findings_for_file(path)
|
|
179
|
+
File.readlines(path).filter_map.with_index(1) do |line, line_number|
|
|
180
|
+
match = line.match(pattern)
|
|
181
|
+
next unless match
|
|
182
|
+
|
|
183
|
+
version = SemanticVersion.parse(match[:version])
|
|
184
|
+
next if version == current_version
|
|
185
|
+
|
|
186
|
+
Finding.new(
|
|
187
|
+
path: relative_path(path),
|
|
188
|
+
line: line_number,
|
|
189
|
+
column: match.begin(:version) + 1,
|
|
190
|
+
version: version
|
|
191
|
+
)
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
##
|
|
196
|
+
# Fixed content and replacement count.
|
|
197
|
+
#
|
|
198
|
+
# @param [String] content
|
|
199
|
+
#
|
|
200
|
+
# @return [Array(String, Integer)]
|
|
201
|
+
def fixed_content(content)
|
|
202
|
+
replacement_count = 0
|
|
203
|
+
|
|
204
|
+
fixed = content.lines.map do |line|
|
|
205
|
+
match = line.match(pattern)
|
|
206
|
+
next line unless match
|
|
207
|
+
|
|
208
|
+
version = SemanticVersion.parse(match[:version])
|
|
209
|
+
next line if version == current_version
|
|
210
|
+
|
|
211
|
+
replacement_count += 1
|
|
212
|
+
replace_matched_version(line)
|
|
213
|
+
end.join
|
|
214
|
+
|
|
215
|
+
[fixed, replacement_count]
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
##
|
|
219
|
+
# Replaces only the named version capture in the first pattern match.
|
|
220
|
+
#
|
|
221
|
+
# @param [String] line
|
|
222
|
+
#
|
|
223
|
+
# @return [String]
|
|
224
|
+
def replace_matched_version(line)
|
|
225
|
+
line.sub(pattern) do |matched_text|
|
|
226
|
+
match = Regexp.last_match
|
|
227
|
+
version_start = match.begin(:version) - match.begin(0)
|
|
228
|
+
version_end = match.end(:version) - match.begin(0)
|
|
229
|
+
|
|
230
|
+
"#{matched_text[0...version_start]}#{current_version}#{matched_text[version_end..]}"
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
##
|
|
235
|
+
# Path relative to the project root.
|
|
236
|
+
#
|
|
237
|
+
# @param [String] path
|
|
238
|
+
#
|
|
239
|
+
# @return [String]
|
|
240
|
+
def relative_path(path)
|
|
241
|
+
path.delete_prefix("#{root}/")
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
end
|