hashira 0.4.0 → 0.5.1

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 (96) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +86 -0
  3. data/README.md +90 -30
  4. data/lib/hashira/analysis/finding.rb +2 -2
  5. data/lib/hashira/analysis/syntax.rb +6 -2
  6. data/lib/hashira/churn.rb +1 -1
  7. data/lib/hashira/ci/ratchet.rb +5 -9
  8. data/lib/hashira/cli/command_line.rb +10 -49
  9. data/lib/hashira/cli/fail_on.rb +9 -5
  10. data/lib/hashira/cli/flag.rb +29 -0
  11. data/lib/hashira/cli/flags.rb +50 -0
  12. data/lib/hashira/cli/format.rb +13 -0
  13. data/lib/hashira/cli/options.rb +2 -0
  14. data/lib/hashira/cli/run.rb +8 -4
  15. data/lib/hashira/cli/usage.rb +20 -21
  16. data/lib/hashira/cli.rb +1 -1
  17. data/lib/hashira/complexity/boolean_run.rb +5 -3
  18. data/lib/hashira/complexity/cognitive_score.rb +21 -14
  19. data/lib/hashira/complexity/if_chain.rb +14 -14
  20. data/lib/hashira/complexity/method_finding.rb +4 -19
  21. data/lib/hashira/complexity/rescue_scan.rb +9 -9
  22. data/lib/hashira/complexity/{analyzer.rb → scores.rb} +6 -5
  23. data/lib/hashira/coupling/audiences.rb +49 -0
  24. data/lib/hashira/{analysis → coupling}/catalog.rb +3 -3
  25. data/lib/hashira/{analysis → coupling}/census.rb +17 -8
  26. data/lib/hashira/{analysis → coupling}/constant_registry.rb +11 -6
  27. data/lib/hashira/coupling/cycle_findings.rb +31 -0
  28. data/lib/hashira/{analysis → coupling}/cycle_search.rb +1 -1
  29. data/lib/hashira/{analysis → coupling}/cycles.rb +2 -2
  30. data/lib/hashira/{analysis → coupling}/definition.rb +6 -2
  31. data/lib/hashira/{analysis → coupling}/definitions.rb +5 -2
  32. data/lib/hashira/{analysis → coupling}/edge.rb +1 -1
  33. data/lib/hashira/{analysis → coupling}/edge_map.rb +10 -10
  34. data/lib/hashira/{analysis → coupling}/folder_placement.rb +2 -2
  35. data/lib/hashira/{analysis → coupling}/folding.rb +4 -2
  36. data/lib/hashira/{analysis → coupling}/graph.rb +18 -8
  37. data/lib/hashira/{analysis → coupling}/metric.rb +1 -1
  38. data/lib/hashira/coupling/mixed_audience_findings.rb +32 -0
  39. data/lib/hashira/{analysis → coupling}/namespace_placement.rb +3 -3
  40. data/lib/hashira/{analysis → coupling}/namespace_prefix.rb +1 -1
  41. data/lib/hashira/{analysis → coupling}/naming.rb +2 -2
  42. data/lib/hashira/{analysis → coupling}/no_folding.rb +1 -1
  43. data/lib/hashira/{analysis → coupling}/placement.rb +3 -3
  44. data/lib/hashira/coupling/references.rb +55 -0
  45. data/lib/hashira/coupling/report.rb +28 -0
  46. data/lib/hashira/coupling/roll_call.rb +45 -0
  47. data/lib/hashira/coupling/roll_call_findings.rb +30 -0
  48. data/lib/hashira/{analysis → coupling}/roster.rb +6 -2
  49. data/lib/hashira/{analysis → coupling}/rule.rb +1 -1
  50. data/lib/hashira/{analysis/resolver.rb → coupling/scope.rb} +7 -3
  51. data/lib/hashira/{analysis → coupling}/sdp_check.rb +1 -1
  52. data/lib/hashira/coupling/sdp_violation_findings.rb +25 -0
  53. data/lib/hashira/coupling/wide_edge_findings.rb +25 -0
  54. data/lib/hashira/coupling/words.rb +29 -0
  55. data/lib/hashira/diagram/{renderer.rb → source.rb} +2 -2
  56. data/lib/hashira/duplication/{analyzer.rb → clones.rb} +3 -3
  57. data/lib/hashira/duplication/{clusterer.rb → clusters.rb} +3 -3
  58. data/lib/hashira/duplication/delta.rb +6 -14
  59. data/lib/hashira/duplication/duplication_finding.rb +8 -10
  60. data/lib/hashira/duplication/{extractor.rb → harvest.rb} +1 -1
  61. data/lib/hashira/duplication/sequence.rb +3 -1
  62. data/lib/hashira/duplication/similarity.rb +5 -1
  63. data/lib/hashira/duplication/variance.rb +3 -3
  64. data/lib/hashira/hotspots/rollup.rb +1 -1
  65. data/lib/hashira/pipeline.rb +18 -10
  66. data/lib/hashira/report/complexity_table.rb +1 -1
  67. data/lib/hashira/report/finding_lines.rb +1 -1
  68. data/lib/hashira/report/json.rb +5 -3
  69. data/lib/hashira/report/phrases.rb +98 -0
  70. data/lib/hashira/report/smell_phrases.rb +68 -0
  71. data/lib/hashira/smells/census.rb +32 -0
  72. data/lib/hashira/smells/check.rb +34 -0
  73. data/lib/hashira/smells/conditions.rb +44 -0
  74. data/lib/hashira/smells/contexts.rb +64 -0
  75. data/lib/hashira/smells/control_parameter.rb +22 -0
  76. data/lib/hashira/smells/data_clump.rb +32 -0
  77. data/lib/hashira/smells/duplicate_method_call.rb +54 -0
  78. data/lib/hashira/smells/feature_envy.rb +17 -0
  79. data/lib/hashira/smells/instance_variable_assumption.rb +46 -0
  80. data/lib/hashira/smells/manual_dispatch.rb +16 -0
  81. data/lib/hashira/smells/module_initialize.rb +11 -0
  82. data/lib/hashira/smells/nil_check.rb +32 -0
  83. data/lib/hashira/smells/param_check.rb +51 -0
  84. data/lib/hashira/smells/refs.rb +53 -0
  85. data/lib/hashira/smells/repeated_conditional.rb +31 -0
  86. data/lib/hashira/smells/report.rb +34 -0
  87. data/lib/hashira/smells/scope.rb +29 -0
  88. data/lib/hashira/smells/too_many_instance_variables.rb +26 -0
  89. data/lib/hashira/smells/utility_function.rb +13 -0
  90. data/lib/hashira/smells/visibility.rb +64 -0
  91. data/lib/hashira/version.rb +1 -1
  92. data/lib/hashira.rb +68 -30
  93. metadata +63 -31
  94. data/lib/hashira/analysis/cycle_findings.rb +0 -38
  95. data/lib/hashira/analysis/references.rb +0 -40
  96. data/lib/hashira/analysis/sdp_violation_findings.rb +0 -29
@@ -1,38 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "rule"
4
-
5
- class Hashira::Analysis::CycleFindings < Hashira::Analysis::Rule
6
- KIND = "cycle"
7
-
8
- def list
9
- loops.map { entry(it) }
10
- end
11
-
12
- private
13
-
14
- def loops
15
- cycles = graph.cycles
16
- graph.packages.select { cycles.through?(it) }.sort.map { cycles.path(it) }.uniq { it[..-2].sort }
17
- end
18
-
19
- def entry(path)
20
- package = path.first
21
- finding(
22
- package:, cycle: path, evidence: evidence(path),
23
- message: message(package, path, graph.cycles.weakest(path))
24
- )
25
- end
26
-
27
- def message(package, path, weak_edge)
28
- from, to = weak_edge
29
- weight = graph.weight(from, to)
30
- "#{package} can reach itself: #{path.join(" -> ")} — any change may ripple back " \
31
- "around. The lightest edge on this cycle is #{from} -> #{to} " \
32
- "(#{weight} ref#{"s" unless weight == 1})."
33
- end
34
-
35
- def evidence(path)
36
- path.each_cons(2).flat_map { |from, to| graph.evidence(from, to).to_a.first(2) }
37
- end
38
- end
@@ -1,40 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "prism"
4
-
5
- module Hashira
6
- module Analysis
7
- module References
8
- module_function
9
-
10
- def list(tree)
11
- sightings(tree).map(&:first)
12
- end
13
-
14
- def sightings(tree, roots = nil)
15
- [].tap { collect(tree, [], it, roots) }
16
- end
17
-
18
- def collect(node, nesting, accumulator, roots, home = nesting)
19
- return unless node
20
- return accumulator << sighting(node, nesting, home) if constant?(node)
21
- return enter(node, nesting, accumulator, roots) if definition?(node)
22
- node.compact_child_nodes.each { collect(it, nesting, accumulator, roots, home) }
23
- end
24
-
25
- def sighting(node, nesting, home)
26
- [Syntax.segments(node), node.location.start_line, Syntax.cbase?(node) ? nil : nesting, home]
27
- end
28
-
29
- def constant?(node) = node.is_a?(Prism::ConstantPathNode) || node.is_a?(Prism::ConstantReadNode)
30
-
31
- def definition?(node) = node.is_a?(Prism::ClassNode) || node.is_a?(Prism::ModuleNode)
32
-
33
- def enter(node, nesting, accumulator, roots)
34
- opened = nesting + [Syntax.anchor(nesting, Syntax.segments(node.constant_path), roots)]
35
- collect(node.superclass, nesting, accumulator, roots, opened) if node.is_a?(Prism::ClassNode)
36
- collect(node.body, opened, accumulator, roots)
37
- end
38
- end
39
- end
40
- end
@@ -1,29 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "rule"
4
-
5
- class Hashira::Analysis::SdpViolationFindings < Hashira::Analysis::Rule
6
- KIND = "sdp_violation"
7
-
8
- def list
9
- ranked.map { |from, to| violation(from, to) }
10
- end
11
-
12
- private
13
-
14
- def ranked = graph.violations.sort_by { |from, to| instability(from) - instability(to) }
15
-
16
- def violation(from, to)
17
- finding(package: from, evidence: graph.evidence(from, to).to_a.first(5), message: message(from, to))
18
- end
19
-
20
- def message(from, to)
21
- "#{from} (I=#{label(from)}) depends on the LESS stable #{to} " \
22
- "(I=#{label(to)}) — churn in #{to} will force churn in #{from}. " \
23
- "Invert the edge or extract the stable part of #{to} that #{from} needs."
24
- end
25
-
26
- def instability(package) = metrics[package].instability
27
-
28
- def label(package) = format("%.2f", instability(package))
29
- end