rubocop-metz 0.5.3
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/LICENSE +21 -0
- data/config/default.yml +76 -0
- data/lib/metz/cop_metadata.rb +50 -0
- data/lib/metz/erb_ruby_extractor.rb +107 -0
- data/lib/metz/file_classifier.rb +46 -0
- data/lib/metz/haml_ruby_extractor.rb +33 -0
- data/lib/metz/slim_ruby_extractor.rb +34 -0
- data/lib/metz/template_ruby_extractor.rb +153 -0
- data/lib/rubocop/cop/metz/classes_too_long.rb +59 -0
- data/lib/rubocop/cop/metz/controllers_too_many_direct_collaborators.rb +235 -0
- data/lib/rubocop/cop/metz/demeter_train_wreck/type_inference.rb +234 -0
- data/lib/rubocop/cop/metz/demeter_train_wreck.rb +144 -0
- data/lib/rubocop/cop/metz/methods_too_long.rb +62 -0
- data/lib/rubocop/cop/metz/methods_too_many_parameters.rb +62 -0
- data/lib/rubocop/cop/metz/on_send_csend_bridge.rb +23 -0
- data/lib/rubocop/cop/metz/views_deep_navigation.rb +57 -0
- data/lib/rubocop/cop/metz_cops.rb +12 -0
- data/lib/rubocop/formatter/metz_json_formatter.rb +50 -0
- data/lib/rubocop/metz/plugin.rb +36 -0
- data/lib/rubocop/metz/version.rb +7 -0
- data/lib/rubocop-metz.rb +19 -0
- data/rubocop-metz.gemspec +42 -0
- metadata +111 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rubocop"
|
|
4
|
+
require_relative "../../../metz/cop_metadata"
|
|
5
|
+
|
|
6
|
+
module RuboCop
|
|
7
|
+
module Cop
|
|
8
|
+
module Metz
|
|
9
|
+
# Flags methods whose parameter list exceeds the configured `Max`.
|
|
10
|
+
# Counts positional, optional, rest, keyword, and keyword-rest
|
|
11
|
+
# parameters using the same rule as core's `Metrics/ParameterLists`,
|
|
12
|
+
# with a stricter Metz default of 4.
|
|
13
|
+
class MethodsTooManyParameters < RuboCop::Cop::Base
|
|
14
|
+
extend RuboCop::ExcludeLimit
|
|
15
|
+
extend ::Metz::CopMetadata
|
|
16
|
+
|
|
17
|
+
why_it_matters "Long parameter lists hide coupling and make callers responsible for too many decisions."
|
|
18
|
+
fix_safety :manual
|
|
19
|
+
suggested_next_moves [
|
|
20
|
+
"Group related parameters into a parameter object or value object.",
|
|
21
|
+
"Replace flag arguments with separate, well-named methods.",
|
|
22
|
+
"Inject collaborators through the constructor so action methods stay narrow."
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
MSG = "Avoid parameter lists longer than %<max>d parameters. [%<count>d/%<max>d]"
|
|
26
|
+
|
|
27
|
+
exclude_limit "Max"
|
|
28
|
+
|
|
29
|
+
def on_args(node)
|
|
30
|
+
return unless method_definition_args?(node)
|
|
31
|
+
|
|
32
|
+
register_offense(node, args_count(node))
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def register_offense(node, count)
|
|
38
|
+
return unless count > max_params
|
|
39
|
+
|
|
40
|
+
add_offense(node, message: format(MSG, max: max_params, count: count)) do
|
|
41
|
+
self.max = count
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def method_definition_args?(node)
|
|
46
|
+
parent = node.parent
|
|
47
|
+
return false unless parent
|
|
48
|
+
|
|
49
|
+
parent.def_type? || parent.defs_type?
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def args_count(node)
|
|
53
|
+
node.children.count { |arg| !arg.blockarg_type? }
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def max_params
|
|
57
|
+
cop_config["Max"]
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
module Metz
|
|
6
|
+
# Gives a cop identical `send` and `csend` dispatch when it defines
|
|
7
|
+
# `on_send`, preserving the project-wide safe-navigation invariant.
|
|
8
|
+
module OnSendCsendBridge
|
|
9
|
+
def self.included(base)
|
|
10
|
+
base.extend(ClassMethods)
|
|
11
|
+
base.alias_method(:on_csend, :on_send) if base.method_defined?(:on_send)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
module ClassMethods
|
|
15
|
+
def method_added(name)
|
|
16
|
+
super
|
|
17
|
+
alias_method(:on_csend, :on_send) if name == :on_send
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "demeter_train_wreck"
|
|
4
|
+
require_relative "../../../metz/file_classifier"
|
|
5
|
+
|
|
6
|
+
module RuboCop
|
|
7
|
+
module Cop
|
|
8
|
+
module Metz
|
|
9
|
+
# Flags Rails view templates whose ERB / HAML / SLIM expressions
|
|
10
|
+
# traverse the object graph deeper than `MaxChainLength`. Reuses
|
|
11
|
+
# `Metz/DemeterTrainWreck`'s chain walker and value-object skip logic,
|
|
12
|
+
# so a chain like `name.upcase.strip.split(' ').first` (every link a
|
|
13
|
+
# recognised value-object call) stays silent. Path-classified through
|
|
14
|
+
# `Metz::FileClassifier.view?`: a deep chain inside an `.erb` file
|
|
15
|
+
# outside `app/views/` will not fire.
|
|
16
|
+
# DDR: docs/ddrs/2026-06-24-views-deep-navigation-inherits-demeter.md
|
|
17
|
+
# explains why this specialization uses inheritance instead of composition.
|
|
18
|
+
class ViewsDeepNavigation < DemeterTrainWreck
|
|
19
|
+
include OnSendCsendBridge
|
|
20
|
+
|
|
21
|
+
MSG = "View object-graph traversal of %<count>d exceeds MaxChainLength (%<max>d). " \
|
|
22
|
+
"Push the lookup into the controller or expose it via a presenter."
|
|
23
|
+
|
|
24
|
+
why_it_matters "Deep object-graph chains in views couple templates to the internal " \
|
|
25
|
+
"structure of every collaborator they touch, making refactors and " \
|
|
26
|
+
"test setup painful."
|
|
27
|
+
fix_safety :manual
|
|
28
|
+
suggested_next_moves [
|
|
29
|
+
"Compute the value in the controller and expose it via an instance variable.",
|
|
30
|
+
"Wrap the chain in a presenter or view model that exposes one explicit method.",
|
|
31
|
+
"Use Rails `delegate :foo, to: :collaborator` to flatten the chain at the model boundary."
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
# RuboCop's dispatcher needs the subclass to own the callback, while the
|
|
35
|
+
# inherited implementation remains the shared chain analyzer.
|
|
36
|
+
# rubocop:disable Lint/UselessMethodDefinition -- required for subclass callback registration.
|
|
37
|
+
def on_send(node)
|
|
38
|
+
super
|
|
39
|
+
end
|
|
40
|
+
# rubocop:enable Lint/UselessMethodDefinition
|
|
41
|
+
|
|
42
|
+
def relevant_file?(file)
|
|
43
|
+
return super if file.nil? || file.empty? || file == "(string)"
|
|
44
|
+
|
|
45
|
+
!file_name_matches_any?(file, "Exclude", false) &&
|
|
46
|
+
::Metz::FileClassifier.view?(file)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def max
|
|
52
|
+
cop_config["MaxChainLength"] || 3
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Central require point for every Metz cop. Keep one
|
|
4
|
+
# `require_relative "metz/<cop_name>"` line per cop so registry wiring stays in
|
|
5
|
+
# a single discoverable location.
|
|
6
|
+
|
|
7
|
+
require_relative "metz/classes_too_long"
|
|
8
|
+
require_relative "metz/methods_too_long"
|
|
9
|
+
require_relative "metz/methods_too_many_parameters"
|
|
10
|
+
require_relative "metz/demeter_train_wreck"
|
|
11
|
+
require_relative "metz/controllers_too_many_direct_collaborators"
|
|
12
|
+
require_relative "metz/views_deep_navigation"
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rubocop"
|
|
4
|
+
|
|
5
|
+
module RuboCop
|
|
6
|
+
module Formatter
|
|
7
|
+
# Wraps RuboCop's standard JSON formatter and enriches every offense whose
|
|
8
|
+
# `cop_name` starts with `Metz/` with the metadata DSL fields published by
|
|
9
|
+
# `Metz::CopMetadata`. Non-Metz offenses are passed through unchanged so the
|
|
10
|
+
# top-level JSON shape and any consumer expecting standard RuboCop output
|
|
11
|
+
# keeps working.
|
|
12
|
+
class MetzJsonFormatter < JSONFormatter
|
|
13
|
+
METZ_PREFIX = "Metz/"
|
|
14
|
+
|
|
15
|
+
EMPTY_METADATA = {
|
|
16
|
+
why_it_matters: nil,
|
|
17
|
+
fix_safety: nil,
|
|
18
|
+
suggested_next_moves: []
|
|
19
|
+
}.freeze
|
|
20
|
+
|
|
21
|
+
def hash_for_offense(offense)
|
|
22
|
+
hash = super
|
|
23
|
+
return hash unless offense.cop_name.to_s.start_with?(METZ_PREFIX)
|
|
24
|
+
|
|
25
|
+
hash.merge!(metz_metadata_for(offense.cop_name))
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def metz_metadata_for(cop_name)
|
|
31
|
+
cop_class = RuboCop::Cop::Registry.global.find_by_cop_name(cop_name)
|
|
32
|
+
return EMPTY_METADATA.dup unless cop_class.respond_to?(:metz_metadata)
|
|
33
|
+
|
|
34
|
+
format_metadata(cop_class.metz_metadata)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def format_metadata(meta)
|
|
38
|
+
{
|
|
39
|
+
why_it_matters: stringify_or_nil(meta[:why_it_matters]),
|
|
40
|
+
fix_safety: stringify_or_nil(meta[:fix_safety]),
|
|
41
|
+
suggested_next_moves: Array(meta[:suggested_next_moves]).map(&:to_s)
|
|
42
|
+
}
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def stringify_or_nil(value)
|
|
46
|
+
value&.to_s
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "lint_roller"
|
|
4
|
+
require "pathname"
|
|
5
|
+
|
|
6
|
+
require_relative "version"
|
|
7
|
+
|
|
8
|
+
module RuboCop
|
|
9
|
+
module Metz
|
|
10
|
+
class Plugin < LintRoller::Plugin
|
|
11
|
+
ABOUT = LintRoller::About.new(
|
|
12
|
+
name: "rubocop-metz",
|
|
13
|
+
version: RuboCop::Metz::VERSION,
|
|
14
|
+
homepage: "https://github.com/users/fuentesjr/packages/rubygems/package/rubocop-metz",
|
|
15
|
+
description: "Sandi-Metz-inspired RuboCop cops."
|
|
16
|
+
).freeze
|
|
17
|
+
private_constant :ABOUT
|
|
18
|
+
|
|
19
|
+
def about
|
|
20
|
+
ABOUT
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def supported?(context)
|
|
24
|
+
context.engine == :rubocop
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def rules(_context)
|
|
28
|
+
LintRoller::Rules.new(
|
|
29
|
+
type: :path,
|
|
30
|
+
config_format: :rubocop,
|
|
31
|
+
value: Pathname.new(__dir__).join("../../../config/default.yml").expand_path
|
|
32
|
+
)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
data/lib/rubocop-metz.rb
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rubocop"
|
|
4
|
+
require "rubocop-ast"
|
|
5
|
+
|
|
6
|
+
require_relative "rubocop/metz/version"
|
|
7
|
+
require_relative "rubocop/metz/plugin"
|
|
8
|
+
require_relative "metz/cop_metadata"
|
|
9
|
+
require_relative "metz/file_classifier"
|
|
10
|
+
require_relative "metz/erb_ruby_extractor"
|
|
11
|
+
require_relative "metz/haml_ruby_extractor"
|
|
12
|
+
require_relative "metz/slim_ruby_extractor"
|
|
13
|
+
require_relative "rubocop/cop/metz/on_send_csend_bridge"
|
|
14
|
+
require_relative "rubocop/cop/metz_cops"
|
|
15
|
+
require_relative "rubocop/formatter/metz_json_formatter"
|
|
16
|
+
|
|
17
|
+
Metz::ErbRubyExtractor.install!
|
|
18
|
+
Metz::HamlRubyExtractor.install!
|
|
19
|
+
Metz::SlimRubyExtractor.install!
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "lib/rubocop/metz/version"
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = "rubocop-metz"
|
|
7
|
+
spec.version = RuboCop::Metz::VERSION
|
|
8
|
+
spec.authors = ["Salvador Fuentes Jr"]
|
|
9
|
+
spec.email = ["fuentesjr@duck.com"]
|
|
10
|
+
|
|
11
|
+
spec.summary = "Sandi-Metz-inspired RuboCop cops."
|
|
12
|
+
spec.description = "A RuboCop plugin that ships custom cops capturing " \
|
|
13
|
+
"Sandi-Metz-style design heuristics for Ruby and Rails code."
|
|
14
|
+
spec.homepage = "https://github.com/fuentesjr/metz-scan"
|
|
15
|
+
spec.license = "MIT"
|
|
16
|
+
|
|
17
|
+
spec.required_ruby_version = ">= 3.3"
|
|
18
|
+
|
|
19
|
+
spec.metadata["source_code_uri"] = spec.homepage
|
|
20
|
+
spec.metadata["changelog_uri"] = "#{spec.homepage}/releases"
|
|
21
|
+
spec.metadata["bug_tracker_uri"] = "#{spec.homepage}/issues"
|
|
22
|
+
spec.metadata["github_repo"] = "ssh://github.com/fuentesjr/metz-scan"
|
|
23
|
+
spec.metadata["github_package_uri"] = "https://github.com/users/fuentesjr/packages/rubygems/package/rubocop-metz"
|
|
24
|
+
spec.metadata["default_lint_roller_plugin"] = "RuboCop::Metz::Plugin"
|
|
25
|
+
|
|
26
|
+
spec.files = Dir.glob("{lib,config}/**/*", File::FNM_DOTMATCH).reject { |f| File.directory?(f) } +
|
|
27
|
+
["LICENSE", "rubocop-metz.gemspec"].select { |f| File.exist?(f) }
|
|
28
|
+
# This gemspec's Dir.glob is relative to the build CWD, so building from the
|
|
29
|
+
# repo root (`gem build rubocop-metz/rubocop-metz.gemspec`) would silently
|
|
30
|
+
# package the wrapper's lib/metz_scan files instead of these cops. Fail loudly
|
|
31
|
+
# instead of shipping a corrupt gem. Build from this directory:
|
|
32
|
+
# cd rubocop-metz && gem build rubocop-metz.gemspec
|
|
33
|
+
unless spec.files.include?("lib/rubocop-metz.rb")
|
|
34
|
+
raise "rubocop-metz.gemspec: lib/rubocop-metz.rb missing from packaged files; " \
|
|
35
|
+
"build from rubocop-metz/ (cd rubocop-metz && gem build rubocop-metz.gemspec)."
|
|
36
|
+
end
|
|
37
|
+
spec.require_paths = ["lib"]
|
|
38
|
+
|
|
39
|
+
spec.add_dependency "lint_roller", "~> 1.1"
|
|
40
|
+
spec.add_dependency "rubocop", "~> 1.80"
|
|
41
|
+
spec.add_dependency "rubocop-ast", "~> 1.49"
|
|
42
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: rubocop-metz
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.5.3
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Salvador Fuentes Jr
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: lint_roller
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '1.1'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '1.1'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: rubocop
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '1.80'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '1.80'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: rubocop-ast
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '1.49'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '1.49'
|
|
54
|
+
description: A RuboCop plugin that ships custom cops capturing Sandi-Metz-style design
|
|
55
|
+
heuristics for Ruby and Rails code.
|
|
56
|
+
email:
|
|
57
|
+
- fuentesjr@duck.com
|
|
58
|
+
executables: []
|
|
59
|
+
extensions: []
|
|
60
|
+
extra_rdoc_files: []
|
|
61
|
+
files:
|
|
62
|
+
- LICENSE
|
|
63
|
+
- config/default.yml
|
|
64
|
+
- lib/metz/cop_metadata.rb
|
|
65
|
+
- lib/metz/erb_ruby_extractor.rb
|
|
66
|
+
- lib/metz/file_classifier.rb
|
|
67
|
+
- lib/metz/haml_ruby_extractor.rb
|
|
68
|
+
- lib/metz/slim_ruby_extractor.rb
|
|
69
|
+
- lib/metz/template_ruby_extractor.rb
|
|
70
|
+
- lib/rubocop-metz.rb
|
|
71
|
+
- lib/rubocop/cop/metz/classes_too_long.rb
|
|
72
|
+
- lib/rubocop/cop/metz/controllers_too_many_direct_collaborators.rb
|
|
73
|
+
- lib/rubocop/cop/metz/demeter_train_wreck.rb
|
|
74
|
+
- lib/rubocop/cop/metz/demeter_train_wreck/type_inference.rb
|
|
75
|
+
- lib/rubocop/cop/metz/methods_too_long.rb
|
|
76
|
+
- lib/rubocop/cop/metz/methods_too_many_parameters.rb
|
|
77
|
+
- lib/rubocop/cop/metz/on_send_csend_bridge.rb
|
|
78
|
+
- lib/rubocop/cop/metz/views_deep_navigation.rb
|
|
79
|
+
- lib/rubocop/cop/metz_cops.rb
|
|
80
|
+
- lib/rubocop/formatter/metz_json_formatter.rb
|
|
81
|
+
- lib/rubocop/metz/plugin.rb
|
|
82
|
+
- lib/rubocop/metz/version.rb
|
|
83
|
+
- rubocop-metz.gemspec
|
|
84
|
+
homepage: https://github.com/fuentesjr/metz-scan
|
|
85
|
+
licenses:
|
|
86
|
+
- MIT
|
|
87
|
+
metadata:
|
|
88
|
+
source_code_uri: https://github.com/fuentesjr/metz-scan
|
|
89
|
+
changelog_uri: https://github.com/fuentesjr/metz-scan/releases
|
|
90
|
+
bug_tracker_uri: https://github.com/fuentesjr/metz-scan/issues
|
|
91
|
+
github_repo: ssh://github.com/fuentesjr/metz-scan
|
|
92
|
+
github_package_uri: https://github.com/users/fuentesjr/packages/rubygems/package/rubocop-metz
|
|
93
|
+
default_lint_roller_plugin: RuboCop::Metz::Plugin
|
|
94
|
+
rdoc_options: []
|
|
95
|
+
require_paths:
|
|
96
|
+
- lib
|
|
97
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
98
|
+
requirements:
|
|
99
|
+
- - ">="
|
|
100
|
+
- !ruby/object:Gem::Version
|
|
101
|
+
version: '3.3'
|
|
102
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
103
|
+
requirements:
|
|
104
|
+
- - ">="
|
|
105
|
+
- !ruby/object:Gem::Version
|
|
106
|
+
version: '0'
|
|
107
|
+
requirements: []
|
|
108
|
+
rubygems_version: 4.0.8
|
|
109
|
+
specification_version: 4
|
|
110
|
+
summary: Sandi-Metz-inspired RuboCop cops.
|
|
111
|
+
test_files: []
|