hashira 0.3.0 → 0.5.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 +155 -0
- data/README.md +127 -25
- data/lib/hashira/analysis/finding.rb +7 -6
- data/lib/hashira/analysis/node_walk.rb +3 -3
- data/lib/hashira/analysis/syntax.rb +21 -6
- data/lib/hashira/analysis/type_walk.rb +7 -9
- data/lib/hashira/churn.rb +11 -13
- data/lib/hashira/ci/accepted.rb +27 -31
- data/lib/hashira/ci/baseline.rb +22 -24
- data/lib/hashira/ci/diff.rb +8 -7
- data/lib/hashira/ci/edge_diff_report.rb +13 -17
- data/lib/hashira/ci/finding_diff_report.rb +14 -18
- data/lib/hashira/ci/gate.rb +19 -23
- data/lib/hashira/ci/improvement.rb +9 -14
- data/lib/hashira/ci/ratchet.rb +32 -27
- data/lib/hashira/ci/ratchet_report.rb +26 -32
- data/lib/hashira/cli/arguments.rb +23 -0
- data/lib/hashira/cli/command_line.rb +55 -71
- data/lib/hashira/cli/fail_on.rb +24 -19
- data/lib/hashira/cli/options.rb +9 -4
- data/lib/hashira/cli/package_by.rb +14 -0
- data/lib/hashira/cli/run.rb +33 -27
- data/lib/hashira/cli/skip.rb +14 -20
- data/lib/hashira/cli/usage.rb +44 -41
- data/lib/hashira/cli.rb +17 -19
- data/lib/hashira/complexity/boolean_run.rb +13 -15
- data/lib/hashira/complexity/cognitive_score.rb +68 -66
- data/lib/hashira/complexity/if_chain.rb +33 -38
- data/lib/hashira/complexity/method_finding.rb +20 -42
- data/lib/hashira/complexity/method_score.rb +9 -7
- data/lib/hashira/complexity/rescue_scan.rb +16 -21
- data/lib/hashira/complexity/rollup.rb +13 -15
- data/lib/hashira/complexity/scores.rb +44 -0
- data/lib/hashira/coupling/audiences.rb +49 -0
- data/lib/hashira/coupling/catalog.rb +27 -0
- data/lib/hashira/coupling/census.rb +55 -0
- data/lib/hashira/coupling/constant_registry.rb +47 -0
- data/lib/hashira/coupling/cycle_findings.rb +31 -0
- data/lib/hashira/coupling/cycle_search.rb +37 -0
- data/lib/hashira/coupling/cycles.rb +14 -0
- data/lib/hashira/coupling/definition.rb +22 -0
- data/lib/hashira/coupling/definitions.rb +31 -0
- data/lib/hashira/coupling/edge.rb +10 -0
- data/lib/hashira/coupling/edge_map.rb +33 -0
- data/lib/hashira/coupling/folder_placement.rb +17 -0
- data/lib/hashira/coupling/folding.rb +56 -0
- data/lib/hashira/coupling/graph.rb +63 -0
- data/lib/hashira/coupling/metric.rb +15 -0
- data/lib/hashira/coupling/mixed_audience_findings.rb +32 -0
- data/lib/hashira/coupling/namespace_placement.rb +28 -0
- data/lib/hashira/{analysis → coupling}/namespace_prefix.rb +11 -11
- data/lib/hashira/coupling/naming.rb +14 -0
- data/lib/hashira/coupling/no_folding.rb +13 -0
- data/lib/hashira/coupling/placement.rb +19 -0
- data/lib/hashira/coupling/references.rb +55 -0
- data/lib/hashira/coupling/report.rb +28 -0
- data/lib/hashira/coupling/roll_call.rb +45 -0
- data/lib/hashira/coupling/roll_call_findings.rb +30 -0
- data/lib/hashira/coupling/roster.rb +25 -0
- data/lib/hashira/coupling/rule.rb +18 -0
- data/lib/hashira/coupling/scope.rb +28 -0
- data/lib/hashira/coupling/sdp_check.rb +14 -0
- data/lib/hashira/coupling/sdp_violation_findings.rb +25 -0
- data/lib/hashira/coupling/wide_edge_findings.rb +25 -0
- data/lib/hashira/coupling/words.rb +29 -0
- data/lib/hashira/diagram/dot.rb +8 -11
- data/lib/hashira/diagram/mermaid.rb +11 -16
- data/lib/hashira/diagram/source.rb +15 -0
- data/lib/hashira/duplication/clones.rb +17 -0
- data/lib/hashira/duplication/cluster.rb +14 -13
- data/lib/hashira/duplication/clusters.rb +46 -0
- data/lib/hashira/duplication/delta.rb +21 -30
- data/lib/hashira/duplication/duplication_finding.rb +17 -23
- data/lib/hashira/duplication/fragment.rb +19 -23
- data/lib/hashira/duplication/grouping.rb +12 -16
- data/lib/hashira/duplication/harvest.rb +27 -0
- data/lib/hashira/duplication/index.rb +20 -24
- data/lib/hashira/duplication/maximal.rb +11 -15
- data/lib/hashira/duplication/near_miss.rb +19 -22
- data/lib/hashira/duplication/sequence.rb +20 -22
- data/lib/hashira/duplication/similarity.rb +31 -34
- data/lib/hashira/duplication/union_find.rb +10 -14
- data/lib/hashira/duplication/variance.rb +35 -43
- data/lib/hashira/error.rb +4 -2
- data/lib/hashira/hotspots/file_cost.rb +8 -7
- data/lib/hashira/hotspots/rollup.rb +19 -21
- data/lib/hashira/pipeline.rb +41 -31
- data/lib/hashira/project.rb +50 -49
- data/lib/hashira/report/complexity_table.rb +27 -31
- data/lib/hashira/report/dependency_map.rb +17 -22
- data/lib/hashira/report/finding_lines.rb +15 -19
- data/lib/hashira/report/graph_payload.rb +14 -20
- data/lib/hashira/report/hotspot_table.rb +32 -37
- data/lib/hashira/report/json.rb +32 -32
- data/lib/hashira/report/metrics_table.rb +46 -33
- data/lib/hashira/report/phrases.rb +98 -0
- data/lib/hashira/report/smell_phrases.rb +68 -0
- data/lib/hashira/report/text.rb +59 -52
- data/lib/hashira/smells/census.rb +32 -0
- data/lib/hashira/smells/check.rb +30 -0
- data/lib/hashira/smells/conditions.rb +44 -0
- data/lib/hashira/smells/contexts.rb +64 -0
- data/lib/hashira/smells/control_parameter.rb +22 -0
- data/lib/hashira/smells/data_clump.rb +30 -0
- data/lib/hashira/smells/duplicate_method_call.rb +54 -0
- data/lib/hashira/smells/feature_envy.rb +17 -0
- data/lib/hashira/smells/instance_variable_assumption.rb +44 -0
- data/lib/hashira/smells/manual_dispatch.rb +16 -0
- data/lib/hashira/smells/module_initialize.rb +9 -0
- data/lib/hashira/smells/nil_check.rb +32 -0
- data/lib/hashira/smells/param_check.rb +51 -0
- data/lib/hashira/smells/refs.rb +53 -0
- data/lib/hashira/smells/repeated_conditional.rb +29 -0
- data/lib/hashira/smells/report.rb +31 -0
- data/lib/hashira/smells/scope.rb +29 -0
- data/lib/hashira/smells/too_many_instance_variables.rb +24 -0
- data/lib/hashira/smells/utility_function.rb +13 -0
- data/lib/hashira/smells/visibility.rb +64 -0
- data/lib/hashira/version.rb +1 -1
- data/lib/hashira.rb +131 -60
- metadata +62 -20
- data/lib/hashira/analysis/census.rb +0 -39
- data/lib/hashira/analysis/constant_registry.rb +0 -40
- data/lib/hashira/analysis/cycle_findings.rb +0 -33
- data/lib/hashira/analysis/cycle_search.rb +0 -44
- data/lib/hashira/analysis/definitions.rb +0 -27
- data/lib/hashira/analysis/edge.rb +0 -9
- data/lib/hashira/analysis/edge_map.rb +0 -36
- data/lib/hashira/analysis/graph.rb +0 -56
- data/lib/hashira/analysis/metric.rb +0 -14
- data/lib/hashira/analysis/references.rb +0 -39
- data/lib/hashira/analysis/rule.rb +0 -22
- data/lib/hashira/analysis/sdp_check.rb +0 -18
- data/lib/hashira/analysis/sdp_violation_findings.rb +0 -27
- data/lib/hashira/complexity/analyzer.rb +0 -45
- data/lib/hashira/diagram/renderer.rb +0 -20
- data/lib/hashira/duplication/analyzer.rb +0 -21
- data/lib/hashira/duplication/clusterer.rb +0 -50
- data/lib/hashira/duplication/extractor.rb +0 -31
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
|
|
5
|
+
class Hashira::Smells::InstanceVariableAssumption < Hashira::Smells::Check
|
|
6
|
+
SETTERS = [
|
|
7
|
+
Prism::InstanceVariableWriteNode, Prism::InstanceVariableOrWriteNode,
|
|
8
|
+
Prism::InstanceVariableAndWriteNode, Prism::InstanceVariableOperatorWriteNode,
|
|
9
|
+
Prism::InstanceVariableTargetNode
|
|
10
|
+
].freeze
|
|
11
|
+
|
|
12
|
+
MAYBES = [
|
|
13
|
+
Prism::LocalVariableOrWriteNode, Prism::InstanceVariableOrWriteNode,
|
|
14
|
+
Prism::ClassVariableOrWriteNode, Prism::GlobalVariableOrWriteNode,
|
|
15
|
+
Prism::ConstantOrWriteNode, Prism::ConstantPathOrWriteNode,
|
|
16
|
+
Prism::CallOrWriteNode, Prism::IndexOrWriteNode
|
|
17
|
+
].freeze
|
|
18
|
+
|
|
19
|
+
module Harvest
|
|
20
|
+
module_function
|
|
21
|
+
|
|
22
|
+
def reads(node)
|
|
23
|
+
return [] if MAYBES.include?(node.class)
|
|
24
|
+
below = node.compact_child_nodes.flat_map { reads(it) }
|
|
25
|
+
node.is_a?(Prism::InstanceVariableReadNode) ? below + [node.name] : below
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def smelly? = subject.kind == :class && assumed.any?
|
|
32
|
+
|
|
33
|
+
def assumed = @assumed ||= (read - prepared).uniq.sort
|
|
34
|
+
|
|
35
|
+
def read = subject.owned.flat_map { Harvest.reads(it.node) }
|
|
36
|
+
|
|
37
|
+
def prepared
|
|
38
|
+
starters.flat_map { Hashira::Smells::Scope.inside(it.node) }.select { SETTERS.include?(it.class) }.map(&:name)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def starters = subject.owned.select { it.node.name == :initialize }
|
|
42
|
+
|
|
43
|
+
def evidence = assumed.map(&:to_s)
|
|
44
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
|
|
5
|
+
class Hashira::Smells::ManualDispatch < Hashira::Smells::Check
|
|
6
|
+
private
|
|
7
|
+
|
|
8
|
+
def smelly? = sightings.any?
|
|
9
|
+
|
|
10
|
+
def sightings
|
|
11
|
+
@sightings ||= Hashira::Smells::Scope.inside(subject.node)
|
|
12
|
+
.select { it.is_a?(Prism::CallNode) && it.name == :respond_to? }
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def detail = { site: spots(sightings) }
|
|
16
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
|
|
5
|
+
class Hashira::Smells::NilCheck < Hashira::Smells::Check
|
|
6
|
+
EQUALITY = %i[== ===].freeze
|
|
7
|
+
|
|
8
|
+
private
|
|
9
|
+
|
|
10
|
+
def smelly? = checks.any?
|
|
11
|
+
|
|
12
|
+
def checks = @checks ||= Hashira::Smells::Scope.inside(subject.node).select { check?(it) }
|
|
13
|
+
|
|
14
|
+
def check?(node)
|
|
15
|
+
case node
|
|
16
|
+
when Prism::CallNode then query?(node)
|
|
17
|
+
when Prism::WhenNode then node.conditions.any?(Prism::NilNode)
|
|
18
|
+
else false
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def query?(node)
|
|
23
|
+
name = node.name
|
|
24
|
+
name == :nil? || (EQUALITY.include?(name) && equated?(node))
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def equated?(node) = sides(node).any?(Prism::NilNode)
|
|
28
|
+
|
|
29
|
+
def sides(node) = [node.receiver] + (node.arguments&.arguments || [])
|
|
30
|
+
|
|
31
|
+
def detail = { site: spots(checks) }
|
|
32
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
|
|
5
|
+
class Hashira::Smells::ParamCheck
|
|
6
|
+
COMPARISONS = %i[== != =~].freeze
|
|
7
|
+
|
|
8
|
+
def initialize(node, name)
|
|
9
|
+
@node = node
|
|
10
|
+
@name = name
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def matches
|
|
14
|
+
return [] if legitimate?
|
|
15
|
+
nested.flat_map(&:matches) + tested
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def legitimate?
|
|
19
|
+
absolved? || working? || nested.any?(&:legitimate?)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def nested
|
|
25
|
+
@nested ||= Hashira::Smells::Conditions.nested(branches).map { self.class.new(it, @name) }
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def branches = Hashira::Smells::Conditions.branches(@node)
|
|
29
|
+
|
|
30
|
+
def predicate
|
|
31
|
+
@predicate ||= spread(Hashira::Smells::Conditions.condition(@node))
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def spread(condition) = condition ? Hashira::Analysis::NodeWalk.collect(condition) : []
|
|
35
|
+
|
|
36
|
+
def tested = reads(predicate)
|
|
37
|
+
|
|
38
|
+
def working?
|
|
39
|
+
reads(branches.compact.flat_map { Hashira::Smells::Conditions.plain(it) }).any?
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def absolved?
|
|
43
|
+
predicate.grep(Prism::CallNode).any? { absolves?(it) }
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def absolves?(call)
|
|
47
|
+
!COMPARISONS.include?(call.name) && reads(Hashira::Analysis::NodeWalk.collect(call)).any?
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def reads(nodes) = nodes.grep(Prism::LocalVariableReadNode).select { it.name == @name }
|
|
51
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
|
|
5
|
+
class Hashira::Smells::Refs
|
|
6
|
+
SELVES = [
|
|
7
|
+
Prism::SelfNode, Prism::SuperNode, Prism::ForwardingSuperNode,
|
|
8
|
+
Prism::InstanceVariableReadNode, Prism::InstanceVariableWriteNode,
|
|
9
|
+
Prism::InstanceVariableOrWriteNode, Prism::InstanceVariableAndWriteNode,
|
|
10
|
+
Prism::InstanceVariableOperatorWriteNode, Prism::InstanceVariableTargetNode
|
|
11
|
+
].freeze
|
|
12
|
+
|
|
13
|
+
def initialize(def_node)
|
|
14
|
+
@tallies = {}
|
|
15
|
+
Hashira::Smells::Scope.inside(def_node).each { record(it) }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def ego = lines(:self).size
|
|
19
|
+
|
|
20
|
+
def lines(name) = @tallies.fetch(name, [])
|
|
21
|
+
|
|
22
|
+
def envious
|
|
23
|
+
peak = @tallies.values.map(&:size).max
|
|
24
|
+
names = @tallies.filter_map { |name, sightings| name if sightings.size == peak }
|
|
25
|
+
names.include?(:self) ? [] : names
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def record(node)
|
|
31
|
+
return note(:self, node) if selfish?(node)
|
|
32
|
+
case node
|
|
33
|
+
when Prism::CallNode then named(node)
|
|
34
|
+
when Prism::LocalVariableOperatorWriteNode then note(node.name, node)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def selfish?(node)
|
|
39
|
+
SELVES.include?(node.class) || implicit?(node)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def implicit?(node) = node.is_a?(Prism::CallNode) && !node.receiver
|
|
43
|
+
|
|
44
|
+
def named(node)
|
|
45
|
+
case (receiver = node.receiver)
|
|
46
|
+
when Prism::SelfNode then note(:self, node)
|
|
47
|
+
when Prism::LocalVariableReadNode, Prism::LocalVariableWriteNode
|
|
48
|
+
note(receiver.name, node) unless node.name == :new
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def note(name, node) = (@tallies[name] ||= []) << node.location.start_line
|
|
53
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
|
|
5
|
+
class Hashira::Smells::RepeatedConditional < Hashira::Smells::Check
|
|
6
|
+
LIMIT = 2
|
|
7
|
+
|
|
8
|
+
private
|
|
9
|
+
|
|
10
|
+
def smelly? = subject.kind == :class && repeats.any?
|
|
11
|
+
|
|
12
|
+
def repeats
|
|
13
|
+
@repeats ||= predicates.group_by(&:slice).except("block_given?").select { |_test, nodes| nodes.size > LIMIT }
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def predicates = Hashira::Smells::Scope.sweep(subject.node).filter_map { predicate(it) }
|
|
17
|
+
|
|
18
|
+
def predicate(node)
|
|
19
|
+
node.predicate if node.is_a?(Prism::IfNode) || node.is_a?(Prism::UnlessNode) || node.is_a?(Prism::CaseNode)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def detail = { site:, count: repeats.values.map(&:size).max }
|
|
23
|
+
|
|
24
|
+
def evidence = repeats.map { |test, nodes| row(test, nodes) }
|
|
25
|
+
|
|
26
|
+
def row(test, nodes)
|
|
27
|
+
"#{test} × #{nodes.size} (lines #{nodes.map { |node| node.location.start_line }.join(", ")})"
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Hashira::Smells::Report
|
|
4
|
+
def initialize(project, trees)
|
|
5
|
+
@types = Hashira::Smells::Census.new(project, trees).types
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def findings = @findings ||= sniff(@types, judges) + sniff(@types.flat_map(&:defs), probes)
|
|
9
|
+
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
def sniff(subjects, checks) = subjects.flat_map { |subject| verdicts(subject, checks) }
|
|
13
|
+
|
|
14
|
+
def verdicts(subject, checks) = checks.filter_map { |check| check.new(subject).finding }
|
|
15
|
+
|
|
16
|
+
def judges
|
|
17
|
+
[
|
|
18
|
+
Hashira::Smells::DataClump, Hashira::Smells::InstanceVariableAssumption,
|
|
19
|
+
Hashira::Smells::ModuleInitialize, Hashira::Smells::RepeatedConditional,
|
|
20
|
+
Hashira::Smells::TooManyInstanceVariables
|
|
21
|
+
]
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def probes
|
|
25
|
+
[
|
|
26
|
+
Hashira::Smells::ControlParameter, Hashira::Smells::DuplicateMethodCall,
|
|
27
|
+
Hashira::Smells::FeatureEnvy, Hashira::Smells::ManualDispatch,
|
|
28
|
+
Hashira::Smells::NilCheck, Hashira::Smells::UtilityFunction
|
|
29
|
+
]
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
|
|
5
|
+
module Hashira::Smells::Scope
|
|
6
|
+
TYPES = [Prism::ClassNode, Prism::ModuleNode, Prism::SingletonClassNode].freeze
|
|
7
|
+
|
|
8
|
+
FENCES = (TYPES + [Prism::DefNode]).freeze
|
|
9
|
+
|
|
10
|
+
module_function
|
|
11
|
+
|
|
12
|
+
def inside(node) = below(node, FENCES)
|
|
13
|
+
|
|
14
|
+
def sweep(node) = below(node, TYPES)
|
|
15
|
+
|
|
16
|
+
def below(root, fences)
|
|
17
|
+
found = []
|
|
18
|
+
visit(root, fences) { found << it }
|
|
19
|
+
found
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def visit(root, fences, &)
|
|
23
|
+
root.compact_child_nodes.each do |child|
|
|
24
|
+
next if fences.include?(child.class)
|
|
25
|
+
yield(child)
|
|
26
|
+
visit(child, fences, &)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
|
|
5
|
+
class Hashira::Smells::TooManyInstanceVariables < Hashira::Smells::Check
|
|
6
|
+
LIMIT = 4
|
|
7
|
+
|
|
8
|
+
COUNTED = [
|
|
9
|
+
Prism::InstanceVariableWriteNode, Prism::InstanceVariableAndWriteNode,
|
|
10
|
+
Prism::InstanceVariableOperatorWriteNode, Prism::InstanceVariableTargetNode
|
|
11
|
+
].freeze
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
def smelly? = subject.kind == :class && names.size > LIMIT
|
|
16
|
+
|
|
17
|
+
def names
|
|
18
|
+
@names ||= Hashira::Smells::Scope.sweep(subject.node).select { COUNTED.include?(it.class) }.map(&:name).uniq.sort
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def detail = { site:, count: names.size }
|
|
22
|
+
|
|
23
|
+
def evidence = names.map(&:to_s)
|
|
24
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
|
|
5
|
+
class Hashira::Smells::UtilityFunction < Hashira::Smells::Check
|
|
6
|
+
private
|
|
7
|
+
|
|
8
|
+
def smelly? = subject.public? && calls? && refs.ego.zero?
|
|
9
|
+
|
|
10
|
+
def refs = Hashira::Smells::Refs.new(subject.node)
|
|
11
|
+
|
|
12
|
+
def calls? = Hashira::Smells::Scope.inside(subject.node).any?(Prism::CallNode)
|
|
13
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
|
|
5
|
+
class Hashira::Smells::Visibility
|
|
6
|
+
MARKERS = %i[public private protected module_function].freeze
|
|
7
|
+
|
|
8
|
+
def initialize(type_node)
|
|
9
|
+
@section = :public
|
|
10
|
+
@overrides = {}
|
|
11
|
+
@found = []
|
|
12
|
+
scan(statements(type_node))
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def entries = @found.map { |node, section| [node, @overrides.fetch(node.name, section)] }
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def statements(node)
|
|
20
|
+
body = node.body
|
|
21
|
+
body.is_a?(Prism::StatementsNode) ? body.body : []
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def scan(nodes) = nodes.each { classify(it) }
|
|
25
|
+
|
|
26
|
+
def classify(node)
|
|
27
|
+
case node
|
|
28
|
+
when Prism::DefNode then @found << [node, @section]
|
|
29
|
+
when Prism::CallNode then heed(node)
|
|
30
|
+
else descend(node)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def descend(node)
|
|
35
|
+
case node
|
|
36
|
+
when Prism::SingletonClassNode then shadow(node)
|
|
37
|
+
when Prism::ClassNode, Prism::ModuleNode, Prism::ConstantWriteNode then nil
|
|
38
|
+
else scan(node.compact_child_nodes)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def heed(node)
|
|
43
|
+
bare?(node) ? switch(node.name, node.arguments) : scan(node.compact_child_nodes)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def bare?(node) = MARKERS.include?(node.name) && !node.receiver
|
|
47
|
+
|
|
48
|
+
def switch(name, arguments)
|
|
49
|
+
arguments ? tag(name, arguments.arguments) : (@section = name)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def tag(section, arguments) = arguments.each { note(it, section) }
|
|
53
|
+
|
|
54
|
+
def note(argument, section)
|
|
55
|
+
case argument
|
|
56
|
+
when Prism::DefNode then @found << [argument, section]
|
|
57
|
+
when Prism::SymbolNode then @overrides[argument.unescaped.to_sym] = section
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def shadow(node)
|
|
62
|
+
statements(node).each { @found << [it, :singleton] if it.is_a?(Prism::DefNode) }
|
|
63
|
+
end
|
|
64
|
+
end
|
data/lib/hashira/version.rb
CHANGED
data/lib/hashira.rb
CHANGED
|
@@ -1,78 +1,149 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
module Hashira
|
|
4
|
+
module Analysis
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
module CI
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
module Coupling
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
module Complexity
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
module Diagram
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
module Duplication
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
module Hotspots
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
module Report
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
module Smells
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
require_relative "hashira/analysis/finding"
|
|
8
33
|
require_relative "hashira/analysis/node_walk"
|
|
34
|
+
require_relative "hashira/analysis/syntax"
|
|
9
35
|
require_relative "hashira/analysis/type_walk"
|
|
10
|
-
require_relative "hashira/
|
|
11
|
-
require_relative "hashira/
|
|
12
|
-
require_relative "hashira/
|
|
13
|
-
require_relative "hashira/
|
|
14
|
-
require_relative "hashira/
|
|
15
|
-
require_relative "hashira/
|
|
16
|
-
require_relative "hashira/
|
|
17
|
-
require_relative "hashira/
|
|
18
|
-
require_relative "hashira/
|
|
19
|
-
require_relative "hashira/
|
|
20
|
-
require_relative "hashira/
|
|
21
|
-
require_relative "hashira/
|
|
22
|
-
require_relative "hashira/
|
|
23
|
-
require_relative "hashira/
|
|
24
|
-
require_relative "hashira/
|
|
25
|
-
require_relative "hashira/
|
|
26
|
-
require_relative "hashira/
|
|
27
|
-
require_relative "hashira/
|
|
36
|
+
require_relative "hashira/churn"
|
|
37
|
+
require_relative "hashira/ci/accepted"
|
|
38
|
+
require_relative "hashira/ci/baseline"
|
|
39
|
+
require_relative "hashira/ci/diff"
|
|
40
|
+
require_relative "hashira/ci/edge_diff_report"
|
|
41
|
+
require_relative "hashira/ci/finding_diff_report"
|
|
42
|
+
require_relative "hashira/ci/gate"
|
|
43
|
+
require_relative "hashira/ci/improvement"
|
|
44
|
+
require_relative "hashira/ci/ratchet"
|
|
45
|
+
require_relative "hashira/ci/ratchet_report"
|
|
46
|
+
require_relative "hashira/cli"
|
|
47
|
+
require_relative "hashira/cli/arguments"
|
|
48
|
+
require_relative "hashira/cli/command_line"
|
|
49
|
+
require_relative "hashira/cli/fail_on"
|
|
50
|
+
require_relative "hashira/cli/options"
|
|
51
|
+
require_relative "hashira/cli/package_by"
|
|
52
|
+
require_relative "hashira/cli/run"
|
|
53
|
+
require_relative "hashira/cli/skip"
|
|
54
|
+
require_relative "hashira/cli/usage"
|
|
28
55
|
require_relative "hashira/complexity/boolean_run"
|
|
29
|
-
require_relative "hashira/complexity/rescue_scan"
|
|
30
56
|
require_relative "hashira/complexity/cognitive_score"
|
|
57
|
+
require_relative "hashira/complexity/if_chain"
|
|
31
58
|
require_relative "hashira/complexity/method_finding"
|
|
32
|
-
require_relative "hashira/complexity/
|
|
33
|
-
require_relative "hashira/
|
|
34
|
-
require_relative "hashira/
|
|
35
|
-
require_relative "hashira/
|
|
36
|
-
require_relative "hashira/
|
|
37
|
-
require_relative "hashira/
|
|
59
|
+
require_relative "hashira/complexity/method_score"
|
|
60
|
+
require_relative "hashira/complexity/rescue_scan"
|
|
61
|
+
require_relative "hashira/complexity/rollup"
|
|
62
|
+
require_relative "hashira/complexity/scores"
|
|
63
|
+
require_relative "hashira/coupling/audiences"
|
|
64
|
+
require_relative "hashira/coupling/catalog"
|
|
65
|
+
require_relative "hashira/coupling/census"
|
|
66
|
+
require_relative "hashira/coupling/constant_registry"
|
|
67
|
+
require_relative "hashira/coupling/cycle_findings"
|
|
68
|
+
require_relative "hashira/coupling/cycle_search"
|
|
69
|
+
require_relative "hashira/coupling/cycles"
|
|
70
|
+
require_relative "hashira/coupling/definition"
|
|
71
|
+
require_relative "hashira/coupling/definitions"
|
|
72
|
+
require_relative "hashira/coupling/edge"
|
|
73
|
+
require_relative "hashira/coupling/edge_map"
|
|
74
|
+
require_relative "hashira/coupling/folder_placement"
|
|
75
|
+
require_relative "hashira/coupling/folding"
|
|
76
|
+
require_relative "hashira/coupling/graph"
|
|
77
|
+
require_relative "hashira/coupling/metric"
|
|
78
|
+
require_relative "hashira/coupling/mixed_audience_findings"
|
|
79
|
+
require_relative "hashira/coupling/namespace_placement"
|
|
80
|
+
require_relative "hashira/coupling/namespace_prefix"
|
|
81
|
+
require_relative "hashira/coupling/naming"
|
|
82
|
+
require_relative "hashira/coupling/no_folding"
|
|
83
|
+
require_relative "hashira/coupling/placement"
|
|
84
|
+
require_relative "hashira/coupling/references"
|
|
85
|
+
require_relative "hashira/coupling/report"
|
|
86
|
+
require_relative "hashira/coupling/roll_call"
|
|
87
|
+
require_relative "hashira/coupling/roll_call_findings"
|
|
88
|
+
require_relative "hashira/coupling/roster"
|
|
89
|
+
require_relative "hashira/coupling/rule"
|
|
90
|
+
require_relative "hashira/coupling/scope"
|
|
91
|
+
require_relative "hashira/coupling/sdp_check"
|
|
92
|
+
require_relative "hashira/coupling/sdp_violation_findings"
|
|
93
|
+
require_relative "hashira/coupling/wide_edge_findings"
|
|
94
|
+
require_relative "hashira/coupling/words"
|
|
95
|
+
require_relative "hashira/diagram/dot"
|
|
96
|
+
require_relative "hashira/diagram/mermaid"
|
|
97
|
+
require_relative "hashira/diagram/source"
|
|
98
|
+
require_relative "hashira/duplication/clones"
|
|
38
99
|
require_relative "hashira/duplication/cluster"
|
|
100
|
+
require_relative "hashira/duplication/clusters"
|
|
101
|
+
require_relative "hashira/duplication/delta"
|
|
102
|
+
require_relative "hashira/duplication/duplication_finding"
|
|
103
|
+
require_relative "hashira/duplication/fragment"
|
|
39
104
|
require_relative "hashira/duplication/grouping"
|
|
105
|
+
require_relative "hashira/duplication/harvest"
|
|
40
106
|
require_relative "hashira/duplication/index"
|
|
41
|
-
require_relative "hashira/duplication/near_miss"
|
|
42
107
|
require_relative "hashira/duplication/maximal"
|
|
43
|
-
require_relative "hashira/duplication/
|
|
108
|
+
require_relative "hashira/duplication/near_miss"
|
|
109
|
+
require_relative "hashira/duplication/sequence"
|
|
110
|
+
require_relative "hashira/duplication/similarity"
|
|
111
|
+
require_relative "hashira/duplication/union_find"
|
|
44
112
|
require_relative "hashira/duplication/variance"
|
|
45
|
-
require_relative "hashira/
|
|
46
|
-
require_relative "hashira/duplication/duplication_finding"
|
|
47
|
-
require_relative "hashira/duplication/analyzer"
|
|
48
|
-
require_relative "hashira/diagram/dot"
|
|
49
|
-
require_relative "hashira/diagram/mermaid"
|
|
50
|
-
require_relative "hashira/diagram/renderer"
|
|
51
|
-
require_relative "hashira/ci/accepted"
|
|
52
|
-
require_relative "hashira/ci/baseline"
|
|
53
|
-
require_relative "hashira/ci/diff"
|
|
54
|
-
require_relative "hashira/ci/improvement"
|
|
55
|
-
require_relative "hashira/ci/edge_diff_report"
|
|
56
|
-
require_relative "hashira/ci/finding_diff_report"
|
|
57
|
-
require_relative "hashira/ci/ratchet_report"
|
|
58
|
-
require_relative "hashira/ci/gate"
|
|
59
|
-
require_relative "hashira/ci/ratchet"
|
|
113
|
+
require_relative "hashira/error"
|
|
60
114
|
require_relative "hashira/hotspots/file_cost"
|
|
61
115
|
require_relative "hashira/hotspots/rollup"
|
|
62
|
-
require_relative "hashira/
|
|
63
|
-
require_relative "hashira/
|
|
64
|
-
require_relative "hashira/report/finding_lines"
|
|
65
|
-
require_relative "hashira/report/dependency_map"
|
|
66
|
-
require_relative "hashira/report/metrics_table"
|
|
116
|
+
require_relative "hashira/pipeline"
|
|
117
|
+
require_relative "hashira/project"
|
|
67
118
|
require_relative "hashira/report/complexity_table"
|
|
119
|
+
require_relative "hashira/report/dependency_map"
|
|
120
|
+
require_relative "hashira/report/finding_lines"
|
|
121
|
+
require_relative "hashira/report/graph_payload"
|
|
68
122
|
require_relative "hashira/report/hotspot_table"
|
|
69
123
|
require_relative "hashira/report/json"
|
|
124
|
+
require_relative "hashira/report/metrics_table"
|
|
125
|
+
require_relative "hashira/report/phrases"
|
|
126
|
+
require_relative "hashira/report/smell_phrases"
|
|
70
127
|
require_relative "hashira/report/text"
|
|
71
|
-
require_relative "hashira/
|
|
72
|
-
require_relative "hashira/
|
|
73
|
-
require_relative "hashira/
|
|
74
|
-
require_relative "hashira/
|
|
75
|
-
require_relative "hashira/
|
|
76
|
-
require_relative "hashira/
|
|
77
|
-
require_relative "hashira/
|
|
78
|
-
require_relative "hashira/
|
|
128
|
+
require_relative "hashira/report/view"
|
|
129
|
+
require_relative "hashira/smells/census"
|
|
130
|
+
require_relative "hashira/smells/check"
|
|
131
|
+
require_relative "hashira/smells/conditions"
|
|
132
|
+
require_relative "hashira/smells/contexts"
|
|
133
|
+
require_relative "hashira/smells/control_parameter"
|
|
134
|
+
require_relative "hashira/smells/data_clump"
|
|
135
|
+
require_relative "hashira/smells/duplicate_method_call"
|
|
136
|
+
require_relative "hashira/smells/feature_envy"
|
|
137
|
+
require_relative "hashira/smells/instance_variable_assumption"
|
|
138
|
+
require_relative "hashira/smells/manual_dispatch"
|
|
139
|
+
require_relative "hashira/smells/module_initialize"
|
|
140
|
+
require_relative "hashira/smells/nil_check"
|
|
141
|
+
require_relative "hashira/smells/param_check"
|
|
142
|
+
require_relative "hashira/smells/refs"
|
|
143
|
+
require_relative "hashira/smells/repeated_conditional"
|
|
144
|
+
require_relative "hashira/smells/report"
|
|
145
|
+
require_relative "hashira/smells/scope"
|
|
146
|
+
require_relative "hashira/smells/too_many_instance_variables"
|
|
147
|
+
require_relative "hashira/smells/utility_function"
|
|
148
|
+
require_relative "hashira/smells/visibility"
|
|
149
|
+
require_relative "hashira/version"
|