rubocop-gradual 0.1.1 → 0.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: 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