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.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +3 -0
  3. data/CHANGELOG.md +29 -14
  4. data/README.md +10 -4
  5. data/lib/churn_vs_complexity/churn.rb +9 -1
  6. data/lib/churn_vs_complexity/cli/main.rb +46 -0
  7. data/lib/churn_vs_complexity/cli/parser.rb +91 -0
  8. data/lib/churn_vs_complexity/cli.rb +11 -94
  9. data/lib/churn_vs_complexity/complexity/eslint_calculator.rb +6 -0
  10. data/lib/churn_vs_complexity/complexity/pmd/files_calculator.rb +26 -0
  11. data/lib/churn_vs_complexity/complexity/pmd/folder_calculator.rb +20 -0
  12. data/lib/churn_vs_complexity/complexity/{pmd_calculator.rb → pmd.rb} +14 -21
  13. data/lib/churn_vs_complexity/complexity.rb +1 -1
  14. data/lib/churn_vs_complexity/complexity_validator.rb +14 -0
  15. data/lib/churn_vs_complexity/concurrent_calculator.rb +5 -3
  16. data/lib/churn_vs_complexity/delta/checker.rb +54 -0
  17. data/lib/churn_vs_complexity/delta/commit_hydrator.rb +22 -0
  18. data/lib/churn_vs_complexity/delta/complexity_annotator.rb +30 -0
  19. data/lib/churn_vs_complexity/delta/config.rb +50 -0
  20. data/lib/churn_vs_complexity/delta/factory.rb +22 -0
  21. data/lib/churn_vs_complexity/delta/multi_checker.rb +48 -0
  22. data/lib/churn_vs_complexity/delta/serializer.rb +69 -0
  23. data/lib/churn_vs_complexity/delta.rb +52 -0
  24. data/lib/churn_vs_complexity/engine.rb +1 -1
  25. data/lib/churn_vs_complexity/file_selector.rb +47 -4
  26. data/lib/churn_vs_complexity/git_strategy.rb +62 -0
  27. data/lib/churn_vs_complexity/language_validator.rb +9 -0
  28. data/lib/churn_vs_complexity/normal/config.rb +85 -0
  29. data/lib/churn_vs_complexity/normal/serializer/csv.rb +16 -0
  30. data/lib/churn_vs_complexity/normal/serializer/graph.rb +26 -0
  31. data/lib/churn_vs_complexity/normal/serializer/pass_through.rb +23 -0
  32. data/lib/churn_vs_complexity/normal/serializer/summary.rb +29 -0
  33. data/lib/churn_vs_complexity/normal/serializer/summary_hash.rb +56 -0
  34. data/lib/churn_vs_complexity/normal/serializer.rb +29 -0
  35. data/lib/churn_vs_complexity/normal.rb +45 -0
  36. data/lib/churn_vs_complexity/timetravel/config.rb +75 -0
  37. data/lib/churn_vs_complexity/timetravel/factory.rb +12 -0
  38. data/lib/churn_vs_complexity/{serializer/timetravel → timetravel/serializer}/quality_calculator.rb +2 -2
  39. data/lib/churn_vs_complexity/{serializer/timetravel → timetravel/serializer}/stats_calculator.rb +2 -2
  40. data/lib/churn_vs_complexity/{serializer/timetravel.rb → timetravel/serializer.rb} +6 -6
  41. data/lib/churn_vs_complexity/timetravel/traveller.rb +5 -11
  42. data/lib/churn_vs_complexity/timetravel/worktree.rb +30 -14
  43. data/lib/churn_vs_complexity/timetravel.rb +36 -39
  44. data/lib/churn_vs_complexity/version.rb +1 -1
  45. data/lib/churn_vs_complexity.rb +23 -7
  46. data/tmp/test-support/delta/ruby-summary.txt +50 -0
  47. data/tmp/test-support/delta/ruby.csv +12 -0
  48. metadata +38 -20
  49. data/.travis.yml +0 -7
  50. data/lib/churn_vs_complexity/config.rb +0 -159
  51. data/lib/churn_vs_complexity/serializer/csv.rb +0 -14
  52. data/lib/churn_vs_complexity/serializer/graph.rb +0 -24
  53. data/lib/churn_vs_complexity/serializer/pass_through.rb +0 -21
  54. data/lib/churn_vs_complexity/serializer/summary.rb +0 -27
  55. data/lib/churn_vs_complexity/serializer/summary_hash.rb +0 -54
  56. 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