semverve 0.2.1 → 0.4.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.
@@ -0,0 +1,518 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "error"
4
+ require_relative "package_metadata"
5
+ require_relative "rails_config_metadata"
6
+ require_relative "version_code_references"
7
+ require_relative "version_references"
8
+
9
+ module Semverve
10
+ ##
11
+ # Registry and check adapters for version-maintenance surfaces.
12
+ module VersionChecks
13
+ class << self
14
+ ##
15
+ # Registers a core version check.
16
+ #
17
+ # @param [#name] check
18
+ #
19
+ # @return [#name]
20
+ def register(check)
21
+ core_checks[check.name] = check
22
+ end
23
+
24
+ ##
25
+ # All checks available to task dispatch and validation.
26
+ #
27
+ # @param [Array<#name>] extra_checks
28
+ #
29
+ # @return [Array<#name>]
30
+ def all(extra_checks: [])
31
+ (core_checks.values + extra_checks).each_with_object({}) do |check, checks|
32
+ checks[check.name] = check
33
+ end.values
34
+ end
35
+
36
+ ##
37
+ # Fetches a registered check by name.
38
+ #
39
+ # @param [Symbol, String] name
40
+ # @param [Array<#name>] extra_checks
41
+ #
42
+ # @return [#name]
43
+ def fetch(name, extra_checks: [])
44
+ normalized_name = normalize_name(name)
45
+ check = all(extra_checks: extra_checks).find { |candidate| candidate.name == normalized_name }
46
+ return check if check
47
+
48
+ raise Error, unknown_check_message([normalized_name], extra_checks: extra_checks)
49
+ end
50
+
51
+ ##
52
+ # Validates and normalizes configured check names.
53
+ #
54
+ # @param [Array<Symbol, String>] checks
55
+ # @param [Array<#name>] extra_checks
56
+ #
57
+ # @return [Array<Symbol>]
58
+ def normalize(checks, extra_checks: [])
59
+ normalized_checks = Array(checks).map { |check| normalize_name(check) }
60
+ valid_names = names(extra_checks: extra_checks)
61
+ return normalized_checks if normalized_checks.all? { |check| valid_names.include?(check) }
62
+
63
+ invalid_checks = normalized_checks.reject { |check| valid_names.include?(check) }
64
+ raise Error, unknown_check_message(invalid_checks, extra_checks: extra_checks)
65
+ end
66
+
67
+ ##
68
+ # Registered check names.
69
+ #
70
+ # @param [Array<#name>] extra_checks
71
+ #
72
+ # @return [Array<Symbol>]
73
+ def names(extra_checks: [])
74
+ all(extra_checks: extra_checks).map(&:name)
75
+ end
76
+
77
+ ##
78
+ # Default core version-maintenance surfaces for package projects.
79
+ #
80
+ # @return [Array<Symbol>]
81
+ def default_names
82
+ [:doc_references, :code_references, :package_metadata]
83
+ end
84
+
85
+ private
86
+
87
+ ##
88
+ # Core check registry.
89
+ #
90
+ # @return [Hash<Symbol, #name>]
91
+ def core_checks
92
+ @core_checks ||= {}
93
+ end
94
+
95
+ ##
96
+ # Normalizes a check name.
97
+ #
98
+ # @param [Object] name
99
+ #
100
+ # @return [Symbol, Object]
101
+ def normalize_name(name)
102
+ name.respond_to?(:to_sym) ? name.to_sym : name
103
+ end
104
+
105
+ ##
106
+ # Error message for invalid checks.
107
+ #
108
+ # @param [Array<Symbol>] invalid_checks
109
+ # @param [Array<#name>] extra_checks
110
+ #
111
+ # @return [String]
112
+ def unknown_check_message(invalid_checks, extra_checks:)
113
+ valid_check_names = names(extra_checks: extra_checks).map(&:inspect)
114
+ valid_checks = "#{valid_check_names[0...-1].join(", ")}, or #{valid_check_names.last}"
115
+ "Unknown version check #{invalid_checks.map(&:inspect).join(", ")}. Use #{valid_checks}."
116
+ end
117
+ end
118
+
119
+ ##
120
+ # Base behavior for check adapters. Public check objects must expose name,
121
+ # task_name, descriptions, labels, findings, and fix.
122
+ class Check
123
+ ##
124
+ # Whether focused task accepts an exact target version argument.
125
+ #
126
+ # @return [Boolean]
127
+ def targetable?
128
+ false
129
+ end
130
+
131
+ ##
132
+ # Whether findings should print a note when an exact target is already current.
133
+ #
134
+ # @return [Boolean]
135
+ def exact_target_fix_noop_notice?
136
+ false
137
+ end
138
+
139
+ ##
140
+ # Rake task argument names for the focused task.
141
+ #
142
+ # @return [Array<Symbol>]
143
+ def task_arguments
144
+ targetable? ? [:version] : []
145
+ end
146
+
147
+ ##
148
+ # Label used when reporting replacement counts.
149
+ #
150
+ # @return [String]
151
+ def fix_label
152
+ finding_label
153
+ end
154
+
155
+ ##
156
+ # Finds mismatches.
157
+ #
158
+ # @return [Array]
159
+ def findings(_configuration, _current_version, include_ignored: false, target_version: nil)
160
+ raise NotImplementedError
161
+ end
162
+
163
+ ##
164
+ # Fixes mismatches.
165
+ #
166
+ # @return [#changed_files, #replacement_count]
167
+ def fix(_configuration, _current_version, target_version: nil)
168
+ raise NotImplementedError
169
+ end
170
+ end
171
+
172
+ ##
173
+ # Documentation/reference version check.
174
+ class DocReferences < Check
175
+ ##
176
+ # Initializes the check.
177
+ #
178
+ # @param [Class] scanner
179
+ #
180
+ # @return [Semverve::VersionChecks::DocReferences]
181
+ def initialize(scanner: VersionReferences)
182
+ @scanner = scanner
183
+ end
184
+
185
+ ##
186
+ # Public check name used in +config.version_checks+.
187
+ #
188
+ # @return [Symbol]
189
+ def name
190
+ :doc_references
191
+ end
192
+
193
+ ##
194
+ # Focused Rake task suffix.
195
+ #
196
+ # @return [Symbol]
197
+ def task_name
198
+ :references
199
+ end
200
+
201
+ ##
202
+ # Focused check task description.
203
+ #
204
+ # @return [String]
205
+ def check_description
206
+ "Check configured files for stale version references"
207
+ end
208
+
209
+ ##
210
+ # Focused fix task description.
211
+ #
212
+ # @return [String]
213
+ def fix_description
214
+ "Replace stale version references in configured files"
215
+ end
216
+
217
+ ##
218
+ # Label printed for findings and replacement counts.
219
+ #
220
+ # @return [String]
221
+ def finding_label
222
+ "version reference"
223
+ end
224
+
225
+ ##
226
+ # Message printed when no findings or replacements exist.
227
+ #
228
+ # @return [String]
229
+ def clean_message
230
+ "Version references are current."
231
+ end
232
+
233
+ def targetable?
234
+ true
235
+ end
236
+
237
+ def exact_target_fix_noop_notice?
238
+ true
239
+ end
240
+
241
+ def findings(configuration, current_version, include_ignored: false, target_version: nil)
242
+ scanner.new(
243
+ configuration,
244
+ current_version,
245
+ include_ignored: include_ignored,
246
+ target_version: target_version
247
+ ).findings
248
+ end
249
+
250
+ def fix(configuration, current_version, target_version: nil)
251
+ scanner.new(configuration, current_version, target_version: target_version).fix
252
+ end
253
+
254
+ private
255
+
256
+ ##
257
+ # Scanner/fixer class used by the check.
258
+ #
259
+ # @return [Class]
260
+ attr_reader :scanner
261
+ end
262
+
263
+ ##
264
+ # Code literal version check.
265
+ class CodeReferences < Check
266
+ ##
267
+ # Initializes the check.
268
+ #
269
+ # @param [Class] scanner
270
+ #
271
+ # @return [Semverve::VersionChecks::CodeReferences]
272
+ def initialize(scanner: VersionCodeReferences)
273
+ @scanner = scanner
274
+ end
275
+
276
+ ##
277
+ # Public check name used in +config.version_checks+.
278
+ #
279
+ # @return [Symbol]
280
+ def name
281
+ :code_references
282
+ end
283
+
284
+ ##
285
+ # Focused Rake task suffix.
286
+ #
287
+ # @return [Symbol]
288
+ def task_name
289
+ :code
290
+ end
291
+
292
+ ##
293
+ # Focused check task description.
294
+ #
295
+ # @return [String]
296
+ def check_description
297
+ "Check configured code files for version literals"
298
+ end
299
+
300
+ ##
301
+ # Focused fix task description.
302
+ #
303
+ # @return [String]
304
+ def fix_description
305
+ "Replace safe code version literals in configured files"
306
+ end
307
+
308
+ ##
309
+ # Label printed for findings and replacement counts.
310
+ #
311
+ # @return [String]
312
+ def finding_label
313
+ "code version literal"
314
+ end
315
+
316
+ ##
317
+ # Message printed when no findings or replacements exist.
318
+ #
319
+ # @return [String]
320
+ def clean_message
321
+ "Code version literals are current."
322
+ end
323
+
324
+ def targetable?
325
+ true
326
+ end
327
+
328
+ def exact_target_fix_noop_notice?
329
+ true
330
+ end
331
+
332
+ def findings(configuration, current_version, include_ignored: false, target_version: nil)
333
+ scanner.new(
334
+ configuration,
335
+ current_version,
336
+ include_ignored: include_ignored,
337
+ target_version: target_version
338
+ ).findings
339
+ end
340
+
341
+ def fix(configuration, current_version, target_version: nil)
342
+ scanner.new(configuration, current_version, target_version: target_version).fix
343
+ end
344
+
345
+ private
346
+
347
+ ##
348
+ # Scanner/fixer class used by the check.
349
+ #
350
+ # @return [Class]
351
+ attr_reader :scanner
352
+ end
353
+
354
+ ##
355
+ # Package metadata version check.
356
+ class PackageMetadataCheck < Check
357
+ ##
358
+ # Initializes the check.
359
+ #
360
+ # @param [Class] scanner
361
+ #
362
+ # @return [Semverve::VersionChecks::PackageMetadataCheck]
363
+ def initialize(scanner: PackageMetadata)
364
+ @scanner = scanner
365
+ end
366
+
367
+ ##
368
+ # Public check name used in +config.version_checks+.
369
+ #
370
+ # @return [Symbol]
371
+ def name
372
+ :package_metadata
373
+ end
374
+
375
+ ##
376
+ # Focused Rake task suffix.
377
+ #
378
+ # @return [Symbol]
379
+ def task_name
380
+ :package_metadata
381
+ end
382
+
383
+ ##
384
+ # Focused check task description.
385
+ #
386
+ # @return [String]
387
+ def check_description
388
+ "Check package metadata for version mismatches"
389
+ end
390
+
391
+ ##
392
+ # Focused fix task description.
393
+ #
394
+ # @return [String]
395
+ def fix_description
396
+ "Fix safe package metadata version mismatches"
397
+ end
398
+
399
+ ##
400
+ # Package findings provide their own labels.
401
+ #
402
+ # @return [nil]
403
+ def finding_label
404
+ nil
405
+ end
406
+
407
+ def fix_label
408
+ "package metadata version"
409
+ end
410
+
411
+ ##
412
+ # Message printed when no findings or replacements exist.
413
+ #
414
+ # @return [String]
415
+ def clean_message
416
+ "Package metadata is current."
417
+ end
418
+
419
+ def findings(configuration, current_version, include_ignored: false, target_version: nil)
420
+ scanner.new(configuration, current_version).findings
421
+ end
422
+
423
+ def fix(configuration, current_version, target_version: nil)
424
+ scanner.new(configuration, current_version).fix
425
+ end
426
+
427
+ private
428
+
429
+ ##
430
+ # Scanner/fixer class used by the check.
431
+ #
432
+ # @return [Class]
433
+ attr_reader :scanner
434
+ end
435
+
436
+ ##
437
+ # Rails config metadata version check.
438
+ class RailsConfigMetadataCheck < Check
439
+ ##
440
+ # Initializes the check.
441
+ #
442
+ # @param [Class] scanner
443
+ #
444
+ # @return [Semverve::VersionChecks::RailsConfigMetadataCheck]
445
+ def initialize(scanner: RailsConfigMetadata)
446
+ @scanner = scanner
447
+ end
448
+
449
+ ##
450
+ # Public check name used in +config.version_checks+.
451
+ #
452
+ # @return [Symbol]
453
+ def name
454
+ :rails_config_metadata
455
+ end
456
+
457
+ ##
458
+ # Focused Rake task suffix.
459
+ #
460
+ # @return [Symbol]
461
+ def task_name
462
+ :rails_config_metadata
463
+ end
464
+
465
+ ##
466
+ # Focused check task description.
467
+ #
468
+ # @return [String]
469
+ def check_description
470
+ "Check Rails config metadata for version mismatches"
471
+ end
472
+
473
+ ##
474
+ # Focused fix task description.
475
+ #
476
+ # @return [String]
477
+ def fix_description
478
+ "Fix safe Rails config metadata version mismatches"
479
+ end
480
+
481
+ ##
482
+ # Label printed for Rails config metadata findings and replacements.
483
+ #
484
+ # @return [String]
485
+ def finding_label
486
+ "Rails config version"
487
+ end
488
+
489
+ ##
490
+ # Message printed when no findings or replacements exist.
491
+ #
492
+ # @return [String]
493
+ def clean_message
494
+ "Rails config metadata is current."
495
+ end
496
+
497
+ def findings(configuration, current_version, include_ignored: false, target_version: nil)
498
+ scanner.new(configuration, current_version).findings
499
+ end
500
+
501
+ def fix(configuration, current_version, target_version: nil)
502
+ scanner.new(configuration, current_version).fix
503
+ end
504
+
505
+ private
506
+
507
+ ##
508
+ # Scanner/fixer class used by the check.
509
+ #
510
+ # @return [Class]
511
+ attr_reader :scanner
512
+ end
513
+ end
514
+ end
515
+
516
+ Semverve::VersionChecks.register(Semverve::VersionChecks::DocReferences.new)
517
+ Semverve::VersionChecks.register(Semverve::VersionChecks::CodeReferences.new)
518
+ Semverve::VersionChecks.register(Semverve::VersionChecks::PackageMetadataCheck.new)