hashira 0.6.0 → 0.7.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 +4 -4
- data/CHANGELOG.md +68 -0
- data/README.md +119 -53
- data/lib/hashira/analysis/finding.rb +4 -0
- data/lib/hashira/churn.rb +10 -2
- data/lib/hashira/ci/accepted.rb +1 -4
- data/lib/hashira/ci/baseline.rb +28 -5
- data/lib/hashira/ci/diff.rb +15 -4
- data/lib/hashira/ci/finding_diff_report.rb +10 -0
- data/lib/hashira/ci/gate.rb +10 -2
- data/lib/hashira/ci/improvement.rb +1 -1
- data/lib/hashira/ci/ratchet.rb +33 -8
- data/lib/hashira/ci/ratchet_report.rb +4 -2
- data/lib/hashira/ci/scope.rb +11 -0
- data/lib/hashira/ci/status.rb +7 -0
- data/lib/hashira/cli/arguments.rb +6 -1
- data/lib/hashira/cli/fail_on.rb +12 -2
- data/lib/hashira/cli/flags.rb +13 -0
- data/lib/hashira/cli/needs.rb +34 -0
- data/lib/hashira/cli/options.rb +14 -3
- data/lib/hashira/cli/run.rb +32 -5
- data/lib/hashira/cli/top.rb +13 -0
- data/lib/hashira/cli/usage.rb +5 -1
- data/lib/hashira/cli.rb +24 -5
- data/lib/hashira/coupling/metric.rb +7 -1
- data/lib/hashira/diagram/dot.rb +9 -6
- data/lib/hashira/diagram/mermaid.rb +11 -8
- data/lib/hashira/diagram/source.rb +5 -2
- data/lib/hashira/pipeline.rb +12 -18
- data/lib/hashira/project.rb +19 -5
- data/lib/hashira/report/columns.rb +48 -0
- data/lib/hashira/report/complexity_table.rb +12 -16
- data/lib/hashira/report/dependency_map.rb +1 -0
- data/lib/hashira/report/hotspot_table.rb +6 -14
- data/lib/hashira/report/json.rb +9 -2
- data/lib/hashira/report/metrics_table.rb +19 -20
- data/lib/hashira/report/notices.rb +37 -0
- data/lib/hashira/report/phrases.rb +2 -0
- data/lib/hashira/report/text.rb +24 -9
- data/lib/hashira/report/view.rb +4 -1
- data/lib/hashira/trees.rb +22 -0
- data/lib/hashira/version.rb +1 -1
- data/lib/hashira.rb +6 -0
- metadata +8 -1
|
@@ -1,47 +1,46 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
class Hashira::Report::MetricsTable
|
|
4
|
-
|
|
4
|
+
TOP = 25
|
|
5
|
+
|
|
6
|
+
def initialize(graph, top: TOP, io: $stdout)
|
|
5
7
|
@graph = graph
|
|
8
|
+
@top = top
|
|
6
9
|
@io = io
|
|
7
10
|
end
|
|
8
11
|
|
|
9
|
-
|
|
12
|
+
HEADERS = %w[package TC Ca Ce I Cyc].freeze
|
|
10
13
|
|
|
11
14
|
def print
|
|
12
|
-
|
|
13
|
-
kept.each { |package, metric| @io.puts(row(package, metric)) }
|
|
15
|
+
Hashira::Report::Columns.new(HEADERS, kept.map { |package, metric| row(package, metric) }, io: @io).print
|
|
14
16
|
note unless spared.empty?
|
|
17
|
+
more unless over.zero?
|
|
15
18
|
legend
|
|
16
19
|
end
|
|
17
20
|
|
|
18
21
|
private
|
|
19
22
|
|
|
20
|
-
def ranked = @graph.metrics.sort_by { |_package, metric| metric.
|
|
23
|
+
def ranked = @graph.metrics.sort_by { |_package, metric| metric.order }
|
|
21
24
|
|
|
22
|
-
def split = @split ||= (ranked.size >
|
|
25
|
+
def split = @split ||= (ranked.size > @top ? ranked.partition { |_package, metric| !leaf?(metric) } : [ranked, []])
|
|
23
26
|
|
|
24
|
-
def kept = split.first
|
|
27
|
+
def kept = split.first.first(@top)
|
|
25
28
|
|
|
26
29
|
def spared = split.last
|
|
27
30
|
|
|
31
|
+
def over = split.first.size - kept.size
|
|
32
|
+
|
|
28
33
|
def leaf?(metric) = metric.types <= 1 && metric.efferent.zero? && metric.afferent <= 1
|
|
29
34
|
|
|
30
|
-
def note
|
|
31
|
-
@io.puts(" + #{spared.size} single-type leaf packages (TC ≤ 1, Ca ≤ 1, Ce = 0) — hidden for brevity")
|
|
32
|
-
end
|
|
35
|
+
def note = @io.puts(" + #{leaves} (TC ≤ 1, Ca ≤ 1, Ce = 0) — hidden for brevity")
|
|
33
36
|
|
|
34
|
-
def
|
|
35
|
-
@io.puts(format("%-12s %3s %3s %3s %5s %-3s", *%w[package TC Ca Ce I Cyc]))
|
|
36
|
-
@io.puts("-" * 40)
|
|
37
|
-
end
|
|
37
|
+
def leaves = Hashira::Report::Phrases.count(spared.size, "single-type leaf package")
|
|
38
38
|
|
|
39
|
-
def
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
end
|
|
39
|
+
def more = @io.puts(" … and #{over} more — raise the cap with --top, or read them all with --json")
|
|
40
|
+
|
|
41
|
+
def row(package, metric) = [package, *metric.cells, cyc(package)]
|
|
42
|
+
|
|
43
|
+
def cyc(package) = @graph.cycles.through?(package) ? "YES" : "-"
|
|
45
44
|
|
|
46
45
|
def legend
|
|
47
46
|
@io.puts("\nLegend: TC total types, Ca afferent (incoming), Ce efferent (outgoing),")
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Hashira::Report::Notices
|
|
4
|
+
ISSUES = "https://github.com/giacope/hashira/issues"
|
|
5
|
+
|
|
6
|
+
def initialize(io: $stderr)
|
|
7
|
+
@io = io
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def scanning(files) = interactive { @io.puts("hashira: reading #{files} files…") }
|
|
11
|
+
|
|
12
|
+
def finished(files, seconds) = interactive { @io.puts("hashira: #{files} files in #{seconds}s") }
|
|
13
|
+
|
|
14
|
+
def rails
|
|
15
|
+
@io.puts("hashira: this looks like a Rails root — `hashira app` reads the application, not just lib/")
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def churn(label)
|
|
19
|
+
@io.puts("hashira: no git history for #{label} — hotspots are ranked by cost alone")
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def crashed(error)
|
|
23
|
+
@io.puts("hashira: internal error — #{error.class}: #{error.message}")
|
|
24
|
+
@io.puts(" at #{error.backtrace.first}")
|
|
25
|
+
@io.puts(" this is a bug in hashira — please report it at #{ISSUES}")
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def unparsed(count, sample)
|
|
29
|
+
@io.puts("hashira: #{count} of the files did not parse — #{sample}")
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def interactive
|
|
35
|
+
yield if @io.tty?
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -81,6 +81,8 @@ module Hashira::Report::Phrases
|
|
|
81
81
|
|
|
82
82
|
def score(value) = format("%.2f", value)
|
|
83
83
|
|
|
84
|
+
def count(number, noun) = "#{number} #{number == 1 ? noun : "#{noun}s"}"
|
|
85
|
+
|
|
84
86
|
def clause(part) = "#{part[:users].join(", ")} #{verb(part)} #{part[:constants].join(", ")}"
|
|
85
87
|
|
|
86
88
|
def verb(part)
|
data/lib/hashira/report/text.rb
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
class Hashira::Report::Text
|
|
4
|
+
FINDINGS = 25
|
|
5
|
+
|
|
4
6
|
def initialize(view, io: $stdout)
|
|
5
7
|
@view = view
|
|
6
8
|
@io = io
|
|
@@ -19,31 +21,37 @@ class Hashira::Report::Text
|
|
|
19
21
|
def coupling
|
|
20
22
|
graph = @view.graph
|
|
21
23
|
header(graph)
|
|
22
|
-
Hashira::Report::MetricsTable
|
|
24
|
+
table(Hashira::Report::MetricsTable, graph)
|
|
23
25
|
Hashira::Report::DependencyMap.new(graph, io: @io).print
|
|
24
26
|
folded(graph.folds)
|
|
25
27
|
end
|
|
26
28
|
|
|
27
29
|
def folded(folds)
|
|
28
30
|
return if folds.empty?
|
|
29
|
-
@io.puts("
|
|
31
|
+
@io.puts("Folded (single-type classes joined to their base or domain):")
|
|
30
32
|
folds.each { @io.puts(" #{it[:from]} -> #{it[:to]} (#{it[:via]})") }
|
|
33
|
+
@io.puts
|
|
31
34
|
end
|
|
32
35
|
|
|
33
|
-
def
|
|
36
|
+
def table(kind, subject) = kind.new(subject, top: @view.top || kind::TOP, io: @io).print
|
|
37
|
+
|
|
38
|
+
def complexity = table(Hashira::Report::ComplexityTable, @view.complexity)
|
|
34
39
|
|
|
35
|
-
def hotspots = Hashira::Report::HotspotTable
|
|
40
|
+
def hotspots = table(Hashira::Report::HotspotTable, @view.hotspots)
|
|
36
41
|
|
|
37
42
|
def header(graph)
|
|
38
43
|
packages = graph.packages.size
|
|
39
|
-
@io.puts(banner(packages))
|
|
44
|
+
@io.puts(banner(graph.packaging, packages))
|
|
40
45
|
caveat if packages == 1
|
|
41
46
|
end
|
|
42
47
|
|
|
43
|
-
def banner(packages)
|
|
44
|
-
"Package (
|
|
48
|
+
def banner(grouping, packages)
|
|
49
|
+
"Package (#{grouping}) metrics for #{@view.project.label} " \
|
|
50
|
+
"(#{count(packages, "package")}, #{count(total, "file")})\n\n"
|
|
45
51
|
end
|
|
46
52
|
|
|
53
|
+
def count(number, noun) = Hashira::Report::Phrases.count(number, noun)
|
|
54
|
+
|
|
47
55
|
def total = @view.project.files.size
|
|
48
56
|
|
|
49
57
|
def caveat
|
|
@@ -55,7 +63,7 @@ class Hashira::Report::Text
|
|
|
55
63
|
|
|
56
64
|
def findings
|
|
57
65
|
all = @view.findings.all
|
|
58
|
-
@io.puts("
|
|
66
|
+
@io.puts("Findings (#{all.size}):")
|
|
59
67
|
list(all)
|
|
60
68
|
accepted
|
|
61
69
|
@io.puts("\n Full evidence + machine format: hashira --json") unless all.empty?
|
|
@@ -63,7 +71,14 @@ class Hashira::Report::Text
|
|
|
63
71
|
|
|
64
72
|
def list(all)
|
|
65
73
|
return @io.puts(" none ✓ — structure is healthy") if all.empty?
|
|
66
|
-
|
|
74
|
+
shown = all.first(@view.top || FINDINGS)
|
|
75
|
+
shown.each { Hashira::Report::FindingLines.new(it, indent: " ", io: @io).emit }
|
|
76
|
+
elided(all.size - shown.size)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def elided(rest)
|
|
80
|
+
return if rest.zero?
|
|
81
|
+
@io.puts(" … and #{rest} more — raise the cap with --top, or read them all with --json")
|
|
67
82
|
end
|
|
68
83
|
|
|
69
84
|
def accepted
|
data/lib/hashira/report/view.rb
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
module Hashira
|
|
4
4
|
module Report
|
|
5
|
-
View =
|
|
5
|
+
View =
|
|
6
|
+
Data.define(:project, :graph, :complexity, :duplication, :hotspots, :findings, :top, :compact) do
|
|
7
|
+
def initialize(top: nil, compact: nil, **) = super
|
|
8
|
+
end
|
|
6
9
|
end
|
|
7
10
|
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
|
|
5
|
+
class Hashira::Trees
|
|
6
|
+
def initialize(project)
|
|
7
|
+
@unparsed = []
|
|
8
|
+
@all = project.files.to_h { [it, parse(it)] }
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
attr_reader :all, :unparsed
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
def parse(path)
|
|
16
|
+
result = Prism.parse_file(path)
|
|
17
|
+
@unparsed << path if result.failure?
|
|
18
|
+
result.value
|
|
19
|
+
rescue SystemCallError => error
|
|
20
|
+
raise(Hashira::Error, "cannot read #{path} (#{error.message})")
|
|
21
|
+
end
|
|
22
|
+
end
|
data/lib/hashira/version.rb
CHANGED
data/lib/hashira.rb
CHANGED
|
@@ -43,6 +43,8 @@ require_relative "hashira/ci/gate"
|
|
|
43
43
|
require_relative "hashira/ci/improvement"
|
|
44
44
|
require_relative "hashira/ci/ratchet"
|
|
45
45
|
require_relative "hashira/ci/ratchet_report"
|
|
46
|
+
require_relative "hashira/ci/scope"
|
|
47
|
+
require_relative "hashira/ci/status"
|
|
46
48
|
require_relative "hashira/cli"
|
|
47
49
|
require_relative "hashira/cli/arguments"
|
|
48
50
|
require_relative "hashira/cli/command_line"
|
|
@@ -50,6 +52,7 @@ require_relative "hashira/cli/fail_on"
|
|
|
50
52
|
require_relative "hashira/cli/flag"
|
|
51
53
|
require_relative "hashira/cli/flags"
|
|
52
54
|
require_relative "hashira/cli/format"
|
|
55
|
+
require_relative "hashira/cli/needs"
|
|
53
56
|
require_relative "hashira/cli/options"
|
|
54
57
|
require_relative "hashira/cli/package_by"
|
|
55
58
|
require_relative "hashira/cli/run"
|
|
@@ -118,6 +121,7 @@ require_relative "hashira/hotspots/file_cost"
|
|
|
118
121
|
require_relative "hashira/hotspots/rollup"
|
|
119
122
|
require_relative "hashira/pipeline"
|
|
120
123
|
require_relative "hashira/project"
|
|
124
|
+
require_relative "hashira/report/columns"
|
|
121
125
|
require_relative "hashira/report/complexity_table"
|
|
122
126
|
require_relative "hashira/report/dependency_map"
|
|
123
127
|
require_relative "hashira/report/finding_lines"
|
|
@@ -125,6 +129,7 @@ require_relative "hashira/report/graph_payload"
|
|
|
125
129
|
require_relative "hashira/report/hotspot_table"
|
|
126
130
|
require_relative "hashira/report/json"
|
|
127
131
|
require_relative "hashira/report/metrics_table"
|
|
132
|
+
require_relative "hashira/report/notices"
|
|
128
133
|
require_relative "hashira/report/phrases"
|
|
129
134
|
require_relative "hashira/report/smell_phrases"
|
|
130
135
|
require_relative "hashira/report/text"
|
|
@@ -149,4 +154,5 @@ require_relative "hashira/smells/scope"
|
|
|
149
154
|
require_relative "hashira/smells/too_many_instance_variables"
|
|
150
155
|
require_relative "hashira/smells/utility_function"
|
|
151
156
|
require_relative "hashira/smells/visibility"
|
|
157
|
+
require_relative "hashira/trees"
|
|
152
158
|
require_relative "hashira/version"
|
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.
|
|
4
|
+
version: 0.7.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Giacomo GK
|
|
@@ -40,6 +40,8 @@ files:
|
|
|
40
40
|
- lib/hashira/ci/improvement.rb
|
|
41
41
|
- lib/hashira/ci/ratchet.rb
|
|
42
42
|
- lib/hashira/ci/ratchet_report.rb
|
|
43
|
+
- lib/hashira/ci/scope.rb
|
|
44
|
+
- lib/hashira/ci/status.rb
|
|
43
45
|
- lib/hashira/cli.rb
|
|
44
46
|
- lib/hashira/cli/arguments.rb
|
|
45
47
|
- lib/hashira/cli/command_line.rb
|
|
@@ -47,10 +49,12 @@ files:
|
|
|
47
49
|
- lib/hashira/cli/flag.rb
|
|
48
50
|
- lib/hashira/cli/flags.rb
|
|
49
51
|
- lib/hashira/cli/format.rb
|
|
52
|
+
- lib/hashira/cli/needs.rb
|
|
50
53
|
- lib/hashira/cli/options.rb
|
|
51
54
|
- lib/hashira/cli/package_by.rb
|
|
52
55
|
- lib/hashira/cli/run.rb
|
|
53
56
|
- lib/hashira/cli/skip.rb
|
|
57
|
+
- lib/hashira/cli/top.rb
|
|
54
58
|
- lib/hashira/cli/usage.rb
|
|
55
59
|
- lib/hashira/complexity/boolean_run.rb
|
|
56
60
|
- lib/hashira/complexity/cognitive_score.rb
|
|
@@ -115,6 +119,7 @@ files:
|
|
|
115
119
|
- lib/hashira/hotspots/rollup.rb
|
|
116
120
|
- lib/hashira/pipeline.rb
|
|
117
121
|
- lib/hashira/project.rb
|
|
122
|
+
- lib/hashira/report/columns.rb
|
|
118
123
|
- lib/hashira/report/complexity_table.rb
|
|
119
124
|
- lib/hashira/report/dependency_map.rb
|
|
120
125
|
- lib/hashira/report/finding_lines.rb
|
|
@@ -122,6 +127,7 @@ files:
|
|
|
122
127
|
- lib/hashira/report/hotspot_table.rb
|
|
123
128
|
- lib/hashira/report/json.rb
|
|
124
129
|
- lib/hashira/report/metrics_table.rb
|
|
130
|
+
- lib/hashira/report/notices.rb
|
|
125
131
|
- lib/hashira/report/phrases.rb
|
|
126
132
|
- lib/hashira/report/smell_phrases.rb
|
|
127
133
|
- lib/hashira/report/text.rb
|
|
@@ -149,6 +155,7 @@ files:
|
|
|
149
155
|
- lib/hashira/smells/too_many_instance_variables.rb
|
|
150
156
|
- lib/hashira/smells/utility_function.rb
|
|
151
157
|
- lib/hashira/smells/visibility.rb
|
|
158
|
+
- lib/hashira/trees.rb
|
|
152
159
|
- lib/hashira/version.rb
|
|
153
160
|
homepage: https://github.com/giacope/hashira
|
|
154
161
|
licenses:
|