churn_vs_complexity 1.7.0 → 1.8.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dbfecd03ad25cb8f79ae5b52564f591b122eab2b8ee13e3fe80d1d33883e2747
4
- data.tar.gz: 4ba6ffb8794bb11c9e87103f26b481b9b6bb29230eb1c55fa7fff7c46aaa0687
3
+ metadata.gz: b5a149d026d33ac7319cc78d8c57af7e03113edf42d69f35612bbd72be708c77
4
+ data.tar.gz: c87fb3252fc699c681fd8da590fe02fe2d06e47b85fd5b7667fe314bd8ce3393
5
5
  SHA512:
6
- metadata.gz: c7463ac718037f9f8d13ba08d1c39b775557246f85bd3e29852ee573b1af55625026e792d239e3d38df118923f80b68d75243508f5dd98b6739bd1b80408fe20
7
- data.tar.gz: 35358bac1704bc9bca9cb9204d053c3484d9aa5e4d1e378b0feb48e2236f014354494b8ddcd36d12d127865361add543f511e514bfa412d01f7b34f038fb1042
6
+ metadata.gz: '09d8eddfd30cd8c56abbd200d5f4e31529ac5fc1171837e3420ccfe2536bf203e3d003a8fbebe7c4c6b0fb54be9a84d71071ca447b9526538edd814953c8b436'
7
+ data.tar.gz: ad11518d39553ff5b03fb6c8ea0bc44989ba47d034cbd3b82582279055cad86806612cf0f71c1888b6ecf2aa312a20afce3ad80c8d36b5ab85480152e213e570
data/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ ## [1.8.0] - 2026-04-05
2
+
3
+ ### Added
4
+ - Rust support for complexity calculation (via [lizard](https://github.com/terryyin/lizard), install with `pip install lizard`)
5
+ - Swift support for complexity calculation (via [lizard](https://github.com/terryyin/lizard), install with `pip install lizard`)
6
+
1
7
  ## [1.7.0] - 2026-04-05
2
8
 
3
9
  ### Added
data/CLAUDE.md CHANGED
@@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
4
4
 
5
5
  ## Project Overview
6
6
 
7
- `churn_vs_complexity` is a Ruby gem that analyzes code quality by correlating file churn (how often files change) with complexity scores. It supports Ruby (via Flog), JavaScript/TypeScript (via ESLint), Java (via PMD), Python (via Radon), Go (via gocognit), and Kotlin (via lizard). Requires Ruby >= 3.3.
7
+ `churn_vs_complexity` is a Ruby gem that analyzes code quality by correlating file churn (how often files change) with complexity scores. It supports Ruby (via Flog), JavaScript/TypeScript (via ESLint), Java (via PMD), Python (via Radon), Go (via gocognit), Kotlin (via lizard), Rust (via lizard), and Swift (via lizard). Requires Ruby >= 3.3.
8
8
 
9
9
  ## Commands
10
10
 
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  # ChurnVsComplexity
4
4
 
5
- Correlates file churn (how often files change) with complexity scores to identify refactoring hotspots and track codebase health over time. Supports Ruby, JavaScript/TypeScript, Java, Python, Go, and Kotlin.
5
+ Correlates file churn (how often files change) with complexity scores to identify refactoring hotspots and track codebase health over time. Supports Ruby, JavaScript/TypeScript, Java, Python, Go, Kotlin, Rust, and Swift.
6
6
 
7
7
  Modes include hotspots ranking, triage assessment, CI quality gate, diff comparison, focus sessions, and timetravel history.
8
8
 
@@ -34,6 +34,8 @@ External tool dependencies per language:
34
34
  - **Python**: Requires [Radon](https://radon.readthedocs.io) on the search path as `radon`. Install with `pip install radon`.
35
35
  - **Go**: Requires [gocyclo](https://github.com/fzipp/gocyclo) on the search path. Install with `go install github.com/fzipp/gocyclo/cmd/gocyclo@latest`.
36
36
  - **Kotlin**: Requires [lizard](https://github.com/terryyin/lizard) on the search path. Install with `pip install lizard`.
37
+ - **Rust**: Requires [lizard](https://github.com/terryyin/lizard) on the search path. Install with `pip install lizard`.
38
+ - **Swift**: Requires [lizard](https://github.com/terryyin/lizard) on the search path. Install with `pip install lizard`.
37
39
 
38
40
  ## Usage
39
41
 
@@ -50,6 +52,8 @@ Languages:
50
52
  --python Check complexity of python files
51
53
  --go Check complexity of go files
52
54
  --kotlin Check complexity of kotlin files
55
+ --rust Check complexity of rust files
56
+ --swift Check complexity of swift files
53
57
 
54
58
  Modes (mutually exclusive):
55
59
  --timetravel N Calculate summary for all commits at intervals of N days throughout project history or from the date specified with --since
@@ -126,6 +130,12 @@ churn_vs_complexity --js --delta HEAD --summary my_js_project
126
130
 
127
131
  # Kotlin project hotspots
128
132
  churn_vs_complexity --kotlin --hotspots my_kotlin_project
133
+
134
+ # Rust project CSV report
135
+ churn_vs_complexity --rust --csv my_rust_project > ~/Desktop/rust-demo.csv
136
+
137
+ # Swift project summary
138
+ churn_vs_complexity --swift --summary -q my_swift_project
129
139
  ```
130
140
 
131
141
  ## Development
@@ -38,6 +38,14 @@ module ChurnVsComplexity
38
38
  options[:language] = :kotlin
39
39
  end
40
40
 
41
+ opts.on('--rust', 'Check complexity of rust files') do
42
+ options[:language] = :rust
43
+ end
44
+
45
+ opts.on('--swift', 'Check complexity of swift files') do
46
+ options[:language] = :swift
47
+ end
48
+
41
49
  opts.separator ''
42
50
  opts.separator 'Modes (mutually exclusive):'
43
51
 
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'open3'
4
+
5
+ module ChurnVsComplexity
6
+ module Complexity
7
+ module RustCalculator
8
+ class << self
9
+ attr_writer :command_runner
10
+
11
+ def folder_based? = false
12
+
13
+ def calculate(files:)
14
+ csv_output = run_lizard(files)
15
+ parse_lizard_output(csv_output, files:)
16
+ end
17
+
18
+ # Lizard CSV format: NLOC,CCN,tokens,params,length,"loc","file","func","long_func",start,end
19
+ LIZARD_LINE_PATTERN = /^\d+,(\d+),\d+,\d+,\d+,"[^"]*","([^"]*)"/
20
+
21
+ def parse_lizard_output(csv_output, files:)
22
+ scores = Hash.new(0)
23
+ csv_output.each_line do |line|
24
+ match = line.match(LIZARD_LINE_PATTERN)
25
+ next unless match
26
+
27
+ scores[match[2]] += match[1].to_i
28
+ end
29
+ files.to_h { |file| [file, scores[file] || 0] }
30
+ end
31
+
32
+ def check_dependencies!
33
+ command_runner.call('lizard --version 2>&1')
34
+ rescue Errno::ENOENT
35
+ raise Error, 'Needs lizard installed (pip install lizard)'
36
+ end
37
+
38
+ private
39
+
40
+ def command_runner
41
+ @command_runner || Open3.method(:capture2)
42
+ end
43
+
44
+ def run_lizard(files)
45
+ files_arg = files.map { |f| "'#{f}'" }.join(' ')
46
+ stdout, status = command_runner.call("lizard --csv #{files_arg}")
47
+ raise Error, "lizard failed (exit #{status.exitstatus}). Is it installed?" unless status.success?
48
+
49
+ stdout
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'open3'
4
+
5
+ module ChurnVsComplexity
6
+ module Complexity
7
+ module SwiftCalculator
8
+ class << self
9
+ attr_writer :command_runner
10
+
11
+ def folder_based? = false
12
+
13
+ def calculate(files:)
14
+ csv_output = run_lizard(files)
15
+ parse_lizard_output(csv_output, files:)
16
+ end
17
+
18
+ # Lizard CSV format: NLOC,CCN,tokens,params,length,"loc","file","func","long_func",start,end
19
+ LIZARD_LINE_PATTERN = /^\d+,(\d+),\d+,\d+,\d+,"[^"]*","([^"]*)"/
20
+
21
+ def parse_lizard_output(csv_output, files:)
22
+ scores = Hash.new(0)
23
+ csv_output.each_line do |line|
24
+ match = line.match(LIZARD_LINE_PATTERN)
25
+ next unless match
26
+
27
+ scores[match[2]] += match[1].to_i
28
+ end
29
+ files.to_h { |file| [file, scores[file] || 0] }
30
+ end
31
+
32
+ def check_dependencies!
33
+ command_runner.call('lizard --version 2>&1')
34
+ rescue Errno::ENOENT
35
+ raise Error, 'Needs lizard installed (pip install lizard)'
36
+ end
37
+
38
+ private
39
+
40
+ def command_runner
41
+ @command_runner || Open3.method(:capture2)
42
+ end
43
+
44
+ def run_lizard(files)
45
+ files_arg = files.map { |f| "'#{f}'" }.join(' ')
46
+ stdout, status = command_runner.call("lizard --csv #{files_arg}")
47
+ raise Error, "lizard failed (exit #{status.exitstatus}). Is it installed?" unless status.success?
48
+
49
+ stdout
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -6,6 +6,8 @@ require_relative 'complexity/eslint_calculator'
6
6
  require_relative 'complexity/python_calculator'
7
7
  require_relative 'complexity/go_calculator'
8
8
  require_relative 'complexity/kotlin_calculator'
9
+ require_relative 'complexity/rust_calculator'
10
+ require_relative 'complexity/swift_calculator'
9
11
 
10
12
  module ChurnVsComplexity
11
13
  module Complexity
@@ -14,6 +14,10 @@ module ChurnVsComplexity
14
14
  Complexity::GoCalculator.check_dependencies!
15
15
  when :kotlin
16
16
  Complexity::KotlinCalculator.check_dependencies!
17
+ when :rust
18
+ Complexity::RustCalculator.check_dependencies!
19
+ when :swift
20
+ Complexity::SwiftCalculator.check_dependencies!
17
21
  end
18
22
  end
19
23
  end
@@ -40,6 +40,10 @@ module ChurnVsComplexity
40
40
  FileSelector::Go.predefined(included:, excluded:)
41
41
  when :kotlin
42
42
  FileSelector::Kotlin.predefined(included:, excluded:)
43
+ when :rust
44
+ FileSelector::Rust.predefined(included:, excluded:)
45
+ when :swift
46
+ FileSelector::Swift.predefined(included:, excluded:)
43
47
  end
44
48
  end
45
49
 
@@ -57,6 +61,10 @@ module ChurnVsComplexity
57
61
  Complexity::GoCalculator
58
62
  when :kotlin
59
63
  Complexity::KotlinCalculator
64
+ when :rust
65
+ Complexity::RustCalculator
66
+ when :swift
67
+ Complexity::SwiftCalculator
60
68
  end
61
69
  end
62
70
  end
@@ -16,6 +16,10 @@ module ChurnVsComplexity
16
16
  ['.go']
17
17
  when :kotlin
18
18
  ['.kt', '.kts']
19
+ when :rust
20
+ ['.rs']
21
+ when :swift
22
+ ['.swift']
19
23
  else
20
24
  raise Error, "Unsupported language: #{language}"
21
25
  end
@@ -142,5 +146,25 @@ module ChurnVsComplexity
142
146
  Predefined.new(included:, extensions: FileSelector.extensions(:kotlin), excluded:)
143
147
  end
144
148
  end
149
+
150
+ module Rust
151
+ def self.excluding(excluded)
152
+ Excluding.new(FileSelector.extensions(:rust), excluded)
153
+ end
154
+
155
+ def self.predefined(included:, excluded:)
156
+ Predefined.new(included:, extensions: FileSelector.extensions(:rust), excluded:)
157
+ end
158
+ end
159
+
160
+ module Swift
161
+ def self.excluding(excluded)
162
+ Excluding.new(FileSelector.extensions(:swift), excluded)
163
+ end
164
+
165
+ def self.predefined(included:, excluded:)
166
+ Predefined.new(included:, extensions: FileSelector.extensions(:swift), excluded:)
167
+ end
168
+ end
145
169
  end
146
170
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module ChurnVsComplexity
4
4
  module LanguageValidator
5
- SUPPORTED = %i[java ruby javascript python go kotlin].freeze
5
+ SUPPORTED = %i[java ruby javascript python go kotlin rust swift].freeze
6
6
 
7
7
  def self.validate!(language)
8
8
  raise ValidationError, "Unsupported language: #{language}" unless SUPPORTED.include?(language)
@@ -83,6 +83,22 @@ module ChurnVsComplexity
83
83
  serializer:,
84
84
  since: @since || @relative_period,
85
85
  )
86
+ when :rust
87
+ Engine.concurrent(
88
+ complexity: Complexity::RustCalculator,
89
+ churn:,
90
+ file_selector: FileSelector::Rust.excluding(@excluded),
91
+ serializer:,
92
+ since: @since || @relative_period,
93
+ )
94
+ when :swift
95
+ Engine.concurrent(
96
+ complexity: Complexity::SwiftCalculator,
97
+ churn:,
98
+ file_selector: FileSelector::Swift.excluding(@excluded),
99
+ serializer:,
100
+ since: @since || @relative_period,
101
+ )
86
102
  end
87
103
  end
88
104
 
@@ -15,6 +15,8 @@ module ChurnVsComplexity
15
15
  # Java PMD Cyclomatic complexity 1 ~39 1-40
16
16
  # Go gocognit Cognitive complexity 0 ~87 0-50
17
17
  # Kotlin lizard Cyclomatic complexity 1 ~40 1-40
18
+ # Rust lizard Cyclomatic complexity 1 ~40 1-40
19
+ # Swift lizard Cyclomatic complexity 1 ~40 1-40
18
20
  #
19
21
  # The DEFAULT_LOW and DEFAULT_HIGH thresholds below are rough midpoints
20
22
  # suitable for Java/Python/JS. They are too aggressive for Ruby (Flog
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ChurnVsComplexity
4
- VERSION = '1.7.0'
4
+ VERSION = '1.8.0'
5
5
  end
@@ -0,0 +1,7 @@
1
+ fn main() {
2
+ println!("Hello, world!");
3
+ }
4
+
5
+ fn add(a: i32, b: i32) -> i32 {
6
+ a + b
7
+ }
@@ -0,0 +1,20 @@
1
+ fn calculate_sum(numbers: &[i32]) -> i32 {
2
+ let mut total = 0;
3
+ for n in numbers {
4
+ total += n;
5
+ }
6
+ total
7
+ }
8
+
9
+ fn classify(value: i32) -> &'static str {
10
+ match value {
11
+ 0 => "zero",
12
+ 1..=10 => "low",
13
+ 11..=100 => "medium",
14
+ _ => "high",
15
+ }
16
+ }
17
+
18
+ fn is_even(n: i32) -> bool {
19
+ n % 2 == 0
20
+ }
@@ -0,0 +1,7 @@
1
+ func main() {
2
+ print("Hello, world!")
3
+ }
4
+
5
+ func add(_ a: Int, _ b: Int) -> Int {
6
+ return a + b
7
+ }
@@ -0,0 +1,24 @@
1
+ func calculateSum(_ numbers: [Int]) -> Int {
2
+ var total = 0
3
+ for n in numbers {
4
+ total += n
5
+ }
6
+ return total
7
+ }
8
+
9
+ func classify(_ value: Int) -> String {
10
+ switch value {
11
+ case 0:
12
+ return "zero"
13
+ case 1...10:
14
+ return "low"
15
+ case 11...100:
16
+ return "medium"
17
+ default:
18
+ return "high"
19
+ }
20
+ }
21
+
22
+ func isEven(_ n: Int) -> Bool {
23
+ return n % 2 == 0
24
+ }
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: churn_vs_complexity
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.7.0
4
+ version: 1.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Erik T. Madsen
@@ -39,9 +39,9 @@ dependencies:
39
39
  version: '2.1'
40
40
  description: Correlates file churn (how often files change) with complexity scores
41
41
  to identify refactoring hotspots. Supports Ruby, JavaScript/TypeScript, Java, Python,
42
- Go, and Kotlin. Modes include hotspots ranking, triage assessment, CI quality gate,
43
- diff comparison, focus sessions, and timetravel history. Inspired by Michael Feathers'
44
- article "Getting Empirical about Refactoring".
42
+ Go, Kotlin, Rust, and Swift. Modes include hotspots ranking, triage assessment,
43
+ CI quality gate, diff comparison, focus sessions, and timetravel history. Inspired
44
+ by Michael Feathers' article "Getting Empirical about Refactoring".
45
45
  email:
46
46
  - beatmadsen@gmail.com
47
47
  executables:
@@ -74,6 +74,8 @@ files:
74
74
  - lib/churn_vs_complexity/complexity/pmd/files_calculator.rb
75
75
  - lib/churn_vs_complexity/complexity/pmd/folder_calculator.rb
76
76
  - lib/churn_vs_complexity/complexity/python_calculator.rb
77
+ - lib/churn_vs_complexity/complexity/rust_calculator.rb
78
+ - lib/churn_vs_complexity/complexity/swift_calculator.rb
77
79
  - lib/churn_vs_complexity/complexity_validator.rb
78
80
  - lib/churn_vs_complexity/concurrent_calculator.rb
79
81
  - lib/churn_vs_complexity/delta.rb
@@ -150,6 +152,10 @@ files:
150
152
  - tmp/test-support/kotlin/Utils.kt
151
153
  - tmp/test-support/python/example.py
152
154
  - tmp/test-support/python/utils.py
155
+ - tmp/test-support/rust/main.rs
156
+ - tmp/test-support/rust/utils.rs
157
+ - tmp/test-support/swift/main.swift
158
+ - tmp/test-support/swift/utils.swift
153
159
  - tmp/test-support/txt/abc.txt
154
160
  - tmp/test-support/txt/d.txt
155
161
  - tmp/test-support/txt/ef.txt