Class: Guardrails::CrossCodebasePatterns
- Inherits:
-
Object
- Object
- Guardrails::CrossCodebasePatterns
- Defined in:
- lib/guardrails/cross_codebase_patterns.rb
Overview
Finds recurring structural patterns across the codebase — element subtrees that appear in 3+ places and could be extracted into a shared partial or ViewComponent.
Distinct from PartialSimilarity: that one compares EXISTING partials against each other (“are these two partials near-duplicates?”). CrossCodebasePatterns looks at the structural shape of any subtree in any view (“this 8-element shape appears in 12 places, only one of which is a partial — refactor candidate”).
Defined Under Namespace
Classes: Occurrence, Pattern
Constant Summary collapse
- DEFAULT_MIN_SIZE =
Minimum number of element nodes in a subtree before we consider it. Below this, the structural shape is too generic to be a refactor candidate — ‘<div>` alone, or `<span><a></a></span>`, would match constantly. A useful pattern starts around 5 elements (card body, form row, table cell with controls, etc.).
5- DEFAULT_MIN_OCCURRENCES =
Subtree fingerprint must appear at least this many times to surface. 2 occurrences are common and rarely actionable; 3+ implies a real repeated shape.
3- DEFAULT_MAX_OCCURRENCES_SHOWN =
Max occurrences printed per pattern before we elide the rest.
10- VIEW_PATTERNS =
[ "app/views/**/*.html.erb", "app/components/**/*.html.erb" ].freeze
- IMPLICIT_IGNORE_SEGMENTS =
%w[vendor node_modules tmp public log].freeze
- IMPLICIT_IGNORE_PATTERNS =
[/\A(?:\w+_)?mailer\z/].freeze
Instance Method Summary collapse
- #find_patterns ⇒ Object
-
#initialize(root:, output: $stdout, min_size: DEFAULT_MIN_SIZE, min_occurrences: DEFAULT_MIN_OCCURRENCES, max_occurrences_shown: DEFAULT_MAX_OCCURRENCES_SHOWN, style: nil) ⇒ CrossCodebasePatterns
constructor
A new instance of CrossCodebasePatterns.
- #run ⇒ Object
Constructor Details
#initialize(root:, output: $stdout, min_size: DEFAULT_MIN_SIZE, min_occurrences: DEFAULT_MIN_OCCURRENCES, max_occurrences_shown: DEFAULT_MAX_OCCURRENCES_SHOWN, style: nil) ⇒ CrossCodebasePatterns
Returns a new instance of CrossCodebasePatterns.
51 52 53 54 55 56 57 58 59 60 61 62 |
# File 'lib/guardrails/cross_codebase_patterns.rb', line 51 def initialize(root:, output: $stdout, min_size: DEFAULT_MIN_SIZE, min_occurrences: DEFAULT_MIN_OCCURRENCES, max_occurrences_shown: DEFAULT_MAX_OCCURRENCES_SHOWN, style: nil) @root = Pathname(root) @output = output @min_size = min_size @min_occurrences = min_occurrences @max_occurrences_shown = max_occurrences_shown @style = style || Report::Style.new(io: output) end |
Instance Method Details
#find_patterns ⇒ Object
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 |
# File 'lib/guardrails/cross_codebase_patterns.rb', line 70 def find_patterns occurrences = Hash.new { |h, k| h[k] = [] } shapes = {} view_files.each do |file| content = File.read(file, encoding: Encoding::UTF_8) result = ErbParser.parse(content) relative = file.relative_path_from(@root).to_s walk_subtrees(result.document) do |node, fingerprint, shape, size| next if size < @min_size line, column = ErbParser.start_position(node) occurrences[fingerprint] << Occurrence.new( file: relative, line: line, column: column, size: size ) shapes[fingerprint] ||= shape end end patterns = occurrences .select { |_, occs| occs.size >= @min_occurrences } .map { |fp, occs| Pattern.new(fingerprint: fp, shape: shapes[fp], size: occs.first.size, occurrences: occs) } .sort_by { |p| [-p.count, -p.size] } dedupe_nested(patterns) end |
#run ⇒ Object
64 65 66 67 68 |
# File 'lib/guardrails/cross_codebase_patterns.rb', line 64 def run patterns = find_patterns print_report(patterns) patterns end |