rspec-let-analyzer 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/README.md +183 -0
- data/bin/rspec-let-analyzer +117 -0
- data/lib/rspec_let_analyzer/adapter.rb +48 -0
- data/lib/rspec_let_analyzer/adapters/parser_adapter.rb +27 -0
- data/lib/rspec_let_analyzer/adapters/prism_adapter.rb +21 -0
- data/lib/rspec_let_analyzer/analyzer.rb +144 -0
- data/lib/rspec_let_analyzer/formatters/ascii_formatter.rb +128 -0
- data/lib/rspec_let_analyzer/formatters/html_formatter.rb +211 -0
- data/lib/rspec_let_analyzer/formatters/json_formatter.rb +42 -0
- data/lib/rspec_let_analyzer/formatters/llm_formatter.rb +145 -0
- data/lib/rspec_let_analyzer/progress_reporter.rb +32 -0
- data/lib/rspec_let_analyzer/version.rb +5 -0
- data/lib/rspec_let_analyzer/visitors/parser_visitor.rb +172 -0
- data/lib/rspec_let_analyzer/visitors/prism_visitor.rb +125 -0
- data/lib/rspec_let_analyzer.rb +16 -0
- metadata +103 -0
@@ -0,0 +1,125 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'prism'
|
4
|
+
|
5
|
+
module RSpecLetAnalyzer
|
6
|
+
module Visitors
|
7
|
+
class PrismVisitor < Prism::Visitor
|
8
|
+
attr_reader :root_lets, :total_its, :redefinitions, :nesting_counts, :factory_usage, :before_creates
|
9
|
+
|
10
|
+
def initialize(max_nesting_depth, track_factories)
|
11
|
+
@root_lets = 0
|
12
|
+
@total_its = 0
|
13
|
+
@redefinitions = 0
|
14
|
+
@depth = 0
|
15
|
+
@let_names_by_depth = {}
|
16
|
+
@max_nesting_depth = max_nesting_depth
|
17
|
+
@nesting_counts = Array.new(max_nesting_depth, 0) if max_nesting_depth
|
18
|
+
@track_factories = track_factories
|
19
|
+
@factory_usage = Hash.new(0) if track_factories
|
20
|
+
@before_creates = 0
|
21
|
+
@in_before_block = false
|
22
|
+
end
|
23
|
+
|
24
|
+
def visit_call_node(node)
|
25
|
+
method_name = node.name
|
26
|
+
|
27
|
+
case method_name
|
28
|
+
when :describe, :context, :shared_examples, :shared_context
|
29
|
+
@depth += 1
|
30
|
+
super
|
31
|
+
@depth -= 1
|
32
|
+
# Clean up let names at this depth when exiting
|
33
|
+
@let_names_by_depth.delete(@depth + 1)
|
34
|
+
when :before
|
35
|
+
# Check if this is before or before(:each)
|
36
|
+
arg = node.arguments&.arguments&.first
|
37
|
+
is_before_each = arg.nil? || (arg.is_a?(Prism::SymbolNode) && arg.unescaped == 'each')
|
38
|
+
|
39
|
+
if is_before_each
|
40
|
+
@in_before_block = true
|
41
|
+
super
|
42
|
+
@in_before_block = false
|
43
|
+
else
|
44
|
+
super
|
45
|
+
end
|
46
|
+
when :let, :let!
|
47
|
+
# Get the let variable name
|
48
|
+
let_name = extract_let_name(node)
|
49
|
+
|
50
|
+
if let_name
|
51
|
+
# Check if this let redefines one from an outer scope
|
52
|
+
@redefinitions += 1 if redefinition?(let_name)
|
53
|
+
|
54
|
+
# Track this let name at current depth
|
55
|
+
@let_names_by_depth[@depth] ||= []
|
56
|
+
@let_names_by_depth[@depth] << let_name
|
57
|
+
end
|
58
|
+
|
59
|
+
# Count by nesting depth
|
60
|
+
if @depth == 1
|
61
|
+
@root_lets += 1
|
62
|
+
elsif @max_nesting_depth
|
63
|
+
# Track nesting counts: depth 2 -> index 0, depth 3 -> index 1, etc.
|
64
|
+
nesting_index = @depth - 2
|
65
|
+
if nesting_index < @max_nesting_depth - 1
|
66
|
+
@nesting_counts[nesting_index] += 1
|
67
|
+
else
|
68
|
+
# Everything at max depth or beyond goes into the last bucket
|
69
|
+
@nesting_counts[@max_nesting_depth - 1] += 1
|
70
|
+
end
|
71
|
+
end
|
72
|
+
super
|
73
|
+
when :it, :specify
|
74
|
+
@total_its += 1
|
75
|
+
super
|
76
|
+
when :create, :build, :build_stubbed
|
77
|
+
# Track if create is inside a before block
|
78
|
+
if @in_before_block && method_name == :create
|
79
|
+
@before_creates += 1
|
80
|
+
end
|
81
|
+
|
82
|
+
# Track FactoryBot usage
|
83
|
+
if @track_factories
|
84
|
+
factory_name = extract_factory_name(node)
|
85
|
+
@factory_usage[factory_name] += 1 if factory_name
|
86
|
+
end
|
87
|
+
super
|
88
|
+
else
|
89
|
+
super
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
private
|
94
|
+
|
95
|
+
def extract_let_name(node)
|
96
|
+
# The first argument to let() is typically a symbol representing the variable name
|
97
|
+
return nil unless node.arguments&.arguments&.any?
|
98
|
+
|
99
|
+
first_arg = node.arguments.arguments.first
|
100
|
+
case first_arg
|
101
|
+
when Prism::SymbolNode
|
102
|
+
first_arg.unescaped
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def extract_factory_name(node)
|
107
|
+
# Handle both create(:factory) and FactoryBot.create(:factory)
|
108
|
+
return nil unless node.arguments&.arguments&.any?
|
109
|
+
|
110
|
+
first_arg = node.arguments.arguments.first
|
111
|
+
case first_arg
|
112
|
+
when Prism::SymbolNode
|
113
|
+
first_arg.unescaped.to_sym
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def redefinition?(let_name)
|
118
|
+
# Check all outer scopes (lower depths) for this let name
|
119
|
+
(1...@depth).any? do |outer_depth|
|
120
|
+
@let_names_by_depth[outer_depth]&.include?(let_name)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'prism'
|
4
|
+
require_relative 'rspec_let_analyzer/version'
|
5
|
+
require_relative 'rspec_let_analyzer/adapter'
|
6
|
+
require_relative 'rspec_let_analyzer/visitors/prism_visitor'
|
7
|
+
require_relative 'rspec_let_analyzer/visitors/parser_visitor'
|
8
|
+
require_relative 'rspec_let_analyzer/analyzer'
|
9
|
+
require_relative 'rspec_let_analyzer/progress_reporter'
|
10
|
+
require_relative 'rspec_let_analyzer/formatters/ascii_formatter'
|
11
|
+
require_relative 'rspec_let_analyzer/formatters/json_formatter'
|
12
|
+
require_relative 'rspec_let_analyzer/formatters/html_formatter'
|
13
|
+
require_relative 'rspec_let_analyzer/formatters/llm_formatter'
|
14
|
+
|
15
|
+
module RSpecLetAnalyzer
|
16
|
+
end
|
metadata
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rspec-let-analyzer
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- James Cook
|
8
|
+
bindir: bin
|
9
|
+
cert_chain: []
|
10
|
+
date: 2025-10-08 00:00:00.000000000 Z
|
11
|
+
dependencies:
|
12
|
+
- !ruby/object:Gem::Dependency
|
13
|
+
name: prism
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
15
|
+
requirements:
|
16
|
+
- - "~>"
|
17
|
+
- !ruby/object:Gem::Version
|
18
|
+
version: '0.19'
|
19
|
+
type: :runtime
|
20
|
+
prerelease: false
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
22
|
+
requirements:
|
23
|
+
- - "~>"
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: '0.19'
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: rake
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
29
|
+
requirements:
|
30
|
+
- - "~>"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '13.0'
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '13.0'
|
40
|
+
- !ruby/object:Gem::Dependency
|
41
|
+
name: rspec
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - "~>"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '3.12'
|
47
|
+
type: :development
|
48
|
+
prerelease: false
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '3.12'
|
54
|
+
description: A tool that parses RSpec test files to identify let/let! declarations
|
55
|
+
that could be refactored to use test-prof's let_it_be helper for improved test performance
|
56
|
+
email:
|
57
|
+
- ''
|
58
|
+
executables:
|
59
|
+
- rspec-let-analyzer
|
60
|
+
extensions: []
|
61
|
+
extra_rdoc_files: []
|
62
|
+
files:
|
63
|
+
- README.md
|
64
|
+
- bin/rspec-let-analyzer
|
65
|
+
- lib/rspec_let_analyzer.rb
|
66
|
+
- lib/rspec_let_analyzer/adapter.rb
|
67
|
+
- lib/rspec_let_analyzer/adapters/parser_adapter.rb
|
68
|
+
- lib/rspec_let_analyzer/adapters/prism_adapter.rb
|
69
|
+
- lib/rspec_let_analyzer/analyzer.rb
|
70
|
+
- lib/rspec_let_analyzer/formatters/ascii_formatter.rb
|
71
|
+
- lib/rspec_let_analyzer/formatters/html_formatter.rb
|
72
|
+
- lib/rspec_let_analyzer/formatters/json_formatter.rb
|
73
|
+
- lib/rspec_let_analyzer/formatters/llm_formatter.rb
|
74
|
+
- lib/rspec_let_analyzer/progress_reporter.rb
|
75
|
+
- lib/rspec_let_analyzer/version.rb
|
76
|
+
- lib/rspec_let_analyzer/visitors/parser_visitor.rb
|
77
|
+
- lib/rspec_let_analyzer/visitors/prism_visitor.rb
|
78
|
+
homepage: https://github.com/jamescook/rspec-let-analyzer
|
79
|
+
licenses:
|
80
|
+
- MIT
|
81
|
+
metadata:
|
82
|
+
homepage_uri: https://github.com/jamescook/rspec-let-analyzer
|
83
|
+
source_code_uri: https://github.com/jamescook/rspec-let-analyzer
|
84
|
+
rubygems_mfa_required: 'true'
|
85
|
+
rdoc_options: []
|
86
|
+
require_paths:
|
87
|
+
- lib
|
88
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
89
|
+
requirements:
|
90
|
+
- - ">="
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: 2.7.0
|
93
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
94
|
+
requirements:
|
95
|
+
- - ">="
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '0'
|
98
|
+
requirements: []
|
99
|
+
rubygems_version: 3.6.2
|
100
|
+
specification_version: 4
|
101
|
+
summary: Analyze RSpec let declarations to identify refactoring opportunities for
|
102
|
+
let_it_be
|
103
|
+
test_files: []
|