churn_vs_complexity 1.4.0 → 1.5.2

Sign up to get free protection for your applications and to get access to all the features.
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