hashira 0.6.0 → 0.8.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 (110) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +106 -0
  3. data/README.md +148 -53
  4. data/exe/hashira +1 -1
  5. data/lib/hashira/analysis/finding.rb +5 -1
  6. data/lib/hashira/analysis/syntax.rb +6 -6
  7. data/lib/hashira/churn.rb +3 -4
  8. data/lib/hashira/ci/accepted.rb +9 -10
  9. data/lib/hashira/ci/baseline.rb +39 -12
  10. data/lib/hashira/ci/comparison.rb +24 -0
  11. data/lib/hashira/ci/diff.rb +4 -8
  12. data/lib/hashira/ci/finding_diff_report.rb +10 -0
  13. data/lib/hashira/ci/gate.rb +10 -2
  14. data/lib/hashira/ci/improvement.rb +1 -1
  15. data/lib/hashira/ci/ratchet.rb +41 -10
  16. data/lib/hashira/ci/ratchet_report.rb +8 -6
  17. data/lib/hashira/ci/scope.rb +10 -0
  18. data/lib/hashira/ci/slice.rb +17 -0
  19. data/lib/hashira/ci/status.rb +7 -0
  20. data/lib/hashira/ci/sweep.rb +13 -0
  21. data/lib/hashira/cli/arguments.rb +17 -8
  22. data/lib/hashira/cli/choice.rb +9 -0
  23. data/lib/hashira/cli/command_line.rb +8 -5
  24. data/lib/hashira/cli/fail_on.rb +17 -7
  25. data/lib/hashira/cli/flag.rb +3 -1
  26. data/lib/hashira/cli/flags.rb +23 -0
  27. data/lib/hashira/cli/format.rb +1 -1
  28. data/lib/hashira/cli/needs.rb +45 -0
  29. data/lib/hashira/cli/only.rb +16 -0
  30. data/lib/hashira/cli/options.rb +12 -3
  31. data/lib/hashira/cli/package_by.rb +1 -1
  32. data/lib/hashira/cli/run.rb +36 -8
  33. data/lib/hashira/cli/top.rb +13 -0
  34. data/lib/hashira/cli/usage.rb +5 -1
  35. data/lib/hashira/cli.rb +31 -11
  36. data/lib/hashira/complexity/cognitive_score.rb +44 -33
  37. data/lib/hashira/complexity/method_finding.rb +7 -5
  38. data/lib/hashira/complexity/method_score.rb +1 -1
  39. data/lib/hashira/complexity/scores.rb +11 -9
  40. data/lib/hashira/coupling/audiences.rb +3 -3
  41. data/lib/hashira/coupling/catalog.rb +8 -6
  42. data/lib/hashira/coupling/census.rb +42 -22
  43. data/lib/hashira/coupling/cycle_search.rb +11 -7
  44. data/lib/hashira/coupling/definitions.rb +1 -1
  45. data/lib/hashira/coupling/edge_map.rb +9 -8
  46. data/lib/hashira/coupling/folding.rb +5 -5
  47. data/lib/hashira/coupling/graph.rb +16 -9
  48. data/lib/hashira/coupling/metric.rb +9 -1
  49. data/lib/hashira/coupling/naming.rb +4 -4
  50. data/lib/hashira/coupling/references.rb +12 -12
  51. data/lib/hashira/coupling/report.rb +7 -9
  52. data/lib/hashira/coupling/roll_call.rb +1 -1
  53. data/lib/hashira/coupling/roll_call_findings.rb +1 -1
  54. data/lib/hashira/coupling/roster.rb +37 -12
  55. data/lib/hashira/coupling/rule.rb +1 -1
  56. data/lib/hashira/coupling/sdp_violation_findings.rb +3 -1
  57. data/lib/hashira/diagram/dot.rb +9 -6
  58. data/lib/hashira/diagram/mermaid.rb +11 -8
  59. data/lib/hashira/diagram/source.rb +5 -2
  60. data/lib/hashira/duplication/clones.rb +1 -1
  61. data/lib/hashira/duplication/clusters.rb +10 -7
  62. data/lib/hashira/duplication/delta.rb +1 -4
  63. data/lib/hashira/duplication/duplication_finding.rb +4 -2
  64. data/lib/hashira/duplication/fragment.rb +2 -2
  65. data/lib/hashira/duplication/harvest.rb +7 -5
  66. data/lib/hashira/duplication/index.rb +3 -2
  67. data/lib/hashira/duplication/similarity.rb +2 -2
  68. data/lib/hashira/duplication/variance.rb +1 -1
  69. data/lib/hashira/error.rb +0 -3
  70. data/lib/hashira/focus.rb +29 -0
  71. data/lib/hashira/git_log.rb +19 -0
  72. data/lib/hashira/hotspots/file_cost.rb +1 -1
  73. data/lib/hashira/hotspots/rollup.rb +2 -2
  74. data/lib/hashira/pipeline.rb +26 -24
  75. data/lib/hashira/project.rb +47 -32
  76. data/lib/hashira/report/columns.rb +54 -0
  77. data/lib/hashira/report/complexity_table.rb +12 -16
  78. data/lib/hashira/report/dependency_map.rb +1 -0
  79. data/lib/hashira/report/graph_payload.rb +1 -1
  80. data/lib/hashira/report/hotspot_table.rb +6 -14
  81. data/lib/hashira/report/json.rb +10 -2
  82. data/lib/hashira/report/metrics_table.rb +19 -20
  83. data/lib/hashira/report/notices.rb +37 -0
  84. data/lib/hashira/report/phrases.rb +25 -18
  85. data/lib/hashira/report/text.rb +24 -9
  86. data/lib/hashira/report/view.rb +4 -1
  87. data/lib/hashira/smells/census.rb +3 -4
  88. data/lib/hashira/smells/check.rb +1 -5
  89. data/lib/hashira/smells/contexts.rb +4 -4
  90. data/lib/hashira/smells/control_parameter.rb +1 -1
  91. data/lib/hashira/smells/data_clump.rb +2 -4
  92. data/lib/hashira/smells/duplicate_method_call.rb +3 -3
  93. data/lib/hashira/smells/feature_envy.rb +3 -3
  94. data/lib/hashira/smells/foreign.rb +14 -14
  95. data/lib/hashira/smells/instance_variable_assumption.rb +3 -3
  96. data/lib/hashira/smells/kind.rb +9 -0
  97. data/lib/hashira/smells/manual_dispatch.rb +1 -1
  98. data/lib/hashira/smells/module_initialize.rb +0 -2
  99. data/lib/hashira/smells/nil_check.rb +1 -1
  100. data/lib/hashira/smells/ownership.rb +19 -7
  101. data/lib/hashira/smells/param_check.rb +2 -2
  102. data/lib/hashira/smells/refs.rb +13 -7
  103. data/lib/hashira/smells/repeated_conditional.rb +1 -3
  104. data/lib/hashira/smells/report.rb +16 -7
  105. data/lib/hashira/smells/too_many_instance_variables.rb +3 -3
  106. data/lib/hashira/smells/visibility.rb +18 -11
  107. data/lib/hashira/trees.rb +26 -0
  108. data/lib/hashira/version.rb +1 -1
  109. data/lib/hashira.rb +14 -0
  110. metadata +16 -1
@@ -5,15 +5,13 @@ class Hashira::Smells::DataClump < Hashira::Smells::Check
5
5
 
6
6
  MIN_SIZE = 2
7
7
 
8
- def self.judge? = true
9
-
10
8
  private
11
9
 
12
10
  def smelly? = clumps.any?
13
11
 
14
- def candidates = @candidates ||= subject.owned.map { [it, it.arguments.sort] }
12
+ def candidates = @_candidates ||= subject.owned.map { [it, it.arguments.sort] }
15
13
 
16
- def clumps = @clumps ||= shared.map { |clump| [clump, holders(clump)] }
14
+ def clumps = @_clumps ||= shared.map { |clump| [clump, holders(clump)] }
17
15
 
18
16
  def shared = candidates.combination(MAX_COPIES + 1).filter_map { |group| clump(group) }.uniq
19
17
 
@@ -9,14 +9,14 @@ class Hashira::Smells::DuplicateMethodCall < Hashira::Smells::Check
9
9
 
10
10
  def smelly? = repeats.any?
11
11
 
12
- def calls = @calls ||= Hashira::Smells::Scope.inside(subject.node).grep(Prism::CallNode)
12
+ def calls = @_calls ||= Hashira::Smells::Scope.inside(subject.node).grep(Prism::CallNode)
13
13
 
14
14
  def plain?(node)
15
15
  !node.receiver && !node.arguments && !node.block.is_a?(Prism::BlockArgumentNode)
16
16
  end
17
17
 
18
18
  def repeats
19
- @repeats ||= usual.reject { |_handle, nodes| whole.value?(nodes) }.merge(whole)
19
+ @_repeats ||= usual.reject { |_handle, nodes| whole.value?(nodes) }.merge(whole)
20
20
  end
21
21
 
22
22
  def usual
@@ -25,7 +25,7 @@ class Hashira::Smells::DuplicateMethodCall < Hashira::Smells::Check
25
25
  end
26
26
 
27
27
  def whole
28
- @whole ||=
28
+ @_whole ||=
29
29
  calls.select { it.block.is_a?(Prism::BlockNode) }
30
30
  .group_by { it.slice.gsub(/\s+/, " ") }.select { |_handle, group| group.size > LIMIT }
31
31
  end
@@ -7,11 +7,11 @@ class Hashira::Smells::FeatureEnvy < Hashira::Smells::Check
7
7
  !subject.singleton? && !subject.mixin? && refs.ego.positive? && envied.any?
8
8
  end
9
9
 
10
- def refs = @refs ||= Hashira::Smells::Refs.new(subject.node)
10
+ def refs = @_refs ||= Hashira::Smells::Refs.new(subject.node)
11
11
 
12
- def envied = @envied ||= refs.envious.reject { foreign.dismiss?(it) }
12
+ def envied = @_envied ||= refs.envious.reject { foreign.dismiss?(it) }
13
13
 
14
- def foreign = @foreign ||= Hashira::Smells::Foreign.new(subject, subject.ownership)
14
+ def foreign = @_foreign ||= Hashira::Smells::Foreign.new(subject, subject.ownership)
15
15
 
16
16
  def detail = { site:, names: envied }
17
17
 
@@ -18,9 +18,7 @@ class Hashira::Smells::Foreign
18
18
  ].freeze
19
19
 
20
20
  def initialize(subject, ownership)
21
- node = subject.node
22
- @body = Hashira::Smells::Scope.inside(node)
23
- @tail = tail(node)
21
+ @subject = subject
24
22
  @ownership = ownership
25
23
  end
26
24
 
@@ -35,12 +33,16 @@ class Hashira::Smells::Foreign
35
33
 
36
34
  private
37
35
 
36
+ def body = @_body ||= Hashira::Smells::Scope.inside(@subject.node)
37
+
38
+ def tail = @_tail ||= Hashira::Analysis::Syntax.statements(@subject.node).compact.last
39
+
38
40
  def convert?
39
- @tail.is_a?(Prism::CallNode) && @tail.name == :new &&
40
- Hashira::Analysis::Syntax.segments(@tail.receiver).any? && stateless?
41
+ tail.is_a?(Prism::CallNode) && tail.name == :new &&
42
+ Hashira::Analysis::Syntax.segments(tail.receiver).any? && stateless?
41
43
  end
42
44
 
43
- def stateless? = @body.none? { STATE.include?(it.class) }
45
+ def stateless? = body.none? { STATE.include?(it.class) }
44
46
 
45
47
  def fenced?(name)
46
48
  tested = tests { it == name }
@@ -48,7 +50,7 @@ class Hashira::Smells::Foreign
48
50
  end
49
51
 
50
52
  def wire?(name)
51
- calls = @body.grep(Prism::CallNode).select { |call| local?(call.receiver) { it == name } }
53
+ calls = body.grep(Prism::CallNode).select { |call| local?(call.receiver) { it == name } }
52
54
  calls.any? && calls.all? { keyed?(it) } && reassignments(name).none?
53
55
  end
54
56
 
@@ -75,11 +77,11 @@ class Hashira::Smells::Foreign
75
77
  def reassignments(name) = among(Prism::LocalVariableOperatorWriteNode, name)
76
78
 
77
79
  def among(kind, name)
78
- @body.grep(kind).select { it.name == name }
80
+ body.grep(kind).select { it.name == name }
79
81
  end
80
82
 
81
83
  def snares(name)
82
- @body.grep(Prism::RescueNode).select { it.reference&.name == name }
84
+ body.grep(Prism::RescueNode).select { it.reference&.name == name }
83
85
  end
84
86
 
85
87
  def alien?(exceptions)
@@ -107,15 +109,15 @@ class Hashira::Smells::Foreign
107
109
  end
108
110
 
109
111
  def probes(&)
110
- @body.grep(Prism::CallNode).select { TYPE_TESTS.include?(it.name) && local?(it.receiver, &) }.filter_map { key(it) }
112
+ body.grep(Prism::CallNode).select { TYPE_TESTS.include?(it.name) && local?(it.receiver, &) }.filter_map { key(it) }
111
113
  end
112
114
 
113
115
  def arms(&)
114
- @body.grep(Prism::CaseNode).select { local?(it.predicate, &) }.flat_map(&:conditions).flat_map(&:conditions)
116
+ body.grep(Prism::CaseNode).select { local?(it.predicate, &) }.flat_map(&:conditions).flat_map(&:conditions)
115
117
  end
116
118
 
117
119
  def lookups(&)
118
- @body.grep(Prism::CallNode).select { it.name == :[] && sorts?(key(it), &) }.flat_map { @ownership.keys(Hashira::Analysis::Syntax.segments(it.receiver)) }
120
+ body.grep(Prism::CallNode).select { it.name == :[] && sorts?(key(it), &) }.flat_map { @ownership.keys(Hashira::Analysis::Syntax.segments(it.receiver)) }
119
121
  end
120
122
 
121
123
  def sorts?(argument, &)
@@ -126,6 +128,4 @@ class Hashira::Smells::Foreign
126
128
  def local?(node, &) = node.is_a?(Prism::LocalVariableReadNode) && yield(node.name)
127
129
 
128
130
  def key(call) = call.arguments&.arguments&.first
129
-
130
- def tail(def_node) = Hashira::Analysis::Syntax.statements(def_node).compact.last
131
131
  end
@@ -26,13 +26,13 @@ class Hashira::Smells::InstanceVariableAssumption < Hashira::Smells::Check
26
26
  end
27
27
  end
28
28
 
29
- def self.judge? = true
30
-
31
29
  private
32
30
 
33
31
  def smelly? = subject.kind == :class && assumed.any?
34
32
 
35
- def assumed = @assumed ||= (read - prepared).uniq.sort
33
+ def assumed = @_assumed ||= (read - prepared).uniq.sort.reject { cache?(it) }
34
+
35
+ def cache?(name) = name.start_with?("@_")
36
36
 
37
37
  def read = subject.owned.flat_map { Harvest.reads(it.node) }
38
38
 
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Hashira::Smells::Kind
4
+ def initialize(check)
5
+ @check = check
6
+ end
7
+
8
+ def to_s = @check.name.split("::").last.gsub(/(?<=[a-z])(?=[A-Z])/, "_").downcase
9
+ end
@@ -8,7 +8,7 @@ class Hashira::Smells::ManualDispatch < Hashira::Smells::Check
8
8
  def smelly? = sightings.any?
9
9
 
10
10
  def sightings
11
- @sightings ||= Hashira::Smells::Scope.inside(subject.node)
11
+ @_sightings ||= Hashira::Smells::Scope.inside(subject.node)
12
12
  .select { it.is_a?(Prism::CallNode) && it.name == :respond_to? }
13
13
  end
14
14
 
@@ -1,8 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Hashira::Smells::ModuleInitialize < Hashira::Smells::Check
4
- def self.judge? = true
5
-
6
4
  private
7
5
 
8
6
  def smelly? = subject.kind == :module && flagged
@@ -9,7 +9,7 @@ class Hashira::Smells::NilCheck < Hashira::Smells::Check
9
9
 
10
10
  def smelly? = checks.any?
11
11
 
12
- def checks = @checks ||= Hashira::Smells::Scope.inside(subject.node).select { check?(it) }
12
+ def checks = @_checks ||= Hashira::Smells::Scope.inside(subject.node).select { check?(it) }
13
13
 
14
14
  def check?(node)
15
15
  case node
@@ -2,17 +2,29 @@
2
2
 
3
3
  class Hashira::Smells::Ownership
4
4
  def initialize(trees)
5
- @suffixes = Set.new
6
- @tables = {}
7
- trees.each { survey(it) }
5
+ @trees = trees
8
6
  end
9
7
 
10
- def owned?(segments) = @suffixes.include?(segments.join("::"))
8
+ def owned?(segments)
9
+ surveyed
10
+ @_suffixes.include?(segments.join("::"))
11
+ end
11
12
 
12
- def keys(segments) = @tables.fetch(segments.join("::"), [])
13
+ def keys(segments)
14
+ surveyed
15
+ @_tables.fetch(segments.join("::"), [])
16
+ end
13
17
 
14
18
  private
15
19
 
20
+ def surveyed
21
+ return if @_surveyed
22
+ @_surveyed = true
23
+ @_suffixes = Set.new
24
+ @_tables = {}
25
+ @trees.each { survey(it) }
26
+ end
27
+
16
28
  def survey(tree)
17
29
  Hashira::Analysis::TypeWalk.each(tree) do |node, full|
18
30
  absorb(full)
@@ -27,14 +39,14 @@ class Hashira::Smells::Ownership
27
39
  end
28
40
 
29
41
  def absorb(path)
30
- @suffixes.merge(suffixes(path))
42
+ @_suffixes.merge(suffixes(path))
31
43
  end
32
44
 
33
45
  def chart(path, value)
34
46
  return unless value.is_a?(Prism::HashNode)
35
47
  keys = value.elements.map { spine(it) }
36
48
  return if keys.empty? || keys.any?(&:nil?)
37
- suffixes(path).each { @tables[it] = keys }
49
+ suffixes(path).each { @_tables[it] = keys }
38
50
  end
39
51
 
40
52
  def thaw(value)
@@ -22,13 +22,13 @@ class Hashira::Smells::ParamCheck
22
22
  private
23
23
 
24
24
  def nested
25
- @nested ||= Hashira::Smells::Conditions.nested(branches).map { self.class.new(it, @name) }
25
+ @_nested ||= Hashira::Smells::Conditions.nested(branches).map { self.class.new(it, @name) }
26
26
  end
27
27
 
28
28
  def branches = Hashira::Smells::Conditions.branches(@node)
29
29
 
30
30
  def predicate
31
- @predicate ||= spread(Hashira::Smells::Conditions.condition(@node))
31
+ @_predicate ||= spread(Hashira::Smells::Conditions.condition(@node))
32
32
  end
33
33
 
34
34
  def spread(condition) = condition ? Hashira::Analysis::NodeWalk.collect(condition) : []
@@ -10,23 +10,29 @@ class Hashira::Smells::Refs
10
10
  Prism::InstanceVariableOperatorWriteNode, Prism::InstanceVariableTargetNode
11
11
  ].freeze
12
12
 
13
- def initialize(def_node)
14
- @tallies = {}
15
- Hashira::Smells::Scope.inside(def_node).each { record(it) }
13
+ def initialize(definition)
14
+ @node = definition
16
15
  end
17
16
 
18
17
  def ego = lines(:self).size
19
18
 
20
- def lines(name) = @tallies.fetch(name, [])
19
+ def lines(name) = tallies.fetch(name, [])
21
20
 
22
21
  def envious
23
- peak = @tallies.values.map(&:size).max
24
- names = @tallies.filter_map { |name, sightings| name if sightings.size == peak }
22
+ peak = tallies.values.map(&:size).max
23
+ names = tallies.filter_map { |name, sightings| name if sightings.size == peak }
25
24
  names.include?(:self) ? [] : names
26
25
  end
27
26
 
28
27
  private
29
28
 
29
+ def tallies
30
+ return @_tallies if @_tallies
31
+ @_tallies = {}
32
+ Hashira::Smells::Scope.inside(@node).each { record(it) }
33
+ @_tallies
34
+ end
35
+
30
36
  def record(node)
31
37
  return note(:self, node) if selfish?(node)
32
38
  case node
@@ -49,5 +55,5 @@ class Hashira::Smells::Refs
49
55
  end
50
56
  end
51
57
 
52
- def note(name, node) = (@tallies[name] ||= []) << node.location.start_line
58
+ def note(name, node) = (@_tallies[name] ||= []) << node.location.start_line
53
59
  end
@@ -5,14 +5,12 @@ require "prism"
5
5
  class Hashira::Smells::RepeatedConditional < Hashira::Smells::Check
6
6
  LIMIT = 2
7
7
 
8
- def self.judge? = true
9
-
10
8
  private
11
9
 
12
10
  def smelly? = subject.kind == :class && repeats.any?
13
11
 
14
12
  def repeats
15
- @repeats ||= predicates.group_by(&:slice).except("block_given?").select { |_test, nodes| nodes.size > LIMIT }
13
+ @_repeats ||= predicates.group_by(&:slice).except("block_given?").select { |_test, nodes| nodes.size > LIMIT }
16
14
  end
17
15
 
18
16
  def predicates = Hashira::Smells::Scope.sweep(subject.node).filter_map { predicate(it) }
@@ -8,6 +8,7 @@ require_relative "duplicate_method_call"
8
8
  require_relative "feature_envy"
9
9
  require_relative "foreign"
10
10
  require_relative "instance_variable_assumption"
11
+ require_relative "kind"
11
12
  require_relative "manual_dispatch"
12
13
  require_relative "module_initialize"
13
14
  require_relative "nil_check"
@@ -19,22 +20,30 @@ require_relative "utility_function"
19
20
  class Hashira::Smells::Report
20
21
  CHECKS = Hashira::Smells::Check.subclasses.sort_by(&:name).freeze
21
22
 
22
- JUDGES = CHECKS.select(&:judge?).freeze
23
+ JUDGES = [
24
+ Hashira::Smells::DataClump, Hashira::Smells::InstanceVariableAssumption,
25
+ Hashira::Smells::ModuleInitialize, Hashira::Smells::RepeatedConditional,
26
+ Hashira::Smells::TooManyInstanceVariables
27
+ ].freeze
23
28
 
24
- PROBES = CHECKS.reject(&:judge?).freeze
29
+ PROBES = (CHECKS - JUDGES).freeze
25
30
 
26
31
  def initialize(project, trees)
27
- @census = Hashira::Smells::Census.new(project, trees)
28
- @types = @census.types
32
+ @project = project
33
+ @trees = trees
29
34
  end
30
35
 
31
- def findings = @findings ||= sniff(@types, JUDGES) + sniff(methods, PROBES) + sprawl
36
+ def findings = @_findings ||= sniff(types, JUDGES) + sniff(methods, PROBES) + sprawl
32
37
 
33
38
  private
34
39
 
35
- def methods = @types.flat_map(&:defs)
40
+ def census = @_census ||= Hashira::Smells::Census.new(@project, @trees)
36
41
 
37
- def sprawl = Hashira::Smells::BoundarySprawl.new(methods, @census.ownership).findings
42
+ def types = @_types ||= census.types
43
+
44
+ def methods = types.flat_map(&:defs)
45
+
46
+ def sprawl = Hashira::Smells::BoundarySprawl.new(methods, census.ownership).findings
38
47
 
39
48
  def sniff(subjects, checks) = subjects.flat_map { |subject| verdicts(subject, checks) }
40
49
 
@@ -10,14 +10,14 @@ class Hashira::Smells::TooManyInstanceVariables < Hashira::Smells::Check
10
10
  Prism::InstanceVariableOperatorWriteNode, Prism::InstanceVariableTargetNode
11
11
  ].freeze
12
12
 
13
- def self.judge? = true
14
-
15
13
  private
16
14
 
17
15
  def smelly? = subject.kind == :class && names.size > LIMIT
18
16
 
19
17
  def names
20
- @names ||= Hashira::Smells::Scope.sweep(subject.node).select { COUNTED.include?(it.class) }.map(&:name).uniq.sort
18
+ @_names ||=
19
+ Hashira::Smells::Scope.sweep(subject.node).select { COUNTED.include?(it.class) }
20
+ .map(&:name).reject { it.start_with?("@_") }.uniq.sort
21
21
  end
22
22
 
23
23
  def detail = { site:, count: names.size }
@@ -5,17 +5,24 @@ require "prism"
5
5
  class Hashira::Smells::Visibility
6
6
  MARKERS = %i[public private protected module_function].freeze
7
7
 
8
- def initialize(type_node)
9
- @section = :public
10
- @overrides = {}
11
- @found = []
12
- scan(statements(type_node))
8
+ def initialize(type)
9
+ @node = type
13
10
  end
14
11
 
15
- def entries = @found.map { |node, section| [node, @overrides.fetch(node.name, section)] }
12
+ def entries
13
+ blank
14
+ scan(statements(@node))
15
+ @_found.map { |node, section| [node, @_overrides.fetch(node.name, section)] }
16
+ end
16
17
 
17
18
  private
18
19
 
20
+ def blank
21
+ @_section = :public
22
+ @_overrides = {}
23
+ @_found = []
24
+ end
25
+
19
26
  def statements(node)
20
27
  body = node.body
21
28
  body.is_a?(Prism::StatementsNode) ? body.body : []
@@ -25,7 +32,7 @@ class Hashira::Smells::Visibility
25
32
 
26
33
  def classify(node)
27
34
  case node
28
- when Prism::DefNode then @found << [node, @section]
35
+ when Prism::DefNode then @_found << [node, @_section]
29
36
  when Prism::CallNode then heed(node)
30
37
  else descend(node)
31
38
  end
@@ -46,19 +53,19 @@ class Hashira::Smells::Visibility
46
53
  def bare?(node) = MARKERS.include?(node.name) && !node.receiver
47
54
 
48
55
  def switch(name, arguments)
49
- arguments ? tag(name, arguments.arguments) : (@section = name)
56
+ arguments ? tag(name, arguments.arguments) : (@_section = name)
50
57
  end
51
58
 
52
59
  def tag(section, arguments) = arguments.each { note(it, section) }
53
60
 
54
61
  def note(argument, section)
55
62
  case argument
56
- when Prism::DefNode then @found << [argument, section]
57
- when Prism::SymbolNode then @overrides[argument.unescaped.to_sym] = section
63
+ when Prism::DefNode then @_found << [argument, section]
64
+ when Prism::SymbolNode then @_overrides[argument.unescaped.to_sym] = section
58
65
  end
59
66
  end
60
67
 
61
68
  def shadow(node)
62
- statements(node).each { @found << [it, :singleton] if it.is_a?(Prism::DefNode) }
69
+ statements(node).each { @_found << [it, :singleton] if it.is_a?(Prism::DefNode) }
63
70
  end
64
71
  end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "prism"
4
+
5
+ class Hashira::Trees
6
+ def initialize(project)
7
+ @project = project
8
+ end
9
+
10
+ def all = @_all ||= @project.files.to_h { [it, parse(it)] }
11
+
12
+ def unparsed
13
+ all
14
+ @_unparsed ||= []
15
+ end
16
+
17
+ private
18
+
19
+ def parse(path)
20
+ result = Prism.parse_file(path)
21
+ (@_unparsed ||= []) << path if result.failure?
22
+ result.value
23
+ rescue SystemCallError => error
24
+ raise(Hashira::Error, "cannot read #{path} (#{error.message})")
25
+ end
26
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Hashira
4
- VERSION = "0.6.0"
4
+ VERSION = "0.8.0"
5
5
  end
data/lib/hashira.rb CHANGED
@@ -34,8 +34,10 @@ require_relative "hashira/analysis/node_walk"
34
34
  require_relative "hashira/analysis/syntax"
35
35
  require_relative "hashira/analysis/type_walk"
36
36
  require_relative "hashira/churn"
37
+ require_relative "hashira/git_log"
37
38
  require_relative "hashira/ci/accepted"
38
39
  require_relative "hashira/ci/baseline"
40
+ require_relative "hashira/ci/comparison"
39
41
  require_relative "hashira/ci/diff"
40
42
  require_relative "hashira/ci/edge_diff_report"
41
43
  require_relative "hashira/ci/finding_diff_report"
@@ -43,13 +45,20 @@ require_relative "hashira/ci/gate"
43
45
  require_relative "hashira/ci/improvement"
44
46
  require_relative "hashira/ci/ratchet"
45
47
  require_relative "hashira/ci/ratchet_report"
48
+ require_relative "hashira/ci/scope"
49
+ require_relative "hashira/ci/slice"
50
+ require_relative "hashira/ci/status"
51
+ require_relative "hashira/ci/sweep"
46
52
  require_relative "hashira/cli"
47
53
  require_relative "hashira/cli/arguments"
54
+ require_relative "hashira/cli/choice"
48
55
  require_relative "hashira/cli/command_line"
49
56
  require_relative "hashira/cli/fail_on"
50
57
  require_relative "hashira/cli/flag"
51
58
  require_relative "hashira/cli/flags"
52
59
  require_relative "hashira/cli/format"
60
+ require_relative "hashira/cli/needs"
61
+ require_relative "hashira/cli/only"
53
62
  require_relative "hashira/cli/options"
54
63
  require_relative "hashira/cli/package_by"
55
64
  require_relative "hashira/cli/run"
@@ -114,10 +123,12 @@ require_relative "hashira/duplication/similarity"
114
123
  require_relative "hashira/duplication/union_find"
115
124
  require_relative "hashira/duplication/variance"
116
125
  require_relative "hashira/error"
126
+ require_relative "hashira/focus"
117
127
  require_relative "hashira/hotspots/file_cost"
118
128
  require_relative "hashira/hotspots/rollup"
119
129
  require_relative "hashira/pipeline"
120
130
  require_relative "hashira/project"
131
+ require_relative "hashira/report/columns"
121
132
  require_relative "hashira/report/complexity_table"
122
133
  require_relative "hashira/report/dependency_map"
123
134
  require_relative "hashira/report/finding_lines"
@@ -125,12 +136,14 @@ require_relative "hashira/report/graph_payload"
125
136
  require_relative "hashira/report/hotspot_table"
126
137
  require_relative "hashira/report/json"
127
138
  require_relative "hashira/report/metrics_table"
139
+ require_relative "hashira/report/notices"
128
140
  require_relative "hashira/report/phrases"
129
141
  require_relative "hashira/report/smell_phrases"
130
142
  require_relative "hashira/report/text"
131
143
  require_relative "hashira/report/view"
132
144
  require_relative "hashira/smells/census"
133
145
  require_relative "hashira/smells/check"
146
+ require_relative "hashira/smells/kind"
134
147
  require_relative "hashira/smells/conditions"
135
148
  require_relative "hashira/smells/contexts"
136
149
  require_relative "hashira/smells/control_parameter"
@@ -149,4 +162,5 @@ require_relative "hashira/smells/scope"
149
162
  require_relative "hashira/smells/too_many_instance_variables"
150
163
  require_relative "hashira/smells/utility_function"
151
164
  require_relative "hashira/smells/visibility"
165
+ require_relative "hashira/trees"
152
166
  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.6.0
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Giacomo GK
@@ -33,6 +33,7 @@ files:
33
33
  - lib/hashira/churn.rb
34
34
  - lib/hashira/ci/accepted.rb
35
35
  - lib/hashira/ci/baseline.rb
36
+ - lib/hashira/ci/comparison.rb
36
37
  - lib/hashira/ci/diff.rb
37
38
  - lib/hashira/ci/edge_diff_report.rb
38
39
  - lib/hashira/ci/finding_diff_report.rb
@@ -40,17 +41,25 @@ files:
40
41
  - lib/hashira/ci/improvement.rb
41
42
  - lib/hashira/ci/ratchet.rb
42
43
  - lib/hashira/ci/ratchet_report.rb
44
+ - lib/hashira/ci/scope.rb
45
+ - lib/hashira/ci/slice.rb
46
+ - lib/hashira/ci/status.rb
47
+ - lib/hashira/ci/sweep.rb
43
48
  - lib/hashira/cli.rb
44
49
  - lib/hashira/cli/arguments.rb
50
+ - lib/hashira/cli/choice.rb
45
51
  - lib/hashira/cli/command_line.rb
46
52
  - lib/hashira/cli/fail_on.rb
47
53
  - lib/hashira/cli/flag.rb
48
54
  - lib/hashira/cli/flags.rb
49
55
  - lib/hashira/cli/format.rb
56
+ - lib/hashira/cli/needs.rb
57
+ - lib/hashira/cli/only.rb
50
58
  - lib/hashira/cli/options.rb
51
59
  - lib/hashira/cli/package_by.rb
52
60
  - lib/hashira/cli/run.rb
53
61
  - lib/hashira/cli/skip.rb
62
+ - lib/hashira/cli/top.rb
54
63
  - lib/hashira/cli/usage.rb
55
64
  - lib/hashira/complexity/boolean_run.rb
56
65
  - lib/hashira/complexity/cognitive_score.rb
@@ -111,10 +120,13 @@ files:
111
120
  - lib/hashira/duplication/union_find.rb
112
121
  - lib/hashira/duplication/variance.rb
113
122
  - lib/hashira/error.rb
123
+ - lib/hashira/focus.rb
124
+ - lib/hashira/git_log.rb
114
125
  - lib/hashira/hotspots/file_cost.rb
115
126
  - lib/hashira/hotspots/rollup.rb
116
127
  - lib/hashira/pipeline.rb
117
128
  - lib/hashira/project.rb
129
+ - lib/hashira/report/columns.rb
118
130
  - lib/hashira/report/complexity_table.rb
119
131
  - lib/hashira/report/dependency_map.rb
120
132
  - lib/hashira/report/finding_lines.rb
@@ -122,6 +134,7 @@ files:
122
134
  - lib/hashira/report/hotspot_table.rb
123
135
  - lib/hashira/report/json.rb
124
136
  - lib/hashira/report/metrics_table.rb
137
+ - lib/hashira/report/notices.rb
125
138
  - lib/hashira/report/phrases.rb
126
139
  - lib/hashira/report/smell_phrases.rb
127
140
  - lib/hashira/report/text.rb
@@ -137,6 +150,7 @@ files:
137
150
  - lib/hashira/smells/feature_envy.rb
138
151
  - lib/hashira/smells/foreign.rb
139
152
  - lib/hashira/smells/instance_variable_assumption.rb
153
+ - lib/hashira/smells/kind.rb
140
154
  - lib/hashira/smells/manual_dispatch.rb
141
155
  - lib/hashira/smells/module_initialize.rb
142
156
  - lib/hashira/smells/nil_check.rb
@@ -149,6 +163,7 @@ files:
149
163
  - lib/hashira/smells/too_many_instance_variables.rb
150
164
  - lib/hashira/smells/utility_function.rb
151
165
  - lib/hashira/smells/visibility.rb
166
+ - lib/hashira/trees.rb
152
167
  - lib/hashira/version.rb
153
168
  homepage: https://github.com/giacope/hashira
154
169
  licenses: