hashira 0.9.0 → 0.10.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 +24 -0
- data/README.md +34 -4
- data/exe/hashira +1 -1
- data/lib/hashira/boundaries.rb +64 -0
- data/lib/hashira/ci/baseline.rb +6 -3
- data/lib/hashira/ci/{edge_diff_report.rb → edge_changes.rb} +1 -1
- data/lib/hashira/ci/{finding_diff_report.rb → finding_changes.rb} +1 -1
- data/lib/hashira/ci/ratchet_report.rb +2 -2
- data/lib/hashira/cli/flag.rb +1 -1
- data/lib/hashira/cli/flags.rb +1 -1
- data/lib/hashira/cli/options.rb +16 -6
- data/lib/hashira/cli/run.rb +1 -6
- data/lib/hashira/{cli.rb → cli/session.rb} +7 -5
- data/lib/hashira/coupling/{cycle_findings.rb → cycle.rb} +1 -1
- data/lib/hashira/coupling/{roll_call_findings.rb → echo.rb} +1 -1
- data/lib/hashira/coupling/{sdp_violation_findings.rb → imbalance.rb} +3 -3
- data/lib/hashira/coupling/{mixed_audience_findings.rb → mixed_audience.rb} +1 -1
- data/lib/hashira/coupling/report.rb +5 -5
- data/lib/hashira/coupling/{wide_edge_findings.rb → wide_edge.rb} +1 -1
- data/lib/hashira/pipeline.rb +8 -1
- data/lib/hashira/project.rb +4 -1
- data/lib/hashira/report/smell_phrases.rb +3 -3
- data/lib/hashira/smells/{instance_variable_assumption.rb → assumed_state.rb} +1 -1
- data/lib/hashira/smells/boundary_sprawl.rb +3 -2
- data/lib/hashira/smells/{duplicate_method_call.rb → repeated_call.rb} +1 -1
- data/lib/hashira/smells/report.rb +10 -7
- data/lib/hashira/smells/{too_many_instance_variables.rb → state_sprawl.rb} +1 -1
- data/lib/hashira/version.rb +1 -1
- data/lib/hashira.rb +15 -11
- metadata +13 -12
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 28f5f325c96b2579257212ed846fba744718bf8bbdc16ea46f0f0225abdb15af
|
|
4
|
+
data.tar.gz: 587bc337f5e0bbd94243d7e6cb7b4d4268a8617d913750fdf85156f0f4168c78
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 28516d160aa0888bf1e9de0e00f80b1992444554309cc2a414a68dfc3c9272580e314b6593ec4b1a113b50185b7aea99bd82af7c1ac7143da9fb802b115aec46
|
|
7
|
+
data.tar.gz: eb711b0c77caff20baca0e36d8526126dde23e40bda06c6ed8efe5b1fd62d180e372b3a4447d66ab5be3b19abdf000e34ae94629ea45142373840992589cd6b7
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,30 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.10.0] - 2026-08-30
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **Interpreted foreign models are verified architecture, not accepted debt.**
|
|
13
|
+
A baseline can declare a foreign root with `role: interpreted_model`, its one
|
|
14
|
+
API entrypoint, and a reason. Type dispatch over that model no longer emits
|
|
15
|
+
`boundary_sprawl`, while a missing root call or a call which bypasses the
|
|
16
|
+
entrypoint is rejected as misuse. Baseline schema 6 preserves these
|
|
17
|
+
declarations on `--update-baseline`. Hashira now declares Prism this way and
|
|
18
|
+
dogfoods with zero findings and zero acceptances.
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- `boundary_sprawl` now distinguishes a missing adapter from a program which
|
|
23
|
+
deliberately interprets a foreign data model. Undeclared foreign roots keep
|
|
24
|
+
the same 12-method, 3-file threshold and behavior.
|
|
25
|
+
|
|
26
|
+
- Three smell kinds are renamed to name the smell, not the mechanism:
|
|
27
|
+
`duplicate_method_call` is now `repeated_call`, `too_many_instance_variables`
|
|
28
|
+
is `state_sprawl`, and `instance_variable_assumption` is `assumed_state`. A
|
|
29
|
+
saved baseline reports the old kinds as resolved and the new ones as new;
|
|
30
|
+
accept once to move on.
|
|
31
|
+
|
|
8
32
|
## [0.9.0] - 2026-08-20
|
|
9
33
|
|
|
10
34
|
### Fixed
|
data/README.md
CHANGED
|
@@ -340,7 +340,9 @@ What each one catches:
|
|
|
340
340
|
the method" needs a destination you own.
|
|
341
341
|
- **boundary_sprawl** — 12+ methods across 3+ files each type-guard against the
|
|
342
342
|
same foreign root (`Prism`, `ActiveRecord`, ...). One method inspecting a
|
|
343
|
-
foreign type is a fact of life; a sprawl of them
|
|
343
|
+
foreign type is a fact of life; a sprawl of them usually means a missing
|
|
344
|
+
adapter. An analyzer or interpreter which deliberately understands a foreign
|
|
345
|
+
data model can declare and verify that boundary instead.
|
|
344
346
|
- **utility_function** — a public instance method that touches no instance state;
|
|
345
347
|
it isn't really a method of this class. Private stateless helpers are fine, and
|
|
346
348
|
`module_function` modules are exempt — that's what they're for.
|
|
@@ -348,7 +350,7 @@ What each one catches:
|
|
|
348
350
|
caller already knew which branch it wanted.
|
|
349
351
|
- **data_clump** — the same two-plus parameters travel through three or more
|
|
350
352
|
methods; a value object is missing.
|
|
351
|
-
- **
|
|
353
|
+
- **repeated_call** — the identical receiver-and-arguments call repeated
|
|
352
354
|
inside one method; name the result once. Quiet wherever naming it would be
|
|
353
355
|
wrong: calls that mint a fresh value every time (`"".b`, `rand`, `dup`,
|
|
354
356
|
`SecureRandom.hex`) are meant to differ, and a repeat no single run can reach
|
|
@@ -356,9 +358,9 @@ What each one catches:
|
|
|
356
358
|
— has nothing to hoist.
|
|
357
359
|
- **repeated_conditional** — one class testing the same condition in three or
|
|
358
360
|
more places; polymorphism is overdue.
|
|
359
|
-
- **
|
|
361
|
+
- **state_sprawl** — more than four per class. Memoization
|
|
360
362
|
(`@x ||=`) doesn't count as state.
|
|
361
|
-
- **
|
|
363
|
+
- **assumed_state** — an ivar read that nothing the class can
|
|
362
364
|
reach ever assigns: not `initialize`, not another of its own methods, not an
|
|
363
365
|
`attr_writer`, not a reopening of the class, not a module it mixes in or a
|
|
364
366
|
class it inherits. Usually a typo, or state some other object is expected to
|
|
@@ -547,6 +549,34 @@ English, so the codes are distinct:
|
|
|
547
549
|
baseline is stale. Failing on `1` while treating `3` as a nudge lets a build
|
|
548
550
|
block regressions without blocking progress.
|
|
549
551
|
|
|
552
|
+
### Declaring interpreted models
|
|
553
|
+
|
|
554
|
+
Some foreign models are the subject of the program rather than a dependency to
|
|
555
|
+
hide. A compiler interprets an AST; a serializer interprets a schema. Declare
|
|
556
|
+
that architecture in the baseline instead of accepting a `boundary_sprawl`
|
|
557
|
+
finding:
|
|
558
|
+
|
|
559
|
+
```json
|
|
560
|
+
"boundaries": [
|
|
561
|
+
{
|
|
562
|
+
"root": "Prism",
|
|
563
|
+
"role": "interpreted_model",
|
|
564
|
+
"entrypoint": "lib/my_tool/trees.rb",
|
|
565
|
+
"reason": "the Prism AST is the input model"
|
|
566
|
+
}
|
|
567
|
+
]
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
Type dispatch over that root no longer counts as boundary sprawl. The
|
|
571
|
+
declaration is checked, not waived: the root API must be called, every such call
|
|
572
|
+
must go through the named entrypoint, and every field needs a value. A missing,
|
|
573
|
+
unknown, unused, or bypassed declaration exits as misuse. `--update-baseline`
|
|
574
|
+
preserves declarations.
|
|
575
|
+
|
|
576
|
+
This is deliberately narrower than acceptance. A declared boundary says what
|
|
577
|
+
architecture the project conforms to; an accepted finding records why a real
|
|
578
|
+
violation is allowed to remain.
|
|
579
|
+
|
|
550
580
|
### Accepting by design
|
|
551
581
|
|
|
552
582
|
Anything deliberate goes in the baseline with a reason. It leaves reports and
|
data/exe/hashira
CHANGED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
|
|
5
|
+
class Hashira::Boundaries
|
|
6
|
+
ROLE = "interpreted_model"
|
|
7
|
+
|
|
8
|
+
Declaration =
|
|
9
|
+
Data.define(:root, :role, :entrypoint, :reason) do
|
|
10
|
+
def verify
|
|
11
|
+
missing = members.find { !present?(public_send(it)) }
|
|
12
|
+
root, role, _entrypoint, _reason = deconstruct
|
|
13
|
+
raise(Hashira::Error, "boundary #{root || "(unknown)"} #{missing} is missing") if missing
|
|
14
|
+
raise(Hashira::Error, "boundary #{root} has unknown role #{role.inspect}") unless role == Hashira::Boundaries::ROLE
|
|
15
|
+
end
|
|
16
|
+
private
|
|
17
|
+
def present?(value) = value.is_a?(String) && !value.empty?
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def initialize(records, trees)
|
|
21
|
+
@records = records
|
|
22
|
+
@trees = trees
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def interpreted
|
|
26
|
+
return @_interpreted if @_interpreted
|
|
27
|
+
verify
|
|
28
|
+
@_interpreted = entries.map(&:root)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def entries = @_entries ||= @records.map { declaration(it) }
|
|
34
|
+
|
|
35
|
+
def declaration(record)
|
|
36
|
+
raise(Hashira::Error, "boundary declaration is not an object") unless record.is_a?(Hash)
|
|
37
|
+
Declaration.new(*%w[root role entrypoint reason].map { record[it] })
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def verify
|
|
41
|
+
entries.each do
|
|
42
|
+
it.verify
|
|
43
|
+
route(it)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def route(entry)
|
|
48
|
+
root, _role, entrypoint, _reason = entry.deconstruct
|
|
49
|
+
files = calls(root)
|
|
50
|
+
raise(Hashira::Error, "boundary #{root} has no root calls") if files.empty?
|
|
51
|
+
bypasses = files.reject { same?(it, entrypoint) }
|
|
52
|
+
raise(Hashira::Error, "boundary #{root} bypasses #{entrypoint}: #{bypasses.join(", ")}") unless bypasses.empty?
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def calls(root)
|
|
56
|
+
@trees.filter_map { |file, tree| file if Hashira::Analysis::NodeWalk.collect(tree).any? { invocation?(it, root) } }
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def invocation?(node, root)
|
|
60
|
+
node.is_a?(Prism::CallNode) && Hashira::Analysis::Syntax.segments(node.receiver).first == root
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def same?(file, entrypoint) = File.expand_path(file) == File.expand_path(entrypoint)
|
|
64
|
+
end
|
data/lib/hashira/ci/baseline.rb
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
require "json"
|
|
4
4
|
|
|
5
5
|
class Hashira::CI::Baseline
|
|
6
|
-
SCHEMA_VERSION =
|
|
6
|
+
SCHEMA_VERSION = 6
|
|
7
7
|
|
|
8
8
|
def initialize(path, analyzers: [], targets: [])
|
|
9
9
|
@path = path
|
|
@@ -33,6 +33,8 @@ class Hashira::CI::Baseline
|
|
|
33
33
|
|
|
34
34
|
def accepted = recorded.fetch("accepted", [])
|
|
35
35
|
|
|
36
|
+
def boundaries = recorded.fetch("boundaries", [])
|
|
37
|
+
|
|
36
38
|
def packaging = recorded.fetch("packaging", "folder")
|
|
37
39
|
|
|
38
40
|
def analyzers = recorded.fetch("analyzers", wanted[:analyzers])
|
|
@@ -75,7 +77,8 @@ class Hashira::CI::Baseline
|
|
|
75
77
|
end
|
|
76
78
|
|
|
77
79
|
def kept
|
|
78
|
-
|
|
79
|
-
entries.empty? ? {} : { accepted: entries }
|
|
80
|
+
optional(:accepted, Hashira::CI::Accepted.new(accepted).entries).merge(optional(:boundaries, boundaries))
|
|
80
81
|
end
|
|
82
|
+
|
|
83
|
+
def optional(name, values) = values.empty? ? {} : { name => values }
|
|
81
84
|
end
|
|
@@ -26,8 +26,8 @@ class Hashira::CI::RatchetReport
|
|
|
26
26
|
def verdict(*diffs) = diffs.compact.any?(&:worse?) ? Hashira::CI::Status::WORSE : Hashira::CI::Status::BETTER
|
|
27
27
|
|
|
28
28
|
def details(edges, findings)
|
|
29
|
-
Hashira::CI::
|
|
30
|
-
Hashira::CI::
|
|
29
|
+
Hashira::CI::EdgeChanges.new(@graph, io: @io).print(edges)
|
|
30
|
+
Hashira::CI::FindingChanges.new(@findings, io: @io).print(findings) if findings
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
def advice(*diffs)
|
data/lib/hashira/cli/flag.rb
CHANGED
data/lib/hashira/cli/flags.rb
CHANGED
data/lib/hashira/cli/options.rb
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
module Hashira::CLI
|
|
4
4
|
Options =
|
|
5
5
|
Data.define(:directories, :mode, :baseline, :fail_on, :skip, :only, :packaging, :top, :compact) do
|
|
6
6
|
def self.parse(argv) = CommandLine.new(argv).options
|
|
@@ -13,13 +13,23 @@ class Hashira::CLI
|
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
def pipeline
|
|
16
|
-
chosen = Hashira::Project.new(directories)
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
told.rails if directories.empty? && File.exist?("config/application.rb")
|
|
20
|
-
Hashira::Pipeline.new(chosen, enabled: analyzers, packaging:, only:)
|
|
16
|
+
chosen = Hashira::Project.new(directories, boundaries: decisions.boundaries)
|
|
17
|
+
announce(chosen)
|
|
18
|
+
Hashira::Pipeline.new(chosen, enabled: analyzers, packaging:, only:).verify
|
|
21
19
|
end
|
|
22
20
|
|
|
23
21
|
def analyzers = Hashira::Pipeline::ANALYZERS - skip
|
|
22
|
+
private
|
|
23
|
+
def decisions
|
|
24
|
+
recorded = Hashira::CI::Baseline.new(baseline)
|
|
25
|
+
raise(Hashira::Error, recorded.trouble) if recorded.trouble
|
|
26
|
+
recorded
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def announce(project)
|
|
30
|
+
told = Hashira::Report::Notices.new
|
|
31
|
+
told.scanning(project.files.size)
|
|
32
|
+
told.rails if directories.empty? && File.exist?("config/application.rb")
|
|
33
|
+
end
|
|
24
34
|
end
|
|
25
35
|
end
|
data/lib/hashira/cli/run.rb
CHANGED
|
@@ -17,12 +17,7 @@ class Hashira::CLI::Run
|
|
|
17
17
|
|
|
18
18
|
def findings = @_findings ||= accepted.screen(@pipeline.findings)
|
|
19
19
|
|
|
20
|
-
def accepted
|
|
21
|
-
path = @options.baseline
|
|
22
|
-
stop = Hashira::CI::Baseline.new(path).trouble
|
|
23
|
-
raise(Hashira::Error, stop) if stop
|
|
24
|
-
Hashira::CI::Accepted.build(path)
|
|
25
|
-
end
|
|
20
|
+
def accepted = Hashira::CI::Accepted.build(@options.baseline)
|
|
26
21
|
|
|
27
22
|
def update
|
|
28
23
|
ratchet.update
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
class Hashira::CLI
|
|
3
|
+
class Hashira::CLI::Session
|
|
4
4
|
MISUSE = 2
|
|
5
5
|
BROKEN = 70
|
|
6
6
|
|
|
@@ -9,7 +9,7 @@ class Hashira::CLI
|
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
def status
|
|
12
|
-
dispatch(Options.parse(@argv))
|
|
12
|
+
dispatch(Hashira::CLI::Options.parse(@argv))
|
|
13
13
|
rescue Hashira::Error => error
|
|
14
14
|
failure(error)
|
|
15
15
|
rescue StandardError => error
|
|
@@ -18,14 +18,16 @@ class Hashira::CLI
|
|
|
18
18
|
|
|
19
19
|
private
|
|
20
20
|
|
|
21
|
-
def dispatch(options)
|
|
21
|
+
def dispatch(options)
|
|
22
|
+
usage?(options) ? Hashira::CLI::Usage.public_send(options.mode) : timed(options)
|
|
23
|
+
end
|
|
22
24
|
|
|
23
|
-
def usage?(options) = Usage::PAGES.include?(options.mode)
|
|
25
|
+
def usage?(options) = Hashira::CLI::Usage::PAGES.include?(options.mode)
|
|
24
26
|
|
|
25
27
|
def timed(options)
|
|
26
28
|
started = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
27
29
|
pipeline = options.pipeline
|
|
28
|
-
Run.new(pipeline, options).status.tap do
|
|
30
|
+
Hashira::CLI::Run.new(pipeline, options).status.tap do
|
|
29
31
|
Hashira::Report::Notices.new.finished(pipeline.project.files.size, elapsed(started))
|
|
30
32
|
end
|
|
31
33
|
end
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "rule"
|
|
4
4
|
|
|
5
|
-
class Hashira::Coupling::
|
|
5
|
+
class Hashira::Coupling::Imbalance < Hashira::Coupling::Rule
|
|
6
6
|
KIND = "sdp_violation"
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
Gap = Data.define(:from, :to, :from_instability, :to_instability)
|
|
9
9
|
|
|
10
10
|
def list
|
|
11
11
|
ranked.map { |from, to| violation(from, to) }
|
|
@@ -20,7 +20,7 @@ class Hashira::Coupling::SdpViolationFindings < Hashira::Coupling::Rule
|
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
def detail(from, to)
|
|
23
|
-
|
|
23
|
+
Gap.new(from:, to:, from_instability: instability(from), to_instability: instability(to))
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
def instability(package) = metrics[package].instability
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "
|
|
4
|
-
require_relative "
|
|
5
|
-
require_relative "
|
|
3
|
+
require_relative "cycle"
|
|
4
|
+
require_relative "mixed_audience"
|
|
5
|
+
require_relative "echo"
|
|
6
6
|
require_relative "rule"
|
|
7
|
-
require_relative "
|
|
8
|
-
require_relative "
|
|
7
|
+
require_relative "imbalance"
|
|
8
|
+
require_relative "wide_edge"
|
|
9
9
|
|
|
10
10
|
class Hashira::Coupling::Report
|
|
11
11
|
RULES = Hashira::Coupling::Rule.subclasses.sort_by(&:name).freeze
|
data/lib/hashira/pipeline.rb
CHANGED
|
@@ -22,6 +22,11 @@ class Hashira::Pipeline
|
|
|
22
22
|
|
|
23
23
|
attr_reader :project
|
|
24
24
|
|
|
25
|
+
def verify
|
|
26
|
+
boundaries.interpreted
|
|
27
|
+
self
|
|
28
|
+
end
|
|
29
|
+
|
|
25
30
|
def unparsed = parsed.unparsed
|
|
26
31
|
|
|
27
32
|
def graph = coupling.graph
|
|
@@ -30,7 +35,7 @@ class Hashira::Pipeline
|
|
|
30
35
|
|
|
31
36
|
def duplication = @_duplication ||= analyzed(:duplication, Hashira::Duplication::Clones, churn)
|
|
32
37
|
|
|
33
|
-
def smells = @_smells ||= analyzed(:smells, Hashira::Smells::Report)
|
|
38
|
+
def smells = @_smells ||= analyzed(:smells, Hashira::Smells::Report, boundaries)
|
|
34
39
|
|
|
35
40
|
def hotspots
|
|
36
41
|
@_hotspots ||= Hashira::Hotspots::Rollup.new(complexity, duplication, churn) if complexity || duplication
|
|
@@ -52,6 +57,8 @@ class Hashira::Pipeline
|
|
|
52
57
|
|
|
53
58
|
def parsed = @_parsed ||= Hashira::Trees.new(@project)
|
|
54
59
|
|
|
60
|
+
def boundaries = @_boundaries ||= Hashira::Boundaries.new(@project.boundaries, parsed.all)
|
|
61
|
+
|
|
55
62
|
def coupling
|
|
56
63
|
@_coupling ||= Hashira::Coupling::Report.new(@project, parsed.all, packaging: settle(@packaging))
|
|
57
64
|
end
|
data/lib/hashira/project.rb
CHANGED
|
@@ -3,10 +3,13 @@
|
|
|
3
3
|
class Hashira::Project
|
|
4
4
|
ROOT_PACKAGE = "(root)"
|
|
5
5
|
|
|
6
|
-
def initialize(requested)
|
|
6
|
+
def initialize(requested, boundaries: [])
|
|
7
7
|
@requested = requested
|
|
8
|
+
@boundaries = boundaries
|
|
8
9
|
end
|
|
9
10
|
|
|
11
|
+
attr_reader :boundaries
|
|
12
|
+
|
|
10
13
|
def directories = @_directories ||= resolved
|
|
11
14
|
|
|
12
15
|
def rails? = directories.any? { config?(File.expand_path(it)) }
|
|
@@ -14,7 +14,7 @@ module Hashira::Report::Phrases
|
|
|
14
14
|
"Introduce a parameter object."
|
|
15
15
|
end
|
|
16
16
|
|
|
17
|
-
def
|
|
17
|
+
def on_repeated_call(finding)
|
|
18
18
|
"#{finding.package} repeats identical calls (#{finding.detail[:site]}). " \
|
|
19
19
|
"Name the result in a local variable."
|
|
20
20
|
end
|
|
@@ -32,7 +32,7 @@ module Hashira::Report::Phrases
|
|
|
32
32
|
"The behavior may belong on #{names.first}."
|
|
33
33
|
end
|
|
34
34
|
|
|
35
|
-
def
|
|
35
|
+
def on_assumed_state(finding)
|
|
36
36
|
"#{finding.package} reads instance variables nothing in the class assigns (#{finding.detail[:site]}). " \
|
|
37
37
|
"Assign them where the object is built, or pass the data explicitly."
|
|
38
38
|
end
|
|
@@ -56,7 +56,7 @@ module Hashira::Report::Phrases
|
|
|
56
56
|
tally(finding, "branches on the same test %d times", "Replace the scattered checks with polymorphism.")
|
|
57
57
|
end
|
|
58
58
|
|
|
59
|
-
def
|
|
59
|
+
def on_state_sprawl(finding)
|
|
60
60
|
tally(finding, "holds %d instance variables", "Split the class, or gather related fields into value objects.")
|
|
61
61
|
end
|
|
62
62
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require "prism"
|
|
4
4
|
|
|
5
|
-
class Hashira::Smells::
|
|
5
|
+
class Hashira::Smells::AssumedState < Hashira::Smells::Check
|
|
6
6
|
MAYBES = [
|
|
7
7
|
Prism::LocalVariableOrWriteNode, Prism::InstanceVariableOrWriteNode,
|
|
8
8
|
Prism::ClassVariableOrWriteNode, Prism::GlobalVariableOrWriteNode,
|
|
@@ -9,13 +9,14 @@ class Hashira::Smells::BoundarySprawl
|
|
|
9
9
|
|
|
10
10
|
SHOWN = 4
|
|
11
11
|
|
|
12
|
-
def initialize(subjects, ownership)
|
|
12
|
+
def initialize(subjects, ownership, interpreted = [])
|
|
13
13
|
@subjects = subjects
|
|
14
14
|
@ownership = ownership
|
|
15
|
+
@interpreted = interpreted
|
|
15
16
|
end
|
|
16
17
|
|
|
17
18
|
def findings
|
|
18
|
-
reaches.filter_map { |root, contexts| finding(root, contexts) }
|
|
19
|
+
reaches.except(*@interpreted).filter_map { |root, contexts| finding(root, contexts) }
|
|
19
20
|
end
|
|
20
21
|
|
|
21
22
|
private
|
|
@@ -4,33 +4,34 @@ require_relative "boundary_sprawl"
|
|
|
4
4
|
require_relative "check"
|
|
5
5
|
require_relative "control_parameter"
|
|
6
6
|
require_relative "data_clump"
|
|
7
|
-
require_relative "
|
|
7
|
+
require_relative "repeated_call"
|
|
8
8
|
require_relative "feature_envy"
|
|
9
9
|
require_relative "foreign"
|
|
10
|
-
require_relative "
|
|
10
|
+
require_relative "assumed_state"
|
|
11
11
|
require_relative "kind"
|
|
12
12
|
require_relative "manual_dispatch"
|
|
13
13
|
require_relative "module_initialize"
|
|
14
14
|
require_relative "nil_check"
|
|
15
15
|
require_relative "ownership"
|
|
16
16
|
require_relative "repeated_conditional"
|
|
17
|
-
require_relative "
|
|
17
|
+
require_relative "state_sprawl"
|
|
18
18
|
require_relative "utility_function"
|
|
19
19
|
|
|
20
20
|
class Hashira::Smells::Report
|
|
21
21
|
CHECKS = Hashira::Smells::Check.subclasses.sort_by(&:name).freeze
|
|
22
22
|
|
|
23
23
|
JUDGES = [
|
|
24
|
-
Hashira::Smells::DataClump, Hashira::Smells::
|
|
24
|
+
Hashira::Smells::DataClump, Hashira::Smells::AssumedState,
|
|
25
25
|
Hashira::Smells::ModuleInitialize, Hashira::Smells::RepeatedConditional,
|
|
26
|
-
Hashira::Smells::
|
|
26
|
+
Hashira::Smells::StateSprawl
|
|
27
27
|
].freeze
|
|
28
28
|
|
|
29
29
|
PROBES = (CHECKS - JUDGES).freeze
|
|
30
30
|
|
|
31
|
-
def initialize(project, trees)
|
|
31
|
+
def initialize(project, trees, boundaries = nil)
|
|
32
32
|
@project = project
|
|
33
33
|
@trees = trees
|
|
34
|
+
@boundaries = boundaries
|
|
34
35
|
end
|
|
35
36
|
|
|
36
37
|
def findings = @_findings ||= sniff(types, JUDGES) + sniff(methods, PROBES) + sprawl
|
|
@@ -43,7 +44,9 @@ class Hashira::Smells::Report
|
|
|
43
44
|
|
|
44
45
|
def methods = types.flat_map(&:defs)
|
|
45
46
|
|
|
46
|
-
def sprawl = Hashira::Smells::BoundarySprawl.new(methods, census.ownership).findings
|
|
47
|
+
def sprawl = Hashira::Smells::BoundarySprawl.new(methods, census.ownership, interpreted).findings
|
|
48
|
+
|
|
49
|
+
def interpreted = @boundaries ? @boundaries.interpreted : []
|
|
47
50
|
|
|
48
51
|
def sniff(subjects, checks) = subjects.flat_map { |subject| verdicts(subject, checks) }
|
|
49
52
|
|
data/lib/hashira/version.rb
CHANGED
data/lib/hashira.rb
CHANGED
|
@@ -7,6 +7,9 @@ module Hashira
|
|
|
7
7
|
module CI
|
|
8
8
|
end
|
|
9
9
|
|
|
10
|
+
module CLI
|
|
11
|
+
end
|
|
12
|
+
|
|
10
13
|
module Coupling
|
|
11
14
|
end
|
|
12
15
|
|
|
@@ -39,8 +42,8 @@ require_relative "hashira/ci/accepted"
|
|
|
39
42
|
require_relative "hashira/ci/baseline"
|
|
40
43
|
require_relative "hashira/ci/comparison"
|
|
41
44
|
require_relative "hashira/ci/diff"
|
|
42
|
-
require_relative "hashira/ci/
|
|
43
|
-
require_relative "hashira/ci/
|
|
45
|
+
require_relative "hashira/ci/edge_changes"
|
|
46
|
+
require_relative "hashira/ci/finding_changes"
|
|
44
47
|
require_relative "hashira/ci/gate"
|
|
45
48
|
require_relative "hashira/ci/improvement"
|
|
46
49
|
require_relative "hashira/ci/mark"
|
|
@@ -50,7 +53,6 @@ require_relative "hashira/ci/scope"
|
|
|
50
53
|
require_relative "hashira/ci/slice"
|
|
51
54
|
require_relative "hashira/ci/status"
|
|
52
55
|
require_relative "hashira/ci/sweep"
|
|
53
|
-
require_relative "hashira/cli"
|
|
54
56
|
require_relative "hashira/cli/arguments"
|
|
55
57
|
require_relative "hashira/cli/choice"
|
|
56
58
|
require_relative "hashira/cli/command_line"
|
|
@@ -63,6 +65,7 @@ require_relative "hashira/cli/only"
|
|
|
63
65
|
require_relative "hashira/cli/options"
|
|
64
66
|
require_relative "hashira/cli/package_by"
|
|
65
67
|
require_relative "hashira/cli/run"
|
|
68
|
+
require_relative "hashira/cli/session"
|
|
66
69
|
require_relative "hashira/cli/skip"
|
|
67
70
|
require_relative "hashira/cli/usage"
|
|
68
71
|
require_relative "hashira/complexity/boolean_run"
|
|
@@ -77,7 +80,7 @@ require_relative "hashira/coupling/audiences"
|
|
|
77
80
|
require_relative "hashira/coupling/catalog"
|
|
78
81
|
require_relative "hashira/coupling/census"
|
|
79
82
|
require_relative "hashira/coupling/constant_registry"
|
|
80
|
-
require_relative "hashira/coupling/
|
|
83
|
+
require_relative "hashira/coupling/cycle"
|
|
81
84
|
require_relative "hashira/coupling/cycle_search"
|
|
82
85
|
require_relative "hashira/coupling/cycles"
|
|
83
86
|
require_relative "hashira/coupling/definition"
|
|
@@ -88,7 +91,7 @@ require_relative "hashira/coupling/folder_placement"
|
|
|
88
91
|
require_relative "hashira/coupling/folding"
|
|
89
92
|
require_relative "hashira/coupling/graph"
|
|
90
93
|
require_relative "hashira/coupling/metric"
|
|
91
|
-
require_relative "hashira/coupling/
|
|
94
|
+
require_relative "hashira/coupling/mixed_audience"
|
|
92
95
|
require_relative "hashira/coupling/namespace_placement"
|
|
93
96
|
require_relative "hashira/coupling/namespace_prefix"
|
|
94
97
|
require_relative "hashira/coupling/naming"
|
|
@@ -97,13 +100,13 @@ require_relative "hashira/coupling/placement"
|
|
|
97
100
|
require_relative "hashira/coupling/references"
|
|
98
101
|
require_relative "hashira/coupling/report"
|
|
99
102
|
require_relative "hashira/coupling/roll_call"
|
|
100
|
-
require_relative "hashira/coupling/
|
|
103
|
+
require_relative "hashira/coupling/echo"
|
|
101
104
|
require_relative "hashira/coupling/roster"
|
|
102
105
|
require_relative "hashira/coupling/rule"
|
|
103
106
|
require_relative "hashira/coupling/scope"
|
|
104
107
|
require_relative "hashira/coupling/sdp_check"
|
|
105
|
-
require_relative "hashira/coupling/
|
|
106
|
-
require_relative "hashira/coupling/
|
|
108
|
+
require_relative "hashira/coupling/imbalance"
|
|
109
|
+
require_relative "hashira/coupling/wide_edge"
|
|
107
110
|
require_relative "hashira/coupling/words"
|
|
108
111
|
require_relative "hashira/diagram/dot"
|
|
109
112
|
require_relative "hashira/diagram/mermaid"
|
|
@@ -125,6 +128,7 @@ require_relative "hashira/duplication/similarity"
|
|
|
125
128
|
require_relative "hashira/duplication/union_find"
|
|
126
129
|
require_relative "hashira/duplication/variance"
|
|
127
130
|
require_relative "hashira/error"
|
|
131
|
+
require_relative "hashira/boundaries"
|
|
128
132
|
require_relative "hashira/focus"
|
|
129
133
|
require_relative "hashira/hotspots/file_cost"
|
|
130
134
|
require_relative "hashira/hotspots/rollup"
|
|
@@ -151,9 +155,9 @@ require_relative "hashira/smells/conditions"
|
|
|
151
155
|
require_relative "hashira/smells/contexts"
|
|
152
156
|
require_relative "hashira/smells/control_parameter"
|
|
153
157
|
require_relative "hashira/smells/data_clump"
|
|
154
|
-
require_relative "hashira/smells/
|
|
158
|
+
require_relative "hashira/smells/repeated_call"
|
|
155
159
|
require_relative "hashira/smells/feature_envy"
|
|
156
|
-
require_relative "hashira/smells/
|
|
160
|
+
require_relative "hashira/smells/assumed_state"
|
|
157
161
|
require_relative "hashira/smells/lineage"
|
|
158
162
|
require_relative "hashira/smells/manual_dispatch"
|
|
159
163
|
require_relative "hashira/smells/module_initialize"
|
|
@@ -163,7 +167,7 @@ require_relative "hashira/smells/refs"
|
|
|
163
167
|
require_relative "hashira/smells/repeated_conditional"
|
|
164
168
|
require_relative "hashira/smells/report"
|
|
165
169
|
require_relative "hashira/smells/scope"
|
|
166
|
-
require_relative "hashira/smells/
|
|
170
|
+
require_relative "hashira/smells/state_sprawl"
|
|
167
171
|
require_relative "hashira/smells/utility_function"
|
|
168
172
|
require_relative "hashira/smells/visibility"
|
|
169
173
|
require_relative "hashira/trees"
|
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.10.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Giacomo GK
|
|
@@ -30,13 +30,14 @@ files:
|
|
|
30
30
|
- lib/hashira/analysis/node_walk.rb
|
|
31
31
|
- lib/hashira/analysis/syntax.rb
|
|
32
32
|
- lib/hashira/analysis/type_walk.rb
|
|
33
|
+
- lib/hashira/boundaries.rb
|
|
33
34
|
- lib/hashira/churn.rb
|
|
34
35
|
- lib/hashira/ci/accepted.rb
|
|
35
36
|
- lib/hashira/ci/baseline.rb
|
|
36
37
|
- lib/hashira/ci/comparison.rb
|
|
37
38
|
- lib/hashira/ci/diff.rb
|
|
38
|
-
- lib/hashira/ci/
|
|
39
|
-
- lib/hashira/ci/
|
|
39
|
+
- lib/hashira/ci/edge_changes.rb
|
|
40
|
+
- lib/hashira/ci/finding_changes.rb
|
|
40
41
|
- lib/hashira/ci/gate.rb
|
|
41
42
|
- lib/hashira/ci/improvement.rb
|
|
42
43
|
- lib/hashira/ci/mark.rb
|
|
@@ -46,7 +47,6 @@ files:
|
|
|
46
47
|
- lib/hashira/ci/slice.rb
|
|
47
48
|
- lib/hashira/ci/status.rb
|
|
48
49
|
- lib/hashira/ci/sweep.rb
|
|
49
|
-
- lib/hashira/cli.rb
|
|
50
50
|
- lib/hashira/cli/arguments.rb
|
|
51
51
|
- lib/hashira/cli/choice.rb
|
|
52
52
|
- lib/hashira/cli/command_line.rb
|
|
@@ -59,6 +59,7 @@ files:
|
|
|
59
59
|
- lib/hashira/cli/options.rb
|
|
60
60
|
- lib/hashira/cli/package_by.rb
|
|
61
61
|
- lib/hashira/cli/run.rb
|
|
62
|
+
- lib/hashira/cli/session.rb
|
|
62
63
|
- lib/hashira/cli/skip.rb
|
|
63
64
|
- lib/hashira/cli/top.rb
|
|
64
65
|
- lib/hashira/cli/usage.rb
|
|
@@ -74,18 +75,20 @@ files:
|
|
|
74
75
|
- lib/hashira/coupling/catalog.rb
|
|
75
76
|
- lib/hashira/coupling/census.rb
|
|
76
77
|
- lib/hashira/coupling/constant_registry.rb
|
|
77
|
-
- lib/hashira/coupling/
|
|
78
|
+
- lib/hashira/coupling/cycle.rb
|
|
78
79
|
- lib/hashira/coupling/cycle_search.rb
|
|
79
80
|
- lib/hashira/coupling/cycles.rb
|
|
80
81
|
- lib/hashira/coupling/definition.rb
|
|
81
82
|
- lib/hashira/coupling/definitions.rb
|
|
83
|
+
- lib/hashira/coupling/echo.rb
|
|
82
84
|
- lib/hashira/coupling/edge.rb
|
|
83
85
|
- lib/hashira/coupling/edge_map.rb
|
|
84
86
|
- lib/hashira/coupling/folder_placement.rb
|
|
85
87
|
- lib/hashira/coupling/folding.rb
|
|
86
88
|
- lib/hashira/coupling/graph.rb
|
|
89
|
+
- lib/hashira/coupling/imbalance.rb
|
|
87
90
|
- lib/hashira/coupling/metric.rb
|
|
88
|
-
- lib/hashira/coupling/
|
|
91
|
+
- lib/hashira/coupling/mixed_audience.rb
|
|
89
92
|
- lib/hashira/coupling/namespace_placement.rb
|
|
90
93
|
- lib/hashira/coupling/namespace_prefix.rb
|
|
91
94
|
- lib/hashira/coupling/naming.rb
|
|
@@ -94,13 +97,11 @@ files:
|
|
|
94
97
|
- lib/hashira/coupling/references.rb
|
|
95
98
|
- lib/hashira/coupling/report.rb
|
|
96
99
|
- lib/hashira/coupling/roll_call.rb
|
|
97
|
-
- lib/hashira/coupling/roll_call_findings.rb
|
|
98
100
|
- lib/hashira/coupling/roster.rb
|
|
99
101
|
- lib/hashira/coupling/rule.rb
|
|
100
102
|
- lib/hashira/coupling/scope.rb
|
|
101
103
|
- lib/hashira/coupling/sdp_check.rb
|
|
102
|
-
- lib/hashira/coupling/
|
|
103
|
-
- lib/hashira/coupling/wide_edge_findings.rb
|
|
104
|
+
- lib/hashira/coupling/wide_edge.rb
|
|
104
105
|
- lib/hashira/coupling/words.rb
|
|
105
106
|
- lib/hashira/diagram/dot.rb
|
|
106
107
|
- lib/hashira/diagram/mermaid.rb
|
|
@@ -141,6 +142,7 @@ files:
|
|
|
141
142
|
- lib/hashira/report/smell_phrases.rb
|
|
142
143
|
- lib/hashira/report/text.rb
|
|
143
144
|
- lib/hashira/report/view.rb
|
|
145
|
+
- lib/hashira/smells/assumed_state.rb
|
|
144
146
|
- lib/hashira/smells/boundary_sprawl.rb
|
|
145
147
|
- lib/hashira/smells/branches.rb
|
|
146
148
|
- lib/hashira/smells/census.rb
|
|
@@ -149,10 +151,8 @@ files:
|
|
|
149
151
|
- lib/hashira/smells/contexts.rb
|
|
150
152
|
- lib/hashira/smells/control_parameter.rb
|
|
151
153
|
- lib/hashira/smells/data_clump.rb
|
|
152
|
-
- lib/hashira/smells/duplicate_method_call.rb
|
|
153
154
|
- lib/hashira/smells/feature_envy.rb
|
|
154
155
|
- lib/hashira/smells/foreign.rb
|
|
155
|
-
- lib/hashira/smells/instance_variable_assumption.rb
|
|
156
156
|
- lib/hashira/smells/kind.rb
|
|
157
157
|
- lib/hashira/smells/lineage.rb
|
|
158
158
|
- lib/hashira/smells/manual_dispatch.rb
|
|
@@ -161,10 +161,11 @@ files:
|
|
|
161
161
|
- lib/hashira/smells/ownership.rb
|
|
162
162
|
- lib/hashira/smells/param_check.rb
|
|
163
163
|
- lib/hashira/smells/refs.rb
|
|
164
|
+
- lib/hashira/smells/repeated_call.rb
|
|
164
165
|
- lib/hashira/smells/repeated_conditional.rb
|
|
165
166
|
- lib/hashira/smells/report.rb
|
|
166
167
|
- lib/hashira/smells/scope.rb
|
|
167
|
-
- lib/hashira/smells/
|
|
168
|
+
- lib/hashira/smells/state_sprawl.rb
|
|
168
169
|
- lib/hashira/smells/utility_function.rb
|
|
169
170
|
- lib/hashira/smells/visibility.rb
|
|
170
171
|
- lib/hashira/trees.rb
|