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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d748bfb7d21087a5fbcb360dbc05dcd46b5ec05bf2c2fcc542e2e25368084d78
4
- data.tar.gz: 0d160c582aee2696823061dd7623cf17e9b537d798ae31502eb30b065a61391d
3
+ metadata.gz: de43228c387df735f0bd01eff8f53cfac5bf752474be0f4e901d29d5847781f5
4
+ data.tar.gz: d78475fd751dff01f1aafb45e995cdeabd2d0561348e86de5cf5f951dcc7b6f0
5
5
  SHA512:
6
- metadata.gz: f521d39fddad8e8adf05ab0f21d8b8adcc2460f9a57c553b90c73eb60451aafed19ab11977768d494987ff72838c856dd04c3910781ec0da34f53d82d18d9d4e
7
- data.tar.gz: 3ab17db013540128bd428edb54cf0615f47ce50caa19d1ad418261c20efde494bb36ea8e84408e0ca330f7721013a9e8958f3cd5a16565636186508e59bf7360
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
- formatted_date = since.strftime('%Y-%m-%d')
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(file:)
11
+ def calculate(files:)
14
12
  flog = Flog.new
15
- flog.flog(file)
16
- { file => flog.total_score }
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
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative 'complexity/pmd_calculator'
4
4
  require_relative 'complexity/flog_calculator'
5
+ require_relative 'complexity/eslint_calculator'
5
6
 
6
7
  module ChurnVsComplexity
7
8
  module Complexity
@@ -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].each_with_object({}) do |file, acc|
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
@@ -22,7 +22,7 @@ module ChurnVsComplexity
22
22
  @end_date = end_date
23
23
  end
24
24
 
25
- def effective_start_date = Time.at(0)
25
+ def effective_start_date = Time.at(0).to_date
26
26
 
27
27
  def requested_start_date = nil
28
28
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ChurnVsComplexity
4
- VERSION = '1.2.0'
4
+ VERSION = '1.3.0'
5
5
  end
data/package-lock.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "name": "churn_vs_complexity",
3
+ "lockfileVersion": 3,
4
+ "requires": true,
5
+ "packages": {}
6
+ }
@@ -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,11 @@
1
+ {
2
+ "type": "module",
3
+ "dependencies": {
4
+ "eslint-plugin-complexity": "^1.0.2",
5
+ "@eslint/js": "^9.11.1",
6
+ "@types/eslint__js": "^8.42.3",
7
+ "eslint": "^9.11.1",
8
+ "typescript": "^5.6.2",
9
+ "typescript-eslint": "^8.7.0"
10
+ }
11
+ }
@@ -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,12 @@
1
+ function fibonacci(n) {
2
+ if (n <= 1) return n;
3
+ return fibonacci(n - 1) + fibonacci(n - 2);
4
+ }
5
+
6
+ function printFibonacciSequence(length) {
7
+ for (let i = 0; i < length; i++) {
8
+ console.log(fibonacci(i));
9
+ }
10
+ }
11
+
12
+ printFibonacciSequence(10);
@@ -0,0 +1,5 @@
1
+ function greet(name) {
2
+ return `Hello, ${name}!`;
3
+ }
4
+
5
+ console.log(greet('World'));
@@ -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.2.0
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-20 00:00:00.000000000 Z
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