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 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