Class: QueryPackwerk::Violations

Inherits:
Object
  • Object
show all
Extended by:
T::Generic, T::Sig
Includes:
Enumerable, QueryInterface
Defined in:
lib/query_packwerk/violations.rb

Overview

A collection class for managing and querying sets of Packwerk violations. Provides aggregation, filtering, and analysis methods for violation data, including source extraction, contextual reporting, and consumer relationship mapping. Implements Enumerable and QueryInterface for flexible data manipulation.

Constant Summary collapse

Elem =
type_member { { fixed: QueryPackwerk::Violation } }

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from QueryInterface

#each, included, #size

Constructor Details

#initialize(original_collection, file_cache: QueryPackwerk::FileCache.new) ⇒ Violations

Returns a new instance of Violations.



70
71
72
73
74
75
76
77
78
79
80
# File 'lib/query_packwerk/violations.rb', line 70

def initialize(original_collection, file_cache: QueryPackwerk::FileCache.new)
  @original_collection = original_collection
  @file_cache = T.let(file_cache, QueryPackwerk::FileCache)

  @original_collection.each do |violation|
    violation.set_cache!(file_cache)
  end

  @cache_loaded = T.let(false, T::Boolean)
  @sources_loaded = T.let(false, T::Boolean)
end

Instance Attribute Details

#original_collectionObject (readonly)

Returns the value of attribute original_collection.



21
22
23
# File 'lib/query_packwerk/violations.rb', line 21

def original_collection
  @original_collection
end

Class Method Details

.allObject



32
33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/query_packwerk/violations.rb', line 32

def all
  return @all if @all

  violations = ParsePackwerk.all.flat_map do |pack|
    pack.violations.map do |violation|
      QueryPackwerk::Violation.new(
        original_violation: violation,
        consuming_pack: pack
      )
    end
  end

  @all = QueryPackwerk::Violations.new(violations)
end

.reload!Object



59
60
61
# File 'lib/query_packwerk/violations.rb', line 59

def reload!
  @all = nil
end

.where(**query_params, &query_fn) ⇒ Object



54
55
56
# File 'lib/query_packwerk/violations.rb', line 54

def where(**query_params, &query_fn)
  QueryPackwerk::Violations.new(super(**query_params, &query_fn))
end

Instance Method Details

#anonymous_source_counts(threshold: 0) ⇒ Object



214
215
216
217
218
# File 'lib/query_packwerk/violations.rb', line 214

def anonymous_source_counts(threshold: 0)
  load_sources!

  deep_merge_counts(@original_collection, threshold:) { |v| [v.class_name, v.anonymous_source_counts] }
end

#anonymous_sourcesObject



166
167
168
169
170
# File 'lib/query_packwerk/violations.rb', line 166

def anonymous_sources
  load_sources!

  deep_merge_groups(@original_collection) { |v| [v.class_name, v.anonymous_sources] }.transform_values(&:uniq)
end

#anonymous_sources_with_locationsObject



173
174
175
176
177
# File 'lib/query_packwerk/violations.rb', line 173

def anonymous_sources_with_locations
  load_sources!

  deep_merge_hash_groups(@original_collection) { |v| [v.class_name, v.anonymous_sources_with_locations] }
end

#consumers(threshold: 0) ⇒ Object



222
223
224
225
# File 'lib/query_packwerk/violations.rb', line 222

def consumers(threshold: 0)
  tallies = @original_collection.map { |v| v.consuming_pack.name }.tally
  threshold_filter_sort(tallies, threshold:)
end

#excluding_files(*file_globs) ⇒ Object



246
247
248
249
250
251
252
# File 'lib/query_packwerk/violations.rb', line 246

def excluding_files(*file_globs)
  filtered_violations = @original_collection.reject do |violation|
    T.unsafe(violation).includes_files?(*file_globs) # Sorbet hates splats
  end

  QueryPackwerk::Violations.new(filtered_violations)
end

#file_countObject



120
121
122
# File 'lib/query_packwerk/violations.rb', line 120

def file_count
  @original_collection.sum(&:file_count)
end

#including_files(*file_globs) ⇒ Object



236
237
238
239
240
241
242
# File 'lib/query_packwerk/violations.rb', line 236

def including_files(*file_globs)
  filtered_violations = @original_collection.select do |violation|
    T.unsafe(violation).includes_files?(*file_globs) # Sorbet hates splats
  end

  QueryPackwerk::Violations.new(filtered_violations)
end

#inspectObject



255
256
257
258
259
260
261
# File 'lib/query_packwerk/violations.rb', line 255

def inspect
  [
    "#<#{self.class.name} [",
    to_a.map(&:inspect).join("\n"),
    ']>'
  ].join("\n")
end

#load_cache!Object



83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/query_packwerk/violations.rb', line 83

def load_cache!
  return true if @cache_loaded

  warn "Prepopulating AST cache with #{file_count} files: "
  start_time = Time.now

  @original_collection.each(&:load_cache!)

  finish_time = Time.now - start_time
  warn '', "AST cache loaded in #{finish_time}"
  @cache_loaded = true
end

#load_sources!Object



97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/query_packwerk/violations.rb', line 97

def load_sources!
  return true if @sources_loaded

  unless @cache_loaded
    load_cache!
    warn
  end

  warn "Prepopulating sources cache with #{count} violations: "
  start_time = Time.now

  total_sources_loaded = @original_collection.sum do |violation|
    $stderr.print '.'
    violation.sources.size
  end

  finish_time = Time.now - start_time
  warn "Loaded #{total_sources_loaded} full sources in #{finish_time}"

  @sources_loaded = true
end

#producers(threshold: 0) ⇒ Object



229
230
231
232
# File 'lib/query_packwerk/violations.rb', line 229

def producers(threshold: 0)
  tallies = @original_collection.map { |v| v.producing_pack.name }.tally
  threshold_filter_sort(tallies, threshold:)
end

#raw_sourcesObject



126
127
128
129
130
131
132
# File 'lib/query_packwerk/violations.rb', line 126

def raw_sources
  load_sources!

  deep_merge_groups(@original_collection) do |v|
    [v.class_name, v.sources]
  end
end

#source_counts(threshold: 0) ⇒ Object



154
155
156
157
158
# File 'lib/query_packwerk/violations.rb', line 154

def source_counts(threshold: 0)
  load_sources!

  deep_merge_counts(@original_collection, threshold:) { |v| [v.class_name, v.source_counts] }
end

#sourcesObject



136
137
138
139
140
# File 'lib/query_packwerk/violations.rb', line 136

def sources
  load_sources!

  deep_merge_groups(@original_collection) { |v| [v.class_name, v.sources.map(&:source)] }.transform_values(&:uniq)
end

#sources_with_contexts(start_offset: 3, end_offset: 3) ⇒ Object



182
183
184
185
186
# File 'lib/query_packwerk/violations.rb', line 182

def sources_with_contexts(start_offset: 3, end_offset: 3)
  load_sources!

  deep_merge_hash_groups(@original_collection) { |v| [v.class_name, v.sources_with_contexts] }
end

#sources_with_contexts_report(start_offset: 3, end_offset: 3) ⇒ Object



189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
# File 'lib/query_packwerk/violations.rb', line 189

def sources_with_contexts_report(start_offset: 3, end_offset: 3)
  contexts = sources_with_contexts(start_offset:, end_offset:)
  output = +''

  contexts.each do |violated_constant, anonymized_sources|
    heavy_underline = '=' * violated_constant.size
    output << "#{violated_constant}\n#{heavy_underline}\n\n"

    anonymized_sources.each do |anonymized_source, full_contexts|
      light_underline = '-' * anonymized_source.size
      output << "#{anonymized_source}\n#{light_underline}\n\n"

      full_contexts.each do |context|
        output << highlight_ruby(context)
        output << "\n\n"
      end
    end
  end

  output
end

#sources_with_locationsObject



145
146
147
148
149
# File 'lib/query_packwerk/violations.rb', line 145

def sources_with_locations
  load_sources!

  deep_merge_groups(@original_collection) { |v| [v.class_name, v.sources_with_locations] }
end