rubocop_director 0.1.0 โ 0.2.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 +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
|