rubocop-gradual 0.1.1 → 0.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: 0faebca65c344ef609bb905fb2db0e3f1fd30e5ef0eca7557421c36a01607978
4
- data.tar.gz: 9ccf0c6f68b185c4ac5859abf12ab79ced66fb1c82987655d714a7d365811788
3
+ metadata.gz: a8d8cb41d6a825538a0b0411867bcef6864d16ec393f24c7d9f84e05f886caa8
4
+ data.tar.gz: e7230b837f6f39967e4c472a2a8f466d061da24af7ea9efee060175f0617bb17
5
5
  SHA512:
6
- metadata.gz: 9641cdb414d6c980fec5d0b066b7093022fd68dfb40995f55c41460a2c2bb3cd4793b6bcbabf774cfea2b5eab95ea925569b0aeff0bc1e774375d98749dcb114
7
- data.tar.gz: 72a1abe1d70e2f45e0bebbf4ab96e0254297f37839a11fe2d371e90574ba4a075512f92939a36898d0d150308bdd4b102003c767f04852ef6b4bdadbb9a5fe4b
6
+ metadata.gz: 223e2e205074038f7af48b2be3d06d98136ef7d9d65b8bcf3c153d8eee937d4f6f45bada38eaf6a5f8f257e4a95230c8e9a7b19b3158abe9e599bf3ce5e53d45
7
+ data.tar.gz: 57824cdfee2b8423e88c30f50c95b8f8bba78edfcfd36fe94c73e88e4798eba62c899fb1cefc4719baf08b48422e545e59f86c78affe3a835c3aaa68f9a05c25
data/CHANGELOG.md CHANGED
@@ -7,6 +7,61 @@ and this project adheres to [Semantic Versioning].
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.3.0] - 2022-10-26
11
+
12
+ ### Added
13
+
14
+ - Partial linting (experimental). ([@skryukov])
15
+
16
+ Partial linting is useful when you want to run RuboCop Gradual on a subset of files, for example, on changed files in a pull request:
17
+
18
+ ```shell
19
+ rubocop-gradual path/to/file # run `rubocop-gradual` on a subset of files
20
+ rubocop-gradual --staged # run `rubocop-gradual` on staged files
21
+ rubocop-gradual --unstaged # run `rubocop-gradual` on unstaged files
22
+ rubocop-gradual --commit origin/main # run `rubocop-gradual` on changed files since the commit
23
+
24
+ # it's possible to combine options with autocorrect:
25
+ rubocop-gradual --staged --autocorrect # run `rubocop-gradual` with autocorrect on staged files
26
+ ```
27
+
28
+ - Require mode (experimental). ([@skryukov])
29
+
30
+ RuboCop Gradual can be used in "Require mode", which is a way to replace `rubocop` with `rubocop-gradual`:
31
+
32
+ ```yaml
33
+ # .rubocop.yml
34
+
35
+ require:
36
+ - rubocop-gradual
37
+ ```
38
+
39
+ - Built-in Rake tasks. ([@skryukov])
40
+
41
+ ```ruby
42
+ # Rakefile
43
+ require "rubocop/gradual/rake_task"
44
+
45
+ RuboCop::Gradual::RakeTask.new
46
+ ```
47
+
48
+ ### Fixed
49
+
50
+ - Issues with the same location ordered by the message. ([@skryukov])
51
+
52
+ ## [0.2.0] - 2022-07-26
53
+
54
+ ### Added
55
+
56
+ - Autocorrection options. ([@skryukov])
57
+ Run `rubocop-gradual -a` and `rubocop-gradual -A` to autocorrect new and changed files and then update the lock file.
58
+
59
+ ### Changed
60
+
61
+ - Rename `--ci` to `--check` option. ([@skryukov])
62
+
63
+ - Rename `-u, --update` to `-U, --force-update` option. ([@skryukov])
64
+
10
65
  ## [0.1.1] - 2022-07-05
11
66
 
12
67
  ### Changed
@@ -25,7 +80,9 @@ and this project adheres to [Semantic Versioning].
25
80
 
26
81
  [@skryukov]: https://github.com/skryukov
27
82
 
28
- [Unreleased]: https://github.com/skryukov/rubocop-gradual/compare/v0.1.1...HEAD
83
+ [Unreleased]: https://github.com/skryukov/rubocop-gradual/compare/v0.3.0...HEAD
84
+ [0.3.0]: https://github.com/skryukov/rubocop-gradual/compare/v0.2.0...v0.3.0
85
+ [0.2.0]: https://github.com/skryukov/rubocop-gradual/compare/v0.1.1...v0.2.0
29
86
  [0.1.1]: https://github.com/skryukov/rubocop-gradual/compare/v0.1.0...v0.1.1
30
87
  [0.1.0]: https://github.com/skryukov/rubocop-gradual/commits/v0.1.0
31
88
 
data/README.md CHANGED
@@ -5,6 +5,14 @@
5
5
 
6
6
  RuboCop Gradual is a tool that helps track down and fix RuboCop offenses in your code gradually. It's a more flexible alternative to RuboCop's `--auto-gen-config` option.
7
7
 
8
+ RuboCop Gradual:
9
+
10
+ - generates the lock file with all RuboCop offenses and uses hashes to track each offense **line by line**
11
+ - **automatically** updates the lock file on every successful run, but returns errors on new offenses
12
+ - does not prevent your editor from **showing ignored offenses**
13
+
14
+ Gain full control of gradual improvements: just add `rubocop-gradual` and use it as proxy for `rubocop`.
15
+
8
16
  <a href="https://evilmartians.com/?utm_source=rubocop-gradual&utm_campaign=project_page">
9
17
  <img src="https://evilmartians.com/badges/sponsored-by-evil-martians.svg" alt="Sponsored by Evil Martians" width="236" height="54">
10
18
  </a>
@@ -27,24 +35,106 @@ Run `rubocop-gradual` before commiting changes to update the lock file. RuboCop
27
35
 
28
36
  Proposed workflow:
29
37
 
38
+ - Remove `rubocop_todo.yml` if it exists.
30
39
  - Run `rubocop-gradual` to generate a lock file and commit it to the project repository.
31
-
32
- - Add `rubocop-gradual --CI` to your CI pipeline instead of `rubocop`/`standard`. It will throw an error if the lock file is out of date.
33
-
40
+ - Add `rubocop-gradual --check` to your CI pipeline instead of `rubocop`/`standard`. It will throw an error if the lock file is out of date.
41
+ - Run `rubocop-gradual` to update the lock file, or `rubocop-gradual -a` to run autocorrection for all new and changed files and then update the lock file.
34
42
  - Optionally, add `rubocop-gradual` as a pre-commit hook to your repository (using [lefthook], for example).
35
-
36
- - RuboCop Gradual will throw an error on any new offense, but if you really want to force update the lock file, run `rubocop-gradual --update`.
43
+ - RuboCop Gradual will throw an error on any new offense, but if you really want to force update the lock file, run `rubocop-gradual --force-update`.
37
44
 
38
45
  ## Available options
39
46
 
40
47
  ```
41
- --ci Run Gradual in the CI mode.
42
- -u, --update Force update Gradual lock file.
48
+ -U, --force-update Force update Gradual lock file.
49
+ --check Check Gradual lock file is up-to-date.
50
+ -a, --autocorrect Autocorrect offenses (only when it's safe).
51
+ -A, --autocorrect-all Autocorrect offenses (safe and unsafe).
43
52
  --gradual-file FILE Specify Gradual lock file.
44
- --no-gradual Disable Gradual.
45
53
  -v, --version Display version.
46
- -V, --verbose-version Display verbose version.
47
- -h, --help Display help message.
54
+ -h, --help Prints this help.
55
+ ```
56
+
57
+ ## Rake tasks
58
+
59
+ To use built-in Rake tasks add the following to your Rakefile:
60
+
61
+ ```ruby
62
+ # Rakefile
63
+ require "rubocop/gradual/rake_task"
64
+
65
+ RuboCop::Gradual::RakeTask.new
66
+ ```
67
+
68
+ This will add rake tasks:
69
+
70
+ ```
71
+ bundle exec rake -T
72
+ rake rubocop_gradual # Run RuboCop Gradual
73
+ rake rubocop_gradual:autocorrect # Run RuboCop Gradual with autocorrect (only when it's safe)
74
+ rake rubocop_gradual:autocorrect_all # Run RuboCop Gradual with autocorrect (safe and unsafe)
75
+ rake rubocop_gradual:check # Run RuboCop Gradual to check the lock file
76
+ rake rubocop_gradual:force_update # Run RuboCop Gradual to force update the lock file
77
+ ```
78
+
79
+ It's possible to customize the Rake task name and options:
80
+
81
+ ```ruby
82
+ # Rakefile
83
+
84
+ require "rubocop/gradual/rake_task"
85
+
86
+ RuboCop::Gradual::RakeTask.new(:custom_task_name) do |task|
87
+ task.options = %w[--gradual-file custom_gradual_file.lock]
88
+ task.verbose = false
89
+ end
90
+ ```
91
+
92
+ ## Partial linting (experimental)
93
+
94
+ RuboCop Gradual supports partial linting. It's useful when you want to run RuboCop Gradual on a subset of files, for example, on changed files in a pull request:
95
+
96
+ ```shell
97
+ rubocop-gradual path/to/file # run `rubocop-gradual` on a subset of files
98
+ rubocop-gradual --staged # run `rubocop-gradual` on staged files
99
+ rubocop-gradual --unstaged # run `rubocop-gradual` on unstaged files
100
+ rubocop-gradual --commit origin/main # run `rubocop-gradual` on changed files since the commit
101
+
102
+ # it's possible to combine options with autocorrect:
103
+ rubocop-gradual --staged --autocorrect # run `rubocop-gradual` with autocorrect on staged files
104
+ ```
105
+
106
+ ## Require mode (experimental)
107
+
108
+ RuboCop Gradual can be used in "Require mode", which is a way to replace `rubocop` with `rubocop-gradual`:
109
+
110
+ ```yaml
111
+ # .rubocop.yml
112
+
113
+ require:
114
+ - rubocop-gradual
115
+ ```
116
+
117
+ Now base `rubocop` command will run `rubocop-gradual`:
118
+
119
+ ```shell
120
+ rubocop # run `rubocop-gradual`
121
+ rubocop -a # run `rubocop-gradual` with autocorrect (only when it's safe)
122
+ rubocop -A # run `rubocop-gradual` with autocorrect (safe and unsafe)
123
+ rubocop gradual check # run `rubocop-gradual` to check the lock file
124
+ rubocop gradual force_update # run `rubocop-gradual` to force update the lock file
125
+ ```
126
+
127
+ To set a custom path to Gradual lock file, add `--gradual-file FILE` to a special `.rubocop-gradual` file:
128
+
129
+ ```
130
+ # .rubocop-gradual
131
+ --rubocop-gradual-file path/to/my_lock_file.lock
132
+ ```
133
+
134
+ To temporarily disable RuboCop Gradual, prepend command with `NO_GRADUAL=1`:
135
+
136
+ ```shell
137
+ NO_GRADUAL=1 rubocop # run `rubocop`
48
138
  ```
49
139
 
50
140
  ## Alternatives
data/exe/rubocop-gradual CHANGED
@@ -11,6 +11,6 @@ cli = RuboCop::Gradual::CLI.new
11
11
 
12
12
  time = Benchmark.realtime { exit_status = cli.run }
13
13
 
14
- puts "Finished in #{time} seconds" if cli.options[:debug] || cli.options[:display_time]
14
+ puts "Finished in #{time} seconds" if RuboCop::Gradual::Configuration.display_time?
15
15
 
16
16
  exit exit_status
@@ -1,32 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "configuration"
3
4
  require_relative "options"
4
- require_relative "formatter"
5
5
 
6
6
  module RuboCop
7
7
  module Gradual
8
8
  # CLI is a wrapper around RuboCop::CLI.
9
- class CLI < RuboCop::CLI
10
- def run(args = ARGV)
11
- Gradual.mode = :base
12
- rubocop_args = Options.new.parse(args)
13
- super(rubocop_args)
9
+ class CLI
10
+ def run(argv = ARGV)
11
+ Configuration.apply(*Options.new.parse(argv))
12
+ puts "Gradual mode: #{Configuration.mode}" if Configuration.debug?
13
+ load_command(Configuration.command).call.to_i
14
14
  end
15
15
 
16
16
  private
17
17
 
18
- def apply_default_formatter
19
- return super if Gradual.mode == :disabled
20
- raise OptionArgumentError, "-f, --format cannot be used in gradual mode." if @options[:formatters]
21
-
22
- @options[:formatters] = [[Formatter, nil]]
23
- end
24
-
25
- def execute_runners
26
- raise OptionArgumentError, "--auto-gen-config cannot be used in gradual mode." if @options[:auto_gen_config]
27
-
28
- result = super
29
- Gradual.mode == :disabled ? result : Gradual.exit_code
18
+ def load_command(command)
19
+ require_relative "commands/#{command}"
20
+ ::RuboCop::Gradual::Commands.const_get(command.to_s.capitalize).new
30
21
  end
31
22
  end
32
23
  end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+ require_relative "../formatters/autocorrect"
5
+
6
+ module RuboCop
7
+ module Gradual
8
+ module Commands
9
+ # Autocorrect command runs RuboCop autocorrect before running the base command.
10
+ class Autocorrect
11
+ def call
12
+ runner = RuboCop::CLI::Command::ExecuteRunner.new(
13
+ RuboCop::CLI::Environment.new(
14
+ Configuration.rubocop_options.merge(formatters: [[Formatters::Autocorrect, nil]]),
15
+ Configuration.rubocop_config_store,
16
+ lint_paths
17
+ )
18
+ )
19
+ runner.run
20
+ Base.new.call
21
+ end
22
+
23
+ private
24
+
25
+ def lint_paths
26
+ return Configuration.target_file_paths if Configuration.lint_paths.any?
27
+
28
+ changed_or_untracked_files.map(&:path)
29
+ end
30
+
31
+ def changed_or_untracked_files
32
+ tracked_files = LockFile.new(Configuration.path).read_results&.files || []
33
+
34
+ target_files.reject do |file|
35
+ tracked_files.any? { |r| r.path == file.path && r.file_hash == file.file_hash }
36
+ end
37
+ end
38
+
39
+ def target_files
40
+ Parallel.map(Configuration.target_file_paths) do |path|
41
+ Results::File.new(path: path, issues: [])
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "benchmark"
4
+
5
+ require_relative "../formatters/base"
6
+ require_relative "../process"
7
+
8
+ module RuboCop
9
+ module Gradual
10
+ module Commands
11
+ # Base command runs RuboCop, and processes the results with Gradual.
12
+ class Base
13
+ def call
14
+ exit_code = 0
15
+ run_rubocop
16
+ write_stats_message
17
+ time = Benchmark.realtime { exit_code = Process.new(Configuration.rubocop_results).call }
18
+ puts "Finished Gradual processing in #{time} seconds" if Configuration.display_time?
19
+
20
+ exit_code
21
+ end
22
+
23
+ private
24
+
25
+ def run_rubocop
26
+ rubocop_runner = RuboCop::CLI::Command::ExecuteRunner.new(
27
+ RuboCop::CLI::Environment.new(
28
+ rubocop_options,
29
+ Configuration.rubocop_config_store,
30
+ lint_paths
31
+ )
32
+ )
33
+ rubocop_runner.run
34
+ end
35
+
36
+ def lint_paths
37
+ return [] if Configuration.lint_paths.empty?
38
+
39
+ Configuration.target_file_paths
40
+ end
41
+
42
+ def rubocop_options
43
+ Configuration.rubocop_options
44
+ .slice(:config, :debug, :display_time)
45
+ .merge(formatters: [[Formatters::Base, nil]])
46
+ end
47
+
48
+ def write_stats_message
49
+ issues_count = Configuration.rubocop_results.sum { |f| f[:issues].size }
50
+ puts "\nFound #{Configuration.rubocop_results.size} files with #{issues_count} issue(s)."
51
+ puts "Processing results..."
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Gradual
5
+ # Configuration class stores Gradual and Rubocop options.
6
+ module Configuration
7
+ class << self
8
+ attr_reader :options, :rubocop_options, :rubocop_results, :lint_paths, :target_file_paths
9
+
10
+ def apply(options = {}, rubocop_options = {}, lint_paths = [])
11
+ @options = options
12
+ @rubocop_options = rubocop_options
13
+ @lint_paths = lint_paths
14
+ @target_file_paths = rubocop_target_file_paths
15
+ @rubocop_results = []
16
+ end
17
+
18
+ def command
19
+ options.fetch(:command, :base)
20
+ end
21
+
22
+ def mode
23
+ options.fetch(:mode, :update)
24
+ end
25
+
26
+ def path
27
+ options.fetch(:path, ".rubocop_gradual.lock")
28
+ end
29
+
30
+ def debug?
31
+ rubocop_options[:debug]
32
+ end
33
+
34
+ def display_time?
35
+ rubocop_options[:debug] || rubocop_options[:display_time]
36
+ end
37
+
38
+ def rubocop_config_store
39
+ RuboCop::ConfigStore.new.tap do |config_store|
40
+ config_store.options_config = rubocop_options[:config] if rubocop_options[:config]
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ def rubocop_target_file_paths
47
+ target_finder = RuboCop::TargetFinder.new(rubocop_config_store, rubocop_options)
48
+ mode = if rubocop_options[:only_recognized_file_types]
49
+ :only_recognized_file_types
50
+ else
51
+ :all_file_types
52
+ end
53
+ target_finder
54
+ .find(lint_paths, mode)
55
+ .map { |path| RuboCop::PathUtil.smart_path(path) }
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pathname"
4
+
5
+ module RuboCop
6
+ module Gradual
7
+ module Formatters
8
+ # Formatter is a RuboCop formatter class that collects RuboCop results and
9
+ # calls the Gradual::Process class at the end to process them.
10
+ class Autocorrect < RuboCop::Formatter::BaseFormatter
11
+ include PathUtil
12
+
13
+ def initialize(_output, options = {})
14
+ super
15
+ @corrected_files = 0
16
+ end
17
+
18
+ def started(target_files)
19
+ puts "Inspecting #{target_files.size} file(s) for autocorrection..."
20
+ end
21
+
22
+ def file_finished(_file, offenses)
23
+ print "."
24
+ return if offenses.empty?
25
+
26
+ @corrected_files += 1 if offenses.any?(&:corrected?)
27
+ end
28
+
29
+ def finished(_inspected_files)
30
+ puts "\nFixed #{@corrected_files} file(s).\n"
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pathname"
4
+
5
+ module RuboCop
6
+ module Gradual
7
+ module Formatters
8
+ # Base is a RuboCop formatter class that collects RuboCop results and
9
+ # writes them to Configuration.rubocop_results.
10
+ class Base < RuboCop::Formatter::BaseFormatter
11
+ include PathUtil
12
+
13
+ def file_finished(file, offenses)
14
+ print "."
15
+ return if offenses.empty?
16
+
17
+ Configuration.rubocop_results << {
18
+ path: smart_path(file),
19
+ issues: offenses.reject(&:corrected?).map { |o| issue_offense(o) }
20
+ }
21
+ end
22
+
23
+ private
24
+
25
+ def issue_offense(offense)
26
+ {
27
+ line: offense.line,
28
+ column: offense.real_column,
29
+ length: offense.location.length,
30
+ message: offense.message
31
+ }
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rbconfig"
4
+
5
+ module RuboCop
6
+ module Gradual
7
+ # Git class handles git commands.
8
+ module Git
9
+ class << self
10
+ def paths_by(commit)
11
+ git_installed!
12
+
13
+ case commit
14
+ when :unstaged
15
+ `git ls-files --others --exclude-standard -m`.split("\n")
16
+ when :staged
17
+ `git diff --cached --name-only`.split("\n")
18
+ else
19
+ `git diff --name-only #{commit}`.split("\n")
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def git_installed!
26
+ void = /msdos|mswin|djgpp|mingw/.match?(RbConfig::CONFIG["host_os"]) ? "NUL" : "/dev/null"
27
+ git_found = `git --version >>#{void} 2>&1`
28
+
29
+ raise Error, "Git is not found, please install it first." unless git_found
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -1,73 +1,111 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "rainbow"
4
3
  require "shellwords"
5
4
 
5
+ require_relative "git"
6
+
6
7
  module RuboCop
7
8
  module Gradual
8
9
  # Options class defines RuboCop Gradual cli options.
9
- # It also extracts command line RuboCop Gradual arguments
10
- # before passing leftover arguments to RuboCop::CLI.
11
10
  class Options
11
+ AUTOCORRECT_KEY =
12
+ if Gem::Version.new(RuboCop::Version::STRING) >= Gem::Version.new("1.30")
13
+ :autocorrect
14
+ else
15
+ :auto_correct
16
+ end
17
+
18
+ def initialize
19
+ @options = {}
20
+ end
21
+
12
22
  def parse(args)
13
23
  parser = define_options
14
- @gradual_args, @rubocop_args = filter_args(parser, args_from_file + args)
15
- parser.parse(@gradual_args)
16
- @rubocop_args
24
+ gradual_args, rubocop_args = filter_args(parser, args_from_file + args)
25
+ @rubocop_options, @lint_paths = RuboCop::Options.new.parse(rubocop_args)
26
+ parser.parse(gradual_args)
27
+
28
+ [@options, @rubocop_options, @lint_paths]
17
29
  end
18
30
 
19
31
  private
20
32
 
21
33
  def define_options
22
34
  OptionParser.new do |opts|
23
- opts.banner = rainbow.wrap("\nGradual options:").bright
24
-
35
+ define_mode_options(opts)
25
36
  define_gradual_options(opts)
37
+ define_lint_paths_options(opts)
26
38
 
27
- define_proxy_options(opts)
39
+ define_info_options(opts)
28
40
  end
29
41
  end
30
42
 
31
- def define_gradual_options(opts)
32
- opts.on("-u", "--update", "Force update Gradual lock file.") { Gradual.mode = :update }
43
+ def define_mode_options(opts)
44
+ opts.on("-U", "--force-update", "Force update Gradual lock file.") { @options[:mode] = :force_update }
45
+ opts.on("-u", "--update", "Same as --force-update (deprecated).") do
46
+ warn "-u, --update is deprecated. Use -U, --force-update instead."
47
+ @options[:mode] = :force_update
48
+ end
33
49
 
34
- opts.on("--ci", "Run Gradual in the CI mode.") { Gradual.mode = :ci }
50
+ opts.on("--check", "Check Gradual lock file is up-to-date.") { @options[:mode] = :check }
51
+ opts.on("--ci", "Same as --check (deprecated).") do
52
+ warn "--ci is deprecated. Use --check instead."
53
+ @options[:mode] = :check
54
+ end
55
+ end
35
56
 
36
- opts.on("--gradual-file FILE", "Specify Gradual lock file.") { |path| Gradual.path = path }
57
+ def define_gradual_options(opts)
58
+ opts.on("-a", "--autocorrect", "Autocorrect offenses (only when it's safe).") do
59
+ @rubocop_options[AUTOCORRECT_KEY] = true
60
+ @rubocop_options[:"safe_#{AUTOCORRECT_KEY}"] = true
61
+ @options[:command] = :autocorrect
62
+ end
63
+ opts.on("-A", "--autocorrect-all", "Autocorrect offenses (safe and unsafe).") do
64
+ @rubocop_options[AUTOCORRECT_KEY] = true
65
+ @options[:command] = :autocorrect
66
+ end
37
67
 
38
- opts.on("--no-gradual", "Disable Gradual.") { Gradual.mode = :disabled }
68
+ opts.on("--gradual-file FILE", "Specify Gradual lock file.") { |path| @options[:path] = path }
39
69
  end
40
70
 
41
- def define_proxy_options(opts)
42
- proxy_option(opts, "-v", "--version", "Display version.") do
43
- print "rubocop-gradual: #{VERSION}\nrubocop: "
71
+ def define_lint_paths_options(opts)
72
+ opts.on("--unstaged", "Lint unstaged files.") do
73
+ @lint_paths = git_lint_paths(:unstaged)
44
74
  end
75
+ opts.on("--staged", "Lint staged files.") do
76
+ @lint_paths = git_lint_paths(:staged)
77
+ end
78
+ opts.on("--commit COMMIT", "Lint files changed since the commit.") do |commit|
79
+ @lint_paths = git_lint_paths(commit)
80
+ end
81
+ end
45
82
 
46
- proxy_option(opts, "-V", "--verbose-version", "Display verbose version.") do
47
- print "rubocop-gradual: #{VERSION}\nrubocop:"
83
+ def define_info_options(opts)
84
+ opts.on("-v", "--version", "Display version.") do
85
+ puts "rubocop-gradual: #{VERSION}, rubocop: #{RuboCop::Version.version}"
86
+ exit
48
87
  end
49
88
 
50
- proxy_option(opts, "-h", "--help", "Display help message.") do
51
- at_exit { puts opts }
89
+ opts.on("-h", "--help", "Prints this help.") do
90
+ puts opts
91
+ exit
52
92
  end
53
93
  end
54
94
 
55
- def proxy_option(opts, *attrs)
56
- opts.on(*attrs) do
57
- @rubocop_args << attrs[0]
58
- yield
59
- end
95
+ def git_lint_paths(commit)
96
+ @rubocop_options[:only_recognized_file_types] = true
97
+ RuboCop::Gradual::Git.paths_by(commit)
60
98
  end
61
99
 
62
100
  def filter_args(parser, original_args, self_args = [])
63
- extract_all_args(parser).each do |arg|
101
+ extract_all_args(parser).each do |option|
64
102
  loop do
65
- break unless (i = original_args.index { |a| a.start_with?(arg) })
103
+ break unless (i = original_args.index { |a| a.start_with?(option[:name]) })
104
+
105
+ self_args << original_args.delete_at(i)
106
+ next if option[:no_args] || original_args.size <= i || original_args[i].start_with?("-")
66
107
 
67
- loop do
68
- self_args << original_args.delete_at(i)
69
- break if original_args.size <= i || original_args[i].start_with?("-")
70
- end
108
+ self_args << original_args.delete_at(i)
71
109
  end
72
110
  end
73
111
  [self_args, original_args]
@@ -75,7 +113,9 @@ module RuboCop
75
113
 
76
114
  def extract_all_args(parser)
77
115
  parser.top.list.reduce([]) do |res, option|
78
- res + option.long + option.short
116
+ no_args = option.is_a?(OptionParser::Switch::NoArgument)
117
+ options = (option.long + option.short).map { |o| { name: o, no_args: no_args } }
118
+ res + options
79
119
  end
80
120
  end
81
121
 
@@ -86,12 +126,6 @@ module RuboCop
86
126
  []
87
127
  end
88
128
  end
89
-
90
- def rainbow
91
- @rainbow ||= Rainbow.new.tap do |r|
92
- r.enabled = false if ARGV.include?("--no-color")
93
- end
94
- end
95
129
  end
96
130
  end
97
131
  end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rubocop-gradual"
4
+
5
+ module RuboCop
6
+ module Gradual
7
+ # Patching RuboCop::CLI to enable require mode.
8
+ module Patch
9
+ def run_command(name)
10
+ return super if name != :execute_runner || (ARGV & %w[--stdin -s]).any?
11
+
12
+ Configuration.apply(*parse_options)
13
+ puts "Gradual mode: #{Configuration.mode}" if Configuration.debug?
14
+ load_command(Configuration.command).call.to_i
15
+ end
16
+
17
+ private
18
+
19
+ def load_command(command)
20
+ require_relative "commands/#{command}"
21
+ ::RuboCop::Gradual::Commands.const_get(command.to_s.capitalize).new
22
+ end
23
+
24
+ def parse_options
25
+ options, rubocop_options = Options.new.parse(ARGV)
26
+ options[:mode] = :force_update if @env.paths[0..1] == %w[gradual force_update]
27
+ options[:mode] = :check if @env.paths[0..1] == %w[gradual check]
28
+ [options, rubocop_options]
29
+ end
30
+ end
31
+ end
32
+ end
@@ -10,7 +10,7 @@ module RuboCop
10
10
  module CalculateDiff
11
11
  class << self
12
12
  def call(new_result, old_result)
13
- return Diff.new.add_new(new_result.files) if old_result.nil?
13
+ return Diff.new.add_files(new_result.files, :new) if old_result.nil?
14
14
 
15
15
  diff_results(new_result, old_result)
16
16
  end
@@ -20,7 +20,7 @@ module RuboCop
20
20
  def diff_results(new_result, old_result)
21
21
  new_files, fixed_files, path_files_match, moved_files_match = split_files(new_result, old_result)
22
22
 
23
- diff = Diff.new.add_new(new_files).add_fixed(fixed_files)
23
+ diff = Diff.new.add_files(new_files, :new).add_files(fixed_files, :fixed)
24
24
  path_files_match.chain(moved_files_match).each do |result_file, old_file|
25
25
  diff_issues(diff, result_file, old_file)
26
26
  end
@@ -73,19 +73,6 @@ module RuboCop
73
73
  end
74
74
  possibilities.min_by { |possibility| issue.distance(possibility) }
75
75
  end
76
-
77
- def map_same_files(left, right)
78
- map_files(left.files, right.files) do |new_file, old_file|
79
- new_file.path == old_file.path
80
- end
81
- end
82
-
83
- def map_files(key_files, value_files)
84
- key_files.each_with_object({}) do |key_file, res|
85
- same_file = value_files.find { |value_file| yield(key_file, value_file) }
86
- res[key_file] = same_file if same_file
87
- end
88
- end
89
76
  end
90
77
  end
91
78
  end
@@ -33,16 +33,9 @@ module RuboCop
33
33
  end
34
34
  end
35
35
 
36
- def add_new(files)
36
+ def add_files(files, key)
37
37
  files.each do |file|
38
- add_issues(file.path, new: file.issues)
39
- end
40
- self
41
- end
42
-
43
- def add_fixed(files)
44
- files.each do |file|
45
- add_issues(file.path, fixed: file.issues)
38
+ add_issues(file.path, **{ key => file.issues })
46
39
  end
47
40
  self
48
41
  end
@@ -54,7 +47,7 @@ module RuboCop
54
47
  new: new,
55
48
  unchanged: unchanged
56
49
  }
57
- log_file_issues(path) if RuboCop::Gradual.debug
50
+ log_file_issues(path) if Configuration.debug?
58
51
  self
59
52
  end
60
53
 
@@ -10,7 +10,7 @@ module RuboCop
10
10
  end
11
11
 
12
12
  def print_results
13
- puts diff.statistics if RuboCop::Gradual.debug
13
+ puts diff.statistics if Configuration.debug?
14
14
 
15
15
  send "print_#{diff.state}"
16
16
  end
@@ -23,7 +23,7 @@ module RuboCop
23
23
  - Run `rubocop-gradual` locally and commit the results, or
24
24
  - EVEN BETTER: before doing the above, try to fix the remaining issues in those files!
25
25
 
26
- #{bold("`#{Gradual.path}` diff:")}
26
+ #{bold("`#{Configuration.path}` diff:")}
27
27
 
28
28
  #{diff.to_s(ARGV.include?("--no-color") ? :text : :color)}
29
29
  MSG
@@ -35,7 +35,7 @@ module RuboCop
35
35
 
36
36
  def print_complete
37
37
  puts bold("RuboCop Gradual is complete!")
38
- puts "Removing `#{Gradual.path}` lock file..."
38
+ puts "Removing `#{Configuration.path}` lock file..." if diff.statistics[:fixed].positive?
39
39
  end
40
40
 
41
41
  def print_updated
@@ -49,7 +49,7 @@ module RuboCop
49
49
  def print_new
50
50
  issues_left = diff.statistics[:left]
51
51
  puts bold("RuboCop Gradual got results for the first time. #{issues_left} issue(s) found.")
52
- puts "Don't forget to commit `#{Gradual.path}` log file."
52
+ puts "Don't forget to commit `#{Configuration.path}` log file."
53
53
  end
54
54
 
55
55
  def print_better
@@ -61,7 +61,7 @@ module RuboCop
61
61
  def print_worse
62
62
  puts bold("Uh oh, RuboCop Gradual got worse:")
63
63
  print_new_issues
64
- puts bold("Force updating lock file...") if Gradual.mode == :update
64
+ puts bold("Force updating lock file...") if Configuration.mode == :force_update
65
65
  end
66
66
 
67
67
  def print_new_issues
@@ -9,46 +9,52 @@ module RuboCop
9
9
  module Gradual
10
10
  # Process is a class that handles the processing of RuboCop results.
11
11
  class Process
12
- attr_reader :new_results, :lock_file
12
+ attr_reader :lock_file, :old_results, :new_results
13
13
 
14
14
  def initialize(rubocop_result)
15
- @lock_file = LockFile.new(Gradual.path)
16
- @new_results = Results.new(**rubocop_result)
15
+ @lock_file = LockFile.new(Configuration.path)
16
+ @old_results = lock_file.read_results
17
+ @new_results = Results.new(files: rubocop_result)
18
+ add_skipped_files_to_new_results!
17
19
  end
18
20
 
19
21
  def call
20
- diff = CalculateDiff.call(new_results, lock_file.read_results)
22
+ diff = CalculateDiff.call(new_results, old_results)
21
23
  printer = Printer.new(diff)
22
- if print_ci_warning?(diff)
23
- printer.print_ci_warning(lock_file.diff(new_results))
24
- else
25
- printer.print_results
26
- end
27
24
 
28
- Gradual.exit_code = error_code(diff)
25
+ printer.print_results
26
+ printer.print_ci_warning(lock_file.diff(new_results)) if fail_with_outdated_lock?(diff)
29
27
 
30
- sync_lock_file(diff)
28
+ exit_code = error_code(diff)
29
+ sync_lock_file(diff) if exit_code.zero?
30
+ exit_code
31
31
  end
32
32
 
33
33
  private
34
34
 
35
- def print_ci_warning?(diff)
36
- Gradual.mode == :ci && diff.state != :no_changes && diff.state != :worse
35
+ def fail_with_outdated_lock?(diff)
36
+ Configuration.mode == :check && diff.state != :no_changes
37
37
  end
38
38
 
39
39
  def sync_lock_file(diff)
40
- return unless Gradual.exit_code.zero?
41
40
  return lock_file.delete if diff.state == :complete
42
41
 
43
42
  lock_file.write_results(new_results)
44
43
  end
45
44
 
46
45
  def error_code(diff)
47
- return 1 if print_ci_warning?(diff)
48
- return 1 if diff.state == :worse && Gradual.mode != :update
46
+ return 1 if fail_with_outdated_lock?(diff)
47
+ return 1 if diff.state == :worse && Configuration.mode != :force_update
49
48
 
50
49
  0
51
50
  end
51
+
52
+ def add_skipped_files_to_new_results!
53
+ return if Configuration.lint_paths.none? || old_results.nil?
54
+
55
+ skipped_files = old_results.files.reject { |file| Configuration.target_file_paths.include?(file.path) }
56
+ new_results.files.concat(skipped_files).sort!
57
+ end
52
58
  end
53
59
  end
54
60
  end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rake"
4
+ require "rake/tasklib"
5
+
6
+ module RuboCop
7
+ module Gradual
8
+ # Rake tasks for RuboCop::Gradual.
9
+ #
10
+ # @example
11
+ # require "rubocop/gradual/rake_task"
12
+ # RuboCop::Gradual::RakeTask.new
13
+ #
14
+ class RakeTask < ::Rake::TaskLib
15
+ attr_accessor :name, :verbose, :options
16
+
17
+ def initialize(name = :rubocop_gradual, *args, &task_block)
18
+ super()
19
+ @name = name
20
+ @verbose = true
21
+ @options = []
22
+ define(args, &task_block)
23
+ end
24
+
25
+ private
26
+
27
+ def define(args, &task_block)
28
+ desc "Run RuboCop Gradual" unless ::Rake.application.last_description
29
+ define_task(name, nil, args, &task_block)
30
+ setup_subtasks(args, &task_block)
31
+ end
32
+
33
+ def setup_subtasks(args, &task_block)
34
+ namespace(name) do
35
+ desc "Run RuboCop Gradual with autocorrect (only when it's safe)."
36
+ define_task(:autocorrect, "--autocorrect", args, &task_block)
37
+
38
+ desc "Run RuboCop Gradual with autocorrect (safe and unsafe)."
39
+ define_task(:autocorrect_all, "--autocorrect-all", args, &task_block)
40
+
41
+ desc "Run RuboCop Gradual to check the lock file."
42
+ define_task(:check, "--check", args, &task_block)
43
+
44
+ desc "Run RuboCop Gradual to force update the lock file."
45
+ define_task(:force_update, "--force-update", args, &task_block)
46
+ end
47
+ end
48
+
49
+ def define_task(name, option, args, &task_block)
50
+ task(name, *args) do |_, task_args|
51
+ RakeFileUtils.verbose(verbose) do
52
+ yield(*[self, task_args].slice(0, task_block.arity)) if task_block
53
+ run_cli(verbose, option)
54
+ end
55
+ end
56
+ end
57
+
58
+ def run_cli(verbose, option)
59
+ require "rubocop-gradual"
60
+
61
+ cli = CLI.new
62
+ puts "Running RuboCop Gradual..." if verbose
63
+ result = cli.run(full_options(option))
64
+ abort("RuboCop Gradual failed!") if result.nonzero?
65
+ end
66
+
67
+ def full_options(option)
68
+ option ? options.flatten.unshift(option) : options.flatten
69
+ end
70
+ end
71
+ end
72
+ end
@@ -16,7 +16,7 @@ module RuboCop
16
16
  end
17
17
 
18
18
  def <=>(other)
19
- [line, column, length] <=> [other.line, other.column, other.length]
19
+ [line, column, length, message] <=> [other.line, other.column, other.length, other.message]
20
20
  end
21
21
 
22
22
  def to_s
@@ -2,6 +2,6 @@
2
2
 
3
3
  module RuboCop
4
4
  module Gradual
5
- VERSION = "0.1.1"
5
+ VERSION = "0.3.0"
6
6
  end
7
7
  end
@@ -9,18 +9,10 @@ module RuboCop
9
9
  # RuboCop Gradual project namespace
10
10
  module Gradual
11
11
  class Error < StandardError; end
12
-
13
- class << self
14
- attr_accessor :debug, :exit_code, :mode, :path
15
-
16
- def set_defaults!
17
- self.debug = false
18
- self.exit_code = 0
19
- self.mode = :base
20
- self.path = ".rubocop_gradual.lock"
21
- end
22
- end
23
-
24
- set_defaults!
25
12
  end
26
13
  end
14
+
15
+ if ENV["NO_GRADUAL"] != "1" && (RuboCop::ConfigLoader.loaded_features & %w[rubocop-gradual rubocop/gradual]).any?
16
+ require_relative "gradual/patch"
17
+ RuboCop::CLI.prepend(RuboCop::Gradual::Patch)
18
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubocop-gradual
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Svyatoslav Kryukov
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-07-05 00:00:00.000000000 Z
11
+ date: 2022-10-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: diff-lcs
@@ -107,14 +107,21 @@ files:
107
107
  - lib/rubocop-gradual.rb
108
108
  - lib/rubocop/gradual.rb
109
109
  - lib/rubocop/gradual/cli.rb
110
- - lib/rubocop/gradual/formatter.rb
110
+ - lib/rubocop/gradual/commands/autocorrect.rb
111
+ - lib/rubocop/gradual/commands/base.rb
112
+ - lib/rubocop/gradual/configuration.rb
113
+ - lib/rubocop/gradual/formatters/autocorrect.rb
114
+ - lib/rubocop/gradual/formatters/base.rb
115
+ - lib/rubocop/gradual/git.rb
111
116
  - lib/rubocop/gradual/lock_file.rb
112
117
  - lib/rubocop/gradual/options.rb
118
+ - lib/rubocop/gradual/patch.rb
113
119
  - lib/rubocop/gradual/process.rb
114
120
  - lib/rubocop/gradual/process/calculate_diff.rb
115
121
  - lib/rubocop/gradual/process/diff.rb
116
122
  - lib/rubocop/gradual/process/matcher.rb
117
123
  - lib/rubocop/gradual/process/printer.rb
124
+ - lib/rubocop/gradual/rake_task.rb
118
125
  - lib/rubocop/gradual/results.rb
119
126
  - lib/rubocop/gradual/results/file.rb
120
127
  - lib/rubocop/gradual/results/issue.rb
@@ -145,7 +152,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
145
152
  - !ruby/object:Gem::Version
146
153
  version: '0'
147
154
  requirements: []
148
- rubygems_version: 3.2.15
155
+ rubygems_version: 3.3.7
149
156
  signing_key:
150
157
  specification_version: 4
151
158
  summary: Gradual RuboCop plugin
@@ -1,60 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "benchmark"
4
- require "pathname"
5
-
6
- require_relative "process"
7
-
8
- module RuboCop
9
- module Gradual
10
- # Formatter is a RuboCop formatter class that collects RuboCop results and
11
- # calls the Gradual::Process class at the end to process them.
12
- class Formatter < RuboCop::Formatter::BaseFormatter
13
- include PathUtil
14
-
15
- attr_reader :output_hash
16
-
17
- def initialize(_output, options = {})
18
- super
19
- Gradual.debug = options[:debug]
20
- puts "Gradual mode: #{Gradual.mode}" if Gradual.debug
21
- @output_hash = { files: [] }
22
- end
23
-
24
- def file_finished(file, offenses)
25
- print "."
26
- return if offenses.empty?
27
-
28
- output_hash[:files] << {
29
- path: smart_path(file),
30
- issues: offenses.reject(&:corrected?).map { |o| issue_offense(o) }
31
- }
32
- end
33
-
34
- def finished(_inspected_files)
35
- puts "\n#{stats_message}"
36
- puts "Processing results..."
37
-
38
- time = Benchmark.realtime { Process.new(output_hash).call }
39
-
40
- puts "Finished Gradual processing in #{time} seconds" if options[:debug] || options[:display_time]
41
- end
42
-
43
- private
44
-
45
- def issue_offense(offense)
46
- {
47
- line: offense.line,
48
- column: offense.real_column,
49
- length: offense.location.length,
50
- message: offense.message
51
- }
52
- end
53
-
54
- def stats_message
55
- issues_count = output_hash[:files].sum { |f| f[:issues].size }
56
- "Found #{output_hash[:files].size} files with #{issues_count} issue(s)."
57
- end
58
- end
59
- end
60
- end