rubocop_director 0.1.0 โ 0.2.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 +15 -3
- data/README.md +15 -13
- data/lib/rubocop_director/commands/generate_config.rb +30 -13
- data/lib/rubocop_director/commands/plan.rb +8 -8
- data/lib/rubocop_director/file_stats_builder.rb +11 -5
- data/lib/rubocop_director/output_formatter.rb +7 -5
- data/lib/rubocop_director/rubocop_stats.rb +33 -7
- data/lib/rubocop_director/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 06b576908b0f9b2cf1470350c38e53c7339caca11fb7abca712909feac9508f7
|
4
|
+
data.tar.gz: 2087b4e9a5002c5a2c2294a573426def90556ec98866c07421b67355d928fc24
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c798e0ac5e7b7b53339bc0ec001d5165f4bcbe794952f0a297b70cba7ca5cba1fdffd3ea88395e177c72e5d43bc19afe4f56e02e7b77994e56f4e698d77b0dc1
|
7
|
+
data.tar.gz: d46b526303bd4930b5a4b4e9d63054d08ae472ed813bb1fbf366ccd08c18f873a8f1e9b1b8cbb7b357abce89face540b22ca66bf406db56f9130a5878d646479
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,17 @@
|
|
1
|
-
|
1
|
+
# Change log
|
2
2
|
|
3
|
-
##
|
3
|
+
## master
|
4
4
|
|
5
|
-
|
5
|
+
## 0.2.0 (2023-06-03)
|
6
|
+
|
7
|
+
- [PR #13](https://github.com/DmitryTsepelev/rubocop_director/pull/13) Prettify output formatter and change stats calculation algorithm ([@DmitryTsepelev][])
|
8
|
+
- [PR #9](https://github.com/DmitryTsepelev/rubocop_director/pull/9) Removed sed dependency: stats generated using temp config ([@samarthkathal][])
|
9
|
+
- [PR #8](https://github.com/DmitryTsepelev/rubocop_director/pull/8) Reorder plan flow to run expensive check first ([@DmitryTsepelev][])
|
10
|
+
- [PR #7](https://github.com/DmitryTsepelev/rubocop_director/pull/7) GenerateConfig will a prompt to overwrite when config already exists ([@samarthkathal][])
|
11
|
+
|
12
|
+
## 0.1.0 (2023-05-08)
|
13
|
+
|
14
|
+
- Initial version ([@DmitryTsepelev][])
|
15
|
+
|
16
|
+
[@DmitryTsepelev]: https://github.com/DmitryTsepelev
|
17
|
+
[@samarthkathal]: https://github.com/samarthkathal
|
data/README.md
CHANGED
@@ -6,7 +6,6 @@ A commandโline utility for refactoring planning. It uses `.rubocop_todo.yml` a
|
|
6
6
|
|
7
7
|
Prerequisites:
|
8
8
|
|
9
|
-
- `sed`;
|
10
9
|
- `git` repo;
|
11
10
|
- generated `.rubocop_todo.yml`.
|
12
11
|
|
@@ -44,21 +43,24 @@ As a result you'll get something like this:
|
|
44
43
|
๐ก Checking git history since 1995-01-01 to find hot files...
|
45
44
|
๐ก๐ฅ Running rubocop to get the list of offences to fix...
|
46
45
|
๐ก๐ฅ๐ฌ Calculating a list of files to refactor...
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
46
|
+
|
47
|
+
Path: app/controllers/user_controller.rb
|
48
|
+
Updated 99 times since 2023-01-01
|
49
|
+
Offenses:
|
50
|
+
๐ Rails/SomeCop - 2
|
51
|
+
Refactoring value: 1.5431217598108933 (54.79575%)
|
52
|
+
|
53
|
+
Path: app/models/user.rb
|
54
|
+
Updated 136 times since 2023-01-01
|
55
|
+
Offenses:
|
56
|
+
๐ Rails/SomeCop - 1
|
57
|
+
๐ Rails/AnotherCop - 1
|
58
|
+
Refactoring value: 1.2730122208719792 (45.20425%)
|
57
59
|
```
|
58
60
|
|
59
61
|
> Want a different output format (e.g., CSV)? Let me know, open an issue!
|
60
62
|
|
61
|
-
Value is calculated using a formula: `sum of value from each cop (<
|
63
|
+
Value is calculated using a formula: `sum of value from each cop (<count of offences> * <cop weight> * (<count of file updates> / <total count of updates>) ** <update weight>)`.
|
62
64
|
|
63
65
|
If you need to count updates from a specific dateโuse `--since`:
|
64
66
|
|
@@ -68,7 +70,7 @@ bundle exec rubocop-director --since=2023-01-01
|
|
68
70
|
|
69
71
|
## Development
|
70
72
|
|
71
|
-
After checking out the repo, run `bundle
|
73
|
+
After checking out the repo, run `bundle install` to install dependencies
|
72
74
|
|
73
75
|
## Contributing
|
74
76
|
|
@@ -9,20 +9,10 @@ module RubocopDirector
|
|
9
9
|
RUBOCOP_TODO = ".rubocop_todo.yml"
|
10
10
|
|
11
11
|
def run
|
12
|
-
|
12
|
+
rubocop_todo = yield load_config
|
13
|
+
yield check_config_already_exists
|
13
14
|
|
14
|
-
|
15
|
-
acc.merge!(cop => 1)
|
16
|
-
end
|
17
|
-
|
18
|
-
# TODO: warn if file exists
|
19
|
-
File.write(".rubocop-director.yml", {
|
20
|
-
"update_weight" => 1,
|
21
|
-
"default_cop_weight" => 1,
|
22
|
-
"weights" => weights
|
23
|
-
}.to_yaml)
|
24
|
-
|
25
|
-
Success("Config generated")
|
15
|
+
create_config(rubocop_todo)
|
26
16
|
end
|
27
17
|
|
28
18
|
private
|
@@ -32,6 +22,33 @@ module RubocopDirector
|
|
32
22
|
rescue Errno::ENOENT
|
33
23
|
Failure("#{RUBOCOP_TODO} not found, generate it using `rubocop --regenerate-todo`")
|
34
24
|
end
|
25
|
+
|
26
|
+
def check_config_already_exists
|
27
|
+
return Success() if config_not_exists? || override_config?
|
28
|
+
|
29
|
+
Failure("previous version of #{CONFIG_NAME} was preserved.")
|
30
|
+
end
|
31
|
+
|
32
|
+
def config_not_exists?
|
33
|
+
!File.file?(CONFIG_NAME)
|
34
|
+
end
|
35
|
+
|
36
|
+
def override_config?
|
37
|
+
puts("#{CONFIG_NAME} already exists, do you want to override it? (y/n)")
|
38
|
+
gets.chomp == "y"
|
39
|
+
end
|
40
|
+
|
41
|
+
def create_config(rubocop_todo)
|
42
|
+
weights = rubocop_todo.keys.to_h { |key| [key, 1] }
|
43
|
+
|
44
|
+
File.write(CONFIG_NAME, {
|
45
|
+
"update_weight" => 1,
|
46
|
+
"default_cop_weight" => 1,
|
47
|
+
"weights" => weights
|
48
|
+
}.to_yaml)
|
49
|
+
|
50
|
+
Success("Config generated")
|
51
|
+
end
|
35
52
|
end
|
36
53
|
end
|
37
54
|
end
|
@@ -20,8 +20,8 @@ module RubocopDirector
|
|
20
20
|
|
21
21
|
def run
|
22
22
|
config = yield load_config
|
23
|
-
update_counts = yield load_git_stats
|
24
23
|
rubocop_json = yield load_rubocop_json
|
24
|
+
update_counts = yield load_git_stats
|
25
25
|
ranged_files = yield range_files(rubocop_json: rubocop_json, update_counts: update_counts, config: config)
|
26
26
|
|
27
27
|
OutputFormatter.new(ranged_files: ranged_files, since: @since).call
|
@@ -35,19 +35,19 @@ module RubocopDirector
|
|
35
35
|
Failure("#{CONFIG_NAME} not found, generate it using `rubocop-director --generate-config`")
|
36
36
|
end
|
37
37
|
|
38
|
-
def load_git_stats
|
39
|
-
puts "๐ก Checking git history since #{@since} to find hot files..."
|
40
|
-
GitLogStats.new(@since).fetch
|
41
|
-
end
|
42
|
-
|
43
38
|
def load_rubocop_json
|
44
|
-
puts "
|
39
|
+
puts "๐ก Running rubocop to get the list of offences to fix..."
|
45
40
|
RubocopStats.new.fetch
|
46
41
|
end
|
47
42
|
|
43
|
+
def load_git_stats
|
44
|
+
puts "๐ก๐ฅ Checking git history since #{@since} to find hot files..."
|
45
|
+
GitLogStats.new(@since).fetch
|
46
|
+
end
|
47
|
+
|
48
48
|
def range_files(rubocop_json:, update_counts:, config:)
|
49
49
|
puts "๐ก๐ฅ๐ฌ Calculating a list of files to refactor..."
|
50
|
-
|
50
|
+
FileStatsBuilder.new(rubocop_json: rubocop_json, update_counts: update_counts, config: config).build
|
51
51
|
end
|
52
52
|
end
|
53
53
|
end
|
@@ -10,6 +10,8 @@ module RubocopDirector
|
|
10
10
|
end
|
11
11
|
|
12
12
|
def build
|
13
|
+
update_weight = yield fetch_update_weight
|
14
|
+
|
13
15
|
file_stats = files_with_offenses.map do |file|
|
14
16
|
stats = {
|
15
17
|
path: file["path"],
|
@@ -17,11 +19,17 @@ module RubocopDirector
|
|
17
19
|
offense_counts: file["offenses"].group_by { |offense| offense["cop_name"] }.transform_values(&:count)
|
18
20
|
}
|
19
21
|
|
20
|
-
stats[:
|
22
|
+
stats[:cop_value] = yield find_refactoring_value(stats)
|
21
23
|
|
22
24
|
stats
|
23
25
|
end
|
24
26
|
|
27
|
+
total_updates_count = file_stats.sum { |f| f[:updates_count] }
|
28
|
+
|
29
|
+
file_stats = file_stats.each do |stats|
|
30
|
+
stats[:value] = stats[:cop_value] * ((stats[:updates_count] / total_updates_count.to_f)**update_weight)
|
31
|
+
end
|
32
|
+
|
25
33
|
Success(file_stats.sort_by { _1[:value] }.reverse)
|
26
34
|
end
|
27
35
|
|
@@ -32,14 +40,12 @@ module RubocopDirector
|
|
32
40
|
def files_with_offenses = rubocop_json.select { |file| file["offenses"].any? }
|
33
41
|
|
34
42
|
def find_refactoring_value(file)
|
35
|
-
|
36
|
-
|
37
|
-
offence_sum = file[:offense_counts].sum do |cop_name, count|
|
43
|
+
value = file[:offense_counts].sum do |cop_name, count|
|
38
44
|
cop_weight = yield fetch_cop_weight(cop_name)
|
39
45
|
cop_weight * count
|
40
46
|
end
|
41
47
|
|
42
|
-
Success(
|
48
|
+
Success(value)
|
43
49
|
end
|
44
50
|
|
45
51
|
def fetch_cop_weight(cop_name)
|
@@ -15,11 +15,13 @@ module RubocopDirector
|
|
15
15
|
|
16
16
|
def call
|
17
17
|
result = @ranged_files.each_with_object([]) do |file, result|
|
18
|
-
result << "
|
19
|
-
|
20
|
-
result << "
|
21
|
-
result << "
|
22
|
-
result << "
|
18
|
+
result << ""
|
19
|
+
|
20
|
+
result << "Path: #{file[:path]}"
|
21
|
+
result << "Updated #{file[:updates_count]} times since #{@since}"
|
22
|
+
result << "Offenses:"
|
23
|
+
file[:offense_counts].each { |cop, count| result << " ๐ #{cop} - #{count}" }
|
24
|
+
result << "Refactoring value: #{file[:value]} (#{(100 * file[:value] / total_value.to_f).round(5)}%)"
|
23
25
|
end
|
24
26
|
|
25
27
|
Success(result)
|
@@ -7,22 +7,48 @@ require "dry/monads"
|
|
7
7
|
module RubocopDirector
|
8
8
|
class RubocopStats
|
9
9
|
include Dry::Monads[:result]
|
10
|
+
include Dry::Monads::Do.for(:fetch)
|
11
|
+
|
12
|
+
TEMP_CONFIG_PATH = "./.temp_rubocop.yml"
|
10
13
|
|
11
14
|
def fetch
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
15
|
+
config = yield load_config
|
16
|
+
yield generate_temp_rubocop_config_without_todo(initial_config: config)
|
17
|
+
|
18
|
+
stats = yield generate_stats
|
19
|
+
yield remove_temp_config
|
20
|
+
|
21
|
+
Success(stats)
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
16
25
|
|
17
|
-
|
26
|
+
def load_config
|
27
|
+
Success(YAML.load_file("./.rubocop.yml"))
|
28
|
+
rescue Errno::ENOENT
|
29
|
+
Failure("unable to load rubocop config. Please ensure .rubocop.yml file is present at your project's root directory")
|
30
|
+
end
|
31
|
+
|
32
|
+
def generate_temp_rubocop_config_without_todo(initial_config:)
|
33
|
+
initial_config.dig("inherit_from")&.delete(".rubocop_todo.yml")
|
34
|
+
|
35
|
+
Success(File.write(TEMP_CONFIG_PATH, initial_config.to_yaml))
|
36
|
+
rescue IOError => e
|
37
|
+
Failure("Failed to create a temporary config file to generate stats: #{e}")
|
38
|
+
end
|
39
|
+
|
40
|
+
def generate_stats
|
41
|
+
stdout, stderr = Open3.capture3("bundle exec rubocop -c #{TEMP_CONFIG_PATH} --format json")
|
18
42
|
|
19
43
|
if stderr.length > 0
|
20
44
|
Failure("Failed to fetch rubocop stats: #{stderr}")
|
21
45
|
else
|
22
46
|
Success(JSON.parse(stdout)["files"])
|
23
47
|
end
|
24
|
-
|
25
|
-
|
48
|
+
end
|
49
|
+
|
50
|
+
def remove_temp_config
|
51
|
+
Success(File.delete(TEMP_CONFIG_PATH))
|
26
52
|
end
|
27
53
|
end
|
28
54
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rubocop_director
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- DmitryTsepelev
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-06-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: dry-monads
|