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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +53 -0
- data/README.md +327 -32
- data/lib/hashira/analysis/census.rb +0 -2
- data/lib/hashira/analysis/cycle_findings.rb +1 -1
- data/lib/hashira/analysis/cycle_search.rb +1 -1
- data/lib/hashira/analysis/definitions.rb +1 -1
- data/lib/hashira/analysis/finding.rb +6 -2
- data/lib/hashira/analysis/graph.rb +3 -3
- data/lib/hashira/analysis/node_walk.rb +7 -1
- data/lib/hashira/analysis/references.rb +2 -2
- data/lib/hashira/analysis/sdp_check.rb +1 -1
- data/lib/hashira/analysis/type_walk.rb +1 -1
- data/lib/hashira/churn.rb +22 -0
- data/lib/hashira/ci/accepted.rb +11 -7
- data/lib/hashira/ci/baseline.rb +40 -0
- data/lib/hashira/ci/diff.rb +15 -0
- data/lib/hashira/ci/edge_diff_report.rb +4 -27
- data/lib/hashira/ci/finding_diff_report.rb +26 -0
- data/lib/hashira/ci/gate.rb +7 -5
- data/lib/hashira/ci/improvement.rb +19 -0
- data/lib/hashira/ci/ratchet.rb +16 -20
- data/lib/hashira/ci/ratchet_report.rb +42 -0
- data/lib/hashira/cli/command_line.rb +12 -6
- data/lib/hashira/cli/fail_on.rb +4 -2
- data/lib/hashira/cli/options.rb +1 -1
- data/lib/hashira/cli/run.rb +9 -3
- data/lib/hashira/cli/skip.rb +27 -0
- data/lib/hashira/cli/usage.rb +10 -5
- data/lib/hashira/cli.rb +1 -1
- data/lib/hashira/complexity/analyzer.rb +45 -0
- data/lib/hashira/complexity/boolean_run.rb +22 -0
- data/lib/hashira/complexity/cognitive_score.rb +72 -0
- data/lib/hashira/complexity/if_chain.rb +45 -0
- data/lib/hashira/complexity/method_finding.rb +52 -0
- data/lib/hashira/complexity/method_score.rb +17 -0
- data/lib/hashira/complexity/rescue_scan.rb +28 -0
- data/lib/hashira/complexity/rollup.rb +22 -0
- data/lib/hashira/duplication/analyzer.rb +21 -0
- data/lib/hashira/duplication/cluster.rb +24 -0
- data/lib/hashira/duplication/clusterer.rb +44 -0
- data/lib/hashira/duplication/delta.rb +39 -0
- data/lib/hashira/duplication/duplication_finding.rb +30 -0
- data/lib/hashira/duplication/extractor.rb +31 -0
- data/lib/hashira/duplication/fragment.rb +42 -0
- data/lib/hashira/duplication/grouping.rb +24 -0
- data/lib/hashira/duplication/index.rb +35 -0
- data/lib/hashira/duplication/maximal.rb +21 -0
- data/lib/hashira/duplication/near_miss.rb +33 -0
- data/lib/hashira/duplication/sequence.rb +34 -0
- data/lib/hashira/duplication/similarity.rb +50 -0
- data/lib/hashira/duplication/union_find.rb +21 -0
- data/lib/hashira/duplication/variance.rb +57 -0
- data/lib/hashira/hotspots/file_cost.rb +19 -0
- data/lib/hashira/hotspots/rollup.rb +33 -0
- data/lib/hashira/pipeline.rb +28 -6
- data/lib/hashira/project.rb +10 -7
- data/lib/hashira/report/complexity_table.rb +40 -0
- data/lib/hashira/report/dependency_map.rb +8 -5
- data/lib/hashira/report/finding_lines.rb +1 -1
- data/lib/hashira/report/graph_payload.rb +29 -0
- data/lib/hashira/report/hotspot_table.rb +41 -0
- data/lib/hashira/report/json.rb +24 -17
- data/lib/hashira/report/metrics_table.rb +1 -2
- data/lib/hashira/report/text.rb +24 -14
- data/lib/hashira/report/view.rb +7 -0
- data/lib/hashira/version.rb +1 -1
- data/lib/hashira.rb +36 -0
- 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
|
data/lib/hashira/pipeline.rb
CHANGED
|
@@ -4,21 +4,43 @@ require "prism"
|
|
|
4
4
|
|
|
5
5
|
module Hashira
|
|
6
6
|
class Pipeline
|
|
7
|
-
|
|
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
|
-
|
|
10
|
-
@
|
|
11
|
-
@graph = Analysis::Graph.new(project, trees, @
|
|
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
|
-
|
|
20
|
+
def complexity
|
|
21
|
+
@complexity ||= Complexity::Analyzer.new(@project, @trees) if enabled?(:complexity)
|
|
22
|
+
end
|
|
17
23
|
|
|
18
|
-
def
|
|
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
|
data/lib/hashira/project.rb
CHANGED
|
@@ -5,24 +5,27 @@ module Hashira
|
|
|
5
5
|
ROOT_PACKAGE = "(root)"
|
|
6
6
|
|
|
7
7
|
def self.detect(directories)
|
|
8
|
-
|
|
8
|
+
directories.empty? ? new(default_directories) : new(directories)
|
|
9
|
+
end
|
|
9
10
|
|
|
10
|
-
|
|
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
|
-
|
|
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?(
|
|
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 {
|
|
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["#{
|
|
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?("#{
|
|
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(
|
|
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
|
|
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} · #{
|
|
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
|
data/lib/hashira/report/json.rb
CHANGED
|
@@ -5,37 +5,44 @@ require "json"
|
|
|
5
5
|
module Hashira
|
|
6
6
|
module Report
|
|
7
7
|
class Json
|
|
8
|
-
def initialize(
|
|
9
|
-
@
|
|
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(
|
|
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
|
|
23
|
-
|
|
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
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
|
35
|
-
@
|
|
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
|
data/lib/hashira/report/text.rb
CHANGED
|
@@ -3,27 +3,37 @@
|
|
|
3
3
|
module Hashira
|
|
4
4
|
module Report
|
|
5
5
|
class Text
|
|
6
|
-
def initialize(
|
|
7
|
-
@
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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(
|
|
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}):"
|
data/lib/hashira/version.rb
CHANGED
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.
|
|
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
|
|
13
|
-
|
|
14
|
-
|
|
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.
|
|
126
|
+
rubygems_version: 4.0.17
|
|
89
127
|
specification_version: 4
|
|
90
|
-
summary:
|
|
128
|
+
summary: Coupling, cognitive-complexity, and duplication metrics for Ruby, via Prism
|
|
91
129
|
test_files: []
|