churn_vs_complexity 1.4.0 → 1.5.2
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/.rubocop.yml +3 -0
- data/CHANGELOG.md +29 -14
- data/README.md +10 -4
- data/lib/churn_vs_complexity/churn.rb +9 -1
- data/lib/churn_vs_complexity/cli/main.rb +46 -0
- data/lib/churn_vs_complexity/cli/parser.rb +91 -0
- data/lib/churn_vs_complexity/cli.rb +11 -94
- data/lib/churn_vs_complexity/complexity/eslint_calculator.rb +6 -0
- data/lib/churn_vs_complexity/complexity/pmd/files_calculator.rb +26 -0
- data/lib/churn_vs_complexity/complexity/pmd/folder_calculator.rb +20 -0
- data/lib/churn_vs_complexity/complexity/{pmd_calculator.rb → pmd.rb} +14 -21
- data/lib/churn_vs_complexity/complexity.rb +1 -1
- data/lib/churn_vs_complexity/complexity_validator.rb +14 -0
- data/lib/churn_vs_complexity/concurrent_calculator.rb +5 -3
- data/lib/churn_vs_complexity/delta/checker.rb +54 -0
- data/lib/churn_vs_complexity/delta/commit_hydrator.rb +22 -0
- data/lib/churn_vs_complexity/delta/complexity_annotator.rb +30 -0
- data/lib/churn_vs_complexity/delta/config.rb +50 -0
- data/lib/churn_vs_complexity/delta/factory.rb +22 -0
- data/lib/churn_vs_complexity/delta/multi_checker.rb +48 -0
- data/lib/churn_vs_complexity/delta/serializer.rb +69 -0
- data/lib/churn_vs_complexity/delta.rb +52 -0
- data/lib/churn_vs_complexity/engine.rb +1 -1
- data/lib/churn_vs_complexity/file_selector.rb +47 -4
- data/lib/churn_vs_complexity/git_strategy.rb +62 -0
- data/lib/churn_vs_complexity/language_validator.rb +9 -0
- data/lib/churn_vs_complexity/normal/config.rb +85 -0
- data/lib/churn_vs_complexity/normal/serializer/csv.rb +16 -0
- data/lib/churn_vs_complexity/normal/serializer/graph.rb +26 -0
- data/lib/churn_vs_complexity/normal/serializer/pass_through.rb +23 -0
- data/lib/churn_vs_complexity/normal/serializer/summary.rb +29 -0
- data/lib/churn_vs_complexity/normal/serializer/summary_hash.rb +56 -0
- data/lib/churn_vs_complexity/normal/serializer.rb +29 -0
- data/lib/churn_vs_complexity/normal.rb +45 -0
- data/lib/churn_vs_complexity/timetravel/config.rb +75 -0
- data/lib/churn_vs_complexity/timetravel/factory.rb +12 -0
- data/lib/churn_vs_complexity/{serializer/timetravel → timetravel/serializer}/quality_calculator.rb +2 -2
- data/lib/churn_vs_complexity/{serializer/timetravel → timetravel/serializer}/stats_calculator.rb +2 -2
- data/lib/churn_vs_complexity/{serializer/timetravel.rb → timetravel/serializer.rb} +6 -6
- data/lib/churn_vs_complexity/timetravel/traveller.rb +5 -11
- data/lib/churn_vs_complexity/timetravel/worktree.rb +30 -14
- data/lib/churn_vs_complexity/timetravel.rb +36 -39
- data/lib/churn_vs_complexity/version.rb +1 -1
- data/lib/churn_vs_complexity.rb +23 -7
- data/tmp/test-support/delta/ruby-summary.txt +50 -0
- data/tmp/test-support/delta/ruby.csv +12 -0
- metadata +38 -20
- data/.travis.yml +0 -7
- data/lib/churn_vs_complexity/config.rb +0 -159
- data/lib/churn_vs_complexity/serializer/csv.rb +0 -14
- data/lib/churn_vs_complexity/serializer/graph.rb +0 -24
- data/lib/churn_vs_complexity/serializer/pass_through.rb +0 -21
- data/lib/churn_vs_complexity/serializer/summary.rb +0 -27
- data/lib/churn_vs_complexity/serializer/summary_hash.rb +0 -54
- data/lib/churn_vs_complexity/serializer.rb +0 -26
@@ -1,159 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module ChurnVsComplexity
|
4
|
-
class Config
|
5
|
-
def initialize(
|
6
|
-
language:,
|
7
|
-
serializer:,
|
8
|
-
excluded: [],
|
9
|
-
since: nil,
|
10
|
-
relative_period: nil,
|
11
|
-
complexity_validator: ComplexityValidator,
|
12
|
-
since_validator: SinceValidator,
|
13
|
-
**options
|
14
|
-
)
|
15
|
-
@language = language
|
16
|
-
@serializer = serializer
|
17
|
-
@excluded = excluded
|
18
|
-
@since = since
|
19
|
-
@relative_period = relative_period
|
20
|
-
@complexity_validator = complexity_validator
|
21
|
-
@since_validator = since_validator
|
22
|
-
@options = options
|
23
|
-
end
|
24
|
-
|
25
|
-
def validate!
|
26
|
-
raise ValidationError, "Unsupported language: #{@language}" unless %i[java ruby javascript].include?(@language)
|
27
|
-
|
28
|
-
SerializerValidator.validate!(serializer: @serializer, mode: @options[:mode])
|
29
|
-
|
30
|
-
@since_validator.validate!(since: @since, relative_period: @relative_period, mode: @options[:mode])
|
31
|
-
RelativePeriodValidator.validate!(relative_period: @relative_period, mode: @options[:mode])
|
32
|
-
@complexity_validator.validate!(@language)
|
33
|
-
end
|
34
|
-
|
35
|
-
def timetravel
|
36
|
-
engine = timetravel_engine_config.to_engine
|
37
|
-
Timetravel::Traveller.new(
|
38
|
-
since: @since,
|
39
|
-
relative_period: @relative_period,
|
40
|
-
engine:,
|
41
|
-
jump_days: @options[:jump_days],
|
42
|
-
serializer: @serializer,
|
43
|
-
)
|
44
|
-
end
|
45
|
-
|
46
|
-
def to_engine
|
47
|
-
case @language
|
48
|
-
when :java
|
49
|
-
Engine.concurrent(
|
50
|
-
complexity: Complexity::PMDCalculator,
|
51
|
-
churn:,
|
52
|
-
file_selector: FileSelector::Java.excluding(@excluded),
|
53
|
-
serializer:,
|
54
|
-
since: @since || @relative_period,
|
55
|
-
)
|
56
|
-
when :ruby
|
57
|
-
Engine.concurrent(
|
58
|
-
complexity: Complexity::FlogCalculator,
|
59
|
-
churn:,
|
60
|
-
file_selector: FileSelector::Ruby.excluding(@excluded),
|
61
|
-
serializer:,
|
62
|
-
since: @since || @relative_period,
|
63
|
-
)
|
64
|
-
when :javascript
|
65
|
-
Engine.concurrent(
|
66
|
-
complexity: Complexity::ESLintCalculator,
|
67
|
-
churn:,
|
68
|
-
file_selector: FileSelector::JavaScript.excluding(@excluded),
|
69
|
-
serializer:,
|
70
|
-
since: @since || @relative_period,
|
71
|
-
)
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
private
|
76
|
-
|
77
|
-
def timetravel_engine_config
|
78
|
-
Config.new(
|
79
|
-
language: @language,
|
80
|
-
serializer: :pass_through,
|
81
|
-
excluded: @excluded,
|
82
|
-
since: nil, # since has a different meaning in timetravel mode
|
83
|
-
relative_period: @relative_period,
|
84
|
-
complexity_validator: @complexity_validator,
|
85
|
-
since_validator: @since_validator,
|
86
|
-
**@options,
|
87
|
-
)
|
88
|
-
end
|
89
|
-
|
90
|
-
def churn = Churn::GitCalculator
|
91
|
-
|
92
|
-
def serializer
|
93
|
-
case @serializer
|
94
|
-
when :none
|
95
|
-
Serializer::None
|
96
|
-
when :csv
|
97
|
-
Serializer::CSV
|
98
|
-
when :graph
|
99
|
-
Serializer::Graph.new
|
100
|
-
when :summary
|
101
|
-
Serializer::Summary
|
102
|
-
when :pass_through
|
103
|
-
Serializer::PassThrough
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
|
-
module ComplexityValidator
|
108
|
-
def self.validate!(language)
|
109
|
-
case language
|
110
|
-
when :java
|
111
|
-
Complexity::PMDCalculator.check_dependencies!
|
112
|
-
end
|
113
|
-
end
|
114
|
-
end
|
115
|
-
|
116
|
-
# TODO: unit test
|
117
|
-
module SerializerValidator
|
118
|
-
def self.validate!(serializer:, mode:)
|
119
|
-
raise ValidationError, "Unsupported serializer: #{serializer}" \
|
120
|
-
unless %i[none csv graph summary].include?(serializer)
|
121
|
-
raise ValidationError, 'Does not support --summary in --timetravel mode' \
|
122
|
-
if serializer == :summary && mode == :timetravel
|
123
|
-
end
|
124
|
-
end
|
125
|
-
|
126
|
-
# TODO: unit test
|
127
|
-
module RelativePeriodValidator
|
128
|
-
def self.validate!(relative_period:, mode:)
|
129
|
-
if mode == :timetravel && relative_period.nil?
|
130
|
-
raise ValidationError,
|
131
|
-
'Relative period is required in timetravel mode'
|
132
|
-
end
|
133
|
-
return if relative_period.nil? || %i[month quarter year].include?(relative_period)
|
134
|
-
|
135
|
-
raise ValidationError, "Invalid relative period #{relative_period}"
|
136
|
-
end
|
137
|
-
end
|
138
|
-
|
139
|
-
module SinceValidator
|
140
|
-
def self.validate!(since:, relative_period:, mode:)
|
141
|
-
# since can be nil, a date string or a keyword (:month, :quarter, :year)
|
142
|
-
return if since.nil?
|
143
|
-
|
144
|
-
unless mode == :timetravel || since.nil? || relative_period.nil?
|
145
|
-
raise ValidationError,
|
146
|
-
'--since and relative period (--month, --quarter, --year) can only be used together in --timetravel mode'
|
147
|
-
end
|
148
|
-
|
149
|
-
raise ValidationError, "Invalid since value #{since}" unless since.is_a?(String)
|
150
|
-
|
151
|
-
begin
|
152
|
-
Date.strptime(since, '%Y-%m-%d')
|
153
|
-
rescue Date::Error
|
154
|
-
raise ValidationError, "Invalid date #{since}, please use correct format, YYYY-MM-DD"
|
155
|
-
end
|
156
|
-
end
|
157
|
-
end
|
158
|
-
end
|
159
|
-
end
|
@@ -1,14 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module ChurnVsComplexity
|
4
|
-
module Serializer
|
5
|
-
module CSV
|
6
|
-
def self.serialize(result)
|
7
|
-
values_by_file = result[:values_by_file]
|
8
|
-
values_by_file.map do |file, values|
|
9
|
-
"#{file},#{values[0]},#{values[1]}\n"
|
10
|
-
end.join
|
11
|
-
end
|
12
|
-
end
|
13
|
-
end
|
14
|
-
end
|
@@ -1,24 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module ChurnVsComplexity
|
4
|
-
module Serializer
|
5
|
-
class Graph
|
6
|
-
def initialize(template: Graph.load_template_file)
|
7
|
-
@template = template
|
8
|
-
end
|
9
|
-
|
10
|
-
def serialize(result)
|
11
|
-
data = result[:values_by_file].map do |file, values|
|
12
|
-
"{ file_path: '#{file}', churn: #{values[0]}, complexity: #{values[1]} }"
|
13
|
-
end.join(",\n") + "\n"
|
14
|
-
title = Serializer.title(result)
|
15
|
-
@template.gsub("// INSERT DATA\n", data).gsub('INSERT TITLE', title)
|
16
|
-
end
|
17
|
-
|
18
|
-
def self.load_template_file
|
19
|
-
file_path = File.expand_path('../../../tmp/template/graph.html', __dir__)
|
20
|
-
File.read(file_path)
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
@@ -1,21 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module ChurnVsComplexity
|
4
|
-
module Serializer
|
5
|
-
module PassThrough
|
6
|
-
class << self
|
7
|
-
def serialize(result)
|
8
|
-
values_by_file = result[:values_by_file]
|
9
|
-
end_date = result[:git_period].end_date
|
10
|
-
values = values_by_file.map do |_, values|
|
11
|
-
[values[0].to_f, values[1].to_f]
|
12
|
-
end
|
13
|
-
{
|
14
|
-
end_date:,
|
15
|
-
values:,
|
16
|
-
}
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
@@ -1,27 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module ChurnVsComplexity
|
4
|
-
module Serializer
|
5
|
-
module Summary
|
6
|
-
def self.serialize(result)
|
7
|
-
values_by_file = result[:values_by_file]
|
8
|
-
summary = SummaryHash.serialize(result)
|
9
|
-
|
10
|
-
<<~SUMMARY
|
11
|
-
#{Serializer.title(result)}
|
12
|
-
|
13
|
-
Number of observations: #{values_by_file.size}
|
14
|
-
|
15
|
-
Churn:
|
16
|
-
Mean #{summary[:mean_churn]}, Median #{summary[:median_churn]}
|
17
|
-
|
18
|
-
Complexity:
|
19
|
-
Mean #{summary[:mean_complexity]}, Median #{summary[:median_complexity]}
|
20
|
-
|
21
|
-
Gamma score:
|
22
|
-
Mean #{summary[:mean_gamma_score]}, Median #{summary[:median_gamma_score]}
|
23
|
-
SUMMARY
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
@@ -1,54 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module ChurnVsComplexity
|
4
|
-
module Serializer
|
5
|
-
module SummaryHash
|
6
|
-
class << self
|
7
|
-
def serialize(result)
|
8
|
-
values_by_file = result[:values_by_file]
|
9
|
-
churn_values = values_by_file.map { |_, values| values[0].to_f }
|
10
|
-
complexity_values = values_by_file.map { |_, values| values[1].to_f }
|
11
|
-
|
12
|
-
mean_churn = churn_values.sum / churn_values.size
|
13
|
-
median_churn = churn_values.sort[churn_values.size / 2]
|
14
|
-
mean_complexity = complexity_values.sum / complexity_values.size
|
15
|
-
median_complexity = complexity_values.sort[complexity_values.size / 2]
|
16
|
-
|
17
|
-
max_churn = churn_values.max
|
18
|
-
min_churn = churn_values.min
|
19
|
-
max_complexity = complexity_values.max
|
20
|
-
min_complexity = complexity_values.min
|
21
|
-
|
22
|
-
epsilon = 0.0001
|
23
|
-
gamma_score = values_by_file.map do |_, values|
|
24
|
-
# unnormalised harmonic mean of churn and complexity,
|
25
|
-
# since the summary needs to be comparable over time
|
26
|
-
churn = values[0].to_f + epsilon
|
27
|
-
complexity = values[1].to_f + epsilon
|
28
|
-
|
29
|
-
(2 * churn * complexity) / (churn + complexity)
|
30
|
-
end
|
31
|
-
|
32
|
-
mean_gamma_score = gamma_score.sum / gamma_score.size
|
33
|
-
median_gamma_score = gamma_score.sort[gamma_score.size / 2]
|
34
|
-
|
35
|
-
end_date = result[:git_period].end_date
|
36
|
-
|
37
|
-
{
|
38
|
-
mean_churn:,
|
39
|
-
median_churn:,
|
40
|
-
max_churn:,
|
41
|
-
min_churn:,
|
42
|
-
mean_complexity:,
|
43
|
-
median_complexity:,
|
44
|
-
max_complexity:,
|
45
|
-
min_complexity:,
|
46
|
-
mean_gamma_score:,
|
47
|
-
median_gamma_score:,
|
48
|
-
end_date:,
|
49
|
-
}
|
50
|
-
end
|
51
|
-
end
|
52
|
-
end
|
53
|
-
end
|
54
|
-
end
|
@@ -1,26 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative 'serializer/timetravel'
|
4
|
-
require_relative 'serializer/summary_hash'
|
5
|
-
require_relative 'serializer/summary'
|
6
|
-
require_relative 'serializer/csv'
|
7
|
-
require_relative 'serializer/graph'
|
8
|
-
require_relative 'serializer/pass_through'
|
9
|
-
|
10
|
-
module ChurnVsComplexity
|
11
|
-
module Serializer
|
12
|
-
def self.title(result)
|
13
|
-
requested_start_date = result[:git_period].requested_start_date
|
14
|
-
end_date = result[:git_period].end_date
|
15
|
-
if requested_start_date.nil?
|
16
|
-
"Churn until #{end_date.strftime('%Y-%m-%d')} vs complexity"
|
17
|
-
else
|
18
|
-
"Churn between #{requested_start_date.strftime('%Y-%m-%d')} and #{end_date.strftime('%Y-%m-%d')} vs complexity"
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
module None
|
23
|
-
def self.serialize(result) = result
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|