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