hashira 0.1.0 → 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 (69) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +53 -0
  3. data/README.md +327 -32
  4. data/lib/hashira/analysis/census.rb +0 -2
  5. data/lib/hashira/analysis/cycle_findings.rb +1 -1
  6. data/lib/hashira/analysis/cycle_search.rb +1 -1
  7. data/lib/hashira/analysis/definitions.rb +1 -1
  8. data/lib/hashira/analysis/finding.rb +6 -2
  9. data/lib/hashira/analysis/graph.rb +3 -3
  10. data/lib/hashira/analysis/node_walk.rb +7 -1
  11. data/lib/hashira/analysis/references.rb +2 -2
  12. data/lib/hashira/analysis/sdp_check.rb +1 -1
  13. data/lib/hashira/analysis/type_walk.rb +1 -1
  14. data/lib/hashira/churn.rb +22 -0
  15. data/lib/hashira/ci/accepted.rb +11 -7
  16. data/lib/hashira/ci/baseline.rb +40 -0
  17. data/lib/hashira/ci/diff.rb +15 -0
  18. data/lib/hashira/ci/edge_diff_report.rb +4 -27
  19. data/lib/hashira/ci/finding_diff_report.rb +26 -0
  20. data/lib/hashira/ci/gate.rb +7 -5
  21. data/lib/hashira/ci/improvement.rb +19 -0
  22. data/lib/hashira/ci/ratchet.rb +16 -20
  23. data/lib/hashira/ci/ratchet_report.rb +42 -0
  24. data/lib/hashira/cli/command_line.rb +12 -6
  25. data/lib/hashira/cli/fail_on.rb +4 -2
  26. data/lib/hashira/cli/options.rb +1 -1
  27. data/lib/hashira/cli/run.rb +9 -3
  28. data/lib/hashira/cli/skip.rb +27 -0
  29. data/lib/hashira/cli/usage.rb +10 -5
  30. data/lib/hashira/cli.rb +1 -1
  31. data/lib/hashira/complexity/analyzer.rb +45 -0
  32. data/lib/hashira/complexity/boolean_run.rb +22 -0
  33. data/lib/hashira/complexity/cognitive_score.rb +72 -0
  34. data/lib/hashira/complexity/if_chain.rb +45 -0
  35. data/lib/hashira/complexity/method_finding.rb +52 -0
  36. data/lib/hashira/complexity/method_score.rb +17 -0
  37. data/lib/hashira/complexity/rescue_scan.rb +28 -0
  38. data/lib/hashira/complexity/rollup.rb +22 -0
  39. data/lib/hashira/duplication/analyzer.rb +21 -0
  40. data/lib/hashira/duplication/cluster.rb +24 -0
  41. data/lib/hashira/duplication/clusterer.rb +44 -0
  42. data/lib/hashira/duplication/delta.rb +39 -0
  43. data/lib/hashira/duplication/duplication_finding.rb +30 -0
  44. data/lib/hashira/duplication/extractor.rb +31 -0
  45. data/lib/hashira/duplication/fragment.rb +42 -0
  46. data/lib/hashira/duplication/grouping.rb +24 -0
  47. data/lib/hashira/duplication/index.rb +35 -0
  48. data/lib/hashira/duplication/maximal.rb +21 -0
  49. data/lib/hashira/duplication/near_miss.rb +33 -0
  50. data/lib/hashira/duplication/sequence.rb +34 -0
  51. data/lib/hashira/duplication/similarity.rb +50 -0
  52. data/lib/hashira/duplication/union_find.rb +21 -0
  53. data/lib/hashira/duplication/variance.rb +57 -0
  54. data/lib/hashira/hotspots/file_cost.rb +19 -0
  55. data/lib/hashira/hotspots/rollup.rb +33 -0
  56. data/lib/hashira/pipeline.rb +28 -6
  57. data/lib/hashira/project.rb +10 -7
  58. data/lib/hashira/report/complexity_table.rb +40 -0
  59. data/lib/hashira/report/dependency_map.rb +8 -5
  60. data/lib/hashira/report/finding_lines.rb +1 -1
  61. data/lib/hashira/report/graph_payload.rb +29 -0
  62. data/lib/hashira/report/hotspot_table.rb +41 -0
  63. data/lib/hashira/report/json.rb +24 -17
  64. data/lib/hashira/report/metrics_table.rb +1 -2
  65. data/lib/hashira/report/text.rb +24 -14
  66. data/lib/hashira/report/view.rb +7 -0
  67. data/lib/hashira/version.rb +1 -1
  68. data/lib/hashira.rb +36 -0
  69. metadata +44 -6
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hashira
4
+ module Hotspots
5
+ CHURN_FLOOR = 1
6
+
7
+ FileCost = Data.define(:file, :cognitive, :duplication, :churn) do
8
+ def cost = cognitive + duplication
9
+
10
+ def rank = cost * effective_churn
11
+
12
+ def effective_churn = [churn, CHURN_FLOOR].max
13
+
14
+ def to_h = { file:, cognitive:, duplication:, churn:, cost:, rank: }
15
+
16
+ def cells = [file, cognitive, duplication, churn, rank]
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hashira
4
+ module Hotspots
5
+ class Rollup
6
+ def initialize(complexity, duplication, churn)
7
+ @complexity = complexity
8
+ @duplication = duplication
9
+ @churn = churn
10
+ end
11
+
12
+ def files = costs.reject { it.cost.zero? }.sort_by { [-it.rank, it.file] }
13
+
14
+ private
15
+
16
+ def costs = (cognitive.keys | duplicated.keys).map { cost_for(it) }
17
+
18
+ def cost_for(file)
19
+ FileCost.new(file:, cognitive: cognitive[file], duplication: duplicated[file], churn: @churn.hits(file))
20
+ end
21
+
22
+ def scores = @complexity ? @complexity.methods : []
23
+
24
+ def clusters = @duplication ? @duplication.clusters : []
25
+
26
+ def cognitive = @cognitive ||= per_file(scores.map { [it.file, it.cognitive] })
27
+
28
+ def duplicated = @duplicated ||= per_file(clusters.flat_map(&:site_masses))
29
+
30
+ def per_file(charges) = charges.each_with_object(Hash.new(0)) { |(file, cost), total| total[file] += cost }
31
+ end
32
+ end
33
+ end
@@ -4,21 +4,43 @@ require "prism"
4
4
 
5
5
  module Hashira
6
6
  class Pipeline
7
- def initialize(project)
7
+ ANALYZERS = %i[coupling complexity duplication].freeze
8
+
9
+ RULES = [Analysis::CycleFindings, Analysis::SdpViolationFindings].freeze
10
+
11
+ def initialize(project, enabled: ANALYZERS)
8
12
  @project = project
9
- trees = project.files.to_h { [_1, parse(_1)] }
10
- @census = Analysis::Census.new(project, trees)
11
- @graph = Analysis::Graph.new(project, trees, @census)
13
+ @enabled = enabled
14
+ @trees = project.files.to_h { [it, parse(it)] }
15
+ @graph = Analysis::Graph.new(project, @trees, Analysis::Census.new(project, @trees))
12
16
  end
13
17
 
14
18
  attr_reader :project, :graph
15
19
 
16
- RULES = [Analysis::CycleFindings, Analysis::SdpViolationFindings].freeze
20
+ def complexity
21
+ @complexity ||= Complexity::Analyzer.new(@project, @trees) if enabled?(:complexity)
22
+ end
17
23
 
18
- def findings = RULES.flat_map { _1.new(@project, @graph).list }
24
+ def duplication
25
+ @duplication ||= Duplication::Analyzer.new(@project, @trees, churn) if enabled?(:duplication)
26
+ end
27
+
28
+ def hotspots
29
+ @hotspots ||= Hotspots::Rollup.new(complexity, duplication, churn) if complexity || duplication
30
+ end
31
+
32
+ def churn = @churn ||= Churn.from_git
33
+
34
+ def enabled?(analyzer) = @enabled.include?(analyzer)
35
+
36
+ def findings = coupling_findings + listed(complexity) + listed(duplication)
19
37
 
20
38
  private
21
39
 
40
+ def coupling_findings = enabled?(:coupling) ? RULES.flat_map { it.new(@project, @graph).list } : []
41
+
42
+ def listed(analyzer) = analyzer ? analyzer.findings : []
43
+
22
44
  def parse(path)
23
45
  Prism.parse_file(path).value
24
46
  rescue SystemCallError => error
@@ -5,24 +5,27 @@ module Hashira
5
5
  ROOT_PACKAGE = "(root)"
6
6
 
7
7
  def self.detect(directories)
8
- return new(directories) unless directories.empty?
8
+ directories.empty? ? new(default_directories) : new(directories)
9
+ end
9
10
 
10
- subdirectories = Dir["lib/*/"].map { File.basename(_1) }
11
+ def self.default_directories
12
+ subdirectories = Dir["lib/*/"].map { File.basename(it) }
11
13
  raise Error, "no lib/ directory here — pass the source directory explicitly" if subdirectories.empty?
12
14
 
13
- new(subdirectories.size == 1 ? ["lib/#{subdirectories.first}"] : ["lib"])
15
+ subdirectories.size == 1 ? ["lib/#{subdirectories.first}"] : ["lib"]
14
16
  end
17
+ private_class_method :default_directories
15
18
 
16
19
  def initialize(directories)
17
- missing = directories.reject { Dir.exist?(_1) }
20
+ missing = directories.reject { Dir.exist?(it) }
18
21
  raise Error, "no such directory: #{missing.join(", ")}" unless missing.empty?
19
22
 
20
- @directories = directories.map { _1.delete_suffix("/") }
23
+ @directories = directories.map { it.delete_suffix("/") }
21
24
  end
22
25
 
23
26
  attr_reader :directories
24
27
 
25
- def files = @directories.flat_map { Dir["#{_1}/**/*.rb"] }.sort
28
+ def files = @directories.flat_map { Dir["#{it}/**/*.rb"] }.sort
26
29
 
27
30
  def package_for(path)
28
31
  first, rest = relative(path).delete_suffix(".rb").split("/", 2)
@@ -40,7 +43,7 @@ module Hashira
40
43
  def folder?(path, name) = Dir.exist?("#{directory_of(path)}/#{name}")
41
44
 
42
45
  def directory_of(path)
43
- @directories.find { path.start_with?("#{_1}/") } ||
46
+ @directories.find { path.start_with?("#{it}/") } ||
44
47
  raise(Error, "#{path} is outside the analyzed directories")
45
48
  end
46
49
  end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hashira
4
+ module Report
5
+ class ComplexityTable
6
+ TOP = 10
7
+ METHOD_ROW = "%-44s %4s %6s %s"
8
+ CLASS_ROW = "%-32s %5s %8s %6s"
9
+
10
+ def initialize(complexity, io: $stdout)
11
+ @complexity = complexity
12
+ @io = io
13
+ end
14
+
15
+ def print
16
+ section(@complexity.methods, METHOD_ROW, %w[method Cog Calls Loc],
17
+ "Cognitive complexity — worst methods (Cog = how hard to read, Calls = message sends)")
18
+ section(@complexity.classes, CLASS_ROW, %w[class Cog Methods Peak],
19
+ "Per-class rollup (Cog total survives extract-method; Peak is the worst method it hides)")
20
+ end
21
+
22
+ private
23
+
24
+ def section(scores, row_format, columns, title)
25
+ heading(row_format, columns, title)
26
+ ranked(scores).each { @io.puts format(row_format, *it.cells) }
27
+ @io.puts
28
+ end
29
+
30
+ def heading(row_format, columns, title)
31
+ header = format(row_format, *columns)
32
+ @io.puts "#{title}:\n\n"
33
+ @io.puts header
34
+ @io.puts "-" * header.length
35
+ end
36
+
37
+ def ranked(scores) = scores.select { it.cognitive.positive? }.first(TOP)
38
+ end
39
+ end
40
+ end
@@ -10,18 +10,21 @@ module Hashira
10
10
 
11
11
  def print
12
12
  @io.puts "Dependencies (DependsUpon(refs) -> | <- UsedBy):"
13
- @graph.packages.sort.each { @io.puts row(_1) }
13
+ @graph.packages.sort.each { @io.puts row(it) }
14
14
  end
15
15
 
16
16
  private
17
17
 
18
18
  def row(package)
19
- depends_upon = @graph.dependencies_of(package).map { "#{_1}(#{@graph.weight(package, _1)})" }.join(", ")
20
- used_by = @graph.dependents_of(package).join(", ")
21
19
  format(" %-12s -> %-32s <- %s", package,
22
- depends_upon.empty? ? "(none)" : depends_upon,
23
- used_by.empty? ? "(none)" : used_by)
20
+ list(depends_upon(package)), list(@graph.dependents_of(package)))
24
21
  end
22
+
23
+ def depends_upon(package)
24
+ @graph.dependencies_of(package).map { "#{it}(#{@graph.weight(package, it)})" }
25
+ end
26
+
27
+ def list(items) = items.empty? ? "(none)" : items.join(", ")
25
28
  end
26
29
  end
27
30
  end
@@ -13,7 +13,7 @@ module Hashira
13
13
 
14
14
  def print
15
15
  @io.puts "#{@indent}#{@finding.kind}: #{@finding.message}"
16
- @finding.evidence.first(SAMPLE).each { @io.puts "#{@indent} · #{_1}" }
16
+ @finding.evidence.first(SAMPLE).each { @io.puts "#{@indent} · #{it}" }
17
17
  end
18
18
 
19
19
  def print_with_overflow
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hashira
4
+ module Report
5
+ class GraphPayload
6
+ def initialize(graph)
7
+ @graph = graph
8
+ end
9
+
10
+ def to_h = { packages:, edges: }
11
+
12
+ private
13
+
14
+ def packages
15
+ @graph.metrics.sort_by { |_package, metric| metric.instability }
16
+ .to_h do |package, metric|
17
+ [package,
18
+ metric.to_h.merge(cyclic: @graph.cyclic?(package))]
19
+ end
20
+ end
21
+
22
+ def edges
23
+ @graph.weighted_edges.map do |from, to, weight|
24
+ { from:, to:, weight:, refs: @graph.evidence_for(from, to).to_a.sort }
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hashira
4
+ module Report
5
+ class HotspotTable
6
+ TOP = 10
7
+ ROW = "%-46s %5s %5s %6s %7s"
8
+
9
+ def initialize(hotspots, io: $stdout)
10
+ @hotspots = hotspots
11
+ @io = io
12
+ end
13
+
14
+ def print
15
+ return if ranked.empty?
16
+
17
+ heading
18
+ rows
19
+ legend
20
+ end
21
+
22
+ private
23
+
24
+ def ranked = @ranked ||= @hotspots.files.first(TOP)
25
+
26
+ def rows = ranked.each { @io.puts format(ROW, *it.cells) }
27
+
28
+ def heading
29
+ @io.puts "Hotspots — cost × churn (where refactoring pays the most):\n\n"
30
+ header = format(ROW, *%w[file Cog Dup Churn Rank])
31
+ @io.puts header
32
+ @io.puts "-" * header.length
33
+ end
34
+
35
+ def legend
36
+ @io.puts "\nLegend: Cog cognitive complexity, Dup mass of the clones the file carries,"
37
+ @io.puts " Churn commits touching it, Rank (Cog+Dup) × Churn\n\n"
38
+ end
39
+ end
40
+ end
41
+ end
@@ -5,37 +5,44 @@ require "json"
5
5
  module Hashira
6
6
  module Report
7
7
  class Json
8
- def initialize(graph, findings, io: $stdout)
9
- @graph = graph
10
- @findings = findings
8
+ def initialize(view, io: $stdout)
9
+ @view = view
11
10
  @io = io
12
11
  end
13
12
 
14
13
  def print
15
- @io.puts JSON.pretty_generate(packages:, edges:, findings: @findings.all.map(&:to_h),
16
- accepted: accepted_entries)
14
+ @io.puts JSON.pretty_generate(payload)
17
15
  0
18
16
  end
19
17
 
20
18
  private
21
19
 
22
- def accepted_entries
23
- @findings.accepted.map { |finding, reason| finding.to_h.merge(reason:) }
20
+ def payload = base.merge(graph_payload).merge(sections.compact)
21
+
22
+ def base = { findings: @view.findings.all.map(&:to_h), accepted: accepted_entries }
23
+
24
+ def graph_payload
25
+ graph = @view.graph
26
+ graph ? GraphPayload.new(graph).to_h : {}
24
27
  end
25
28
 
26
- def packages
27
- @graph.metrics.sort_by { |_package, metric| metric.instability }
28
- .to_h do |package, metric|
29
- [package,
30
- metric.to_h.merge(cyclic: @graph.cyclic?(package))]
31
- end
29
+ def sections
30
+ { complexity: (complexity_payload if @view.complexity),
31
+ duplication: (duplication_payload if @view.duplication),
32
+ hotspots: @view.hotspots&.files&.map(&:to_h) }
32
33
  end
33
34
 
34
- def edges
35
- @graph.weighted_edges.map do |from, to, weight|
36
- { from:, to:, weight:, refs: @graph.evidence_for(from, to).to_a.sort }
37
- end
35
+ def accepted_entries
36
+ @view.findings.accepted.map { |finding, reason| finding.to_h.merge(reason:) }
38
37
  end
38
+
39
+ def complexity_payload = { methods: method_scores, classes: class_scores }
40
+
41
+ def method_scores = @view.complexity.methods.map(&:to_h)
42
+
43
+ def class_scores = @view.complexity.classes.map(&:to_h)
44
+
45
+ def duplication_payload = @view.duplication.clusters.map { Duplication::Delta.new(it).to_h }
39
46
  end
40
47
  end
41
48
  end
@@ -25,8 +25,7 @@ module Hashira
25
25
 
26
26
  def row(package, metric)
27
27
  cyclic = @graph.cyclic?(package) ? "YES" : "-"
28
- format("%-12s %3d %3d %3d %5.2f %-3s", package,
29
- metric.type_count, metric.afferent, metric.efferent, metric.instability, cyclic)
28
+ format("%-12s %3d %3d %3d %5.2f %-3s", package, *metric.to_h.values, cyclic)
30
29
  end
31
30
 
32
31
  def legend
@@ -3,27 +3,37 @@
3
3
  module Hashira
4
4
  module Report
5
5
  class Text
6
- def initialize(project, graph, findings, io: $stdout)
7
- @project = project
8
- @graph = graph
9
- @findings = findings
6
+ def initialize(view, io: $stdout)
7
+ @view = view
10
8
  @io = io
11
9
  end
12
10
 
13
11
  def print
14
- header
15
- MetricsTable.new(@graph, io: @io).print
16
- DependencyMap.new(@graph, io: @io).print
12
+ coupling_sections if @view.graph
13
+ complexity_section if @view.complexity
14
+ hotspot_section if @view.hotspots
17
15
  findings_section
18
16
  0
19
17
  end
20
18
 
21
19
  private
22
20
 
23
- def header
24
- packages = @graph.packages.size
25
- @io.puts "Package (layer) metrics for #{@project.label} " \
26
- "(#{packages} packages, #{@project.files.size} files)\n\n"
21
+ def coupling_sections
22
+ graph = @view.graph
23
+ header(graph)
24
+ MetricsTable.new(graph, io: @io).print
25
+ DependencyMap.new(graph, io: @io).print
26
+ end
27
+
28
+ def complexity_section = ComplexityTable.new(@view.complexity, io: @io).print
29
+
30
+ def hotspot_section = HotspotTable.new(@view.hotspots, io: @io).print
31
+
32
+ def header(graph)
33
+ project = @view.project
34
+ packages = graph.packages.size
35
+ @io.puts "Package (layer) metrics for #{project.label} " \
36
+ "(#{packages} packages, #{project.files.size} files)\n\n"
27
37
  single_package_note if packages == 1
28
38
  end
29
39
 
@@ -33,7 +43,7 @@ module Hashira
33
43
  end
34
44
 
35
45
  def findings_section
36
- all = @findings.all
46
+ all = @view.findings.all
37
47
  @io.puts "\nFindings (#{all.size}):"
38
48
  print_findings(all)
39
49
  accepted_section
@@ -43,11 +53,11 @@ module Hashira
43
53
  def print_findings(all)
44
54
  return @io.puts " none ✓ — structure is healthy" if all.empty?
45
55
 
46
- all.each { FindingLines.new(_1, indent: " ", io: @io).print_with_overflow }
56
+ all.each { FindingLines.new(it, indent: " ", io: @io).print_with_overflow }
47
57
  end
48
58
 
49
59
  def accepted_section
50
- accepted = @findings.accepted
60
+ accepted = @view.findings.accepted
51
61
  return if accepted.empty?
52
62
 
53
63
  @io.puts "\nAccepted (#{accepted.size}):"
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hashira
4
+ module Report
5
+ View = Data.define(:project, :graph, :complexity, :duplication, :hotspots, :findings)
6
+ end
7
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Hashira
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.0"
5
5
  end
data/lib/hashira.rb CHANGED
@@ -3,6 +3,7 @@
3
3
  require_relative "hashira/version"
4
4
  require_relative "hashira/error"
5
5
  require_relative "hashira/project"
6
+ require_relative "hashira/churn"
6
7
  require_relative "hashira/analysis/syntax"
7
8
  require_relative "hashira/analysis/node_walk"
8
9
  require_relative "hashira/analysis/type_walk"
@@ -20,21 +21,56 @@ require_relative "hashira/analysis/finding"
20
21
  require_relative "hashira/analysis/rule"
21
22
  require_relative "hashira/analysis/cycle_findings"
22
23
  require_relative "hashira/analysis/sdp_violation_findings"
24
+ require_relative "hashira/complexity/method_score"
25
+ require_relative "hashira/complexity/rollup"
26
+ require_relative "hashira/complexity/if_chain"
27
+ require_relative "hashira/complexity/boolean_run"
28
+ require_relative "hashira/complexity/rescue_scan"
29
+ require_relative "hashira/complexity/cognitive_score"
30
+ require_relative "hashira/complexity/method_finding"
31
+ require_relative "hashira/complexity/analyzer"
32
+ require_relative "hashira/duplication/fragment"
33
+ require_relative "hashira/duplication/sequence"
34
+ require_relative "hashira/duplication/extractor"
35
+ require_relative "hashira/duplication/similarity"
36
+ require_relative "hashira/duplication/union_find"
37
+ require_relative "hashira/duplication/cluster"
38
+ require_relative "hashira/duplication/grouping"
39
+ require_relative "hashira/duplication/index"
40
+ require_relative "hashira/duplication/near_miss"
41
+ require_relative "hashira/duplication/maximal"
42
+ require_relative "hashira/duplication/clusterer"
43
+ require_relative "hashira/duplication/variance"
44
+ require_relative "hashira/duplication/delta"
45
+ require_relative "hashira/duplication/duplication_finding"
46
+ require_relative "hashira/duplication/analyzer"
23
47
  require_relative "hashira/diagram/dot"
24
48
  require_relative "hashira/diagram/mermaid"
25
49
  require_relative "hashira/diagram/renderer"
26
50
  require_relative "hashira/ci/accepted"
51
+ require_relative "hashira/ci/baseline"
52
+ require_relative "hashira/ci/diff"
53
+ require_relative "hashira/ci/improvement"
27
54
  require_relative "hashira/ci/edge_diff_report"
55
+ require_relative "hashira/ci/finding_diff_report"
56
+ require_relative "hashira/ci/ratchet_report"
28
57
  require_relative "hashira/ci/gate"
29
58
  require_relative "hashira/ci/ratchet"
59
+ require_relative "hashira/hotspots/file_cost"
60
+ require_relative "hashira/hotspots/rollup"
61
+ require_relative "hashira/report/view"
62
+ require_relative "hashira/report/graph_payload"
30
63
  require_relative "hashira/report/finding_lines"
31
64
  require_relative "hashira/report/dependency_map"
32
65
  require_relative "hashira/report/metrics_table"
66
+ require_relative "hashira/report/complexity_table"
67
+ require_relative "hashira/report/hotspot_table"
33
68
  require_relative "hashira/report/json"
34
69
  require_relative "hashira/report/text"
35
70
  require_relative "hashira/pipeline"
36
71
  require_relative "hashira/cli"
37
72
  require_relative "hashira/cli/fail_on"
73
+ require_relative "hashira/cli/skip"
38
74
  require_relative "hashira/cli/options"
39
75
  require_relative "hashira/cli/command_line"
40
76
  require_relative "hashira/cli/usage"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hashira
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Giacomo GK
@@ -9,9 +9,11 @@ bindir: exe
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies: []
12
- description: Measures package coupling in a Ruby codebase (Ca/Ce/instability, SDP
13
- violations, cycles) from the AST, with findings backed by file-level evidence and
14
- an edge-set ratchet for CI.
12
+ description: Measures package coupling (Ca/Ce/instability, SDP violations, cycles),
13
+ per-method cognitive complexity, and near-miss code duplication in a Ruby codebase
14
+ from the AST, then ranks files by cost against git churn. Findings are backed by
15
+ file-level evidence, and a baseline ratchets edges and findings for CI — direction,
16
+ not a score.
15
17
  email:
16
18
  - giaco@hey.com
17
19
  executables:
@@ -41,27 +43,63 @@ files:
41
43
  - lib/hashira/analysis/sdp_violation_findings.rb
42
44
  - lib/hashira/analysis/syntax.rb
43
45
  - lib/hashira/analysis/type_walk.rb
46
+ - lib/hashira/churn.rb
44
47
  - lib/hashira/ci/accepted.rb
48
+ - lib/hashira/ci/baseline.rb
49
+ - lib/hashira/ci/diff.rb
45
50
  - lib/hashira/ci/edge_diff_report.rb
51
+ - lib/hashira/ci/finding_diff_report.rb
46
52
  - lib/hashira/ci/gate.rb
53
+ - lib/hashira/ci/improvement.rb
47
54
  - lib/hashira/ci/ratchet.rb
55
+ - lib/hashira/ci/ratchet_report.rb
48
56
  - lib/hashira/cli.rb
49
57
  - lib/hashira/cli/command_line.rb
50
58
  - lib/hashira/cli/fail_on.rb
51
59
  - lib/hashira/cli/options.rb
52
60
  - lib/hashira/cli/run.rb
61
+ - lib/hashira/cli/skip.rb
53
62
  - lib/hashira/cli/usage.rb
63
+ - lib/hashira/complexity/analyzer.rb
64
+ - lib/hashira/complexity/boolean_run.rb
65
+ - lib/hashira/complexity/cognitive_score.rb
66
+ - lib/hashira/complexity/if_chain.rb
67
+ - lib/hashira/complexity/method_finding.rb
68
+ - lib/hashira/complexity/method_score.rb
69
+ - lib/hashira/complexity/rescue_scan.rb
70
+ - lib/hashira/complexity/rollup.rb
54
71
  - lib/hashira/diagram/dot.rb
55
72
  - lib/hashira/diagram/mermaid.rb
56
73
  - lib/hashira/diagram/renderer.rb
74
+ - lib/hashira/duplication/analyzer.rb
75
+ - lib/hashira/duplication/cluster.rb
76
+ - lib/hashira/duplication/clusterer.rb
77
+ - lib/hashira/duplication/delta.rb
78
+ - lib/hashira/duplication/duplication_finding.rb
79
+ - lib/hashira/duplication/extractor.rb
80
+ - lib/hashira/duplication/fragment.rb
81
+ - lib/hashira/duplication/grouping.rb
82
+ - lib/hashira/duplication/index.rb
83
+ - lib/hashira/duplication/maximal.rb
84
+ - lib/hashira/duplication/near_miss.rb
85
+ - lib/hashira/duplication/sequence.rb
86
+ - lib/hashira/duplication/similarity.rb
87
+ - lib/hashira/duplication/union_find.rb
88
+ - lib/hashira/duplication/variance.rb
57
89
  - lib/hashira/error.rb
90
+ - lib/hashira/hotspots/file_cost.rb
91
+ - lib/hashira/hotspots/rollup.rb
58
92
  - lib/hashira/pipeline.rb
59
93
  - lib/hashira/project.rb
94
+ - lib/hashira/report/complexity_table.rb
60
95
  - lib/hashira/report/dependency_map.rb
61
96
  - lib/hashira/report/finding_lines.rb
97
+ - lib/hashira/report/graph_payload.rb
98
+ - lib/hashira/report/hotspot_table.rb
62
99
  - lib/hashira/report/json.rb
63
100
  - lib/hashira/report/metrics_table.rb
64
101
  - lib/hashira/report/text.rb
102
+ - lib/hashira/report/view.rb
65
103
  - lib/hashira/version.rb
66
104
  homepage: https://github.com/giacope/hashira
67
105
  licenses:
@@ -85,7 +123,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
85
123
  - !ruby/object:Gem::Version
86
124
  version: '0'
87
125
  requirements: []
88
- rubygems_version: 4.0.10
126
+ rubygems_version: 4.0.17
89
127
  specification_version: 4
90
- summary: Package coupling metrics for Ruby, via Prism
128
+ summary: Coupling, cognitive-complexity, and duplication metrics for Ruby, via Prism
91
129
  test_files: []