rails_code_health 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/CHANGELOG.md +30 -0
- data/LICENSE.txt +21 -0
- data/README.md +237 -0
- data/bin/rails-health +5 -0
- data/config/tresholds.json +80 -0
- data/lib/rails_code_health/cli.rb +164 -0
- data/lib/rails_code_health/configuration.rb +89 -0
- data/lib/rails_code_health/file_analyzer.rb +117 -0
- data/lib/rails_code_health/health_calculator.rb +370 -0
- data/lib/rails_code_health/project_detector.rb +74 -0
- data/lib/rails_code_health/rails_analyzer.rb +391 -0
- data/lib/rails_code_health/report_generator.rb +335 -0
- data/lib/rails_code_health/ruby_analyzer.rb +319 -0
- data/lib/rails_code_health/version.rb +3 -0
- data/lib/rails_code_health.rb +46 -0
- metadata +186 -0
@@ -0,0 +1,117 @@
|
|
1
|
+
module RailsCodeHealth
|
2
|
+
class FileAnalyzer
|
3
|
+
def initialize(project_path)
|
4
|
+
@project_path = Pathname.new(project_path)
|
5
|
+
end
|
6
|
+
|
7
|
+
def analyze_all
|
8
|
+
ruby_files = find_ruby_files
|
9
|
+
view_files = find_view_files
|
10
|
+
|
11
|
+
all_files = ruby_files + view_files
|
12
|
+
|
13
|
+
all_files.map do |file_path|
|
14
|
+
analyze_file(file_path)
|
15
|
+
end.compact
|
16
|
+
end
|
17
|
+
|
18
|
+
def analyze_file(file_path)
|
19
|
+
file_path = Pathname.new(file_path) unless file_path.is_a?(Pathname)
|
20
|
+
|
21
|
+
return nil unless file_path.exist?
|
22
|
+
|
23
|
+
file_type = ProjectDetector.detect_file_type(file_path, @project_path)
|
24
|
+
return nil unless file_type
|
25
|
+
|
26
|
+
result = {
|
27
|
+
file_path: file_path.to_s,
|
28
|
+
relative_path: file_path.relative_path_from(@project_path).to_s,
|
29
|
+
file_type: file_type,
|
30
|
+
file_size: file_path.size,
|
31
|
+
last_modified: file_path.mtime
|
32
|
+
}
|
33
|
+
|
34
|
+
# Analyze Ruby code if it's a Ruby file
|
35
|
+
if file_path.extname == '.rb'
|
36
|
+
ruby_analyzer = RubyAnalyzer.new(file_path)
|
37
|
+
result[:ruby_analysis] = ruby_analyzer.analyze
|
38
|
+
end
|
39
|
+
|
40
|
+
# Add Rails-specific analysis
|
41
|
+
rails_analyzer = RailsAnalyzer.new(file_path, file_type)
|
42
|
+
rails_analysis = rails_analyzer.analyze
|
43
|
+
result[:rails_analysis] = rails_analysis unless rails_analysis.empty?
|
44
|
+
|
45
|
+
result
|
46
|
+
rescue => e
|
47
|
+
{
|
48
|
+
file_path: file_path.to_s,
|
49
|
+
relative_path: file_path.relative_path_from(@project_path).to_s,
|
50
|
+
file_type: file_type,
|
51
|
+
error: "Analysis failed: #{e.message}"
|
52
|
+
}
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def find_ruby_files
|
58
|
+
ruby_patterns = [
|
59
|
+
@project_path + 'app/**/*.rb',
|
60
|
+
@project_path + 'lib/**/*.rb',
|
61
|
+
@project_path + 'config/**/*.rb',
|
62
|
+
@project_path + 'db/migrate/*.rb'
|
63
|
+
]
|
64
|
+
|
65
|
+
files = []
|
66
|
+
ruby_patterns.each do |pattern|
|
67
|
+
files.concat(Dir.glob(pattern))
|
68
|
+
end
|
69
|
+
|
70
|
+
# Filter out files we don't want to analyze
|
71
|
+
files.reject! do |file|
|
72
|
+
relative_path = Pathname.new(file).relative_path_from(@project_path).to_s
|
73
|
+
should_skip_file?(relative_path)
|
74
|
+
end
|
75
|
+
|
76
|
+
files.map { |f| Pathname.new(f) }
|
77
|
+
end
|
78
|
+
|
79
|
+
def find_view_files
|
80
|
+
view_patterns = [
|
81
|
+
@project_path + 'app/views/**/*.erb',
|
82
|
+
@project_path + 'app/views/**/*.haml',
|
83
|
+
@project_path + 'app/views/**/*.slim'
|
84
|
+
]
|
85
|
+
|
86
|
+
files = []
|
87
|
+
view_patterns.each do |pattern|
|
88
|
+
files.concat(Dir.glob(pattern))
|
89
|
+
end
|
90
|
+
|
91
|
+
files.map { |f| Pathname.new(f) }
|
92
|
+
end
|
93
|
+
|
94
|
+
def should_skip_file?(relative_path)
|
95
|
+
skip_patterns = [
|
96
|
+
%r{^vendor/},
|
97
|
+
%r{^tmp/},
|
98
|
+
%r{^log/},
|
99
|
+
%r{^node_modules/},
|
100
|
+
%r{^coverage/},
|
101
|
+
%r{\.git/},
|
102
|
+
%r{^public/assets/},
|
103
|
+
%r{^db/schema\.rb$},
|
104
|
+
%r{^config/routes\.rb$}, # Often auto-generated and long
|
105
|
+
%r{^config/application\.rb$}, # Framework boilerplate
|
106
|
+
%r{^config/environment\.rb$}, # Framework boilerplate
|
107
|
+
%r{^config/environments/}, # Environment configs
|
108
|
+
%r{^config/initializers/devise\.rb$}, # Often very long generated files
|
109
|
+
%r{^app/assets/},
|
110
|
+
%r{_test\.rb$},
|
111
|
+
%r{_spec\.rb$}
|
112
|
+
]
|
113
|
+
|
114
|
+
skip_patterns.any? { |pattern| relative_path.match?(pattern) }
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,370 @@
|
|
1
|
+
module RailsCodeHealth
|
2
|
+
class HealthCalculator
|
3
|
+
def initialize
|
4
|
+
@config = RailsCodeHealth.configuration
|
5
|
+
@thresholds = @config.thresholds
|
6
|
+
@weights = @thresholds['scoring_weights']
|
7
|
+
end
|
8
|
+
|
9
|
+
def calculate_scores(analysis_results)
|
10
|
+
analysis_results.map do |file_result|
|
11
|
+
health_score = calculate_file_health_score(file_result)
|
12
|
+
file_result.merge(
|
13
|
+
health_score: health_score,
|
14
|
+
health_category: categorize_health(health_score),
|
15
|
+
recommendations: generate_recommendations(file_result)
|
16
|
+
)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def calculate_file_health_score(file_result)
|
23
|
+
# Start with a perfect score of 10
|
24
|
+
base_score = 10.0
|
25
|
+
|
26
|
+
# Apply penalties based on different factors
|
27
|
+
penalties = []
|
28
|
+
|
29
|
+
# Ruby-specific penalties
|
30
|
+
penalties.concat(calculate_ruby_penalties(file_result))
|
31
|
+
|
32
|
+
# Rails-specific penalties
|
33
|
+
penalties.concat(calculate_rails_penalties(file_result))
|
34
|
+
|
35
|
+
# Code smell penalties
|
36
|
+
penalties.concat(calculate_code_smell_penalties(file_result))
|
37
|
+
|
38
|
+
# Apply file type multiplier
|
39
|
+
file_type_multiplier = get_file_type_multiplier(file_result[:file_type])
|
40
|
+
|
41
|
+
# Calculate weighted penalty
|
42
|
+
total_penalty = penalties.sum * file_type_multiplier
|
43
|
+
|
44
|
+
# Ensure score doesn't go below 1
|
45
|
+
final_score = [base_score - total_penalty, 1.0].max
|
46
|
+
|
47
|
+
final_score.round(1)
|
48
|
+
end
|
49
|
+
|
50
|
+
def calculate_ruby_penalties(file_result)
|
51
|
+
return [] unless file_result[:ruby_analysis]
|
52
|
+
|
53
|
+
penalties = []
|
54
|
+
ruby_data = file_result[:ruby_analysis]
|
55
|
+
|
56
|
+
# Method length penalties
|
57
|
+
if ruby_data[:method_metrics]
|
58
|
+
ruby_data[:method_metrics].each do |method|
|
59
|
+
penalty = calculate_method_length_penalty(method[:line_count])
|
60
|
+
penalties << penalty * @weights['method_length'] if penalty > 0
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# Class length penalties
|
65
|
+
if ruby_data[:class_metrics]
|
66
|
+
ruby_data[:class_metrics].each do |klass|
|
67
|
+
penalty = calculate_class_length_penalty(klass[:line_count])
|
68
|
+
penalties << penalty * @weights['class_length'] if penalty > 0
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Complexity penalties
|
73
|
+
if ruby_data[:method_metrics]
|
74
|
+
ruby_data[:method_metrics].each do |method|
|
75
|
+
# Cyclomatic complexity
|
76
|
+
complexity_penalty = calculate_complexity_penalty(method[:cyclomatic_complexity])
|
77
|
+
penalties << complexity_penalty * @weights['cyclomatic_complexity'] if complexity_penalty > 0
|
78
|
+
|
79
|
+
# Nesting depth
|
80
|
+
nesting_penalty = calculate_nesting_penalty(method[:nesting_depth])
|
81
|
+
penalties << nesting_penalty * @weights['nesting_depth'] if nesting_penalty > 0
|
82
|
+
|
83
|
+
# Parameter count
|
84
|
+
param_penalty = calculate_parameter_penalty(method[:parameter_count])
|
85
|
+
penalties << param_penalty * @weights['parameter_count'] if param_penalty > 0
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
penalties
|
90
|
+
end
|
91
|
+
|
92
|
+
def calculate_rails_penalties(file_result)
|
93
|
+
return [] unless file_result[:rails_analysis]
|
94
|
+
|
95
|
+
penalties = []
|
96
|
+
rails_data = file_result[:rails_analysis]
|
97
|
+
file_type = rails_data[:rails_type]
|
98
|
+
|
99
|
+
case file_type
|
100
|
+
when :controller
|
101
|
+
penalties.concat(calculate_controller_penalties(rails_data))
|
102
|
+
when :model
|
103
|
+
penalties.concat(calculate_model_penalties(rails_data))
|
104
|
+
when :view
|
105
|
+
penalties.concat(calculate_view_penalties(rails_data))
|
106
|
+
when :helper
|
107
|
+
penalties.concat(calculate_helper_penalties(rails_data))
|
108
|
+
when :migration
|
109
|
+
penalties.concat(calculate_migration_penalties(rails_data))
|
110
|
+
end
|
111
|
+
|
112
|
+
penalties
|
113
|
+
end
|
114
|
+
|
115
|
+
def calculate_controller_penalties(controller_data)
|
116
|
+
penalties = []
|
117
|
+
|
118
|
+
# Too many actions
|
119
|
+
action_count = controller_data[:action_count] || 0
|
120
|
+
if action_count > @thresholds['rails_specific']['controller_actions']['yellow']
|
121
|
+
severity = action_count > @thresholds['rails_specific']['controller_actions']['red'] ? 2.0 : 1.0
|
122
|
+
penalties << severity * @weights['rails_conventions']
|
123
|
+
end
|
124
|
+
|
125
|
+
# Missing strong parameters
|
126
|
+
unless controller_data[:uses_strong_parameters]
|
127
|
+
penalties << 1.0 * @weights['rails_conventions']
|
128
|
+
end
|
129
|
+
|
130
|
+
# Direct model access
|
131
|
+
if controller_data[:has_direct_model_access]
|
132
|
+
penalties << 1.5 * @weights['rails_conventions']
|
133
|
+
end
|
134
|
+
|
135
|
+
penalties
|
136
|
+
end
|
137
|
+
|
138
|
+
def calculate_model_penalties(model_data)
|
139
|
+
penalties = []
|
140
|
+
|
141
|
+
# Fat model
|
142
|
+
if model_data[:has_fat_model_smell]
|
143
|
+
penalties << 2.0 * @weights['rails_conventions']
|
144
|
+
end
|
145
|
+
|
146
|
+
# Missing validations
|
147
|
+
validation_count = model_data[:validation_count] || 0
|
148
|
+
if validation_count == 0
|
149
|
+
penalties << 0.5 * @weights['rails_conventions']
|
150
|
+
end
|
151
|
+
|
152
|
+
# Too many callbacks
|
153
|
+
callback_count = model_data[:callback_count] || 0
|
154
|
+
if callback_count > 5
|
155
|
+
penalties << 1.0 * @weights['rails_conventions']
|
156
|
+
end
|
157
|
+
|
158
|
+
penalties
|
159
|
+
end
|
160
|
+
|
161
|
+
def calculate_view_penalties(view_data)
|
162
|
+
penalties = []
|
163
|
+
|
164
|
+
# Long views
|
165
|
+
total_lines = view_data[:total_lines] || 0
|
166
|
+
if total_lines > @thresholds['rails_specific']['view_length']['yellow']
|
167
|
+
severity = total_lines > @thresholds['rails_specific']['view_length']['red'] ? 2.0 : 1.0
|
168
|
+
penalties << severity * @weights['rails_conventions']
|
169
|
+
end
|
170
|
+
|
171
|
+
# Logic in views
|
172
|
+
logic_lines = view_data[:logic_lines] || 0
|
173
|
+
if logic_lines > 5
|
174
|
+
penalties << (logic_lines / 5.0) * @weights['rails_conventions']
|
175
|
+
end
|
176
|
+
|
177
|
+
# Inline styles/JavaScript
|
178
|
+
if view_data[:has_inline_styles]
|
179
|
+
penalties << 0.5 * @weights['rails_conventions']
|
180
|
+
end
|
181
|
+
|
182
|
+
if view_data[:has_inline_javascript]
|
183
|
+
penalties << 1.0 * @weights['rails_conventions']
|
184
|
+
end
|
185
|
+
|
186
|
+
penalties
|
187
|
+
end
|
188
|
+
|
189
|
+
def calculate_helper_penalties(helper_data)
|
190
|
+
penalties = []
|
191
|
+
|
192
|
+
method_count = helper_data[:method_count] || 0
|
193
|
+
if method_count > 15
|
194
|
+
penalties << 1.0 * @weights['rails_conventions']
|
195
|
+
end
|
196
|
+
|
197
|
+
penalties
|
198
|
+
end
|
199
|
+
|
200
|
+
def calculate_migration_penalties(migration_data)
|
201
|
+
penalties = []
|
202
|
+
|
203
|
+
if migration_data[:has_data_changes]
|
204
|
+
penalties << 2.0 * @weights['rails_conventions']
|
205
|
+
end
|
206
|
+
|
207
|
+
complexity = migration_data[:complexity_score] || 0
|
208
|
+
if complexity > 20
|
209
|
+
penalties << 1.0 * @weights['rails_conventions']
|
210
|
+
end
|
211
|
+
|
212
|
+
penalties
|
213
|
+
end
|
214
|
+
|
215
|
+
def calculate_code_smell_penalties(file_result)
|
216
|
+
penalties = []
|
217
|
+
|
218
|
+
# Ruby code smells
|
219
|
+
if file_result[:ruby_analysis] && file_result[:ruby_analysis][:code_smells]
|
220
|
+
file_result[:ruby_analysis][:code_smells].each do |smell|
|
221
|
+
penalty = case smell[:severity]
|
222
|
+
when :high then 2.0
|
223
|
+
when :medium then 1.0
|
224
|
+
when :low then 0.5
|
225
|
+
else 0.5
|
226
|
+
end
|
227
|
+
penalties << penalty * @weights['code_smells']
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
# Rails code smells
|
232
|
+
if file_result[:rails_analysis] && file_result[:rails_analysis][:rails_smells]
|
233
|
+
file_result[:rails_analysis][:rails_smells].each do |smell|
|
234
|
+
penalty = case smell[:severity]
|
235
|
+
when :high then 2.0
|
236
|
+
when :medium then 1.0
|
237
|
+
when :low then 0.5
|
238
|
+
else 0.5
|
239
|
+
end
|
240
|
+
penalties << penalty * @weights['code_smells']
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
penalties
|
245
|
+
end
|
246
|
+
|
247
|
+
# Individual penalty calculation methods
|
248
|
+
def calculate_method_length_penalty(line_count)
|
249
|
+
thresholds = @thresholds['ruby_thresholds']['method_length']
|
250
|
+
return 0 if line_count <= thresholds['green']
|
251
|
+
return 1.0 if line_count <= thresholds['yellow']
|
252
|
+
return 2.0 if line_count <= thresholds['red']
|
253
|
+
3.0 # Extremely long methods
|
254
|
+
end
|
255
|
+
|
256
|
+
def calculate_class_length_penalty(line_count)
|
257
|
+
thresholds = @thresholds['ruby_thresholds']['class_length']
|
258
|
+
return 0 if line_count <= thresholds['green']
|
259
|
+
return 1.0 if line_count <= thresholds['yellow']
|
260
|
+
return 2.0 if line_count <= thresholds['red']
|
261
|
+
3.0 # Extremely long classes
|
262
|
+
end
|
263
|
+
|
264
|
+
def calculate_complexity_penalty(complexity)
|
265
|
+
thresholds = @thresholds['ruby_thresholds']['cyclomatic_complexity']
|
266
|
+
return 0 if complexity <= thresholds['green']
|
267
|
+
return 1.0 if complexity <= thresholds['yellow']
|
268
|
+
return 2.0 if complexity <= thresholds['red']
|
269
|
+
3.0 # Extremely complex methods
|
270
|
+
end
|
271
|
+
|
272
|
+
def calculate_nesting_penalty(depth)
|
273
|
+
thresholds = @thresholds['ruby_thresholds']['nesting_depth']
|
274
|
+
return 0 if depth <= thresholds['green']
|
275
|
+
return 1.0 if depth <= thresholds['yellow']
|
276
|
+
return 2.0 if depth <= thresholds['red']
|
277
|
+
3.0 # Extremely nested code
|
278
|
+
end
|
279
|
+
|
280
|
+
def calculate_parameter_penalty(param_count)
|
281
|
+
thresholds = @thresholds['ruby_thresholds']['parameter_count']
|
282
|
+
return 0 if param_count <= thresholds['green']
|
283
|
+
return 0.5 if param_count <= thresholds['yellow']
|
284
|
+
return 1.0 if param_count <= thresholds['red']
|
285
|
+
2.0 # Too many parameters
|
286
|
+
end
|
287
|
+
|
288
|
+
def get_file_type_multiplier(file_type)
|
289
|
+
@thresholds['file_type_multipliers'][file_type.to_s] || 1.0
|
290
|
+
end
|
291
|
+
|
292
|
+
def categorize_health(score)
|
293
|
+
case score
|
294
|
+
when 8.0..10.0
|
295
|
+
:healthy
|
296
|
+
when 4.0...8.0
|
297
|
+
:warning
|
298
|
+
when 1.0...4.0
|
299
|
+
:alert
|
300
|
+
else
|
301
|
+
:critical
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
def generate_recommendations(file_result)
|
306
|
+
recommendations = []
|
307
|
+
|
308
|
+
# Add recommendations based on analysis results
|
309
|
+
if file_result[:ruby_analysis]
|
310
|
+
recommendations.concat(generate_ruby_recommendations(file_result[:ruby_analysis]))
|
311
|
+
end
|
312
|
+
|
313
|
+
if file_result[:rails_analysis]
|
314
|
+
recommendations.concat(generate_rails_recommendations(file_result[:rails_analysis]))
|
315
|
+
end
|
316
|
+
|
317
|
+
recommendations.uniq
|
318
|
+
end
|
319
|
+
|
320
|
+
def generate_ruby_recommendations(ruby_analysis)
|
321
|
+
recommendations = []
|
322
|
+
|
323
|
+
if ruby_analysis[:code_smells]
|
324
|
+
ruby_analysis[:code_smells].each do |smell|
|
325
|
+
case smell[:type]
|
326
|
+
when :long_method
|
327
|
+
recommendations << "Break down the #{smell[:method_name]} method (#{smell[:line_count]} lines) into smaller, focused methods"
|
328
|
+
when :god_class
|
329
|
+
recommendations << "Refactor #{smell[:class_name]} class into smaller, more focused classes"
|
330
|
+
when :high_complexity
|
331
|
+
recommendations << "Reduce complexity of #{smell[:method_name]} method (complexity: #{smell[:complexity]})"
|
332
|
+
when :too_many_parameters
|
333
|
+
recommendations << "Reduce parameter count for #{smell[:method_name]} method or introduce parameter objects"
|
334
|
+
when :nested_conditionals
|
335
|
+
recommendations << "Reduce nesting depth in #{smell[:method_name]} method using guard clauses or extraction"
|
336
|
+
end
|
337
|
+
end
|
338
|
+
end
|
339
|
+
|
340
|
+
recommendations
|
341
|
+
end
|
342
|
+
|
343
|
+
def generate_rails_recommendations(rails_analysis)
|
344
|
+
recommendations = []
|
345
|
+
|
346
|
+
if rails_analysis[:rails_smells]
|
347
|
+
rails_analysis[:rails_smells].each do |smell|
|
348
|
+
case smell[:type]
|
349
|
+
when :too_many_actions
|
350
|
+
recommendations << "Consider splitting this controller - it has #{smell[:count]} actions"
|
351
|
+
when :missing_strong_parameters
|
352
|
+
recommendations << "Implement strong parameters for security"
|
353
|
+
when :direct_model_access
|
354
|
+
recommendations << "Move model logic to the model layer or service objects"
|
355
|
+
when :fat_model
|
356
|
+
recommendations << "Extract business logic into service objects or concerns"
|
357
|
+
when :logic_in_view
|
358
|
+
recommendations << "Move view logic to helpers or presenters (#{smell[:logic_lines]} logic lines found)"
|
359
|
+
when :callback_hell
|
360
|
+
recommendations << "Reduce model callbacks (#{smell[:count]} found) - consider service objects"
|
361
|
+
when :data_changes_in_migration
|
362
|
+
recommendations << "Avoid data changes in migrations - use rake tasks instead"
|
363
|
+
end
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
367
|
+
recommendations
|
368
|
+
end
|
369
|
+
end
|
370
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module RailsCodeHealth
|
2
|
+
class ProjectDetector
|
3
|
+
RAILS_INDICATORS = [
|
4
|
+
'config/application.rb',
|
5
|
+
'config/environment.rb',
|
6
|
+
'Gemfile'
|
7
|
+
].freeze
|
8
|
+
|
9
|
+
RAILS_DIRECTORIES = [
|
10
|
+
'app/controllers',
|
11
|
+
'app/models',
|
12
|
+
'app/views',
|
13
|
+
'config'
|
14
|
+
].freeze
|
15
|
+
|
16
|
+
class << self
|
17
|
+
def rails_project?(path)
|
18
|
+
path = Pathname.new(path) unless path.is_a?(Pathname)
|
19
|
+
|
20
|
+
has_rails_files?(path) && has_rails_structure?(path) && has_rails_gemfile?(path)
|
21
|
+
end
|
22
|
+
|
23
|
+
def detect_file_type(file_path, project_root)
|
24
|
+
relative_path = file_path.relative_path_from(project_root).to_s
|
25
|
+
|
26
|
+
case relative_path
|
27
|
+
when %r{^app/controllers/.*_controller\.rb$}
|
28
|
+
:controller
|
29
|
+
when %r{^app/models/.*\.rb$}
|
30
|
+
:model
|
31
|
+
when %r{^app/views/.*\.(erb|haml|slim)$}
|
32
|
+
:view
|
33
|
+
when %r{^app/helpers/.*_helper\.rb$}
|
34
|
+
:helper
|
35
|
+
when %r{^lib/.*\.rb$}
|
36
|
+
:lib
|
37
|
+
when %r{^db/migrate/.*\.rb$}
|
38
|
+
:migration
|
39
|
+
when %r{^spec/.*_spec\.rb$}, %r{^test/.*_test\.rb$}
|
40
|
+
:test
|
41
|
+
when %r{^config/.*\.rb$}
|
42
|
+
:config
|
43
|
+
else
|
44
|
+
:ruby if file_path.extname == '.rb'
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def has_rails_files?(path)
|
51
|
+
RAILS_INDICATORS.any? { |file| (path + file).exist? }
|
52
|
+
end
|
53
|
+
|
54
|
+
def has_rails_structure?(path)
|
55
|
+
RAILS_DIRECTORIES.all? { |dir| (path + dir).directory? }
|
56
|
+
end
|
57
|
+
|
58
|
+
def has_rails_gemfile?(path)
|
59
|
+
gemfile_path = path + 'Gemfile'
|
60
|
+
return false unless gemfile_path.exist?
|
61
|
+
|
62
|
+
begin
|
63
|
+
gemfile_content = gemfile_path.read
|
64
|
+
gemfile_content.include?('rails') ||
|
65
|
+
gemfile_content.include?('railties') ||
|
66
|
+
gemfile_content.include?('activesupport')
|
67
|
+
rescue => e
|
68
|
+
# If we can't read the Gemfile, assume it's not Rails
|
69
|
+
false
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|