quality_gate 0.1.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 +7 -0
- data/CHANGELOG.md +14 -0
- data/LICENSE.txt +21 -0
- data/README.md +571 -0
- data/config/37signals.yml +26 -0
- data/config/cops.yml +37 -0
- data/config/reek.yml +6 -0
- data/config/rubocop.yml +60 -0
- data/config/ruby.yml +8 -0
- data/docs/codex.md +31 -0
- data/docs/dogfood-log.md +47 -0
- data/docs/incidents.md +18 -0
- data/docs/releasing.md +46 -0
- data/exe/quality_gate +6 -0
- data/lib/generators/quality_gate/install/install_generator.rb +66 -0
- data/lib/generators/quality_gate/install/templates/agents_section.md.tt +15 -0
- data/lib/generators/quality_gate/install/templates/bullet.rb.tt +10 -0
- data/lib/generators/quality_gate/install/templates/claude_settings.json.tt +29 -0
- data/lib/generators/quality_gate/install/templates/hook_log_filesystem.rb.tt +76 -0
- data/lib/generators/quality_gate/install/templates/quality_gate.yml.tt +42 -0
- data/lib/generators/quality_gate/install/templates/quality_gate_fast.rb.tt +248 -0
- data/lib/generators/quality_gate/install/templates/quality_gate_verify_stop.rb.tt +466 -0
- data/lib/generators/quality_gate/install/templates/rubocop.yml.tt +1 -0
- data/lib/generators/quality_gate/install/templates/ruby_agents_section.md.tt +15 -0
- data/lib/generators/quality_gate/install/templates/ruby_quality_gate.yml.tt +40 -0
- data/lib/generators/quality_gate/install/templates/ruby_rubocop.yml.tt +1 -0
- data/lib/generators/quality_gate/install/templates/ruby_simplecov.rb.tt +11 -0
- data/lib/generators/quality_gate/install/templates/simplecov.rb.tt +10 -0
- data/lib/generators/quality_gate/install/templates/strong_migrations.rb.tt +10 -0
- data/lib/quality_gate/adapter.rb +328 -0
- data/lib/quality_gate/adapters/brakeman.rb +125 -0
- data/lib/quality_gate/adapters/bundler_audit.rb +235 -0
- data/lib/quality_gate/adapters/reek.rb +108 -0
- data/lib/quality_gate/adapters/rubocop.rb +142 -0
- data/lib/quality_gate/adapters/simplecov.rb +103 -0
- data/lib/quality_gate/adapters/test_suite.rb +81 -0
- data/lib/quality_gate/adapters/undercover.rb +357 -0
- data/lib/quality_gate/cli.rb +451 -0
- data/lib/quality_gate/config.rb +290 -0
- data/lib/quality_gate/exit_code.rb +14 -0
- data/lib/quality_gate/finding.rb +50 -0
- data/lib/quality_gate/hook_log.rb +131 -0
- data/lib/quality_gate/init_command.rb +90 -0
- data/lib/quality_gate/installation.rb +1577 -0
- data/lib/quality_gate/installer.rb +104 -0
- data/lib/quality_gate/railtie.rb +16 -0
- data/lib/quality_gate/reporters/field_sanitizer.rb +42 -0
- data/lib/quality_gate/reporters/json.rb +59 -0
- data/lib/quality_gate/reporters/text.rb +45 -0
- data/lib/quality_gate/rubocop.rb +34 -0
- data/lib/quality_gate/ruby_profile.rb +170 -0
- data/lib/quality_gate/runner.rb +150 -0
- data/lib/quality_gate/version.rb +5 -0
- data/lib/quality_gate.rb +27 -0
- data/lib/rubocop/cop/quality_gate/association_default_block_value.rb +46 -0
- data/lib/rubocop/cop/quality_gate/broadcast_in_controller.rb +64 -0
- data/lib/rubocop/cop/quality_gate/controller_instance_variables.rb +47 -0
- data/lib/rubocop/cop/quality_gate/prefer_after_save_commit.rb +84 -0
- data/lib/rubocop/cop/quality_gate/private_only_concern.rb +77 -0
- data/llm.txt +13 -0
- data/sig/quality_gate.rbs +226 -0
- metadata +260 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
module QualityGate
|
|
6
|
+
# Defers recognizable request-context defaults until association initialization.
|
|
7
|
+
class AssociationDefaultBlockValue < Base
|
|
8
|
+
MSG = "Wrap the association default in a lambda so Current is read when the record is initialized."
|
|
9
|
+
RESTRICT_ON_SEND = %i[belongs_to].freeze
|
|
10
|
+
|
|
11
|
+
def on_send(node)
|
|
12
|
+
return if node.receiver && !node.receiver.self_type?
|
|
13
|
+
|
|
14
|
+
value = default_value(node.last_argument)
|
|
15
|
+
return unless value && current_read?(value)
|
|
16
|
+
|
|
17
|
+
add_offense(value)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def default_value(options)
|
|
23
|
+
return unless literal_options?(options)
|
|
24
|
+
|
|
25
|
+
defaults = options.pairs.select { |pair| pair.key.sym_type? && pair.key.value == :default }
|
|
26
|
+
defaults.first.value if defaults.one?
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def literal_options?(options)
|
|
30
|
+
options&.hash_type? && options.children.all?(&:pair_type?)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def current_read?(node)
|
|
34
|
+
return false unless node.send_type? || node.csend_type?
|
|
35
|
+
return false if %i[method public_method to_proc].include?(node.method_name)
|
|
36
|
+
|
|
37
|
+
receiver = node.receiver
|
|
38
|
+
return false unless receiver
|
|
39
|
+
return receiver.const_name == "Current" if receiver.const_type?
|
|
40
|
+
|
|
41
|
+
current_read?(receiver)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
module QualityGate
|
|
6
|
+
# Opt-in convention for moving explicit broadcasts into domain behavior.
|
|
7
|
+
class BroadcastInController < Base
|
|
8
|
+
MSG = "Consider moving this broadcast from the controller into model-owned behavior."
|
|
9
|
+
BROADCAST_METHODS = %i[
|
|
10
|
+
broadcast_action broadcast_action_to broadcast_action_later broadcast_action_later_to
|
|
11
|
+
broadcast_append broadcast_append_to broadcast_append_later broadcast_append_later_to
|
|
12
|
+
broadcast_prepend broadcast_prepend_to broadcast_prepend_later broadcast_prepend_later_to
|
|
13
|
+
broadcast_replace broadcast_replace_to broadcast_replace_later broadcast_replace_later_to
|
|
14
|
+
broadcast_update broadcast_update_to broadcast_update_later broadcast_update_later_to
|
|
15
|
+
broadcast_remove broadcast_remove_to
|
|
16
|
+
broadcast_before broadcast_before_to broadcast_before_later broadcast_before_later_to
|
|
17
|
+
broadcast_after broadcast_after_to broadcast_after_later broadcast_after_later_to
|
|
18
|
+
broadcast_render broadcast_render_to broadcast_render_later broadcast_render_later_to
|
|
19
|
+
broadcast_refresh broadcast_refresh_to broadcast_refresh_later broadcast_refresh_later_to
|
|
20
|
+
broadcast_stream_to
|
|
21
|
+
].freeze
|
|
22
|
+
|
|
23
|
+
def on_send(node)
|
|
24
|
+
return unless controller?(node)
|
|
25
|
+
return if Array(cop_config["AllowedMethods"]).include?(node.method_name.to_s)
|
|
26
|
+
return unless broadcast?(node)
|
|
27
|
+
|
|
28
|
+
add_offense(node.loc.selector)
|
|
29
|
+
end
|
|
30
|
+
alias on_csend on_send
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def controller?(node)
|
|
35
|
+
enclosing_class = node.each_ancestor(:class).first
|
|
36
|
+
if enclosing_class
|
|
37
|
+
enclosing_class.identifier.const_name.end_with?("Controller")
|
|
38
|
+
else
|
|
39
|
+
processed_source.file_path.include?("/app/controllers/")
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def broadcast?(node)
|
|
44
|
+
BROADCAST_METHODS.include?(node.method_name) || channel_broadcast?(node) || cable_broadcast?(node)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def channel_broadcast?(node)
|
|
48
|
+
node.method?(:broadcast_to) && node.receiver&.const_type? &&
|
|
49
|
+
node.receiver.const_name.end_with?("Channel")
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def cable_broadcast?(node)
|
|
53
|
+
node.method?(:broadcast) && action_cable_server?(node.receiver)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def action_cable_server?(receiver)
|
|
57
|
+
receiver&.send_type? && receiver.method?(:server) &&
|
|
58
|
+
receiver.arguments.empty? && receiver.receiver&.const_type? &&
|
|
59
|
+
receiver.receiver.const_name == "ActionCable"
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
module QualityGate
|
|
6
|
+
# Sandi Metz rule four: a controller action instantiates one object.
|
|
7
|
+
# Only public instance methods of a *Controller class count as actions.
|
|
8
|
+
class ControllerInstanceVariables < Base
|
|
9
|
+
include VisibilityHelp
|
|
10
|
+
extend ExcludeLimit
|
|
11
|
+
|
|
12
|
+
MSG = "Action `%<action>s` assigns %<count>d instance variables (%<names>s); the limit is %<max>d."
|
|
13
|
+
OWNER_TYPES = %i[class sclass module def defs].freeze
|
|
14
|
+
|
|
15
|
+
exclude_limit "Max"
|
|
16
|
+
|
|
17
|
+
def on_def(node)
|
|
18
|
+
return unless action?(node)
|
|
19
|
+
|
|
20
|
+
names = assigned_names(node)
|
|
21
|
+
return if names.size <= max
|
|
22
|
+
|
|
23
|
+
add_offense(node.loc.name, message: message_for(node, names)) { self.max = names.size }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def action?(node) = controller?(node) && node_visibility(node) == :public
|
|
29
|
+
|
|
30
|
+
def controller?(node)
|
|
31
|
+
owner = node.each_ancestor(*OWNER_TYPES).first
|
|
32
|
+
return false unless owner&.class_type?
|
|
33
|
+
|
|
34
|
+
owner.identifier.const_name.to_s.end_with?("Controller")
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def assigned_names(node) = node.each_descendant(:ivasgn).map { _1.name.to_s }.uniq
|
|
38
|
+
|
|
39
|
+
def message_for(node, names)
|
|
40
|
+
format(MSG, action: node.method_name, count: names.size, names: names.join(", "), max: max)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def max = cop_config.fetch("Max", 1)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
module QualityGate
|
|
6
|
+
# Uses Rails' shorthand for callbacks after both creation and update commits.
|
|
7
|
+
class PreferAfterSaveCommit < Base
|
|
8
|
+
extend AutoCorrector
|
|
9
|
+
|
|
10
|
+
MSG = "Use after_save_commit for callbacks on both create and update."
|
|
11
|
+
RESTRICT_ON_SEND = %i[after_commit].freeze
|
|
12
|
+
|
|
13
|
+
def on_send(node)
|
|
14
|
+
return if node.receiver && !node.receiver.self_type?
|
|
15
|
+
|
|
16
|
+
pair = save_option(node.last_argument)
|
|
17
|
+
return unless pair
|
|
18
|
+
|
|
19
|
+
removal = option_range(node, pair)
|
|
20
|
+
add_offense(node.loc.selector) do |corrector|
|
|
21
|
+
next if commented?(removal)
|
|
22
|
+
|
|
23
|
+
corrector.replace(node.loc.selector, "after_save_commit")
|
|
24
|
+
corrector.remove(removal)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def save_option(options)
|
|
31
|
+
return unless literal_options?(options)
|
|
32
|
+
|
|
33
|
+
pairs = options.pairs.select { |pair| pair.key.sym_type? && pair.key.value == :on }
|
|
34
|
+
pairs.first if pairs.one? && save_events?(pairs.first.value)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def literal_options?(options)
|
|
38
|
+
options&.hash_type? && options.children.all?(&:pair_type?)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def commented?(range)
|
|
42
|
+
processed_source.comments.any? { |comment| range.overlaps?(comment.loc.expression) }
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def save_events?(node)
|
|
46
|
+
node.array_type? && node.values.all?(&:sym_type?) &&
|
|
47
|
+
node.values.map(&:value).sort == %i[create update]
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def option_range(node, pair)
|
|
51
|
+
options = node.last_argument
|
|
52
|
+
return argument_range(node, options) if options.pairs.one?
|
|
53
|
+
|
|
54
|
+
pair_range(options.pairs, pair)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def pair_range(pairs, pair)
|
|
58
|
+
index = pairs.index(pair)
|
|
59
|
+
following = pairs[index + 1]
|
|
60
|
+
if following
|
|
61
|
+
pair.source_range.with(end_pos: following.source_range.begin_pos)
|
|
62
|
+
else
|
|
63
|
+
pair.source_range.with(begin_pos: pairs[index - 1].source_range.end_pos)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def argument_range(node, options)
|
|
68
|
+
previous = node.arguments[-2]
|
|
69
|
+
return options.source_range.with(end_pos: node.loc.end.begin_pos) if !previous && node.parenthesized?
|
|
70
|
+
|
|
71
|
+
start = argument_start(node, options, previous)
|
|
72
|
+
options.source_range.with(begin_pos: start)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def argument_start(node, options, previous)
|
|
76
|
+
return previous.source_range.end_pos if previous
|
|
77
|
+
return node.loc.selector.end_pos unless node.parenthesized?
|
|
78
|
+
|
|
79
|
+
options.source_range.begin_pos
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
module QualityGate
|
|
6
|
+
# Opt-in convention: concerns should expose behavior to their consumers.
|
|
7
|
+
# Unknown declarations are deliberately exempt because they may add behavior.
|
|
8
|
+
class PrivateOnlyConcern < Base
|
|
9
|
+
MSG = "This concern only defines private instance methods; consider keeping them in its consumer."
|
|
10
|
+
VISIBILITIES = %i[public protected private].freeze
|
|
11
|
+
|
|
12
|
+
def on_module(node)
|
|
13
|
+
body = node.body
|
|
14
|
+
return unless body
|
|
15
|
+
|
|
16
|
+
statements = body.begin_type? ? body.children : [body]
|
|
17
|
+
return unless concern?(statements)
|
|
18
|
+
return unless private_methods_only?(statements)
|
|
19
|
+
|
|
20
|
+
add_offense(node.identifier)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def concern?(statements)
|
|
26
|
+
statements.any? { |statement| concern_extension?(statement) } ||
|
|
27
|
+
Array(cop_config["ConcernPaths"]).any? do |pattern|
|
|
28
|
+
File.fnmatch?(pattern, processed_source.file_path, File::FNM_PATHNAME | File::FNM_EXTGLOB)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def concern_extension?(node)
|
|
33
|
+
node.send_type? && node.receiver.nil? && node.method?(:extend) &&
|
|
34
|
+
node.arguments.one? && node.first_argument.const_type? &&
|
|
35
|
+
node.first_argument.const_name == "ActiveSupport::Concern"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def private_methods_only?(statements)
|
|
39
|
+
methods = {}
|
|
40
|
+
visibility = :public
|
|
41
|
+
statements.each do |statement|
|
|
42
|
+
next if concern_extension?(statement)
|
|
43
|
+
|
|
44
|
+
visibility = record_statement(statement, methods, visibility)
|
|
45
|
+
return false unless visibility
|
|
46
|
+
end
|
|
47
|
+
!methods.empty? && methods.values.all?(:private)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def record_statement(statement, methods, visibility)
|
|
51
|
+
if statement.def_type?
|
|
52
|
+
methods[statement.method_name] = visibility
|
|
53
|
+
elsif visibility_declaration?(statement) && apply_visibility(statement, methods)
|
|
54
|
+
statement.arguments.empty? ? statement.method_name : visibility
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def visibility_declaration?(node)
|
|
59
|
+
node.send_type? && node.receiver.nil? && VISIBILITIES.include?(node.method_name)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def apply_visibility(node, methods)
|
|
63
|
+
node.arguments.each do |argument|
|
|
64
|
+
if argument.def_type?
|
|
65
|
+
methods[argument.method_name] = node.method_name
|
|
66
|
+
elsif argument.sym_type? && methods.key?(argument.value)
|
|
67
|
+
methods[argument.value] = node.method_name
|
|
68
|
+
else
|
|
69
|
+
return false
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
true
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
data/llm.txt
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Quality Gate
|
|
2
|
+
|
|
3
|
+
Quality Gate provides staged Ruby code-quality checks through one CLI and
|
|
4
|
+
configuration contract. See the README for installation, gate commands,
|
|
5
|
+
configuration, and optional RuboCop conventions.
|
|
6
|
+
|
|
7
|
+
## Documentation
|
|
8
|
+
|
|
9
|
+
- [README](README.md)
|
|
10
|
+
- [Codex integration](docs/codex.md)
|
|
11
|
+
- [Validation and limitations](docs/dogfood-log.md)
|
|
12
|
+
- [Incident workflow](docs/incidents.md)
|
|
13
|
+
- [Releasing](docs/releasing.md)
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
module QualityGate
|
|
2
|
+
VERSION: String
|
|
3
|
+
|
|
4
|
+
interface _Output
|
|
5
|
+
def puts: (*untyped objects) -> nil
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
class Error < StandardError
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
class ParseError < Error
|
|
12
|
+
attr_reader tool: String
|
|
13
|
+
|
|
14
|
+
def initialize: (tool: String, reason: String) -> void
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
class ConfigError < Error
|
|
18
|
+
attr_reader path: String
|
|
19
|
+
|
|
20
|
+
def initialize: (path: String, cause_message: String) -> void
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
class Railtie
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
class InstallGenerator
|
|
27
|
+
FILES: Array[String]
|
|
28
|
+
MARKER_START: String
|
|
29
|
+
MARKER_END: String
|
|
30
|
+
|
|
31
|
+
def create_settings_file: () -> void
|
|
32
|
+
def create_rules_file: () -> void
|
|
33
|
+
def create_initializers: () -> void
|
|
34
|
+
def inject_coverage: () -> void
|
|
35
|
+
def create_agent_integration: () -> void
|
|
36
|
+
def print_summary: () -> void
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
class Installer
|
|
40
|
+
def initialize: (destination_root: String, ?options: Hash[Symbol, untyped], ?stdout: _Output) -> void
|
|
41
|
+
def call: () -> (0 | 1)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
class RubyProfile
|
|
45
|
+
attr_reader destination_root: String
|
|
46
|
+
attr_reader test_helper: String
|
|
47
|
+
attr_reader test_command: Array[String]
|
|
48
|
+
|
|
49
|
+
def initialize: (destination_root: String, ?options: Hash[Symbol, untyped]) -> void
|
|
50
|
+
def coverage?: () -> bool
|
|
51
|
+
def template_for: (String name) -> String
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
class Config
|
|
55
|
+
DEFAULTS: Hash[Symbol, untyped]
|
|
56
|
+
|
|
57
|
+
def self.defaults: () -> Hash[Symbol, untyped]
|
|
58
|
+
def self.load: (dir: String) -> Config
|
|
59
|
+
|
|
60
|
+
attr_reader path: String?
|
|
61
|
+
attr_reader unknown_keys: Array[String]
|
|
62
|
+
|
|
63
|
+
def initialize: (Hash[Symbol, untyped] settings, ?path: String?, ?unknown_keys: Array[String]) -> void
|
|
64
|
+
def to_h: () -> Hash[Symbol, untyped]
|
|
65
|
+
def fetch: (Symbol key) -> untyped
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
class HookLog
|
|
69
|
+
DEFAULT_PATH: String
|
|
70
|
+
|
|
71
|
+
attr_reader path: String
|
|
72
|
+
|
|
73
|
+
def initialize: (path: String) -> void
|
|
74
|
+
def recent: (?limit: Integer) -> Array[Hash[String, untyped]]
|
|
75
|
+
def unavailable_count: (?limit: Integer) -> Integer
|
|
76
|
+
def warning_line: (?limit: Integer) -> String?
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
class Adapter
|
|
80
|
+
def initialize: (config: Config, ?files: Array[String], ?diagnostic_io: _Output) -> void
|
|
81
|
+
def call: () -> Array[Finding]
|
|
82
|
+
def name: () -> String
|
|
83
|
+
def command: () -> Array[String]
|
|
84
|
+
def parse: (String stdout) -> Array[Finding]
|
|
85
|
+
def timeout: () -> Integer
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
module Adapters
|
|
89
|
+
class Brakeman < Adapter
|
|
90
|
+
CONFIDENCE_MAP: Hash[String, finding_severity]
|
|
91
|
+
|
|
92
|
+
def initialize: (config: Config, ?files: Array[String], ?diagnostic_io: _Output) -> void
|
|
93
|
+
def call: () -> Array[Finding]
|
|
94
|
+
def name: () -> String
|
|
95
|
+
def command: () -> Array[String]
|
|
96
|
+
def parse: (String stdout) -> Array[Finding]
|
|
97
|
+
def timeout: () -> Integer
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
class BundlerAudit < Adapter
|
|
101
|
+
LOCK_FILE: String
|
|
102
|
+
|
|
103
|
+
def initialize: (config: Config, ?files: Array[String], ?diagnostic_io: _Output) -> void
|
|
104
|
+
def call: () -> Array[Finding]
|
|
105
|
+
def name: () -> String
|
|
106
|
+
def command: () -> Array[String]
|
|
107
|
+
def parse: (String stdout) -> Array[Finding]
|
|
108
|
+
def timeout: () -> Integer
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
class RuboCop < Adapter
|
|
112
|
+
SEVERITY_MAP: Hash[String, finding_severity]
|
|
113
|
+
CONFIG_PATH: String
|
|
114
|
+
|
|
115
|
+
def initialize: (config: Config, ?files: Array[String], ?diagnostic_io: _Output) -> void
|
|
116
|
+
def call: () -> Array[Finding]
|
|
117
|
+
def name: () -> String
|
|
118
|
+
def command: () -> Array[String]
|
|
119
|
+
def parse: (String stdout) -> Array[Finding]
|
|
120
|
+
def timeout: () -> Integer
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
class SimpleCov < Adapter
|
|
124
|
+
COVERAGE_PATH: String
|
|
125
|
+
|
|
126
|
+
def initialize: (config: Config, ?files: Array[String], ?diagnostic_io: _Output) -> void
|
|
127
|
+
def call: () -> Array[Finding]
|
|
128
|
+
def name: () -> String
|
|
129
|
+
def timeout: () -> Integer
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
class TestSuite < Adapter
|
|
133
|
+
TAIL_LINES: Integer
|
|
134
|
+
|
|
135
|
+
def initialize: (config: Config, ?files: Array[String], ?diagnostic_io: _Output) -> void
|
|
136
|
+
def call: () -> Array[Finding]
|
|
137
|
+
def name: () -> String
|
|
138
|
+
def command: () -> Array[String]
|
|
139
|
+
def env: () -> Hash[String, String]
|
|
140
|
+
def timeout: () -> Integer
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
class Undercover < Adapter
|
|
144
|
+
COVERAGE_PATH: String
|
|
145
|
+
SKIP_RULE: String
|
|
146
|
+
|
|
147
|
+
def initialize: (config: Config, ?files: Array[String], ?diagnostic_io: _Output) -> void
|
|
148
|
+
def call: () -> Array[Finding]
|
|
149
|
+
def name: () -> String
|
|
150
|
+
def command: () -> Array[String]
|
|
151
|
+
def compare_point: () -> String?
|
|
152
|
+
def parse: (String stdout) -> Array[Finding]
|
|
153
|
+
def timeout: () -> Integer
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
class Runner
|
|
158
|
+
def initialize: (adapters: Array[Adapter], config: Config, ?diagnostic_io: _Output?) -> void
|
|
159
|
+
def call: () -> Runner::Result
|
|
160
|
+
|
|
161
|
+
class Result < Data
|
|
162
|
+
attr_reader findings: Array[Finding]
|
|
163
|
+
attr_reader failed_tools: Array[String]
|
|
164
|
+
attr_reader checks: Array[Hash[Symbol, untyped]]
|
|
165
|
+
|
|
166
|
+
def initialize: (findings: Array[Finding], ?checks: Array[Hash[Symbol, untyped]]) -> void
|
|
167
|
+
def with: (?findings: Array[Finding], ?checks: Array[Hash[Symbol, untyped]]) -> Result
|
|
168
|
+
def exit_code: () -> (0 | 1 | 2)
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
class CLI
|
|
173
|
+
SUBCOMMANDS: Array[String]
|
|
174
|
+
|
|
175
|
+
def self.run: (Array[String] argv, ?stdout: _Output, ?stderr: _Output, ?dir: String) -> (0 | 1 | 2)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
class InitCommand
|
|
179
|
+
def self.run: (Array[String] arguments, stdout: _Output, stderr: _Output, dir: String) -> (0 | 1 | 2)
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
module Reporters
|
|
183
|
+
class Text
|
|
184
|
+
def initialize: (io: _Output) -> void
|
|
185
|
+
def call: (Runner::Result result) -> nil
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
class Json
|
|
189
|
+
def initialize: (io: _Output) -> void
|
|
190
|
+
def call: (Runner::Result result) -> nil
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
type finding_severity = :error | :warning | :info
|
|
195
|
+
|
|
196
|
+
class Finding < Data
|
|
197
|
+
SEVERITIES: Array[finding_severity]
|
|
198
|
+
TOOL_FAILURE_RULE: String
|
|
199
|
+
|
|
200
|
+
attr_reader tool: String
|
|
201
|
+
attr_reader file: String
|
|
202
|
+
attr_reader line: Integer
|
|
203
|
+
attr_reader rule: String
|
|
204
|
+
attr_reader severity: finding_severity
|
|
205
|
+
attr_reader message: String
|
|
206
|
+
|
|
207
|
+
def initialize: (
|
|
208
|
+
tool: String,
|
|
209
|
+
file: String,
|
|
210
|
+
line: Integer,
|
|
211
|
+
rule: String,
|
|
212
|
+
severity: finding_severity,
|
|
213
|
+
message: String
|
|
214
|
+
) -> void
|
|
215
|
+
def self.tool_failure: (tool: String, message: String) -> Finding
|
|
216
|
+
def tool_failure?: () -> bool
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
module ExitCode
|
|
220
|
+
CLEAN: 0
|
|
221
|
+
FINDINGS: 1
|
|
222
|
+
TOOL_FAILURE: 2
|
|
223
|
+
|
|
224
|
+
def self.all: () -> Array[0 | 1 | 2]
|
|
225
|
+
end
|
|
226
|
+
end
|