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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +106 -0
- data/README.md +148 -53
- data/exe/hashira +1 -1
- data/lib/hashira/analysis/finding.rb +5 -1
- data/lib/hashira/analysis/syntax.rb +6 -6
- data/lib/hashira/churn.rb +3 -4
- data/lib/hashira/ci/accepted.rb +9 -10
- data/lib/hashira/ci/baseline.rb +39 -12
- data/lib/hashira/ci/comparison.rb +24 -0
- data/lib/hashira/ci/diff.rb +4 -8
- 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 +41 -10
- data/lib/hashira/ci/ratchet_report.rb +8 -6
- data/lib/hashira/ci/scope.rb +10 -0
- data/lib/hashira/ci/slice.rb +17 -0
- data/lib/hashira/ci/status.rb +7 -0
- data/lib/hashira/ci/sweep.rb +13 -0
- data/lib/hashira/cli/arguments.rb +17 -8
- data/lib/hashira/cli/choice.rb +9 -0
- data/lib/hashira/cli/command_line.rb +8 -5
- data/lib/hashira/cli/fail_on.rb +17 -7
- data/lib/hashira/cli/flag.rb +3 -1
- data/lib/hashira/cli/flags.rb +23 -0
- data/lib/hashira/cli/format.rb +1 -1
- data/lib/hashira/cli/needs.rb +45 -0
- data/lib/hashira/cli/only.rb +16 -0
- data/lib/hashira/cli/options.rb +12 -3
- data/lib/hashira/cli/package_by.rb +1 -1
- data/lib/hashira/cli/run.rb +36 -8
- data/lib/hashira/cli/top.rb +13 -0
- data/lib/hashira/cli/usage.rb +5 -1
- data/lib/hashira/cli.rb +31 -11
- data/lib/hashira/complexity/cognitive_score.rb +44 -33
- data/lib/hashira/complexity/method_finding.rb +7 -5
- data/lib/hashira/complexity/method_score.rb +1 -1
- data/lib/hashira/complexity/scores.rb +11 -9
- data/lib/hashira/coupling/audiences.rb +3 -3
- data/lib/hashira/coupling/catalog.rb +8 -6
- data/lib/hashira/coupling/census.rb +42 -22
- data/lib/hashira/coupling/cycle_search.rb +11 -7
- data/lib/hashira/coupling/definitions.rb +1 -1
- data/lib/hashira/coupling/edge_map.rb +9 -8
- data/lib/hashira/coupling/folding.rb +5 -5
- data/lib/hashira/coupling/graph.rb +16 -9
- data/lib/hashira/coupling/metric.rb +9 -1
- data/lib/hashira/coupling/naming.rb +4 -4
- data/lib/hashira/coupling/references.rb +12 -12
- data/lib/hashira/coupling/report.rb +7 -9
- data/lib/hashira/coupling/roll_call.rb +1 -1
- data/lib/hashira/coupling/roll_call_findings.rb +1 -1
- data/lib/hashira/coupling/roster.rb +37 -12
- data/lib/hashira/coupling/rule.rb +1 -1
- data/lib/hashira/coupling/sdp_violation_findings.rb +3 -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/duplication/clones.rb +1 -1
- data/lib/hashira/duplication/clusters.rb +10 -7
- data/lib/hashira/duplication/delta.rb +1 -4
- data/lib/hashira/duplication/duplication_finding.rb +4 -2
- data/lib/hashira/duplication/fragment.rb +2 -2
- data/lib/hashira/duplication/harvest.rb +7 -5
- data/lib/hashira/duplication/index.rb +3 -2
- data/lib/hashira/duplication/similarity.rb +2 -2
- data/lib/hashira/duplication/variance.rb +1 -1
- data/lib/hashira/error.rb +0 -3
- data/lib/hashira/focus.rb +29 -0
- data/lib/hashira/git_log.rb +19 -0
- data/lib/hashira/hotspots/file_cost.rb +1 -1
- data/lib/hashira/hotspots/rollup.rb +2 -2
- data/lib/hashira/pipeline.rb +26 -24
- data/lib/hashira/project.rb +47 -32
- data/lib/hashira/report/columns.rb +54 -0
- data/lib/hashira/report/complexity_table.rb +12 -16
- data/lib/hashira/report/dependency_map.rb +1 -0
- data/lib/hashira/report/graph_payload.rb +1 -1
- data/lib/hashira/report/hotspot_table.rb +6 -14
- data/lib/hashira/report/json.rb +10 -2
- data/lib/hashira/report/metrics_table.rb +19 -20
- data/lib/hashira/report/notices.rb +37 -0
- data/lib/hashira/report/phrases.rb +25 -18
- data/lib/hashira/report/text.rb +24 -9
- data/lib/hashira/report/view.rb +4 -1
- data/lib/hashira/smells/census.rb +3 -4
- data/lib/hashira/smells/check.rb +1 -5
- data/lib/hashira/smells/contexts.rb +4 -4
- data/lib/hashira/smells/control_parameter.rb +1 -1
- data/lib/hashira/smells/data_clump.rb +2 -4
- data/lib/hashira/smells/duplicate_method_call.rb +3 -3
- data/lib/hashira/smells/feature_envy.rb +3 -3
- data/lib/hashira/smells/foreign.rb +14 -14
- data/lib/hashira/smells/instance_variable_assumption.rb +3 -3
- data/lib/hashira/smells/kind.rb +9 -0
- data/lib/hashira/smells/manual_dispatch.rb +1 -1
- data/lib/hashira/smells/module_initialize.rb +0 -2
- data/lib/hashira/smells/nil_check.rb +1 -1
- data/lib/hashira/smells/ownership.rb +19 -7
- data/lib/hashira/smells/param_check.rb +2 -2
- data/lib/hashira/smells/refs.rb +13 -7
- data/lib/hashira/smells/repeated_conditional.rb +1 -3
- data/lib/hashira/smells/report.rb +16 -7
- data/lib/hashira/smells/too_many_instance_variables.rb +3 -3
- data/lib/hashira/smells/visibility.rb +18 -11
- data/lib/hashira/trees.rb +26 -0
- data/lib/hashira/version.rb +1 -1
- data/lib/hashira.rb +14 -0
- 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 = @
|
|
12
|
+
def candidates = @_candidates ||= subject.owned.map { [it, it.arguments.sort] }
|
|
15
13
|
|
|
16
|
-
def clumps = @
|
|
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 = @
|
|
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
|
-
@
|
|
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
|
-
@
|
|
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 = @
|
|
10
|
+
def refs = @_refs ||= Hashira::Smells::Refs.new(subject.node)
|
|
11
11
|
|
|
12
|
-
def envied = @
|
|
12
|
+
def envied = @_envied ||= refs.envious.reject { foreign.dismiss?(it) }
|
|
13
13
|
|
|
14
|
-
def foreign = @
|
|
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
|
-
|
|
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
|
-
|
|
40
|
-
Hashira::Analysis::Syntax.segments(
|
|
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? =
|
|
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 =
|
|
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
|
-
|
|
80
|
+
body.grep(kind).select { it.name == name }
|
|
79
81
|
end
|
|
80
82
|
|
|
81
83
|
def snares(name)
|
|
82
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 = @
|
|
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
|
|
|
@@ -8,7 +8,7 @@ class Hashira::Smells::ManualDispatch < Hashira::Smells::Check
|
|
|
8
8
|
def smelly? = sightings.any?
|
|
9
9
|
|
|
10
10
|
def sightings
|
|
11
|
-
@
|
|
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
|
|
|
@@ -9,7 +9,7 @@ class Hashira::Smells::NilCheck < Hashira::Smells::Check
|
|
|
9
9
|
|
|
10
10
|
def smelly? = checks.any?
|
|
11
11
|
|
|
12
|
-
def checks = @
|
|
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
|
-
@
|
|
6
|
-
@tables = {}
|
|
7
|
-
trees.each { survey(it) }
|
|
5
|
+
@trees = trees
|
|
8
6
|
end
|
|
9
7
|
|
|
10
|
-
def owned?(segments)
|
|
8
|
+
def owned?(segments)
|
|
9
|
+
surveyed
|
|
10
|
+
@_suffixes.include?(segments.join("::"))
|
|
11
|
+
end
|
|
11
12
|
|
|
12
|
-
def keys(segments)
|
|
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
|
-
@
|
|
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 { @
|
|
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
|
-
@
|
|
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
|
-
@
|
|
31
|
+
@_predicate ||= spread(Hashira::Smells::Conditions.condition(@node))
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
def spread(condition) = condition ? Hashira::Analysis::NodeWalk.collect(condition) : []
|
data/lib/hashira/smells/refs.rb
CHANGED
|
@@ -10,23 +10,29 @@ class Hashira::Smells::Refs
|
|
|
10
10
|
Prism::InstanceVariableOperatorWriteNode, Prism::InstanceVariableTargetNode
|
|
11
11
|
].freeze
|
|
12
12
|
|
|
13
|
-
def initialize(
|
|
14
|
-
@
|
|
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) =
|
|
19
|
+
def lines(name) = tallies.fetch(name, [])
|
|
21
20
|
|
|
22
21
|
def envious
|
|
23
|
-
peak =
|
|
24
|
-
names =
|
|
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) = (@
|
|
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
|
-
@
|
|
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 =
|
|
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
|
|
29
|
+
PROBES = (CHECKS - JUDGES).freeze
|
|
25
30
|
|
|
26
31
|
def initialize(project, trees)
|
|
27
|
-
@
|
|
28
|
-
@
|
|
32
|
+
@project = project
|
|
33
|
+
@trees = trees
|
|
29
34
|
end
|
|
30
35
|
|
|
31
|
-
def findings = @
|
|
36
|
+
def findings = @_findings ||= sniff(types, JUDGES) + sniff(methods, PROBES) + sprawl
|
|
32
37
|
|
|
33
38
|
private
|
|
34
39
|
|
|
35
|
-
def
|
|
40
|
+
def census = @_census ||= Hashira::Smells::Census.new(@project, @trees)
|
|
36
41
|
|
|
37
|
-
def
|
|
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
|
-
@
|
|
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(
|
|
9
|
-
@
|
|
10
|
-
@overrides = {}
|
|
11
|
-
@found = []
|
|
12
|
-
scan(statements(type_node))
|
|
8
|
+
def initialize(type)
|
|
9
|
+
@node = type
|
|
13
10
|
end
|
|
14
11
|
|
|
15
|
-
def entries
|
|
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 @
|
|
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) : (@
|
|
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 @
|
|
57
|
-
when Prism::SymbolNode then @
|
|
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 { @
|
|
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
|
data/lib/hashira/version.rb
CHANGED
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.
|
|
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:
|