churn_vs_complexity 1.2.0 → 1.3.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 +5 -0
- data/README.md +3 -0
- data/lib/churn_vs_complexity/churn.rb +6 -1
- data/lib/churn_vs_complexity/cli.rb +4 -0
- data/lib/churn_vs_complexity/complexity/eslint_calculator.rb +36 -0
- data/lib/churn_vs_complexity/complexity/flog_calculator.rb +6 -5
- data/lib/churn_vs_complexity/complexity.rb +1 -0
- data/lib/churn_vs_complexity/concurrent_calculator.rb +1 -3
- data/lib/churn_vs_complexity/config.rb +9 -1
- data/lib/churn_vs_complexity/file_selector.rb +12 -1
- data/lib/churn_vs_complexity/git_date.rb +1 -1
- data/lib/churn_vs_complexity/version.rb +1 -1
- data/package-lock.json +6 -0
- data/tmp/eslint-support/complexity-calculator.js +51 -0
- data/tmp/eslint-support/package.json +11 -0
- data/tmp/test-support/javascript/complex.js +43 -0
- data/tmp/test-support/javascript/moderate.js +12 -0
- data/tmp/test-support/javascript/simple.js +5 -0
- data/tmp/test-support/javascript/typescript-example.ts +26 -0
- metadata +10 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: de43228c387df735f0bd01eff8f53cfac5bf752474be0f4e901d29d5847781f5
|
4
|
+
data.tar.gz: d78475fd751dff01f1aafb45e995cdeabd2d0561348e86de5cf5f951dcc7b6f0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4297b81854c54d4b60672f89156977299549cc012519b178ce206f3108e3e7dc65b9a9d0341b0f218945b9be580f9cc46fb8b5d049a3e5508f4fb4d026e23f5f
|
7
|
+
data.tar.gz: 1338de4b39496a4af2f9485c14178112558399ab308b03d76ad18294e8a5cb6f87f55f5a0990ea0963ae63d3e6a5c49758c5aed072390e2610e2f0e5b52c2723
|
data/CHANGELOG.md
CHANGED
@@ -12,3 +12,8 @@
|
|
12
12
|
- Fix bug in CLI where new flags and `--since` would not be recognized
|
13
13
|
- Improve selection of observations included in the output
|
14
14
|
- Fixed calculation of churn that would never be zero
|
15
|
+
|
16
|
+
## [1.3.0] - 2024-09-26
|
17
|
+
|
18
|
+
- Add support for javascript and typescript complexity calculation using eslint
|
19
|
+
- Fixed behavior when --since or short-hand flags were not provided
|
data/README.md
CHANGED
@@ -34,6 +34,8 @@ Execute the `churn_vs_complexity` with the applicable arguments. Output in the r
|
|
34
34
|
Usage: churn_vs_complexity [options] folder
|
35
35
|
--java Check complexity of java classes
|
36
36
|
--ruby Check complexity of ruby files
|
37
|
+
--js, --ts, --javascript, --typescript
|
38
|
+
Check complexity of javascript and typescript files
|
37
39
|
--csv Format output as CSV
|
38
40
|
--graph Format output as HTML page with Churn vs Complexity graph
|
39
41
|
--summary Output summary statistics (mean and median) for churn and complexity
|
@@ -42,6 +44,7 @@ Usage: churn_vs_complexity [options] folder
|
|
42
44
|
-m, --month Calculate churn for the month leading up to the most recent commit
|
43
45
|
-q, --quarter Calculate churn for the quarter leading up to the most recent commit
|
44
46
|
-y, --year Calculate churn for the year leading up to the most recent commit
|
47
|
+
--dry-run Echo the chosen options from the CLI
|
45
48
|
-h, --help Display help
|
46
49
|
```
|
47
50
|
|
@@ -8,7 +8,8 @@ module ChurnVsComplexity
|
|
8
8
|
class << self
|
9
9
|
def calculate(folder:, file:, since:)
|
10
10
|
git_dir = File.join(folder, '.git')
|
11
|
-
|
11
|
+
earliest_date = [date_of_first_commit(folder:), since].max
|
12
|
+
formatted_date = earliest_date.strftime('%Y-%m-%d')
|
12
13
|
cmd = %Q(git --git-dir #{git_dir} --work-tree #{folder} log --format="%H" --follow --since="#{formatted_date}" -- #{file} | wc -l)
|
13
14
|
`#{cmd}`.to_i
|
14
15
|
end
|
@@ -19,6 +20,10 @@ module ChurnVsComplexity
|
|
19
20
|
|
20
21
|
private
|
21
22
|
|
23
|
+
def date_of_first_commit(folder:)
|
24
|
+
repo(folder).log.last&.date&.to_date || Time.at(0).to_date
|
25
|
+
end
|
26
|
+
|
22
27
|
def repo(folder)
|
23
28
|
repos[folder] ||= Git.open(folder)
|
24
29
|
end
|
@@ -22,6 +22,10 @@ module ChurnVsComplexity
|
|
22
22
|
options[:language] = :ruby
|
23
23
|
end
|
24
24
|
|
25
|
+
opts.on('--js', '--ts', '--javascript', '--typescript', 'Check complexity of javascript and typescript files') do
|
26
|
+
options[:language] = :javascript
|
27
|
+
end
|
28
|
+
|
25
29
|
opts.on('--csv', 'Format output as CSV') do
|
26
30
|
options[:serializer] = :csv
|
27
31
|
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ChurnVsComplexity
|
4
|
+
module Complexity
|
5
|
+
module ESLintCalculator
|
6
|
+
class << self
|
7
|
+
def folder_based? = false
|
8
|
+
|
9
|
+
def calculate(files:)
|
10
|
+
dir_path = File.join(gem_root, 'tmp', 'eslint-support')
|
11
|
+
script_path = File.join(dir_path, 'complexity-calculator.js')
|
12
|
+
install_command = "npm install --prefix '#{dir_path}'"
|
13
|
+
`#{install_command}`
|
14
|
+
|
15
|
+
|
16
|
+
command = "node #{script_path} '#{files.to_json}'"
|
17
|
+
complexity = `#{command}`
|
18
|
+
|
19
|
+
if complexity.empty?
|
20
|
+
raise Error, "Failed to calculate complexity"
|
21
|
+
end
|
22
|
+
all = JSON.parse(complexity)
|
23
|
+
all.to_h do |abc|
|
24
|
+
[abc['file'], abc['complexity']]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def gem_root
|
31
|
+
File.expand_path('../../..', __dir__)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -5,15 +5,16 @@ require 'flog'
|
|
5
5
|
module ChurnVsComplexity
|
6
6
|
module Complexity
|
7
7
|
module FlogCalculator
|
8
|
-
CONCURRENCY = Etc.nprocessors
|
9
|
-
|
10
8
|
class << self
|
11
9
|
def folder_based? = false
|
12
10
|
|
13
|
-
def calculate(
|
11
|
+
def calculate(files:)
|
14
12
|
flog = Flog.new
|
15
|
-
|
16
|
-
|
13
|
+
# TODO: Run this concurrently
|
14
|
+
files.to_h do |file|
|
15
|
+
flog.flog(file)
|
16
|
+
[file, flog.total_score]
|
17
|
+
end
|
17
18
|
end
|
18
19
|
end
|
19
20
|
end
|
@@ -30,9 +30,7 @@ module ChurnVsComplexity
|
|
30
30
|
files[:explicitly_excluded].each { |file| result.delete(file) }
|
31
31
|
result
|
32
32
|
else
|
33
|
-
files[:included]
|
34
|
-
acc.merge!(@complexity.calculate(file:))
|
35
|
-
end
|
33
|
+
@complexity.calculate(files: files[:included])
|
36
34
|
end
|
37
35
|
end
|
38
36
|
|
@@ -19,7 +19,7 @@ module ChurnVsComplexity
|
|
19
19
|
end
|
20
20
|
|
21
21
|
def validate!
|
22
|
-
raise Error, "Unsupported language: #{@language}" unless %i[java ruby].include?(@language)
|
22
|
+
raise Error, "Unsupported language: #{@language}" unless %i[java ruby javascript].include?(@language)
|
23
23
|
raise Error, "Unsupported serializer: #{@serializer}" unless %i[none csv graph summary].include?(@serializer)
|
24
24
|
|
25
25
|
@since_validator.validate!(@since)
|
@@ -44,6 +44,14 @@ module ChurnVsComplexity
|
|
44
44
|
serializer:,
|
45
45
|
since: @since,
|
46
46
|
)
|
47
|
+
when :javascript
|
48
|
+
Engine.concurrent(
|
49
|
+
complexity: Complexity::ESLintCalculator,
|
50
|
+
churn:,
|
51
|
+
file_selector: FileSelector::JavaScript.excluding(@excluded),
|
52
|
+
serializer:,
|
53
|
+
since: @since,
|
54
|
+
)
|
47
55
|
end
|
48
56
|
end
|
49
57
|
|
@@ -10,9 +10,10 @@ module ChurnVsComplexity
|
|
10
10
|
end
|
11
11
|
|
12
12
|
class Excluding
|
13
|
-
def initialize(extensions, excluded)
|
13
|
+
def initialize(extensions, excluded, convert_to_absolute_path = false)
|
14
14
|
@extensions = extensions
|
15
15
|
@excluded = excluded
|
16
|
+
@convert_to_absolute_path = convert_to_absolute_path
|
16
17
|
end
|
17
18
|
|
18
19
|
def select_files(folder)
|
@@ -25,6 +26,10 @@ module ChurnVsComplexity
|
|
25
26
|
were_included << f
|
26
27
|
end
|
27
28
|
end
|
29
|
+
if @convert_to_absolute_path
|
30
|
+
were_excluded.map! { |f| File.absolute_path(f) }
|
31
|
+
were_included.map! { |f| File.absolute_path(f) }
|
32
|
+
end
|
28
33
|
{ explicitly_excluded: were_excluded, included: were_included }
|
29
34
|
end
|
30
35
|
|
@@ -50,5 +55,11 @@ module ChurnVsComplexity
|
|
50
55
|
Excluding.new(['.rb'], excluded)
|
51
56
|
end
|
52
57
|
end
|
58
|
+
|
59
|
+
module JavaScript
|
60
|
+
def self.excluding(excluded)
|
61
|
+
Excluding.new(['.js', '.jsx', '.ts', '.tsx'], excluded, true)
|
62
|
+
end
|
63
|
+
end
|
53
64
|
end
|
54
65
|
end
|
data/package-lock.json
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
import { ESLint } from 'eslint';
|
2
|
+
|
3
|
+
import eslint from '@eslint/js';
|
4
|
+
import tseslint from 'typescript-eslint';
|
5
|
+
|
6
|
+
async function analyzeComplexity(files) {
|
7
|
+
const overrideConfig = tseslint.config(
|
8
|
+
eslint.configs.recommended,
|
9
|
+
...tseslint.configs.recommended,
|
10
|
+
{
|
11
|
+
rules: {
|
12
|
+
'complexity': ['warn', 0],
|
13
|
+
},
|
14
|
+
|
15
|
+
}
|
16
|
+
);
|
17
|
+
|
18
|
+
const linter = new ESLint({
|
19
|
+
overrideConfigFile: true,
|
20
|
+
overrideConfig,
|
21
|
+
cwd: '/',
|
22
|
+
});
|
23
|
+
|
24
|
+
try {
|
25
|
+
const results = await linter.lintFiles(files);
|
26
|
+
const complexityResults = results.map(result => {
|
27
|
+
const messages = result.messages.filter(msg => msg.ruleId === 'complexity');
|
28
|
+
const complexity = messages.reduce((sum, msg) => {
|
29
|
+
const complexityValue = parseInt(msg.message.match(/\d+/)[0], 10);
|
30
|
+
return sum + complexityValue;
|
31
|
+
}, 0);
|
32
|
+
|
33
|
+
if (complexity === 0) {
|
34
|
+
console.error("File has no complexity", result);
|
35
|
+
}
|
36
|
+
|
37
|
+
return {
|
38
|
+
file: result.filePath,
|
39
|
+
complexity,
|
40
|
+
};
|
41
|
+
});
|
42
|
+
|
43
|
+
console.log(JSON.stringify(complexityResults));
|
44
|
+
} catch (error) {
|
45
|
+
console.error('Error during analysis:', error);
|
46
|
+
process.exit(1);
|
47
|
+
}
|
48
|
+
}
|
49
|
+
|
50
|
+
const files = JSON.parse(process.argv[2]);
|
51
|
+
analyzeComplexity(files);
|
@@ -0,0 +1,43 @@
|
|
1
|
+
function analyzeNumber(num) {
|
2
|
+
let result = '';
|
3
|
+
|
4
|
+
if (num < 0) {
|
5
|
+
result += 'negative ';
|
6
|
+
} else if (num > 0) {
|
7
|
+
result += 'positive ';
|
8
|
+
} else {
|
9
|
+
return 'zero';
|
10
|
+
}
|
11
|
+
|
12
|
+
if (num % 2 === 0) {
|
13
|
+
result += 'even ';
|
14
|
+
} else {
|
15
|
+
result += 'odd ';
|
16
|
+
}
|
17
|
+
|
18
|
+
if (num % 3 === 0) {
|
19
|
+
result += 'divisible by 3 ';
|
20
|
+
}
|
21
|
+
|
22
|
+
if (num % 5 === 0) {
|
23
|
+
result += 'divisible by 5 ';
|
24
|
+
}
|
25
|
+
|
26
|
+
if (isPrime(num)) {
|
27
|
+
result += 'prime ';
|
28
|
+
}
|
29
|
+
|
30
|
+
return result.trim();
|
31
|
+
}
|
32
|
+
|
33
|
+
function isPrime(num) {
|
34
|
+
if (num <= 1) return false;
|
35
|
+
for (let i = 2; i <= Math.sqrt(num); i++) {
|
36
|
+
if (num % i === 0) return false;
|
37
|
+
}
|
38
|
+
return true;
|
39
|
+
}
|
40
|
+
|
41
|
+
console.log(analyzeNumber(17));
|
42
|
+
console.log(analyzeNumber(30));
|
43
|
+
console.log(analyzeNumber(-7));
|
@@ -0,0 +1,26 @@
|
|
1
|
+
interface Person {
|
2
|
+
name: string;
|
3
|
+
age: number;
|
4
|
+
}
|
5
|
+
|
6
|
+
function createGreeting(person: Person): string {
|
7
|
+
let greeting = `Hello, ${person.name}!`;
|
8
|
+
|
9
|
+
if (person.age < 18) {
|
10
|
+
greeting += " You're still a minor.";
|
11
|
+
} else if (person.age >= 18 && person.age < 65) {
|
12
|
+
greeting += " You're an adult.";
|
13
|
+
} else {
|
14
|
+
greeting += " You're a senior citizen.";
|
15
|
+
}
|
16
|
+
|
17
|
+
return greeting;
|
18
|
+
}
|
19
|
+
|
20
|
+
const alice: Person = { name: 'Alice', age: 30 };
|
21
|
+
const bob: Person = { name: 'Bob', age: 17 };
|
22
|
+
const charlie: Person = { name: 'Charlie', age: 70 };
|
23
|
+
|
24
|
+
console.log(createGreeting(alice));
|
25
|
+
console.log(createGreeting(bob));
|
26
|
+
console.log(createGreeting(charlie));
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: churn_vs_complexity
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Erik T. Madsen
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-09-
|
11
|
+
date: 2024-09-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: flog
|
@@ -62,6 +62,7 @@ files:
|
|
62
62
|
- lib/churn_vs_complexity/churn.rb
|
63
63
|
- lib/churn_vs_complexity/cli.rb
|
64
64
|
- lib/churn_vs_complexity/complexity.rb
|
65
|
+
- lib/churn_vs_complexity/complexity/eslint_calculator.rb
|
65
66
|
- lib/churn_vs_complexity/complexity/flog_calculator.rb
|
66
67
|
- lib/churn_vs_complexity/complexity/pmd_calculator.rb
|
67
68
|
- lib/churn_vs_complexity/concurrent_calculator.rb
|
@@ -71,10 +72,17 @@ files:
|
|
71
72
|
- lib/churn_vs_complexity/git_date.rb
|
72
73
|
- lib/churn_vs_complexity/serializer.rb
|
73
74
|
- lib/churn_vs_complexity/version.rb
|
75
|
+
- package-lock.json
|
76
|
+
- tmp/eslint-support/complexity-calculator.js
|
77
|
+
- tmp/eslint-support/package.json
|
74
78
|
- tmp/pmd-support/ruleset.xml
|
75
79
|
- tmp/template/graph.html
|
76
80
|
- tmp/test-support/java/small-example/src/main/java/org/example/Main.java
|
77
81
|
- tmp/test-support/java/small-example/src/main/java/org/example/spice/Checker.java
|
82
|
+
- tmp/test-support/javascript/complex.js
|
83
|
+
- tmp/test-support/javascript/moderate.js
|
84
|
+
- tmp/test-support/javascript/simple.js
|
85
|
+
- tmp/test-support/javascript/typescript-example.ts
|
78
86
|
- tmp/test-support/txt/abc.txt
|
79
87
|
- tmp/test-support/txt/d.txt
|
80
88
|
- tmp/test-support/txt/ef.txt
|