reviewer 0.1.4 → 1.0.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.
Files changed (93) hide show
  1. checksums.yaml +4 -4
  2. data/.alexignore +1 -0
  3. data/.github/FUNDING.yml +3 -0
  4. data/.github/workflows/main.yml +81 -11
  5. data/.github/workflows/release.yml +98 -0
  6. data/.gitignore +1 -1
  7. data/.inch.yml +3 -1
  8. data/.reek.yml +175 -0
  9. data/.reviewer.example.yml +27 -12
  10. data/.reviewer.future.yml +221 -0
  11. data/.reviewer.yml +191 -28
  12. data/.reviewer_stdout +0 -0
  13. data/.rubocop.yml +34 -1
  14. data/CHANGELOG.md +42 -2
  15. data/Gemfile +39 -1
  16. data/Gemfile.lock +294 -72
  17. data/README.md +315 -7
  18. data/RELEASING.md +190 -0
  19. data/Rakefile +117 -0
  20. data/dependency_decisions.yml +61 -0
  21. data/exe/fmt +1 -1
  22. data/exe/rvw +1 -1
  23. data/lib/reviewer/arguments/files.rb +60 -27
  24. data/lib/reviewer/arguments/keywords.rb +39 -43
  25. data/lib/reviewer/arguments/tags.rb +21 -14
  26. data/lib/reviewer/arguments.rb +107 -29
  27. data/lib/reviewer/batch/formatter.rb +87 -0
  28. data/lib/reviewer/batch.rb +46 -35
  29. data/lib/reviewer/capabilities.rb +81 -0
  30. data/lib/reviewer/command/string/env.rb +16 -6
  31. data/lib/reviewer/command/string/flags.rb +14 -5
  32. data/lib/reviewer/command/string.rb +53 -24
  33. data/lib/reviewer/command.rb +69 -39
  34. data/lib/reviewer/configuration/loader.rb +70 -0
  35. data/lib/reviewer/configuration.rb +14 -4
  36. data/lib/reviewer/context.rb +15 -0
  37. data/lib/reviewer/doctor/config_check.rb +46 -0
  38. data/lib/reviewer/doctor/environment_check.rb +58 -0
  39. data/lib/reviewer/doctor/formatter.rb +75 -0
  40. data/lib/reviewer/doctor/keyword_check.rb +85 -0
  41. data/lib/reviewer/doctor/opportunity_check.rb +88 -0
  42. data/lib/reviewer/doctor/report.rb +63 -0
  43. data/lib/reviewer/doctor/tool_inventory.rb +41 -0
  44. data/lib/reviewer/doctor.rb +28 -0
  45. data/lib/reviewer/history.rb +36 -12
  46. data/lib/reviewer/output/formatting.rb +40 -0
  47. data/lib/reviewer/output/printer.rb +105 -0
  48. data/lib/reviewer/output.rb +54 -65
  49. data/lib/reviewer/prompt.rb +38 -0
  50. data/lib/reviewer/report/formatter.rb +124 -0
  51. data/lib/reviewer/report.rb +100 -0
  52. data/lib/reviewer/runner/failed_files.rb +66 -0
  53. data/lib/reviewer/runner/formatter.rb +103 -0
  54. data/lib/reviewer/runner/guidance.rb +79 -0
  55. data/lib/reviewer/runner/result.rb +150 -0
  56. data/lib/reviewer/runner/strategies/captured.rb +232 -0
  57. data/lib/reviewer/runner/strategies/{verbose.rb → passthrough.rb} +15 -24
  58. data/lib/reviewer/runner.rb +179 -35
  59. data/lib/reviewer/session/formatter.rb +87 -0
  60. data/lib/reviewer/session.rb +208 -0
  61. data/lib/reviewer/setup/catalog.rb +233 -0
  62. data/lib/reviewer/setup/detector.rb +61 -0
  63. data/lib/reviewer/setup/formatter.rb +94 -0
  64. data/lib/reviewer/setup/gemfile_lock.rb +55 -0
  65. data/lib/reviewer/setup/generator.rb +54 -0
  66. data/lib/reviewer/setup/tool_block.rb +112 -0
  67. data/lib/reviewer/setup.rb +41 -0
  68. data/lib/reviewer/shell/result.rb +25 -11
  69. data/lib/reviewer/shell/timer.rb +47 -27
  70. data/lib/reviewer/shell.rb +46 -21
  71. data/lib/reviewer/tool/conversions.rb +20 -0
  72. data/lib/reviewer/tool/file_resolver.rb +54 -0
  73. data/lib/reviewer/tool/settings.rb +107 -56
  74. data/lib/reviewer/tool/test_file_mapper.rb +73 -0
  75. data/lib/reviewer/tool/timing.rb +78 -0
  76. data/lib/reviewer/tool.rb +88 -47
  77. data/lib/reviewer/tools.rb +47 -33
  78. data/lib/reviewer/version.rb +1 -1
  79. data/lib/reviewer.rb +114 -54
  80. data/reviewer.gemspec +21 -20
  81. data/structure.svg +1 -0
  82. metadata +113 -148
  83. data/.ruby-version +0 -1
  84. data/lib/reviewer/command/string/verbosity.rb +0 -51
  85. data/lib/reviewer/command/verbosity.rb +0 -65
  86. data/lib/reviewer/conversions.rb +0 -27
  87. data/lib/reviewer/guidance.rb +0 -73
  88. data/lib/reviewer/keywords/git/staged.rb +0 -48
  89. data/lib/reviewer/keywords/git.rb +0 -14
  90. data/lib/reviewer/keywords.rb +0 -9
  91. data/lib/reviewer/loader.rb +0 -59
  92. data/lib/reviewer/printer.rb +0 -25
  93. data/lib/reviewer/runner/strategies/quiet.rb +0 -90
@@ -1,51 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Reviewer
4
- class Command
5
- class String
6
- # Assembles tool settings and provided context for silencing output
7
- class Verbosity
8
- include ::Reviewer::Conversions
9
-
10
- # Even when tools provide a quiet flag, not all treat it as complete silence. In order to
11
- # ensure no extraneous noise is written to the console in some contexts, command output
12
- # occasionally needs to be sent to dev null to ensure there's no clutter.
13
- SEND_TO_DEV_NULL = '> /dev/null'
14
-
15
- attr_reader :flag, :level
16
-
17
- # A wrapper for translating a desired verbosity into the correct strings to append to the
18
- # command so that any output is appropriately silenced for the context under which it's
19
- # currently being executed.
20
- # @param flag [String] the tool-level flag to be used for silencing output
21
- # @param level: Reviewer::Command::Verbosity::TOOL_SILENCE [Symbol] the target level for
22
- # silence for the the command
23
- #
24
- # @return [type] [description]
25
- def initialize(flag, level: Reviewer::Command::Verbosity::TOOL_SILENCE)
26
- @flag = String(flag)
27
- @level = Verbosity(level)
28
- end
29
-
30
- # Converts the verbosity to a string that can be appended to a command string
31
- #
32
- # @return [String] the string to be appended to commands to ensure the correct verbosity
33
- def to_s
34
- to_a.map(&:strip).join(' ').strip
35
- end
36
-
37
- # Collection of values to be joined to ensure the correct verbosity
38
- #
39
- # @return [Array<String>] the values that need to be joined to ensure the correct verbosity
40
- # for the context
41
- def to_a
42
- case level.key
43
- when Reviewer::Command::Verbosity::TOTAL_SILENCE then [flag, SEND_TO_DEV_NULL].compact
44
- when Reviewer::Command::Verbosity::TOOL_SILENCE then [flag].compact
45
- else []
46
- end
47
- end
48
- end
49
- end
50
- end
51
- end
@@ -1,65 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Reviewer
4
- class Command
5
- # Defines the possible verbosity options for running commands
6
- class Verbosity
7
- include Comparable
8
-
9
- class InvalidLevelError < ArgumentError; end
10
-
11
- # Use the quiet flag and send everything to dev/null.
12
- # For some tools "quiet" means "less noisy" rather than truly silent.
13
- # So in those cases, dev/null handles lingering noise.
14
- TOTAL_SILENCE = :total_silence
15
-
16
- # Just the quiet flag for the tool. Basically, let the tool determine the useful output.
17
- TOOL_SILENCE = :tool_silence
18
-
19
- # Let the output scroll for eternity
20
- NO_SILENCE = :no_silence
21
-
22
- # For validation and casting purposes
23
- LEVELS = [
24
- TOTAL_SILENCE,
25
- TOOL_SILENCE,
26
- NO_SILENCE
27
- ].freeze
28
-
29
- attr_accessor :level
30
-
31
- # Create an instance of verbosity
32
- # @param level [Symbol] one of the values of verbosity defined by LEVELS
33
- #
34
- # @return [Command::Verbosity] an instance of verbosity
35
- def initialize(level)
36
- @level = level.to_sym
37
-
38
- verify_level!
39
- end
40
-
41
- def <=>(other)
42
- level <=> other.level
43
- end
44
-
45
- def to_s
46
- level.to_s
47
- end
48
-
49
- def to_i
50
- LEVELS.index(level)
51
- end
52
-
53
- def to_sym
54
- level
55
- end
56
- alias key to_sym
57
-
58
- private
59
-
60
- def verify_level!
61
- raise InvalidLevelError, "Invalid Verbosity Level: '#{level}'" unless LEVELS.include?(level)
62
- end
63
- end
64
- end
65
- end
@@ -1,27 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Reviewer
4
- # Conversion functions for special types in Reviewer
5
- module Conversions
6
- def Tool(value) # rubocop:disable Naming/MethodName
7
- case value
8
- when Tool then value
9
- when Symbol then Tool.new(value)
10
- when String then Tool.new(value.to_sym)
11
- else raise TypeError, "Cannot convert #{value} to Tool"
12
- end
13
- end
14
- module_function :Tool
15
-
16
- def Verbosity(value) # rubocop:disable Naming/MethodName
17
- case value
18
- when Command::Verbosity then value
19
- when Symbol then Command::Verbosity.new(value)
20
- when String then Command::Verbosity.new(value.to_sym)
21
- when Integer then Command::Verbosity.new(Command::Verbosity::LEVELS[value])
22
- else raise TypeError, "Cannot convert #{value} to Verbosity"
23
- end
24
- end
25
- module_function :Verbosity
26
- end
27
- end
@@ -1,73 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'yaml/store'
4
-
5
- module Reviewer
6
- # Handles the logic around what to display after a command has been run
7
- class Guidance
8
- attr_reader :command, :result, :output
9
-
10
- # Create an instance of guidance for suggesting recovery steps after errors
11
- # @param command: [Command] the command that was run and needs recovery guidance
12
- # @param result: [Result] the result of the command
13
- # @param output: Reviewer.output [Output] the output channel for displaying content
14
- #
15
- # @return [Guidance] the guidance class to suggest relevant recovery steps
16
- def initialize(command:, result:, output: Reviewer.output)
17
- @command = command
18
- @result = result
19
- @output = output
20
- end
21
-
22
- # Prints the relevant guidance based on the command and result context
23
- #
24
- # @return [void] prints the relevant guidance to the stream
25
- def show
26
- case result
27
- when executable_not_found? then show_missing_executable_guidance
28
- when cannot_execute? then show_unrecoverable_guidance
29
- else show_syntax_guidance
30
- end
31
- end
32
-
33
- private
34
-
35
- # Conditional check for when the command result was that the executable couldn't be found
36
- #
37
- # @return [Boolean] true if the result indicates the command couldn't be found
38
- def executable_not_found?
39
- ->(result) { result.executable_not_found? }
40
- end
41
-
42
- # Conditional check for when the command result was that it was unable to be executed
43
- #
44
- # @return [Boolean] true if the result indicates the command couldn't be executed
45
- def cannot_execute?
46
- ->(result) { result.cannot_execute? }
47
- end
48
-
49
- # Shows the recovery guidance for when a command is missing
50
- #
51
- # @return [void] prints missing executable guidance
52
- def show_missing_executable_guidance
53
- output.missing_executable_guidance(command)
54
- end
55
-
56
- # Shows the recovery guidance for when a command generates an unrecoverable error
57
- #
58
- # @return [void] prints unrecoverable error guidance
59
- def show_unrecoverable_guidance
60
- output.unrecoverable(result.stderr)
61
- end
62
-
63
- # Shows suggestions for ignoring or disable rules when a command fails after reviewing code
64
- #
65
- # @return [void] prints syntax guidance
66
- def show_syntax_guidance
67
- output.syntax_guidance(
68
- ignore_link: command.tool.links[:ignore_syntax],
69
- disable_link: command.tool.links[:disable_syntax]
70
- )
71
- end
72
- end
73
- end
@@ -1,48 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Reviewer
4
- module Keywords
5
- module Git
6
- # Provides a convenient interface to get the list of staged files via Git
7
- class Staged
8
- OPTIONS = [
9
- 'diff',
10
- '--staged',
11
- '--name-only'
12
- ].freeze
13
-
14
- attr_reader :stdout, :stderr, :status, :exit_status
15
-
16
- def to_a
17
- stdout.strip.empty? ? [] : stdout.split("\n")
18
- end
19
-
20
- def list
21
- @stdout, @stderr, @status = Open3.capture3(command)
22
- @exit_status = @status.exitstatus.to_i
23
-
24
- @status.success? ? to_a : raise_command_line_error
25
- end
26
-
27
- def self.list
28
- new.list
29
- end
30
-
31
- def command
32
- command_parts.join(' ')
33
- end
34
-
35
- private
36
-
37
- def raise_command_line_error
38
- message = "Git Error: #{stderr} (#{command})"
39
- raise SystemCallError.new(message, exit_status)
40
- end
41
-
42
- def command_parts
43
- BASE_COMMAND + OPTIONS
44
- end
45
- end
46
- end
47
- end
48
- end
@@ -1,14 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'git/staged'
4
-
5
- module Reviewer
6
- module Keywords
7
- module Git
8
- BASE_COMMAND = [
9
- 'git',
10
- '--no-pager'
11
- ].freeze
12
- end
13
- end
14
- end
@@ -1,9 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'keywords/git'
4
-
5
- module Keywords
6
- # Grouping for individual commands
7
- module Git
8
- end
9
- end
@@ -1,59 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'yaml'
4
-
5
- module Reviewer
6
- # Provides a collection of the configured tools
7
- class Loader
8
- class MissingConfigurationError < StandardError; end
9
-
10
- class InvalidConfigurationError < StandardError; end
11
-
12
- class MissingReviewCommandError < StandardError; end
13
-
14
- attr_reader :configuration, :file
15
-
16
- def initialize(file = Reviewer.configuration.file)
17
- @file = file
18
- @configuration = configuration_hash
19
-
20
- validate_configuration!
21
- end
22
-
23
- def to_h
24
- configuration
25
- end
26
-
27
- def self.configuration
28
- new.configuration
29
- end
30
-
31
- private
32
-
33
- def validate_configuration!
34
- # Any additional guidance for configuration issues will live here
35
- require_review_commands!
36
- end
37
-
38
- def require_review_commands!
39
- configuration.each do |key, value|
40
- commands = value[:commands]
41
-
42
- next if commands.key?(:review)
43
-
44
- # Ideally, folks would want to fill out everything to receive the most benefit,
45
- # but realistically, the 'review' command is the only required value. If the key
46
- # is missing, or maybe there was a typo, fail right away.
47
- raise MissingReviewCommandError, "'#{key}' does not have a 'review' key under 'commands' in `#{file}`"
48
- end
49
- end
50
-
51
- def configuration_hash
52
- @configuration_hash ||= Psych.safe_load_file(@file, symbolize_names: true)
53
- rescue Errno::ENOENT
54
- raise MissingConfigurationError, "Tools configuration file couldn't be found at `#{file}`"
55
- rescue Psych::SyntaxError => e
56
- raise InvalidConfigurationError, "Tools configuration file (#{file}) has a syntax error: #{e.message}"
57
- end
58
- end
59
- end
@@ -1,25 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Reviewer
4
- # Clean formatter for logging to $stdout
5
- class StandardOutFormatter < ::Logger::Formatter
6
- # Overrides ::Logger::Formatter `call` to present output more concisely
7
- # @param _severity [Logger::Severity] Unused - Logger severity for etnry
8
- # @param _time [DateTime] Unused - Timestamp for entry
9
- # @param _progname [String] Unused - Name of the current program for entry
10
- # @param message [String] The string to print to $stdout
11
- #
12
- # @return [type] [description]
13
- def call(_severity, _time, _progname, message)
14
- "#{message}\n"
15
- end
16
- end
17
-
18
- # Logger for $stdout
19
- class Printer < ::Logger
20
- def initialize(formatter = StandardOutFormatter.new)
21
- super($stdout)
22
- @formatter = formatter
23
- end
24
- end
25
- end
@@ -1,90 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Reviewer
4
- class Runner
5
- module Strategies
6
- # Execution strategy to run a command quietly
7
- class Quiet
8
- attr_accessor :runner
9
-
10
- # Create an instance of the quiet strategy for a command runner so that any output is fully
11
- # suppressed so as to not create too much noise when running multiple commands.
12
- # @param runner [Runner] the instance of the runner to apply the strategy to
13
- #
14
- # @return [Runner::Strategies::Quiet] an instance of the relevant quiet strategy
15
- def initialize(runner)
16
- @runner = runner
17
- @runner.command.verbosity = Reviewer::Command::Verbosity::TOTAL_SILENCE
18
- end
19
-
20
- # The prepare command strategy when running a command quietly
21
- #
22
- # @return [void]
23
- def prepare
24
- # Running the prepare command, so make sure the timestamp is updated
25
- runner.update_last_prepared_at
26
-
27
- # Run the prepare command, suppressing the output and capturing the realtime benchmark
28
- runner.shell.capture_prep(runner.prepare_command)
29
- end
30
-
31
- # The run command strategy when running a command verbosely
32
- #
33
- # @return [void]
34
- def run
35
- # Run the primary command, suppressing the output and capturing the realtime benchmark
36
- runner.shell.capture_main(runner.command)
37
-
38
- # If it's successful, show that it was a success and how long it took to run, otherwise,
39
- # it wasn't successful and we got some explaining to do...
40
- runner.success? ? show_timing_result : show_command_output
41
- end
42
-
43
- private
44
-
45
- # Prints "Success" and the resulting timing details before moving on to the next tool
46
- #
47
- # @return [void]
48
- def show_timing_result
49
- runner.output.success(runner.timer)
50
- end
51
-
52
- # Prints "Failure" and the resulting exit status. Shows the precise command that led to the
53
- # failure for easier copy and paste or making it easier to see any incorrect syntax or
54
- # options that could be corrected.
55
- #
56
- # @return [void]
57
- def show_command_output
58
- runner.output.failure("Exit Status #{runner.exit_status}", command: runner.command)
59
-
60
- # If it can't be rerun, then don't try
61
- return if runner.result.total_failure?
62
-
63
- # If it can be rerun, set the strategy to verbose so the output will be visible, and then
64
- # run it with the verbose strategy so the output isn't suppressed. Long-term, it makes
65
- # sense to add an option for whether to focus on speed or rich output.
66
- #
67
- # For now, the simplest strategy is to re-run the exact same command without suppressing
68
- # the output. However, running a command twice isn't exactly efficient. Since we've
69
- # already run it once and captured the output, we could just display that output, but it
70
- # would be filtered through as a dumb string. That would mean it strips out color and
71
- # formatting. It would save time, but what's the threshold where the time savings is worth
72
- # it?
73
- #
74
- # Ultimately, this will be a tradeoff between how much the tool's original formatting
75
- # makes it easier to scan the results and take action. For example, if a command takes
76
- # 60 seconds to run, it might be faster to show the low-fidelity output immediately rather
77
- # than waiting another 60 seconds for higher-fidelity output.
78
- #
79
- # Most likely, this will be a tool-by-tool decision and need to be added as an option to
80
- # the tool configuration, but that will create additional complexity/overhead when adding
81
- # a new tool.
82
- #
83
- # So for now, we punt. And pay close attention.
84
- runner.strategy = Strategies::Verbose
85
- runner.run
86
- end
87
- end
88
- end
89
- end
90
- end