pronto-rubycritic 0.12.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.
@@ -0,0 +1,168 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pronto
4
+ class RubyCritic < Runner
5
+ # Chooses output markup based on CI environment. GitHub renders rich
6
+ # markdown (headings, tables, <details>) in PR review comments; GitLab
7
+ # does not render <details> reliably in MR / commit comments so plain
8
+ # markdown is used there.
9
+ class Formatter
10
+ GITHUB = :github
11
+ GITLAB = :gitlab
12
+ PLAIN = :plain
13
+
14
+ SEVERITY_EMOJI = {
15
+ info: '💡',
16
+ warning: '⚠️',
17
+ error: '🔴',
18
+ fatal: '🚨'
19
+ }.freeze
20
+
21
+ BRAND_NAME = 'pronto-rubycritic'
22
+ BRAND_URL = 'https://github.com/Rishabhs343/custom-pronto-gem'
23
+
24
+ def self.detect(env: ENV, severity: :warning)
25
+ style = if env['GITHUB_ACTIONS'] then GITHUB
26
+ elsif env['GITLAB_CI'] then GITLAB
27
+ else PLAIN
28
+ end
29
+ new(style: style, severity: severity)
30
+ end
31
+
32
+ def initialize(style: PLAIN, severity: :warning)
33
+ @style = style
34
+ @severity = severity
35
+ end
36
+
37
+ attr_reader :style, :severity
38
+
39
+ def call(mod, smell)
40
+ case @style
41
+ when GITHUB then format_github(mod, smell)
42
+ when GITLAB then format_gitlab(mod, smell)
43
+ else format_plain(mod, smell)
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ def format_github(mod, smell)
50
+ <<~MARKDOWN.strip
51
+ #{severity_emoji} **#{escape_html(smell.type)}** · `#{escape_html(smell.context)}` <sub>#{analyser_badge(smell)}</sub>
52
+
53
+ > #{escape_html(smell.message)}
54
+
55
+ <details><summary>📊 Module metrics</summary>
56
+
57
+ | Metric | Value |
58
+ |---|---:|
59
+ | Complexity | #{fmt(mod.complexity)} |
60
+ | Duplication | #{fmt(mod.duplication)} |
61
+ | Methods | #{fmt(mod.methods_count)} |
62
+ | Cost | #{fmt(mod.cost)} |
63
+ | Churn | #{fmt(mod.churn)} |
64
+
65
+ </details>
66
+
67
+ #{doc_link(smell)}
68
+
69
+ <sub>🧰 reported by <a href="#{BRAND_URL}"><b>#{BRAND_NAME}</b></a> · severity: <code>#{@severity}</code></sub>
70
+ MARKDOWN
71
+ end
72
+
73
+ def format_gitlab(mod, smell)
74
+ # GitLab MR comments don't render <details> reliably — use a flat
75
+ # markdown layout that still scans well in GitLab discussion threads.
76
+ <<~MARKDOWN.strip
77
+ #{severity_emoji} **#{safe(smell.type)}** · `#{safe(smell.context)}` (_#{analyser_text(smell)}_)
78
+
79
+ > #{safe(smell.message)}
80
+
81
+ | Complexity | Duplication | Methods | Cost | Churn |
82
+ |---:|---:|---:|---:|---:|
83
+ | #{fmt(mod.complexity)} | #{fmt(mod.duplication)} | #{fmt(mod.methods_count)} | #{fmt(mod.cost)} | #{fmt(mod.churn)} |
84
+
85
+ #{doc_link(smell)}
86
+
87
+ _reported by [**#{BRAND_NAME}**](#{BRAND_URL}) · severity: `#{@severity}`_
88
+ MARKDOWN
89
+ end
90
+
91
+ def format_plain(mod, smell)
92
+ doc = doc_url_of(smell)
93
+ lines = [
94
+ "[#{@severity}] #{safe(smell.type)} — #{safe(smell.context)} (#{analyser_text(smell)})",
95
+ " Message: #{safe(smell.message)}",
96
+ " Locations: #{format_locations(smell)}",
97
+ " Complexity: #{fmt(mod.complexity)} Duplication: #{fmt(mod.duplication)} " \
98
+ "Methods: #{fmt(mod.methods_count)} Cost: #{fmt(mod.cost)} Churn: #{fmt(mod.churn)}"
99
+ ]
100
+ lines << " Docs: #{doc}" unless doc.nil? || doc.to_s.empty?
101
+ lines.join("\n")
102
+ end
103
+
104
+ def severity_emoji
105
+ SEVERITY_EMOJI.fetch(@severity, SEVERITY_EMOJI[:warning])
106
+ end
107
+
108
+ def analyser_badge(smell)
109
+ "analyser: <code>#{analyser_text(smell)}</code>"
110
+ end
111
+
112
+ def analyser_text(smell)
113
+ return 'unknown' unless smell.respond_to?(:analyser)
114
+
115
+ value = smell.analyser.to_s
116
+ value.empty? ? 'unknown' : value
117
+ rescue StandardError
118
+ 'unknown'
119
+ end
120
+
121
+ def doc_link(smell)
122
+ url = doc_url_of(smell)
123
+ return '' if url.nil? || url.to_s.empty?
124
+
125
+ "📚 [Docs for **#{safe(smell.type)}** →](#{url})"
126
+ end
127
+
128
+ def format_locations(smell)
129
+ locations = Array(smell.locations)
130
+ return 'N/A' if locations.empty?
131
+
132
+ locations.map { |l| "#{l.pathname}:#{l.line}" }.join(', ')
133
+ end
134
+
135
+ def doc_url_of(smell)
136
+ smell.respond_to?(:doc_url) ? smell.doc_url : nil
137
+ rescue StandardError
138
+ nil
139
+ end
140
+
141
+ def fmt(value)
142
+ return 'N/A' if value.nil?
143
+
144
+ str = value.to_s
145
+ return 'N/A' if str.empty?
146
+ return str unless value.is_a?(Float)
147
+ return str if value.nan? || value.infinite?
148
+
149
+ format('%.2f', value)
150
+ end
151
+
152
+ def safe(value)
153
+ return 'N/A' if value.nil?
154
+
155
+ str = value.to_s
156
+ str.empty? ? 'N/A' : str
157
+ end
158
+
159
+ # GitHub renders smell text inside HTML (<details>, <sub>, code spans).
160
+ # Smell context/message come from analysed source (method names — which
161
+ # can be operators like `<=>` — and free-text), so escape the HTML-
162
+ # significant characters to stop them breaking the comment's structure.
163
+ def escape_html(value)
164
+ safe(value).gsub('&', '&amp;').gsub('<', '&lt;').gsub('>', '&gt;')
165
+ end
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pathname'
4
+
5
+ module Pronto
6
+ class RubyCritic < Runner
7
+ # Builds Pronto::Message objects for smells that overlap with added lines
8
+ # in the current PR. Uses a pre-computed relative-path index so lookup is
9
+ # O(1) per smell, not O(N*M) across patches x smells.
10
+ class MessageBuilder
11
+ def initialize(runner:, patches:, severity_level:, formatter: nil)
12
+ @runner = runner
13
+ @patches = patches
14
+ @severity_level = severity_level
15
+ @formatter = formatter || Formatter.detect(severity: severity_level)
16
+ @patch_index = build_patch_index(patches)
17
+ end
18
+
19
+ def call(modules)
20
+ Array(modules).flat_map { |mod| messages_for(mod) }.compact.uniq
21
+ end
22
+
23
+ private
24
+
25
+ def messages_for(mod)
26
+ Array(mod.smells).filter_map do |smell|
27
+ patch = patch_for_smell(smell)
28
+ next nil unless patch
29
+
30
+ added_line = locate_added_line(patch, smell)
31
+ next nil unless added_line
32
+
33
+ build_message(mod, smell, added_line)
34
+ end
35
+ end
36
+
37
+ def build_message(mod, smell, line)
38
+ Pronto::Message.new(
39
+ line.patch.new_file_path,
40
+ line,
41
+ @severity_level,
42
+ @formatter.call(mod, smell),
43
+ nil,
44
+ @runner.class
45
+ )
46
+ end
47
+
48
+ def build_patch_index(patches)
49
+ Array(patches).each_with_object({}) do |patch, acc|
50
+ key = relative(patch.new_file_full_path)
51
+ acc[key] = patch
52
+ end
53
+ end
54
+
55
+ def patch_for_smell(smell)
56
+ loc = Array(smell.locations).first
57
+ return nil unless loc.respond_to?(:pathname)
58
+
59
+ @patch_index[relative(loc.pathname)]
60
+ end
61
+
62
+ def locate_added_line(patch, smell)
63
+ smell_lines = Array(smell.locations).map(&:line)
64
+ return nil if smell_lines.empty?
65
+
66
+ smell_line_set = Set.new(smell_lines)
67
+ Array(patch.added_lines).find { |l| smell_line_set.include?(l.new_lineno) }
68
+ end
69
+
70
+ def relative(path)
71
+ return '' if path.nil?
72
+
73
+ pn = path.is_a?(Pathname) ? path : Pathname.new(path.to_s)
74
+ return pn.to_s unless pn.absolute?
75
+
76
+ pn.relative_path_from(Pathname.pwd).to_s
77
+ rescue ArgumentError
78
+ pn.to_s
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,152 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pathname'
4
+
5
+ module Pronto
6
+ class RubyCritic < Runner
7
+ # Applies user-provided filters from .rubycritic-pronto.yml to the list of
8
+ # analysed modules. Only implements filters that map to real RubyCritic
9
+ # attributes. See CHANGELOG for features dropped in 0.12.0 (reek.min_severity).
10
+ #
11
+ # Every config value is consumed defensively: a malformed section (non-Hash)
12
+ # or a wrong-typed value (non-numeric threshold, scalar `exclude`, junk
13
+ # `max_smells`) is ignored rather than raising. A raise here would be
14
+ # swallowed by the runner's top-level rescue and silently turn the whole
15
+ # quality gate into a false "all clear" — the worst failure mode for a linter.
16
+ class SmellFilter
17
+ SMELL_FILTER_KEYS = %w[flay flog reek].freeze
18
+
19
+ def initialize(config)
20
+ @config = config.is_a?(Hash) ? config : {}
21
+ end
22
+
23
+ def call(modules)
24
+ # Fast path: an empty/nil config is semantically transparent — never
25
+ # mutate input modules, never call mod.smells=. This lets the filter
26
+ # be chained harmlessly.
27
+ return Array(modules) if @config.empty?
28
+
29
+ Array(modules).filter_map { |mod| filter_module(mod) }
30
+ end
31
+
32
+ private
33
+
34
+ def filter_module(mod)
35
+ return nil unless module_within_thresholds?(mod)
36
+
37
+ filtered = filter_smells(Array(mod.smells))
38
+ return nil if filtered.empty? && any_smell_filter?
39
+
40
+ mod.smells = filtered if mod.respond_to?(:smells=)
41
+ mod
42
+ end
43
+
44
+ def filter_smells(smells)
45
+ smells = by_analyser_exclude(smells, 'flay')
46
+ smells = by_analyser_exclude(smells, 'flog')
47
+ smells = by_analyser_max_score(smells, 'flay')
48
+ smells = by_analyser_max_score(smells, 'flog')
49
+ smells = by_reek_smell_types(smells)
50
+ limited(smells)
51
+ end
52
+
53
+ def module_within_thresholds?(mod)
54
+ return false if above_threshold?('complexity', mod, :complexity)
55
+ return false if above_threshold?('churn', mod, :churn)
56
+
57
+ true
58
+ end
59
+
60
+ def above_threshold?(name, mod, attr)
61
+ # Float(_, exception: false) returns nil for non-numeric config (e.g.
62
+ # `complexity.max: high`), so a bad value disables the filter instead of
63
+ # silently coercing to 0.0 (which would drop every module) or raising.
64
+ max = Float(section(name)['max'], exception: false)
65
+ return false if max.nil?
66
+ return false unless mod.respond_to?(attr)
67
+
68
+ value = mod.public_send(attr)
69
+ return false unless value.respond_to?(:to_f)
70
+
71
+ value.to_f > max
72
+ end
73
+
74
+ def by_analyser_max_score(smells, analyser)
75
+ max = Float(section(analyser)['max_score'], exception: false)
76
+ return smells if max.nil?
77
+
78
+ smells.reject do |s|
79
+ smell_from_analyser?(s, analyser) &&
80
+ s.respond_to?(:score) && s.score.to_f > max
81
+ end
82
+ end
83
+
84
+ def by_analyser_exclude(smells, analyser)
85
+ # Normalise to non-empty glob strings so a scalar (`exclude: '*.rb'`) or
86
+ # a list with non-string entries never reaches String#any? / fnmatch.
87
+ patterns = Array(section(analyser)['exclude']).map(&:to_s).reject(&:empty?)
88
+ return smells if patterns.empty?
89
+
90
+ smells.reject do |s|
91
+ smell_from_analyser?(s, analyser) && matches_any_pattern?(s, patterns)
92
+ end
93
+ end
94
+
95
+ def by_reek_smell_types(smells)
96
+ allowed = Array(section('reek')['smell_types']).map(&:to_s).reject(&:empty?)
97
+ return smells if allowed.empty?
98
+
99
+ smells.select do |s|
100
+ next true unless smell_from_analyser?(s, 'reek')
101
+
102
+ allowed.include?(s.type.to_s)
103
+ end
104
+ end
105
+
106
+ def limited(smells)
107
+ max = section('reek')['max_smells']
108
+ return smells if max.nil?
109
+
110
+ count = Integer(max, exception: false)
111
+ return smells if count.nil? || count.negative?
112
+
113
+ smells.first(count)
114
+ end
115
+
116
+ # Returns the named config section only when it is a Hash; a scalar/array/
117
+ # bool value (a plausible YAML typo like `reek: false` or `complexity: 10`)
118
+ # is treated as "no config for that section" so Hash#dig never raises.
119
+ def section(name)
120
+ value = @config[name]
121
+ value.is_a?(Hash) ? value : {}
122
+ end
123
+
124
+ def smell_from_analyser?(smell, analyser)
125
+ smell.respond_to?(:analyser) && smell.analyser.to_s == analyser
126
+ end
127
+
128
+ def matches_any_pattern?(smell, patterns)
129
+ location = Array(smell.locations).first
130
+ return false unless location.respond_to?(:pathname)
131
+
132
+ relative = to_relative(location.pathname.to_s)
133
+ patterns.any? do |pattern|
134
+ File.fnmatch(pattern, relative, File::FNM_PATHNAME | File::FNM_DOTMATCH)
135
+ end
136
+ end
137
+
138
+ def to_relative(path)
139
+ pn = Pathname.new(path)
140
+ return path unless pn.absolute?
141
+
142
+ pn.relative_path_from(Pathname.pwd).to_s
143
+ rescue ArgumentError
144
+ path
145
+ end
146
+
147
+ def any_smell_filter?
148
+ SMELL_FILTER_KEYS.any? { |k| !section(k).empty? }
149
+ end
150
+ end
151
+ end
152
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pronto
4
+ # Version module loaded by the gemspec before Pronto::Runner is available.
5
+ # A separate module is used (instead of Pronto::RubyCritic::VERSION) because
6
+ # the main runner class inherits from Pronto::Runner, which is not yet loaded
7
+ # at gemspec-build time.
8
+ module RubyCriticVersion
9
+ VERSION = '0.12.0'
10
+ end
11
+ end
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Pronto runner for RubyCritic. Analyses Ruby files changed in a PR and reports
4
+ # reek/flay/flog/complexity/churn smells via Pronto::Message — only on lines that
5
+ # were added or modified in the diff.
6
+
7
+ require 'pronto'
8
+ require 'rubycritic'
9
+ require 'rubycritic/analysers_runner'
10
+
11
+ require_relative 'rubycritic/version'
12
+ require_relative 'rubycritic/config_loader'
13
+ require_relative 'rubycritic/analyser'
14
+ require_relative 'rubycritic/smell_filter'
15
+ require_relative 'rubycritic/formatter'
16
+ require_relative 'rubycritic/message_builder'
17
+
18
+ module Pronto
19
+ class RubyCritic < Runner
20
+ VERSION = RubyCriticVersion::VERSION
21
+ VALID_LEVELS = %i[info warning error fatal].freeze
22
+ DEFAULT_LEVEL = :warning
23
+ CONFIG_FILE = '.rubycritic-pronto.yml'
24
+ SEVERITY_ENV = 'PRONTO_RUBYCRITIC_SEVERITY_LEVEL'
25
+ LEGACY_ENV = 'PRONTO_REEK_SEVERITY_LEVEL'
26
+ RAISE_ENV = 'PRONTO_RUBYCRITIC_RAISE_ERRORS'
27
+ DEBUG_ENV = 'PRONTO_RUBYCRITIC_DEBUG'
28
+
29
+ def run
30
+ patches = ruby_patches
31
+ return [] if patches.nil? || patches.empty?
32
+
33
+ process(patches)
34
+ rescue Errno::ENOENT => e
35
+ handle_missing_file(e)
36
+ []
37
+ rescue StandardError => e
38
+ report_error(e)
39
+ raise if truthy_env?(RAISE_ENV)
40
+
41
+ []
42
+ end
43
+
44
+ private
45
+
46
+ # Boolean env vars are opt-in: only 1/true/yes/on enable them. A bare
47
+ # presence check would treat PRONTO_RUBYCRITIC_RAISE_ERRORS=0 (or =false,
48
+ # or an empty value) as "on", which is the opposite of what a user setting
49
+ # it in a CI matrix expects.
50
+ def truthy_env?(name)
51
+ %w[1 true yes on].include?(ENV.fetch(name, '').to_s.strip.downcase)
52
+ end
53
+
54
+ def process(patches)
55
+ modules = Analyser.new(file_paths).call
56
+ filtered = SmellFilter.new(runner_config).call(modules)
57
+ MessageBuilder.new(
58
+ runner: self,
59
+ patches: patches,
60
+ severity_level: severity_level
61
+ ).call(filtered)
62
+ end
63
+
64
+ # Pronto's ruby_executable? helper does File.read(path, 2) on every
65
+ # patched path to check for a shebang; when the working tree is missing
66
+ # a file that's in the diff range, it raises Errno::ENOENT. Translate
67
+ # that into a human-readable message that points at `git status`.
68
+ def handle_missing_file(error)
69
+ warn(
70
+ 'pronto-rubycritic: working tree is missing a file referenced in ' \
71
+ "the diff: #{error.message}. Run `git status` to reconcile."
72
+ )
73
+ raise if truthy_env?(RAISE_ENV)
74
+ end
75
+
76
+ def file_paths
77
+ @file_paths ||= ruby_patches.map { |p| p.new_file_full_path.to_s }
78
+ end
79
+
80
+ def runner_config
81
+ @runner_config ||= ConfigLoader.load_runner_config(CONFIG_FILE)
82
+ end
83
+
84
+ def pronto_config
85
+ @pronto_config ||= ConfigLoader.load_pronto_config
86
+ end
87
+
88
+ def severity_level
89
+ @severity_level ||= ConfigLoader.resolve_severity(
90
+ env_value: ENV[SEVERITY_ENV] || legacy_severity_env,
91
+ pronto_config: pronto_config,
92
+ valid_levels: VALID_LEVELS,
93
+ default: DEFAULT_LEVEL
94
+ )
95
+ end
96
+
97
+ def legacy_severity_env
98
+ value = ENV.fetch(LEGACY_ENV, nil)
99
+ return nil if value.nil? || value.to_s.strip.empty?
100
+
101
+ warn("pronto-rubycritic: #{LEGACY_ENV} is deprecated; use #{SEVERITY_ENV}.")
102
+ value
103
+ end
104
+
105
+ def report_error(error)
106
+ warn("pronto-rubycritic: #{error.class}: #{error.message} " \
107
+ '(no messages reported for this run)')
108
+ return unless truthy_env?(DEBUG_ENV)
109
+
110
+ warn(Array(error.backtrace).first(10).join("\n"))
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ $LOAD_PATH.push File.expand_path('lib', __dir__)
4
+ require 'pronto/rubycritic/version'
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = 'pronto-rubycritic'
8
+ s.version = Pronto::RubyCriticVersion::VERSION
9
+ s.platform = Gem::Platform::RUBY
10
+ s.authors = ['Rishabh Singh']
11
+ s.email = ['rishabhs343@gmail.com']
12
+ s.summary = 'Pronto runner for RubyCritic code quality reports.'
13
+ s.description = <<~DESC
14
+ pronto-rubycritic integrates RubyCritic into the Pronto workflow, reporting
15
+ reek / flay / flog / complexity / churn smells only on lines added or
16
+ modified in a pull request. Supports configurable per-analyser filters and
17
+ GitHub/GitLab output formatting.
18
+ DESC
19
+ s.homepage = 'https://github.com/Rishabhs343/custom-pronto-gem'
20
+ s.licenses = ['MIT']
21
+
22
+ s.required_ruby_version = '>= 3.2.2'
23
+ s.required_rubygems_version = '>= 3.2.3'
24
+
25
+ s.metadata = {
26
+ 'homepage_uri' => s.homepage,
27
+ 'source_code_uri' => "#{s.homepage}/tree/main",
28
+ 'bug_tracker_uri' => "#{s.homepage}/issues",
29
+ 'changelog_uri' => "#{s.homepage}/blob/main/CHANGELOG.md",
30
+ 'documentation_uri' => "#{s.homepage}#readme",
31
+ 'rubygems_mfa_required' => 'true'
32
+ }
33
+
34
+ s.files = Dir.chdir(__dir__) do
35
+ Dir.glob(%w[
36
+ lib/**/*.rb
37
+ CHANGELOG.md
38
+ CONTRIBUTING.md
39
+ LICENSE
40
+ README.md
41
+ pronto-rubycritic.gemspec
42
+ .rubycritic-pronto.yml
43
+ ])
44
+ end
45
+ s.require_paths = ['lib']
46
+ s.extra_rdoc_files = %w[LICENSE README.md CHANGELOG.md]
47
+
48
+ # base64 was removed from Ruby's default gems in 3.4. Pronto's transitive
49
+ # dependency chain (via gitlab 4.20.x → base64) still requires it, so users
50
+ # on 3.4+ need this gem or they hit LoadError when requiring 'pronto'.
51
+ s.add_dependency 'base64', '~> 0.2'
52
+ s.add_dependency 'pronto', '>= 0.11', '< 2.0'
53
+ s.add_dependency 'rubycritic', '>= 4.9', '< 6.0'
54
+ end
metadata ADDED
@@ -0,0 +1,123 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pronto-rubycritic
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.12.0
5
+ platform: ruby
6
+ authors:
7
+ - Rishabh Singh
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-06-20 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: base64
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: pronto
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0.11'
34
+ - - "<"
35
+ - !ruby/object:Gem::Version
36
+ version: '2.0'
37
+ type: :runtime
38
+ prerelease: false
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: '0.11'
44
+ - - "<"
45
+ - !ruby/object:Gem::Version
46
+ version: '2.0'
47
+ - !ruby/object:Gem::Dependency
48
+ name: rubycritic
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '4.9'
54
+ - - "<"
55
+ - !ruby/object:Gem::Version
56
+ version: '6.0'
57
+ type: :runtime
58
+ prerelease: false
59
+ version_requirements: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: '4.9'
64
+ - - "<"
65
+ - !ruby/object:Gem::Version
66
+ version: '6.0'
67
+ description: |
68
+ pronto-rubycritic integrates RubyCritic into the Pronto workflow, reporting
69
+ reek / flay / flog / complexity / churn smells only on lines added or
70
+ modified in a pull request. Supports configurable per-analyser filters and
71
+ GitHub/GitLab output formatting.
72
+ email:
73
+ - rishabhs343@gmail.com
74
+ executables: []
75
+ extensions: []
76
+ extra_rdoc_files:
77
+ - LICENSE
78
+ - README.md
79
+ - CHANGELOG.md
80
+ files:
81
+ - ".rubycritic-pronto.yml"
82
+ - CHANGELOG.md
83
+ - CONTRIBUTING.md
84
+ - LICENSE
85
+ - README.md
86
+ - lib/pronto/rubycritic.rb
87
+ - lib/pronto/rubycritic/analyser.rb
88
+ - lib/pronto/rubycritic/config_loader.rb
89
+ - lib/pronto/rubycritic/formatter.rb
90
+ - lib/pronto/rubycritic/message_builder.rb
91
+ - lib/pronto/rubycritic/smell_filter.rb
92
+ - lib/pronto/rubycritic/version.rb
93
+ - pronto-rubycritic.gemspec
94
+ homepage: https://github.com/Rishabhs343/custom-pronto-gem
95
+ licenses:
96
+ - MIT
97
+ metadata:
98
+ homepage_uri: https://github.com/Rishabhs343/custom-pronto-gem
99
+ source_code_uri: https://github.com/Rishabhs343/custom-pronto-gem/tree/main
100
+ bug_tracker_uri: https://github.com/Rishabhs343/custom-pronto-gem/issues
101
+ changelog_uri: https://github.com/Rishabhs343/custom-pronto-gem/blob/main/CHANGELOG.md
102
+ documentation_uri: https://github.com/Rishabhs343/custom-pronto-gem#readme
103
+ rubygems_mfa_required: 'true'
104
+ post_install_message:
105
+ rdoc_options: []
106
+ require_paths:
107
+ - lib
108
+ required_ruby_version: !ruby/object:Gem::Requirement
109
+ requirements:
110
+ - - ">="
111
+ - !ruby/object:Gem::Version
112
+ version: 3.2.2
113
+ required_rubygems_version: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: 3.2.3
118
+ requirements: []
119
+ rubygems_version: 3.0.3.1
120
+ signing_key:
121
+ specification_version: 4
122
+ summary: Pronto runner for RubyCritic code quality reports.
123
+ test_files: []