solid_score 0.2.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.
Files changed (40) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +427 -0
  4. data/config/default.yml +36 -0
  5. data/exe/solid-score +6 -0
  6. data/lib/rubocop/cop/solid_score/dependency_inversion.rb +22 -0
  7. data/lib/rubocop/cop/solid_score/helpers.rb +74 -0
  8. data/lib/rubocop/cop/solid_score/interface_segregation.rb +22 -0
  9. data/lib/rubocop/cop/solid_score/liskov_substitution.rb +22 -0
  10. data/lib/rubocop/cop/solid_score/open_closed.rb +22 -0
  11. data/lib/rubocop/cop/solid_score/principle_base.rb +47 -0
  12. data/lib/rubocop/cop/solid_score/single_responsibility.rb +22 -0
  13. data/lib/rubocop/cop/solid_score/total_score.rb +54 -0
  14. data/lib/rubocop/solid_score/inject.rb +17 -0
  15. data/lib/rubocop-solid_score.rb +16 -0
  16. data/lib/solid_score/analyzers/base_analyzer.rb +17 -0
  17. data/lib/solid_score/analyzers/dip_analyzer.rb +173 -0
  18. data/lib/solid_score/analyzers/isp_analyzer.rb +131 -0
  19. data/lib/solid_score/analyzers/lsp_analyzer.rb +54 -0
  20. data/lib/solid_score/analyzers/mitigations.rb +19 -0
  21. data/lib/solid_score/analyzers/ocp_analyzer.rb +104 -0
  22. data/lib/solid_score/analyzers/srp_analyzer.rb +177 -0
  23. data/lib/solid_score/cli.rb +83 -0
  24. data/lib/solid_score/configuration.rb +159 -0
  25. data/lib/solid_score/diff_analyzer.rb +58 -0
  26. data/lib/solid_score/diff_classifier.rb +49 -0
  27. data/lib/solid_score/formatters/base_formatter.rb +11 -0
  28. data/lib/solid_score/formatters/html_formatter.rb +223 -0
  29. data/lib/solid_score/formatters/json_formatter.rb +45 -0
  30. data/lib/solid_score/formatters/text_formatter.rb +54 -0
  31. data/lib/solid_score/models/class_info.rb +150 -0
  32. data/lib/solid_score/models/method_info.rb +82 -0
  33. data/lib/solid_score/models/score_result.rb +95 -0
  34. data/lib/solid_score/parser/ruby_parser.rb +451 -0
  35. data/lib/solid_score/presets.rb +64 -0
  36. data/lib/solid_score/runner.rb +82 -0
  37. data/lib/solid_score/scorer.rb +35 -0
  38. data/lib/solid_score/version.rb +5 -0
  39. data/lib/solid_score.rb +28 -0
  40. metadata +114 -0
@@ -0,0 +1,451 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "parser/current"
4
+
5
+ module SolidScore
6
+ module Parser
7
+ # Parses Ruby source files and extracts class/method information.
8
+ #
9
+ # Phase 1 改善:
10
+ # - MethodCallInfo によるレシーバ情報の収集
11
+ # - case/when 分岐数のカウント
12
+ #
13
+ # Phase 2a 改善:
14
+ # - クラスメソッド (def self.xxx) の解析対応
15
+ # - モジュール (module) の解析対応
16
+ # - ネストしたクラス/モジュール対応
17
+ # - Rails DSL認識
18
+ class RubyParser
19
+ # Rails/ActiveSupport DSLメソッド
20
+ # これらはメソッド定義ではなくDSL宣言として扱う
21
+ RAILS_DSL_METHODS = %i[
22
+ has_many has_one belongs_to has_and_belongs_to_many
23
+ validates validate validates_presence_of validates_uniqueness_of
24
+ validates_format_of validates_length_of validates_numericality_of
25
+ validates_inclusion_of validates_exclusion_of validates_associated
26
+ before_action after_action around_action skip_before_action
27
+ before_filter after_filter around_filter
28
+ before_validation after_validation
29
+ before_create after_create before_update after_update
30
+ before_save after_save before_destroy after_destroy
31
+ after_commit after_rollback
32
+ scope enum delegate
33
+ serialize store
34
+ accepts_nested_attributes_for
35
+ ].freeze
36
+
37
+ def parse_file(file_path)
38
+ source = File.read(file_path)
39
+ ast = ::Parser::CurrentRuby.parse(source)
40
+ return [] unless ast
41
+
42
+ extract_definitions(ast, file_path)
43
+ end
44
+
45
+ private
46
+
47
+ # Phase 2a: クラスとモジュールの両方を抽出し、ネストにも対応
48
+ def extract_definitions(node, file_path, classes = [], namespace = nil)
49
+ return classes unless node.is_a?(::AST::Node)
50
+
51
+ case node.type
52
+ when :class
53
+ class_info = build_class_info(node, file_path, namespace)
54
+ classes << class_info
55
+ # ネストしたクラス/モジュールを探索
56
+ extract_nested_definitions(node.children[2], file_path, classes, class_info.name)
57
+ when :module
58
+ module_info = build_module_info(node, file_path, namespace)
59
+ classes << module_info
60
+ # モジュール内のネストしたクラス/モジュールを探索
61
+ extract_nested_definitions(node.children[1], file_path, classes, module_info.name)
62
+ else
63
+ node.children.each { |child| extract_definitions(child, file_path, classes, namespace) }
64
+ end
65
+
66
+ classes
67
+ end
68
+
69
+ # ネストしたクラス/モジュールを探索(親のbodyから)
70
+ def extract_nested_definitions(body, file_path, classes, parent_name)
71
+ return unless body.is_a?(::AST::Node)
72
+
73
+ nodes = body.type == :begin ? body.children : [body]
74
+ nodes.each do |child|
75
+ next unless child.is_a?(::AST::Node)
76
+
77
+ case child.type
78
+ when :class, :module
79
+ extract_definitions(child, file_path, classes, parent_name)
80
+ end
81
+ end
82
+ end
83
+
84
+ def build_class_info(node, file_path, namespace = nil)
85
+ raw_name = extract_class_name(node.children[0])
86
+ name = namespace ? "#{namespace}::#{raw_name}" : raw_name
87
+ superclass = node.children[1] ? extract_class_name(node.children[1]) : nil
88
+ body = node.children[2]
89
+
90
+ build_definition_info(name, file_path, node, body, superclass: superclass, kind: :class)
91
+ end
92
+
93
+ # Phase 2a: モジュール情報を構築
94
+ def build_module_info(node, file_path, namespace = nil)
95
+ raw_name = extract_class_name(node.children[0])
96
+ name = namespace ? "#{namespace}::#{raw_name}" : raw_name
97
+ body = node.children[1]
98
+
99
+ build_definition_info(name, file_path, node, body, kind: :module)
100
+ end
101
+
102
+ # クラス/モジュール共通の情報構築
103
+ def build_definition_info(name, file_path, node, body, superclass: nil, kind: :class)
104
+ methods = []
105
+ includes = []
106
+ extends = []
107
+ attr_readers = []
108
+ attr_writers = []
109
+ dsl_calls = []
110
+ current_visibility = :public
111
+
112
+ if body
113
+ traverse_body(body, methods, includes, extends, attr_readers, attr_writers,
114
+ dsl_calls, current_visibility)
115
+ end
116
+
117
+ instance_variables = methods.flat_map(&:instance_variables).uniq
118
+
119
+ Models::ClassInfo.new(
120
+ name: name,
121
+ file_path: file_path,
122
+ line_start: node.loc.line,
123
+ line_end: node.loc.last_line,
124
+ methods: methods,
125
+ superclass: superclass,
126
+ includes: includes,
127
+ extends: extends,
128
+ instance_variables: instance_variables,
129
+ attr_readers: attr_readers,
130
+ attr_writers: attr_writers,
131
+ kind: kind,
132
+ dsl_calls: dsl_calls
133
+ )
134
+ end
135
+
136
+ def traverse_body(node, methods, includes, extends, attr_readers, attr_writers,
137
+ dsl_calls, current_visibility)
138
+ return unless node.is_a?(::AST::Node)
139
+
140
+ case node.type
141
+ when :begin
142
+ node.children.each do |child|
143
+ current_visibility = traverse_body(child, methods, includes, extends,
144
+ attr_readers, attr_writers,
145
+ dsl_calls, current_visibility)
146
+ end
147
+ when :def
148
+ methods << build_method_info(node, current_visibility, kind: :instance)
149
+ when :defs
150
+ # Phase 2a: クラスメソッド (def self.xxx)
151
+ # :defs ノード構造: [receiver, name, args, body]
152
+ methods << build_method_info(node, :public, kind: :class)
153
+ when :send
154
+ current_visibility = handle_send_node(node, current_visibility, includes, extends,
155
+ attr_readers, attr_writers, dsl_calls)
156
+ when :class, :module
157
+ # ネストしたクラス/モジュールはtraverse_bodyではスキップ
158
+ # extract_nested_definitionsで別途処理する
159
+ end
160
+
161
+ current_visibility
162
+ end
163
+
164
+ def handle_send_node(node, current_visibility, includes, extends,
165
+ attr_readers, attr_writers, dsl_calls)
166
+ method_name = node.children[1]
167
+
168
+ case method_name
169
+ when :private, :protected, :public
170
+ method_name
171
+ when :include
172
+ includes << extract_class_name(node.children[2]) if node.children[2]
173
+ current_visibility
174
+ when :extend
175
+ extends << extract_class_name(node.children[2]) if node.children[2]
176
+ current_visibility
177
+ when :attr_reader
178
+ node.children[2..].each { |arg| attr_readers << arg.children[0] if arg.type == :sym }
179
+ current_visibility
180
+ when :attr_accessor
181
+ node.children[2..].each do |arg|
182
+ next unless arg.type == :sym
183
+
184
+ attr_readers << arg.children[0]
185
+ attr_writers << arg.children[0]
186
+ end
187
+ current_visibility
188
+ when :attr_writer
189
+ node.children[2..].each { |arg| attr_writers << arg.children[0] if arg.type == :sym }
190
+ current_visibility
191
+ else
192
+ # Phase 2a: Rails DSL認識
193
+ if RAILS_DSL_METHODS.include?(method_name)
194
+ dsl_calls << method_name
195
+ end
196
+ current_visibility
197
+ end
198
+ end
199
+
200
+ # Issue #12: factory-style method names that count as instantiation.
201
+ # Mirrors DipAnalyzer::FACTORY_METHODS so the bonus tracks the same set
202
+ # of patterns that are penalised as concrete dependencies.
203
+ MEMOIZED_FACTORY_METHODS = %i[new create build call open].freeze
204
+
205
+ # Issue #10: Statement-like AST nodes that count toward the effective body size.
206
+ #
207
+ # Notes on what we *don't* count and why:
208
+ # - :begin / :rescue / :ensure are sequence wrappers (their handler bodies
209
+ # are counted via their children).
210
+ # - :when is a child of :case and would double-count the dispatch.
211
+ # - :ivar / :lvar are lookups, not statements.
212
+ #
213
+ # Notes on what we *do* count:
214
+ # - :send and :csend (safe-navigation) treat method calls as a statement;
215
+ # chained calls intentionally accumulate because each step is observable
216
+ # behaviour.
217
+ # - :masgn (multiple assignment) joins the explicit assignment forms.
218
+ EFFECTIVE_STATEMENT_TYPES = %i[
219
+ send csend
220
+ if case return
221
+ ivasgn lvasgn gvasgn cvasgn masgn
222
+ and_asgn or_asgn op_asgn
223
+ yield block
224
+ while until for
225
+ next break retry redo super zsuper
226
+ ].freeze
227
+
228
+ # Nested method/lambda boundaries — count_effective_statements stops at
229
+ # these so that statements inside an inner lambda don't leak into the
230
+ # outer method's count.
231
+ NESTED_DEFINITION_TYPES = %i[def defs].freeze
232
+
233
+ # :def ノード構造: [name, args, body]
234
+ # :defs ノード構造: [receiver, name, args, body]
235
+ def build_method_info(node, visibility, kind: :instance)
236
+ name, args, body = extract_method_parts(node, kind)
237
+
238
+ instance_vars = []
239
+ called_methods = []
240
+ raises = []
241
+ method_calls = []
242
+ calls_super = false
243
+ case_when_count = 0
244
+ memoized_receiver = nil
245
+ effective_count = 0
246
+
247
+ if body
248
+ collect_method_details(body, instance_vars, called_methods, raises, method_calls)
249
+ calls_super = contains_super?(body)
250
+ case_when_count = count_case_when_branches(body)
251
+ memoized_receiver = detect_memoized_factory_receiver(body)
252
+ effective_count = count_effective_statements(body)
253
+ end
254
+
255
+ parameters = extract_parameters(args)
256
+ complexity = calculate_cyclomatic_complexity(body)
257
+
258
+ Models::MethodInfo.new(
259
+ name: name,
260
+ visibility: visibility,
261
+ line_start: node.loc.line,
262
+ line_end: node.loc.last_line,
263
+ instance_variables: instance_vars.uniq,
264
+ called_methods: called_methods.uniq,
265
+ parameters: parameters,
266
+ cyclomatic_complexity: complexity,
267
+ raises: raises,
268
+ calls_super: calls_super,
269
+ method_calls: method_calls,
270
+ case_when_count: case_when_count,
271
+ kind: kind,
272
+ effective_statement_count: effective_count,
273
+ memoized_factory_receiver: memoized_receiver
274
+ )
275
+ end
276
+
277
+ # Issue #12: Detects the `@ivar ||= ConstantClass.new(...)` memoised
278
+ # factory pattern (and the broader factory-method variants such as
279
+ # `.create`, `.build`, `.call`, `.open`). Returns the constant's
280
+ # qualified name (e.g. "ProvisioningService", "Foo::Bar") so the
281
+ # analyzer can apply the standard-library whitelist consistently
282
+ # with the concrete-dependency penalty path.
283
+ def detect_memoized_factory_receiver(node)
284
+ return nil unless node.is_a?(::AST::Node)
285
+
286
+ receiver = memoized_factory_receiver_for(node)
287
+ return receiver if receiver
288
+
289
+ node.children.each do |child|
290
+ found = detect_memoized_factory_receiver(child)
291
+ return found if found
292
+ end
293
+ nil
294
+ end
295
+
296
+ def memoized_factory_receiver_for(node)
297
+ return nil unless node.type == :or_asgn
298
+
299
+ lhs, rhs = node.children
300
+ return nil unless lhs.is_a?(::AST::Node) && lhs.type == :ivasgn
301
+ return nil unless rhs.is_a?(::AST::Node) && rhs.type == :send
302
+
303
+ receiver, method_name = rhs.children[0], rhs.children[1]
304
+ return nil unless receiver.is_a?(::AST::Node) && receiver.type == :const
305
+ return nil unless MEMOIZED_FACTORY_METHODS.include?(method_name)
306
+
307
+ extract_class_name(receiver)
308
+ end
309
+
310
+ def count_effective_statements(node, count = 0, root: true)
311
+ return count unless node.is_a?(::AST::Node)
312
+ # Don't recurse into nested method/lambda definitions when counting
313
+ # the parent body. The nested definition's own effective count is
314
+ # tracked separately by build_method_info.
315
+ return count if !root && NESTED_DEFINITION_TYPES.include?(node.type)
316
+
317
+ count += 1 if EFFECTIVE_STATEMENT_TYPES.include?(node.type)
318
+ node.children.each { |child| count = count_effective_statements(child, count, root: false) }
319
+ count
320
+ end
321
+
322
+ def extract_method_parts(node, kind)
323
+ case kind
324
+ when :class
325
+ [node.children[1], node.children[2], node.children[3]]
326
+ else
327
+ [node.children[0], node.children[1], node.children[2]]
328
+ end
329
+ end
330
+
331
+ # Phase 1 改善: レシーバ情報を含むメソッド呼び出し情報を収集
332
+ def collect_method_details(node, instance_vars, called_methods, raises, method_calls)
333
+ return unless node.is_a?(::AST::Node)
334
+
335
+ case node.type
336
+ when :ivar, :ivasgn
337
+ instance_vars << node.children[0]
338
+ when :send
339
+ receiver_node = node.children[0]
340
+ method_name = node.children[1]
341
+
342
+ called_methods << method_name
343
+
344
+ # Collect detailed method call info with receiver
345
+ method_calls << build_method_call_info(receiver_node, method_name)
346
+
347
+ if %i[raise fail].include?(method_name)
348
+ raise_class = node.children[2]
349
+ raises << extract_class_name(raise_class) if raise_class.is_a?(::AST::Node) && raise_class.type == :const
350
+ end
351
+ end
352
+
353
+ node.children.each do |child|
354
+ collect_method_details(child, instance_vars, called_methods, raises, method_calls)
355
+ end
356
+ end
357
+
358
+ # Phase 1 改善: メソッド呼び出し情報を構築
359
+ def build_method_call_info(receiver_node, method_name)
360
+ receiver, receiver_type = extract_receiver_info(receiver_node)
361
+
362
+ Models::MethodCallInfo.new(
363
+ method_name: method_name,
364
+ receiver: receiver,
365
+ receiver_type: receiver_type
366
+ )
367
+ end
368
+
369
+ # Phase 1 改善: レシーバ情報を抽出
370
+ def extract_receiver_info(node)
371
+ return [nil, :self] if node.nil?
372
+ return [nil, :unknown] unless node.is_a?(::AST::Node)
373
+
374
+ case node.type
375
+ when :const
376
+ [extract_class_name(node), :const]
377
+ when :ivar
378
+ [node.children[0].to_s, :ivar]
379
+ when :lvar
380
+ [node.children[0].to_s, :lvar]
381
+ when :self
382
+ [nil, :self]
383
+ when :send
384
+ # Chained method call (e.g., foo.bar.baz)
385
+ [nil, :send]
386
+ else
387
+ [nil, :unknown]
388
+ end
389
+ end
390
+
391
+ # Phase 1 改善: case/when 分岐数をカウント
392
+ def count_case_when_branches(node, count = 0)
393
+ return count unless node.is_a?(::AST::Node)
394
+
395
+ if node.type == :case
396
+ when_count = node.children.count { |child| child.is_a?(::AST::Node) && child.type == :when }
397
+ count += when_count
398
+ end
399
+
400
+ node.children.each do |child|
401
+ count = count_case_when_branches(child, count)
402
+ end
403
+
404
+ count
405
+ end
406
+
407
+ def contains_super?(node)
408
+ return false unless node.is_a?(::AST::Node)
409
+ return true if %i[super zsuper].include?(node.type)
410
+
411
+ node.children.any? { |child| contains_super?(child) }
412
+ end
413
+
414
+ def extract_parameters(args_node)
415
+ return [] unless args_node
416
+
417
+ args_node.children.map do |arg|
418
+ [arg.type, arg.children[0]]
419
+ end
420
+ end
421
+
422
+ def calculate_cyclomatic_complexity(node, complexity = 1)
423
+ return complexity unless node.is_a?(::AST::Node)
424
+
425
+ case node.type
426
+ when :if, :while, :until, :for, :when, :and, :or, :rescue
427
+ complexity += 1
428
+ end
429
+
430
+ node.children.each do |child|
431
+ complexity = calculate_cyclomatic_complexity(child, complexity)
432
+ end
433
+
434
+ complexity
435
+ end
436
+
437
+ def extract_class_name(node)
438
+ return nil unless node.is_a?(::AST::Node)
439
+
440
+ case node.type
441
+ when :const
442
+ parent = node.children[0]
443
+ name = node.children[1].to_s
444
+ parent ? "#{extract_class_name(parent)}::#{name}" : name
445
+ else
446
+ node.to_s
447
+ end
448
+ end
449
+ end
450
+ end
451
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidScore
4
+ module Presets
5
+ RAILS = {
6
+ paths: %w[
7
+ app/models
8
+ app/controllers
9
+ app/services
10
+ app/jobs
11
+ app/mailers
12
+ app/forms
13
+ app/presenters
14
+ app/serializers
15
+ lib
16
+ ],
17
+ exclude: %w[
18
+ spec/**
19
+ test/**
20
+ vendor/**
21
+ db/**
22
+ tmp/**
23
+ ],
24
+ weights: {
25
+ srp: 0.25,
26
+ ocp: 0.15,
27
+ lsp: 0.10,
28
+ isp: 0.20,
29
+ dip: 0.30
30
+ },
31
+ dip_whitelist: %w[
32
+ Rails
33
+ Logger
34
+ FileUtils
35
+ Pathname
36
+ URI
37
+ JSON
38
+ CSV
39
+ Net::HTTP
40
+ OpenSSL
41
+ Digest
42
+ SecureRandom
43
+ ERB
44
+ ActionMailer
45
+ ActiveJob
46
+ ActiveStorage
47
+ ]
48
+ }.freeze
49
+
50
+ REGISTRY = {
51
+ "rails" => RAILS
52
+ }.freeze
53
+
54
+ def self.fetch(name)
55
+ REGISTRY.fetch(name.to_s) do
56
+ raise ArgumentError, "Unknown preset: #{name}. Available: #{REGISTRY.keys.join(', ')}"
57
+ end
58
+ end
59
+
60
+ def self.available
61
+ REGISTRY.keys
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidScore
4
+ class Runner
5
+ attr_reader :results
6
+
7
+ def initialize(config)
8
+ @config = config
9
+ @parser = Parser::RubyParser.new
10
+ @scorer = Scorer.new(weights: config.weights, dip_whitelist: config.dip_whitelist)
11
+ @results = []
12
+ end
13
+
14
+ def run
15
+ files = collect_files
16
+ classes = files.flat_map { |f| parse_file(f) }
17
+ @results = @scorer.score_all(classes)
18
+ end
19
+
20
+ def passing?
21
+ return true if @results.empty?
22
+
23
+ @results.all? { |r| meets_thresholds?(r) }
24
+ end
25
+
26
+ def formatted_output
27
+ formatter = build_formatter
28
+ formatter.format(@results)
29
+ end
30
+
31
+ private
32
+
33
+ def collect_files
34
+ files = @config.paths.flat_map do |path|
35
+ if File.file?(path)
36
+ [path]
37
+ else
38
+ Dir.glob(File.join(path, "**", "*.rb"))
39
+ end
40
+ end
41
+
42
+ files.reject { |f| excluded?(f) }
43
+ end
44
+
45
+ def excluded?(file)
46
+ @config.exclude.any? do |pattern|
47
+ # **/dir/** パターンは部分一致で処理(File.fnmatch?は絶対パスで非対応)
48
+ if pattern.start_with?("**/") && pattern.end_with?("/**")
49
+ dir = pattern.delete_prefix("**/").delete_suffix("/**")
50
+ file.include?("/#{dir}/")
51
+ else
52
+ File.fnmatch?(pattern, file, File::FNM_PATHNAME | File::FNM_DOTMATCH)
53
+ end
54
+ end
55
+ end
56
+
57
+ def parse_file(file)
58
+ @parser.parse_file(file)
59
+ rescue ::Parser::SyntaxError
60
+ []
61
+ end
62
+
63
+ def meets_thresholds?(result)
64
+ return false if result.total < @config.thresholds[:total]
65
+
66
+ %i[srp ocp lsp isp dip].all? do |principle|
67
+ result.send(principle) >= @config.thresholds[principle]
68
+ end
69
+ end
70
+
71
+ def build_formatter
72
+ case @config.format
73
+ when :json
74
+ Formatters::JsonFormatter.new
75
+ when :html
76
+ Formatters::HtmlFormatter.new
77
+ else
78
+ Formatters::TextFormatter.new
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidScore
4
+ class Scorer
5
+ def initialize(weights: Models::ScoreResult::DEFAULT_WEIGHTS, dip_whitelist: [])
6
+ @weights = weights
7
+ @srp_analyzer = Analyzers::SrpAnalyzer.new
8
+ @ocp_analyzer = Analyzers::OcpAnalyzer.new
9
+ @lsp_analyzer = Analyzers::LspAnalyzer.new
10
+ @isp_analyzer = Analyzers::IspAnalyzer.new
11
+ @dip_analyzer = Analyzers::DipAnalyzer.new(user_whitelist: dip_whitelist)
12
+ end
13
+
14
+ def score(class_info)
15
+ srp = @srp_analyzer.analyze_with_breakdown(class_info)
16
+
17
+ Models::ScoreResult.new(
18
+ class_name: class_info.name,
19
+ file_path: class_info.file_path,
20
+ srp: srp[:score],
21
+ ocp: @ocp_analyzer.analyze(class_info),
22
+ lsp: @lsp_analyzer.analyze(class_info),
23
+ isp: @isp_analyzer.analyze(class_info),
24
+ dip: @dip_analyzer.analyze(class_info),
25
+ weights: @weights,
26
+ class_info: class_info,
27
+ subscores: { srp: srp[:breakdown] }
28
+ )
29
+ end
30
+
31
+ def score_all(class_infos)
32
+ class_infos.map { |ci| score(ci) }
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidScore
4
+ VERSION = "0.2.0"
5
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "solid_score/version"
4
+ require_relative "solid_score/models/method_info"
5
+ require_relative "solid_score/models/class_info"
6
+ require_relative "solid_score/models/score_result"
7
+ require_relative "solid_score/parser/ruby_parser"
8
+ require_relative "solid_score/analyzers/base_analyzer"
9
+ require_relative "solid_score/analyzers/mitigations"
10
+ require_relative "solid_score/analyzers/srp_analyzer"
11
+ require_relative "solid_score/analyzers/ocp_analyzer"
12
+ require_relative "solid_score/analyzers/lsp_analyzer"
13
+ require_relative "solid_score/analyzers/isp_analyzer"
14
+ require_relative "solid_score/analyzers/dip_analyzer"
15
+ require_relative "solid_score/scorer"
16
+ require_relative "solid_score/presets"
17
+ require_relative "solid_score/configuration"
18
+ require_relative "solid_score/formatters/base_formatter"
19
+ require_relative "solid_score/formatters/text_formatter"
20
+ require_relative "solid_score/formatters/json_formatter"
21
+ require_relative "solid_score/formatters/html_formatter"
22
+ require_relative "solid_score/diff_analyzer"
23
+ require_relative "solid_score/diff_classifier"
24
+ require_relative "solid_score/runner"
25
+ require_relative "solid_score/cli"
26
+
27
+ module SolidScore
28
+ end