Vendor rubocop-sorbet
.
This commit is contained in:
parent
90b92301ae
commit
f27d7a21d7
1
.gitignore
vendored
1
.gitignore
vendored
@ -35,6 +35,7 @@
|
||||
!**/vendor/bundle/ruby/*/gems/*/lib
|
||||
!**/vendor/bundle/ruby/*/gems/rubocop-performance-*/config
|
||||
!**/vendor/bundle/ruby/*/gems/rubocop-rspec-*/config
|
||||
!**/vendor/bundle/ruby/*/gems/rubocop-sorbet-*/config
|
||||
|
||||
# Ignore partially included gems where we don't need all files
|
||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support.rb
|
||||
|
@ -25,4 +25,5 @@ gem "patchelf"
|
||||
gem "plist"
|
||||
gem "rubocop-performance"
|
||||
gem "rubocop-rspec"
|
||||
gem "rubocop-sorbet"
|
||||
gem "ruby-macho"
|
||||
|
@ -114,6 +114,8 @@ GEM
|
||||
rubocop-ast (>= 0.4.0)
|
||||
rubocop-rspec (1.43.2)
|
||||
rubocop (~> 0.87)
|
||||
rubocop-sorbet (0.5.1)
|
||||
rubocop
|
||||
ruby-macho (2.2.0)
|
||||
ruby-progressbar (1.10.1)
|
||||
simplecov (0.19.0)
|
||||
@ -167,6 +169,7 @@ DEPENDENCIES
|
||||
rubocop
|
||||
rubocop-performance
|
||||
rubocop-rspec
|
||||
rubocop-sorbet
|
||||
ruby-macho
|
||||
simplecov
|
||||
sorbet
|
||||
|
@ -75,6 +75,7 @@ $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/unicode-display_width
|
||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rubocop-0.92.0/lib"
|
||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rubocop-performance-1.8.1/lib"
|
||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rubocop-rspec-1.43.2/lib"
|
||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rubocop-sorbet-0.5.1/lib"
|
||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/ruby-macho-2.2.0/lib"
|
||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/sorbet-static-0.5.5942-universal-darwin-19/lib"
|
||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/sorbet-0.5.5942/lib"
|
||||
|
128
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rubocop-sorbet-0.5.1/config/default.yml
vendored
Normal file
128
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rubocop-sorbet-0.5.1/config/default.yml
vendored
Normal file
@ -0,0 +1,128 @@
|
||||
inherit_mode:
|
||||
merge:
|
||||
- Exclude
|
||||
|
||||
Sorbet/AllowIncompatibleOverride:
|
||||
Description: 'Disallows using `.override(allow_incompatible: true)`.'
|
||||
Enabled: true
|
||||
VersionAdded: 0.2.0
|
||||
|
||||
Sorbet/BindingConstantWithoutTypeAlias:
|
||||
Description: >-
|
||||
Disallows binding the return value of `T.any`, `T.all`, `T.enum`
|
||||
to a constant directly. To bind the value, one must use `T.type_alias`.
|
||||
Enabled: true
|
||||
VersionAdded: 0.2.0
|
||||
|
||||
Sorbet/CheckedTrueInSignature:
|
||||
Description: 'Disallows the usage of `checked(true)` in signatures.'
|
||||
Enabled: true
|
||||
VersionAdded: 0.2.0
|
||||
|
||||
Sorbet/ConstantsFromStrings:
|
||||
Description: >-
|
||||
Forbids constant access through meta-programming.
|
||||
|
||||
For example, things like `constantize` or `const_get`
|
||||
are forbidden.
|
||||
Enabled: true
|
||||
VersionAdded: 0.2.0
|
||||
|
||||
Sorbet/EnforceSigilOrder:
|
||||
Description: 'Ensures that Sorbet sigil comes first in a file.'
|
||||
Enabled: true
|
||||
VersionAdded: 0.3.4
|
||||
|
||||
Sorbet/EnforceSignatures:
|
||||
Description: 'Ensures all methods have a valid signature.'
|
||||
Enabled: false
|
||||
VersionAdded: 0.3.4
|
||||
|
||||
Sorbet/FalseSigil:
|
||||
Description: 'All files must be at least at strictness `false`.'
|
||||
Enabled: true
|
||||
VersionAdded: 0.3.3
|
||||
SuggestedStrictness: true
|
||||
Include:
|
||||
- "**/*.rb"
|
||||
- "**/*.rbi"
|
||||
- "**/*.rake"
|
||||
- "**/*.ru"
|
||||
Exclude:
|
||||
- bin/**/*
|
||||
- db/**/*.rb
|
||||
- script/**/*
|
||||
|
||||
Sorbet/ForbidIncludeConstLiteral:
|
||||
Description: 'Forbids include of non-literal constants.'
|
||||
Enabled: false
|
||||
VersionAdded: 0.2.0
|
||||
VersionChanged: 0.5.0
|
||||
|
||||
Sorbet/ForbidSuperclassConstLiteral:
|
||||
Description: 'Forbid superclasses which are non-literal constants.'
|
||||
Enabled: false
|
||||
VersionAdded: 0.2.0
|
||||
VersionChanged: 0.5.0
|
||||
|
||||
Sorbet/ForbidUntypedStructProps:
|
||||
Description: >-
|
||||
Disallows use of `T.untyped` or `T.nilable(T.untyped)` as a
|
||||
prop type for `T::Struct` subclasses.
|
||||
Enabled: true
|
||||
VersionAdded: 0.4.0
|
||||
|
||||
Sorbet/HasSigil:
|
||||
Description: 'Makes the Sorbet typed sigil mandatory in all files.'
|
||||
Enabled: false
|
||||
VersionAdded: 0.3.3
|
||||
|
||||
Sorbet/IgnoreSigil:
|
||||
Description: 'All files must be at least at strictness `ignore`.'
|
||||
Enabled: false
|
||||
VersionAdded: 0.3.3
|
||||
|
||||
Sorbet/KeywordArgumentOrdering:
|
||||
Description: >-
|
||||
Enforces a compatible keyword arguments with Sorbet.
|
||||
|
||||
All keyword arguments must be at the end of the parameters
|
||||
list, and all keyword arguments with a default value must be
|
||||
after those without default values.
|
||||
Enabled: true
|
||||
VersionAdded: 0.2.0
|
||||
|
||||
Sorbet/ParametersOrderingInSignature:
|
||||
Description: 'Enforces same parameter order between a method and its signature.'
|
||||
Enabled: true
|
||||
VersionAdded: 0.2.0
|
||||
|
||||
Sorbet/SignatureBuildOrder:
|
||||
Description: >-
|
||||
Enforces the order of parts in a signature.
|
||||
|
||||
The order is first inheritance related builders,
|
||||
then params, then return and finally the modifier
|
||||
such as: `abstract.params(...).returns(...).soft`.'
|
||||
Enabled: true
|
||||
VersionAdded: 0.3.0
|
||||
|
||||
Sorbet/StrictSigil:
|
||||
Description: 'All files must be at least at strictness `strict`.'
|
||||
Enabled: false
|
||||
VersionAdded: 0.3.3
|
||||
|
||||
Sorbet/StrongSigil:
|
||||
Description: 'All files must be at least at strictness `strong`.'
|
||||
Enabled: false
|
||||
VersionAdded: 0.3.3
|
||||
|
||||
Sorbet/TrueSigil:
|
||||
Description: 'All files must be at least at strictness `true`.'
|
||||
Enabled: false
|
||||
VersionAdded: 0.3.3
|
||||
|
||||
Sorbet/ValidSigil:
|
||||
Description: 'All files must have a valid sigil.'
|
||||
Enabled: true
|
||||
VersionAdded: 0.3.3
|
11
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rubocop-sorbet-0.5.1/lib/rubocop-sorbet.rb
vendored
Normal file
11
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rubocop-sorbet-0.5.1/lib/rubocop-sorbet.rb
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rubocop'
|
||||
|
||||
require_relative 'rubocop/sorbet'
|
||||
require_relative 'rubocop/sorbet/version'
|
||||
require_relative 'rubocop/sorbet/inject'
|
||||
|
||||
RuboCop::Sorbet::Inject.defaults!
|
||||
|
||||
require_relative 'rubocop/cop/sorbet_cops'
|
@ -0,0 +1,121 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rubocop'
|
||||
|
||||
module RuboCop
|
||||
module Cop
|
||||
module Sorbet
|
||||
# This cop disallows binding the return value of `T.any`, `T.all`, `T.enum`
|
||||
# to a constant directly. To bind the value, one must use `T.type_alias`.
|
||||
#
|
||||
# @example
|
||||
#
|
||||
# # bad
|
||||
# FooOrBar = T.any(Foo, Bar)
|
||||
#
|
||||
# # good
|
||||
# FooOrBar = T.type_alias { T.any(Foo, Bar) }
|
||||
class BindingConstantWithoutTypeAlias < RuboCop::Cop::Cop
|
||||
def_node_matcher(:binding_unaliased_type?, <<-PATTERN)
|
||||
(casgn _ _ [#not_nil? #not_t_let? #not_dynamic_type_creation_with_block? #not_generic_parameter_decl? #method_needing_aliasing_on_t?])
|
||||
PATTERN
|
||||
|
||||
def_node_matcher(:using_type_alias?, <<-PATTERN)
|
||||
(block
|
||||
(send
|
||||
(const nil? :T) :type_alias)
|
||||
_
|
||||
_
|
||||
)
|
||||
PATTERN
|
||||
|
||||
def_node_matcher(:using_deprecated_type_alias_syntax?, <<-PATTERN)
|
||||
(
|
||||
send
|
||||
(const nil? :T)
|
||||
:type_alias
|
||||
_
|
||||
)
|
||||
PATTERN
|
||||
|
||||
def_node_matcher(:t_let?, <<-PATTERN)
|
||||
(
|
||||
send
|
||||
(const nil? :T)
|
||||
:let
|
||||
_
|
||||
_
|
||||
)
|
||||
PATTERN
|
||||
|
||||
def_node_matcher(:dynamic_type_creation_with_block?, <<-PATTERN)
|
||||
(block
|
||||
(send
|
||||
const :new ...)
|
||||
_
|
||||
_
|
||||
)
|
||||
PATTERN
|
||||
|
||||
def_node_matcher(:generic_parameter_decl?, <<-PATTERN)
|
||||
(
|
||||
send nil? {:type_template :type_member} ...
|
||||
)
|
||||
PATTERN
|
||||
|
||||
def_node_search(:method_needing_aliasing_on_t?, <<-PATTERN)
|
||||
(
|
||||
send
|
||||
(const nil? :T)
|
||||
{:any :all :noreturn :class_of :untyped :nilable :self_type :enum :proc}
|
||||
...
|
||||
)
|
||||
PATTERN
|
||||
|
||||
def not_t_let?(node)
|
||||
!t_let?(node)
|
||||
end
|
||||
|
||||
def not_dynamic_type_creation_with_block?(node)
|
||||
!dynamic_type_creation_with_block?(node)
|
||||
end
|
||||
|
||||
def not_generic_parameter_decl?(node)
|
||||
!generic_parameter_decl?(node)
|
||||
end
|
||||
|
||||
def not_nil?(node)
|
||||
!node.nil?
|
||||
end
|
||||
|
||||
def on_casgn(node)
|
||||
return unless binding_unaliased_type?(node) && !using_type_alias?(node.children[2])
|
||||
if using_deprecated_type_alias_syntax?(node.children[2])
|
||||
add_offense(
|
||||
node.children[2],
|
||||
message: "It looks like you're using the old `T.type_alias` syntax. " \
|
||||
'`T.type_alias` now expects a block.' \
|
||||
'Run Sorbet with the options "--autocorrect --error-white-list=5043" ' \
|
||||
'to automatically upgrade to the new syntax.'
|
||||
)
|
||||
return
|
||||
end
|
||||
add_offense(
|
||||
node.children[2],
|
||||
message: "It looks like you're trying to bind a type to a constant. " \
|
||||
'To do this, you must alias the type using `T.type_alias`.'
|
||||
)
|
||||
end
|
||||
|
||||
def autocorrect(node)
|
||||
lambda do |corrector|
|
||||
corrector.replace(
|
||||
node.source_range,
|
||||
"T.type_alias { #{node.source} }"
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -0,0 +1,53 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rubocop'
|
||||
|
||||
module RuboCop
|
||||
module Cop
|
||||
module Sorbet
|
||||
# This cop disallows the calls that are used to get constants fom Strings
|
||||
# such as +constantize+, +const_get+, and +constants+.
|
||||
#
|
||||
# The goal of this cop is to make the code easier to statically analyze,
|
||||
# more IDE-friendly, and more predictable. It leads to code that clearly
|
||||
# expresses which values the constant can have.
|
||||
#
|
||||
# @example
|
||||
#
|
||||
# # bad
|
||||
# class_name.constantize
|
||||
#
|
||||
# # bad
|
||||
# constants.detect { |c| c.name == "User" }
|
||||
#
|
||||
# # bad
|
||||
# const_get(class_name)
|
||||
#
|
||||
# # good
|
||||
# case class_name
|
||||
# when "User"
|
||||
# User
|
||||
# else
|
||||
# raise ArgumentError
|
||||
# end
|
||||
#
|
||||
# # good
|
||||
# { "User" => User }.fetch(class_name)
|
||||
class ConstantsFromStrings < ::RuboCop::Cop::Cop
|
||||
def_node_matcher(:constant_from_string?, <<-PATTERN)
|
||||
(send _ {:constantize :constants :const_get} ...)
|
||||
PATTERN
|
||||
|
||||
def on_send(node)
|
||||
return unless constant_from_string?(node)
|
||||
add_offense(
|
||||
node,
|
||||
location: :selector,
|
||||
message: "Don't use `#{node.method_name}`, it makes the code harder to understand, less editor-friendly, " \
|
||||
"and impossible to analyze. Replace `#{node.method_name}` with a case/when or a hash."
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -0,0 +1,58 @@
|
||||
# encoding: utf-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rubocop'
|
||||
|
||||
# Correct `send` expressions in include statements by constant literals.
|
||||
#
|
||||
# Sorbet, the static checker, is not (yet) able to support constructs on the
|
||||
# following form:
|
||||
#
|
||||
# ```ruby
|
||||
# class MyClass
|
||||
# include send_expr
|
||||
# end
|
||||
# ```
|
||||
#
|
||||
# Multiple occurences of this can be found in Shopify's code base like:
|
||||
#
|
||||
# ```ruby
|
||||
# include Rails.application.routes.url_helpers
|
||||
# ```
|
||||
# or
|
||||
# ```ruby
|
||||
# include Polaris::Engine.helpers
|
||||
# ```
|
||||
module RuboCop
|
||||
module Cop
|
||||
module Sorbet
|
||||
class ForbidIncludeConstLiteral < RuboCop::Cop::Cop
|
||||
MSG = 'Includes must only contain constant literals'
|
||||
|
||||
attr_accessor :used_names
|
||||
|
||||
def_node_matcher :not_lit_const_include?, <<-PATTERN
|
||||
(send nil? {:include :extend :prepend}
|
||||
$_
|
||||
)
|
||||
PATTERN
|
||||
|
||||
def initialize(*)
|
||||
super
|
||||
self.used_names = Set.new
|
||||
end
|
||||
|
||||
def on_send(node)
|
||||
return unless not_lit_const_include?(node) do |send_argument|
|
||||
![:const, :self].include?(send_argument.type)
|
||||
end
|
||||
parent = node.parent
|
||||
return unless parent
|
||||
parent = parent.parent if [:begin, :block].include?(parent.type)
|
||||
return unless [:module, :class, :sclass].include?(parent.type)
|
||||
add_offense(node)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -0,0 +1,45 @@
|
||||
# encoding: utf-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rubocop'
|
||||
|
||||
# Correct superclass `send` expressions by constant literals.
|
||||
#
|
||||
# Sorbet, the static checker, is not (yet) able to support constructs on the
|
||||
# following form:
|
||||
#
|
||||
# ```ruby
|
||||
# class Foo < send_expr; end
|
||||
# ```
|
||||
#
|
||||
# Multiple occurences of this can be found in Shopify's code base like:
|
||||
#
|
||||
# ```ruby
|
||||
# class ShopScope < Component::TrustedIdScope[ShopIdentity::ShopId]
|
||||
# ```
|
||||
# or
|
||||
# ```ruby
|
||||
# class ApiClientEligibility < Struct.new(:api_client, :match_results, :shop)
|
||||
# ```
|
||||
module RuboCop
|
||||
module Cop
|
||||
module Sorbet
|
||||
class ForbidSuperclassConstLiteral < RuboCop::Cop::Cop
|
||||
MSG = 'Superclasses must only contain constant literals'
|
||||
|
||||
def_node_matcher :not_lit_const_superclass?, <<-PATTERN
|
||||
(class
|
||||
(const ...)
|
||||
(send ...)
|
||||
...
|
||||
)
|
||||
PATTERN
|
||||
|
||||
def on_class(node)
|
||||
return unless not_lit_const_superclass?(node)
|
||||
add_offense(node.child_nodes[1])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -0,0 +1,58 @@
|
||||
# encoding: utf-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rubocop'
|
||||
|
||||
module RuboCop
|
||||
module Cop
|
||||
module Sorbet
|
||||
# This cop disallows use of `T.untyped` or `T.nilable(T.untyped)`
|
||||
# as a prop type for `T::Struct`.
|
||||
#
|
||||
# @example
|
||||
#
|
||||
# # bad
|
||||
# class SomeClass
|
||||
# const :foo, T.untyped
|
||||
# prop :bar, T.nilable(T.untyped)
|
||||
# end
|
||||
#
|
||||
# # good
|
||||
# class SomeClass
|
||||
# const :foo, Integer
|
||||
# prop :bar, T.nilable(String)
|
||||
# end
|
||||
class ForbidUntypedStructProps < RuboCop::Cop::Cop
|
||||
MSG = 'Struct props cannot be T.untyped'
|
||||
|
||||
def_node_matcher :t_struct, <<~PATTERN
|
||||
(const (const nil? :T) :Struct)
|
||||
PATTERN
|
||||
|
||||
def_node_matcher :t_untyped, <<~PATTERN
|
||||
(send (const nil? :T) :untyped)
|
||||
PATTERN
|
||||
|
||||
def_node_matcher :t_nilable_untyped, <<~PATTERN
|
||||
(send (const nil? :T) :nilable {#t_untyped #t_nilable_untyped})
|
||||
PATTERN
|
||||
|
||||
def_node_matcher :subclass_of_t_struct?, <<~PATTERN
|
||||
(class (const ...) #t_struct ...)
|
||||
PATTERN
|
||||
|
||||
def_node_search :untyped_props, <<~PATTERN
|
||||
(send nil? {:prop :const} _ {#t_untyped #t_nilable_untyped} ...)
|
||||
PATTERN
|
||||
|
||||
def on_class(node)
|
||||
return unless subclass_of_t_struct?(node)
|
||||
|
||||
untyped_props(node).each do |untyped_prop|
|
||||
add_offense(untyped_prop.child_nodes[1])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -0,0 +1,115 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rubocop'
|
||||
|
||||
module RuboCop
|
||||
module Cop
|
||||
module Sorbet
|
||||
# This cop checks that the Sorbet sigil comes as the first magic comment in the file.
|
||||
#
|
||||
# The expected order for magic comments is: typed, (en)?coding, warn_indent then frozen_string_literal.
|
||||
#
|
||||
# For example, the following bad ordering:
|
||||
#
|
||||
# ```ruby
|
||||
# # frozen_string_literal: true
|
||||
# # typed: true
|
||||
# class Foo; end
|
||||
# ```
|
||||
#
|
||||
# Will be corrected as:
|
||||
#
|
||||
# ```ruby
|
||||
# # typed: true
|
||||
# # frozen_string_literal: true
|
||||
# class Foo; end
|
||||
# ```
|
||||
#
|
||||
# Only `typed`, `(en)?coding`, `warn_indent` and `frozen_string_literal` magic comments are considered,
|
||||
# other comments or magic comments are left in the same place.
|
||||
class EnforceSigilOrder < ValidSigil
|
||||
include RangeHelp
|
||||
|
||||
def investigate(processed_source)
|
||||
return if processed_source.tokens.empty?
|
||||
|
||||
tokens = extract_magic_comments(processed_source)
|
||||
return if tokens.empty?
|
||||
|
||||
check_magic_comments_order(tokens)
|
||||
end
|
||||
|
||||
def autocorrect(_node)
|
||||
lambda do |corrector|
|
||||
tokens = extract_magic_comments(processed_source)
|
||||
|
||||
# Get the magic comments tokens in their expected order
|
||||
expected = PREFERRED_ORDER.keys.map do |re|
|
||||
tokens.select { |token| re.match?(token.text) }
|
||||
end.flatten
|
||||
|
||||
tokens.each_with_index do |token, index|
|
||||
corrector.replace(token.pos, expected[index].text)
|
||||
end
|
||||
|
||||
# Remove blank lines between the magic comments
|
||||
lines = tokens.map(&:line).to_set
|
||||
(lines.min...lines.max).each do |line|
|
||||
next if lines.include?(line)
|
||||
next unless processed_source[line - 1].empty?
|
||||
corrector.remove(source_range(processed_source.buffer, line, 0))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
CODING_REGEX = /#\s+(en)?coding:(?:\s+([\w]+))?/
|
||||
INDENT_REGEX = /#\s+warn_indent:(?:\s+([\w]+))?/
|
||||
FROZEN_REGEX = /#\s+frozen_string_literal:(?:\s+([\w]+))?/
|
||||
|
||||
PREFERRED_ORDER = {
|
||||
CODING_REGEX => 'encoding',
|
||||
SIGIL_REGEX => 'typed',
|
||||
INDENT_REGEX => 'warn_indent',
|
||||
FROZEN_REGEX => 'frozen_string_literal',
|
||||
}.freeze
|
||||
|
||||
MAGIC_REGEX = Regexp.union(*PREFERRED_ORDER.keys)
|
||||
|
||||
# extraction
|
||||
|
||||
# Get all the tokens in `processed_source` that match `MAGIC_REGEX`
|
||||
def extract_magic_comments(processed_source)
|
||||
processed_source.tokens
|
||||
.take_while { |token| token.type == :tCOMMENT }
|
||||
.select { |token| MAGIC_REGEX.match?(token.text) }
|
||||
end
|
||||
|
||||
# checks
|
||||
|
||||
def check_magic_comments_order(tokens)
|
||||
# Get the current magic comments order
|
||||
order = tokens.map do |token|
|
||||
PREFERRED_ORDER.keys.find { |re| re.match?(token.text) }
|
||||
end.compact.uniq
|
||||
|
||||
# Get the expected magic comments order based on the one used in the actual source
|
||||
expected = PREFERRED_ORDER.keys.select do |re|
|
||||
tokens.any? { |token| re.match?(token.text) }
|
||||
end.uniq
|
||||
|
||||
if order != expected
|
||||
tokens.each do |token|
|
||||
add_offense(
|
||||
token,
|
||||
location: token.pos,
|
||||
message: "Magic comments should be in the following order: #{PREFERRED_ORDER.values.join(', ')}."
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -0,0 +1,17 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rubocop'
|
||||
require_relative 'has_sigil'
|
||||
|
||||
module RuboCop
|
||||
module Cop
|
||||
module Sorbet
|
||||
# This cop makes the Sorbet `false` sigil mandatory in all files.
|
||||
class FalseSigil < HasSigil
|
||||
def minimum_strictness
|
||||
'false'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -0,0 +1,26 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rubocop'
|
||||
require_relative 'valid_sigil'
|
||||
|
||||
module RuboCop
|
||||
module Cop
|
||||
module Sorbet
|
||||
# This cop makes the Sorbet typed sigil mandatory in all files.
|
||||
#
|
||||
# Options:
|
||||
#
|
||||
# * `SuggestedStrictness`: Sorbet strictness level suggested in offense messages (default: 'false')
|
||||
# * `MinimumStrictness`: If set, make offense if the strictness level in the file is below this one
|
||||
#
|
||||
# If a `MinimumStrictness` level is specified, it will be used in offense messages and autocorrect.
|
||||
class HasSigil < ValidSigil
|
||||
@registry = Cop.registry # So we can properly subclass this cop
|
||||
|
||||
def require_sigil_on_all_files?
|
||||
true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -0,0 +1,17 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rubocop'
|
||||
require_relative 'has_sigil'
|
||||
|
||||
module RuboCop
|
||||
module Cop
|
||||
module Sorbet
|
||||
# This cop makes the Sorbet `ignore` sigil mandatory in all files.
|
||||
class IgnoreSigil < HasSigil
|
||||
def minimum_strictness
|
||||
'ignore'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -0,0 +1,17 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rubocop'
|
||||
require_relative 'has_sigil'
|
||||
|
||||
module RuboCop
|
||||
module Cop
|
||||
module Sorbet
|
||||
# This cop makes the Sorbet `strict` sigil mandatory in all files.
|
||||
class StrictSigil < HasSigil
|
||||
def minimum_strictness
|
||||
'strict'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -0,0 +1,17 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rubocop'
|
||||
require_relative 'has_sigil'
|
||||
|
||||
module RuboCop
|
||||
module Cop
|
||||
module Sorbet
|
||||
# This cop makes the Sorbet `strong` sigil mandatory in all files.
|
||||
class StrongSigil < HasSigil
|
||||
def minimum_strictness
|
||||
'strong'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -0,0 +1,17 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rubocop'
|
||||
require_relative 'has_sigil'
|
||||
|
||||
module RuboCop
|
||||
module Cop
|
||||
module Sorbet
|
||||
# This cop makes the Sorbet `true` sigil mandatory in all files.
|
||||
class TrueSigil < HasSigil
|
||||
def minimum_strictness
|
||||
'true'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -0,0 +1,160 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rubocop'
|
||||
|
||||
module RuboCop
|
||||
module Cop
|
||||
module Sorbet
|
||||
# This cop checks that every Ruby file contains a valid Sorbet sigil.
|
||||
# Adapted from: https://gist.github.com/clarkdave/85aca4e16f33fd52aceb6a0a29936e52
|
||||
#
|
||||
# Options:
|
||||
#
|
||||
# * `RequireSigilOnAllFiles`: make offense if the Sorbet typed is not found in the file (default: false)
|
||||
# * `SuggestedStrictness`: Sorbet strictness level suggested in offense messages (default: 'false')
|
||||
# * `MinimumStrictness`: If set, make offense if the strictness level in the file is below this one
|
||||
#
|
||||
# If a `MinimumStrictness` level is specified, it will be used in offense messages and autocorrect.
|
||||
class ValidSigil < RuboCop::Cop::Cop
|
||||
@registry = Cop.registry # So we can properly subclass this cop
|
||||
|
||||
def investigate(processed_source)
|
||||
return if processed_source.tokens.empty?
|
||||
|
||||
sigil = extract_sigil(processed_source)
|
||||
return unless check_sigil_present(sigil)
|
||||
|
||||
strictness = extract_strictness(sigil)
|
||||
return unless check_strictness_not_empty(sigil, strictness)
|
||||
return unless check_strictness_valid(sigil, strictness)
|
||||
return unless check_strictness_level(sigil, strictness)
|
||||
end
|
||||
|
||||
def autocorrect(_node)
|
||||
lambda do |corrector|
|
||||
return unless require_sigil_on_all_files?
|
||||
return unless extract_sigil(processed_source).nil?
|
||||
|
||||
token = processed_source.tokens.first
|
||||
replace_with = suggested_strictness_level(minimum_strictness, suggested_strictness)
|
||||
sigil = "# typed: #{replace_with}"
|
||||
if token.text.start_with?("#!") # shebang line
|
||||
corrector.insert_after(token.pos, "\n#{sigil}")
|
||||
else
|
||||
corrector.insert_before(token.pos, "#{sigil}\n")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
STRICTNESS_LEVELS = %w(ignore false true strict strong)
|
||||
SIGIL_REGEX = /#\s+typed:(?:\s+([\w]+))?/
|
||||
|
||||
# extraction
|
||||
|
||||
def extract_sigil(processed_source)
|
||||
processed_source.tokens
|
||||
.take_while { |token| token.type == :tCOMMENT }
|
||||
.find { |token| SIGIL_REGEX.match?(token.text) }
|
||||
end
|
||||
|
||||
def extract_strictness(sigil)
|
||||
sigil.text.match(SIGIL_REGEX)&.captures&.first
|
||||
end
|
||||
|
||||
# checks
|
||||
|
||||
def check_sigil_present(sigil)
|
||||
return true unless sigil.nil?
|
||||
|
||||
token = processed_source.tokens.first
|
||||
if require_sigil_on_all_files?
|
||||
strictness = suggested_strictness_level(minimum_strictness, suggested_strictness)
|
||||
add_offense(
|
||||
token,
|
||||
location: token.pos,
|
||||
message: 'No Sorbet sigil found in file. ' \
|
||||
"Try a `typed: #{strictness}` to start (you can also use `rubocop -a` to automatically add this)."
|
||||
)
|
||||
end
|
||||
false
|
||||
end
|
||||
|
||||
def suggested_strictness_level(minimum_strictness, suggested_strictness)
|
||||
# if no minimum strictness is set (eg. using Sorbet/HasSigil without config) then
|
||||
# we always use the suggested strictness which defaults to `false`
|
||||
return suggested_strictness unless minimum_strictness
|
||||
|
||||
# special case: if you're using Sorbet/IgnoreSigil without config, we should recommend `ignore`
|
||||
return "ignore" if minimum_strictness == "ignore" && cop_config['SuggestedStrictness'].nil?
|
||||
|
||||
# if a minimum strictness is set (eg. you're using Sorbet/FalseSigil)
|
||||
# we want to compare the minimum strictness and suggested strictness. this is because
|
||||
# the suggested strictness might be higher than the minimum (eg. if you want all new files
|
||||
# at a higher strictness level, without having to migrate existing files at lower levels).
|
||||
|
||||
suggested_level = STRICTNESS_LEVELS.index(suggested_strictness)
|
||||
minimum_level = STRICTNESS_LEVELS.index(minimum_strictness)
|
||||
|
||||
suggested_level > minimum_level ? suggested_strictness : minimum_strictness
|
||||
end
|
||||
|
||||
def check_strictness_not_empty(sigil, strictness)
|
||||
return true if strictness
|
||||
|
||||
add_offense(
|
||||
sigil,
|
||||
location: sigil.pos,
|
||||
message: 'Sorbet sigil should not be empty.'
|
||||
)
|
||||
false
|
||||
end
|
||||
|
||||
def check_strictness_valid(sigil, strictness)
|
||||
return true if STRICTNESS_LEVELS.include?(strictness)
|
||||
|
||||
add_offense(
|
||||
sigil,
|
||||
location: sigil.pos,
|
||||
message: "Invalid Sorbet sigil `#{strictness}`."
|
||||
)
|
||||
false
|
||||
end
|
||||
|
||||
def check_strictness_level(sigil, strictness)
|
||||
return true unless minimum_strictness
|
||||
|
||||
minimum_level = STRICTNESS_LEVELS.index(minimum_strictness)
|
||||
current_level = STRICTNESS_LEVELS.index(strictness)
|
||||
if current_level < minimum_level
|
||||
add_offense(
|
||||
sigil,
|
||||
location: sigil.pos,
|
||||
message: "Sorbet sigil should be at least `#{minimum_strictness}` got `#{strictness}`."
|
||||
)
|
||||
return false
|
||||
end
|
||||
true
|
||||
end
|
||||
|
||||
# options
|
||||
|
||||
# Default is `false`
|
||||
def require_sigil_on_all_files?
|
||||
!!cop_config['RequireSigilOnAllFiles']
|
||||
end
|
||||
|
||||
# Default is `'false'`
|
||||
def suggested_strictness
|
||||
STRICTNESS_LEVELS.include?(cop_config['SuggestedStrictness']) ? cop_config['SuggestedStrictness'] : 'false'
|
||||
end
|
||||
|
||||
# Default is `nil`
|
||||
def minimum_strictness
|
||||
cop_config['MinimumStrictness'] if STRICTNESS_LEVELS.include?(cop_config['MinimumStrictness'])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -0,0 +1,59 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rubocop'
|
||||
|
||||
module RuboCop
|
||||
module Cop
|
||||
module Sorbet
|
||||
# This cop disallows using `.override(allow_incompatible: true)`.
|
||||
# Using `allow_incompatible` suggests a violation of the Liskov
|
||||
# Substitution Principle, meaning that a subclass is not a valid
|
||||
# subtype of it's superclass. This Cop prevents these design smells
|
||||
# from occurring.
|
||||
#
|
||||
# @example
|
||||
#
|
||||
# # bad
|
||||
# sig.override(allow_incompatible: true)
|
||||
#
|
||||
# # good
|
||||
# sig.override
|
||||
class AllowIncompatibleOverride < RuboCop::Cop::Cop
|
||||
def_node_search(:sig?, <<-PATTERN)
|
||||
(
|
||||
send
|
||||
nil?
|
||||
:sig
|
||||
...
|
||||
)
|
||||
PATTERN
|
||||
|
||||
def not_nil?(node)
|
||||
!node.nil?
|
||||
end
|
||||
|
||||
def_node_search(:allow_incompatible?, <<-PATTERN)
|
||||
(pair (sym :allow_incompatible) (true))
|
||||
PATTERN
|
||||
|
||||
def_node_matcher(:allow_incompatible_override?, <<-PATTERN)
|
||||
(
|
||||
send
|
||||
[#not_nil? #sig?]
|
||||
:override
|
||||
[#not_nil? #allow_incompatible?]
|
||||
)
|
||||
PATTERN
|
||||
|
||||
def on_send(node)
|
||||
return unless allow_incompatible_override?(node)
|
||||
add_offense(
|
||||
node.children[2],
|
||||
message: 'Usage of `allow_incompatible` suggests a violation of the Liskov Substitution Principle. '\
|
||||
'Instead, strive to write interfaces which respect subtyping principles and remove `allow_incompatible`',
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -0,0 +1,53 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rubocop'
|
||||
require_relative 'signature_cop'
|
||||
|
||||
module RuboCop
|
||||
module Cop
|
||||
module Sorbet
|
||||
# This cop disallows the usage of `checked(true)`. This usage could cause
|
||||
# confusion; it could lead some people to believe that a method would be checked
|
||||
# even if runtime checks have not been enabled on the class or globally.
|
||||
# Additionally, in the event where checks are enabled, `checked(true)` would
|
||||
# be redundant; only `checked(false)` or `soft` would change the behaviour.
|
||||
#
|
||||
# @example
|
||||
#
|
||||
# # bad
|
||||
# sig { void.checked(true) }
|
||||
#
|
||||
# # good
|
||||
# sig { void }
|
||||
class CheckedTrueInSignature < SignatureCop
|
||||
include(RuboCop::Cop::RangeHelp)
|
||||
|
||||
def_node_search(:offending_node, <<~PATTERN)
|
||||
(send _ :checked (true))
|
||||
PATTERN
|
||||
|
||||
MESSAGE =
|
||||
'Using `checked(true)` in a method signature definition is not allowed. ' \
|
||||
'`checked(true)` is the default behavior for modules/classes with runtime checks enabled. ' \
|
||||
'To enable typechecking at runtime for this module, regardless of global settings, ' \
|
||||
'`include(WaffleCone::RuntimeChecks)` to this module and set other methods to `checked(false)`.'
|
||||
private_constant(:MESSAGE)
|
||||
|
||||
def on_signature(node)
|
||||
error = offending_node(node).first
|
||||
return unless error
|
||||
|
||||
add_offense(
|
||||
error,
|
||||
location: source_range(
|
||||
processed_source.buffer,
|
||||
error.location.line,
|
||||
(error.location.selector.begin_pos)..(error.location.end.begin_pos),
|
||||
),
|
||||
message: MESSAGE
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -0,0 +1,135 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rubocop'
|
||||
require 'stringio'
|
||||
require_relative 'signature_cop'
|
||||
|
||||
module RuboCop
|
||||
module Cop
|
||||
module Sorbet
|
||||
# This cop checks that every method definition and attribute accessor has a Sorbet signature.
|
||||
#
|
||||
# It also suggest an autocorrect with placeholders so the following code:
|
||||
#
|
||||
# ```
|
||||
# def foo(a, b, c); end
|
||||
# ```
|
||||
#
|
||||
# Will be corrected as:
|
||||
#
|
||||
# ```
|
||||
# sig { params(a: T.untyped, b: T.untyped, c: T.untyped).returns(T.untyped)
|
||||
# def foo(a, b, c); end
|
||||
# ```
|
||||
#
|
||||
# You can configure the placeholders used by changing the following options:
|
||||
#
|
||||
# * `ParameterTypePlaceholder`: placeholders used for parameter types (default: 'T.untyped')
|
||||
# * `ReturnTypePlaceholder`: placeholders used for return types (default: 'T.untyped')
|
||||
class EnforceSignatures < SignatureCop
|
||||
def_node_matcher(:accessor?, <<-PATTERN)
|
||||
(send nil? {:attr_reader :attr_writer :attr_accessor} ...)
|
||||
PATTERN
|
||||
|
||||
def on_def(node)
|
||||
check_node(node)
|
||||
end
|
||||
|
||||
def on_defs(node)
|
||||
check_node(node)
|
||||
end
|
||||
|
||||
def on_send(node)
|
||||
return unless accessor?(node)
|
||||
check_node(node)
|
||||
end
|
||||
|
||||
def autocorrect(node)
|
||||
lambda do |corrector|
|
||||
suggest = SigSuggestion.new(node.loc.column, param_type_placeholder, return_type_placeholder)
|
||||
|
||||
if node.is_a?(RuboCop::AST::DefNode) # def something
|
||||
node.arguments.each do |arg|
|
||||
suggest.params << arg.children.first
|
||||
end
|
||||
elsif accessor?(node) # attr reader, writer, accessor
|
||||
method = node.children[1]
|
||||
symbol = node.children[2]
|
||||
suggest.params << symbol.value if symbol && (method == :attr_writer || method == :attr_accessor)
|
||||
suggest.returns = 'void' if method == :attr_writer
|
||||
end
|
||||
|
||||
corrector.insert_before(node.loc.expression, suggest.to_autocorrect)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def check_node(node)
|
||||
prev = previous_node(node)
|
||||
unless signature?(prev)
|
||||
add_offense(
|
||||
node,
|
||||
message: "Each method is required to have a signature."
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def previous_node(node)
|
||||
parent = node.parent
|
||||
return nil unless parent
|
||||
parent.children[node.sibling_index - 1]
|
||||
end
|
||||
|
||||
def param_type_placeholder
|
||||
cop_config['ParameterTypePlaceholder'] || 'T.untyped'
|
||||
end
|
||||
|
||||
def return_type_placeholder
|
||||
cop_config['ReturnTypePlaceholder'] || 'T.untyped'
|
||||
end
|
||||
|
||||
class SigSuggestion
|
||||
attr_accessor :params, :returns
|
||||
|
||||
def initialize(indent, param_placeholder, return_placeholder)
|
||||
@params = []
|
||||
@returns = nil
|
||||
@indent = indent
|
||||
@param_placeholder = param_placeholder
|
||||
@return_placeholder = return_placeholder
|
||||
end
|
||||
|
||||
def to_autocorrect
|
||||
out = StringIO.new
|
||||
out << 'sig { '
|
||||
out << generate_params
|
||||
out << generate_return
|
||||
out << " }\n"
|
||||
out << ' ' * @indent # preserve indent for the next line
|
||||
out.string
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def generate_params
|
||||
return if @params.empty?
|
||||
out = StringIO.new
|
||||
out << 'params('
|
||||
out << @params.map do |param|
|
||||
"#{param}: #{@param_placeholder}"
|
||||
end.join(", ")
|
||||
out << ').'
|
||||
out.string
|
||||
end
|
||||
|
||||
def generate_return
|
||||
return "returns(#{@return_placeholder})" if @returns.nil?
|
||||
return @returns if @returns == 'void'
|
||||
"returns(#{@returns})"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -0,0 +1,51 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rubocop'
|
||||
require_relative 'signature_cop'
|
||||
|
||||
module RuboCop
|
||||
module Cop
|
||||
module Sorbet
|
||||
# This cop checks for the ordering of keyword arguments required by
|
||||
# sorbet-runtime. The ordering requires that all keyword arguments
|
||||
# are at the end of the parameters list, and all keyword arguments
|
||||
# with a default value must be after those without default values.
|
||||
#
|
||||
# @example
|
||||
#
|
||||
# # bad
|
||||
# sig { params(a: Integer, b: String).void }
|
||||
# def foo(a: 1, b:); end
|
||||
#
|
||||
# # good
|
||||
# sig { params(b: String, a: Integer).void }
|
||||
# def foo(b:, a: 1); end
|
||||
class KeywordArgumentOrdering < SignatureCop
|
||||
def on_signature(node)
|
||||
method_node = node.parent.children[node.sibling_index + 1]
|
||||
return if method_node.nil?
|
||||
method_parameters = method_node.arguments
|
||||
|
||||
check_order_for_kwoptargs(method_parameters)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def check_order_for_kwoptargs(parameters)
|
||||
out_of_kwoptarg = false
|
||||
|
||||
parameters.reverse.each do |param|
|
||||
out_of_kwoptarg = true unless param.type == :kwoptarg || param.type == :blockarg || param.type == :kwrestarg
|
||||
|
||||
next unless param.type == :kwoptarg && out_of_kwoptarg
|
||||
|
||||
add_offense(
|
||||
param,
|
||||
message: 'Optional keyword arguments must be at the end of the parameter list.'
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -0,0 +1,70 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rubocop'
|
||||
require_relative 'signature_cop'
|
||||
|
||||
module RuboCop
|
||||
module Cop
|
||||
module Sorbet
|
||||
# This cop checks for inconsistent ordering of parameters between the
|
||||
# signature and the method definition. The sorbet-runtime gem raises
|
||||
# when such inconsistency occurs.
|
||||
#
|
||||
# @example
|
||||
#
|
||||
# # bad
|
||||
# sig { params(a: Integer, b: String).void }
|
||||
# def foo(b:, a:); end
|
||||
#
|
||||
# # good
|
||||
# sig { params(a: Integer, b: String).void }
|
||||
# def foo(a:, b:); end
|
||||
class ParametersOrderingInSignature < SignatureCop
|
||||
def_node_search(:signature_params, <<-PATTERN)
|
||||
(send _ :params ...)
|
||||
PATTERN
|
||||
|
||||
def on_signature(node)
|
||||
sig_params = signature_params(node).first
|
||||
|
||||
sig_params_order = extract_parameters(sig_params)
|
||||
return if sig_params_order.nil?
|
||||
method_node = node.parent.children[node.sibling_index + 1]
|
||||
return if method_node.nil? || method_node.type != :def
|
||||
method_parameters = method_node.arguments
|
||||
|
||||
check_for_inconsistent_param_ordering(sig_params_order, method_parameters)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def extract_parameters(sig_params)
|
||||
return [] if sig_params.nil?
|
||||
|
||||
arguments = sig_params.arguments.first
|
||||
return arguments.keys.map(&:value) if RuboCop::AST::HashNode === arguments
|
||||
|
||||
add_offense(
|
||||
sig_params,
|
||||
message: "Invalid signature."
|
||||
)
|
||||
end
|
||||
|
||||
def check_for_inconsistent_param_ordering(sig_params_order, parameters)
|
||||
parameters.each_with_index do |param, index|
|
||||
param_name = param.children[0]
|
||||
sig_param_name = sig_params_order[index]
|
||||
|
||||
next if param_name == sig_param_name
|
||||
|
||||
add_offense(
|
||||
param,
|
||||
message: "Inconsistent ordering of arguments at index #{index}. " \
|
||||
"Expected `#{sig_param_name}` from sig above."
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -0,0 +1,104 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rubocop'
|
||||
require_relative 'signature_cop'
|
||||
|
||||
begin
|
||||
require 'unparser'
|
||||
rescue LoadError
|
||||
nil
|
||||
end
|
||||
|
||||
module RuboCop
|
||||
module Cop
|
||||
module Sorbet
|
||||
class SignatureBuildOrder < SignatureCop
|
||||
ORDER =
|
||||
[
|
||||
:abstract,
|
||||
:override,
|
||||
:overridable,
|
||||
:type_parameters,
|
||||
:params,
|
||||
:returns,
|
||||
:void,
|
||||
:soft,
|
||||
:checked,
|
||||
:on_failure,
|
||||
].each_with_index.to_h.freeze
|
||||
|
||||
def_node_search(:root_call, <<~PATTERN)
|
||||
(send nil? {#{ORDER.keys.map(&:inspect).join(' ')}} ...)
|
||||
PATTERN
|
||||
|
||||
def on_signature(node)
|
||||
calls = call_chain(node.children[2]).map(&:method_name)
|
||||
return unless calls.any?
|
||||
|
||||
expected_order = calls.sort_by { |call| ORDER[call] }
|
||||
return if expected_order == calls
|
||||
|
||||
message = "Sig builders must be invoked in the following order: #{expected_order.join(', ')}."
|
||||
|
||||
unless can_autocorrect?
|
||||
message += ' For autocorrection, add the `unparser` gem to your project.'
|
||||
end
|
||||
|
||||
add_offense(
|
||||
node.children[2],
|
||||
message: message,
|
||||
)
|
||||
node
|
||||
end
|
||||
|
||||
def autocorrect(node)
|
||||
return nil unless can_autocorrect?
|
||||
|
||||
lambda do |corrector|
|
||||
tree = call_chain(node_with_index_sends(node))
|
||||
.sort_by { |call| ORDER[call.method_name] }
|
||||
.reduce(nil) do |receiver, caller|
|
||||
caller.updated(nil, [receiver] + caller.children.drop(1))
|
||||
end
|
||||
|
||||
corrector.replace(
|
||||
node.source_range,
|
||||
Unparser.unparse(tree),
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def node_with_index_sends(node)
|
||||
# This is really dirty hack to reparse the current node with index send
|
||||
# emitting enabled, which is necessary to unparse them back as index accessors.
|
||||
emit_index_value = RuboCop::AST::Builder.emit_index
|
||||
RuboCop::AST::Builder.emit_index = true
|
||||
RuboCop::AST::ProcessedSource.new(node.source, target_ruby_version, processed_source.path).ast
|
||||
ensure
|
||||
RuboCop::AST::Builder.emit_index = emit_index_value
|
||||
end
|
||||
|
||||
def can_autocorrect?
|
||||
defined?(::Unparser)
|
||||
end
|
||||
|
||||
def call_chain(sig_child_node)
|
||||
call_node = root_call(sig_child_node).first
|
||||
return [] unless call_node
|
||||
|
||||
calls = []
|
||||
while call_node != sig_child_node
|
||||
calls << call_node
|
||||
call_node = call_node.parent
|
||||
end
|
||||
|
||||
calls << sig_child_node
|
||||
|
||||
calls
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -0,0 +1,28 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rubocop'
|
||||
|
||||
module RuboCop
|
||||
module Cop
|
||||
module Sorbet
|
||||
# Abstract cop specific to Sorbet signatures
|
||||
#
|
||||
# You can subclass it to use the `on_signature` trigger and the `signature?` node matcher.
|
||||
class SignatureCop < RuboCop::Cop::Cop
|
||||
@registry = Cop.registry # So we can properly subclass this cop
|
||||
|
||||
def_node_matcher(:signature?, <<~PATTERN)
|
||||
(block (send nil? :sig) (args) ...)
|
||||
PATTERN
|
||||
|
||||
def on_block(node)
|
||||
on_signature(node) if signature?(node)
|
||||
end
|
||||
|
||||
def on_signature(_)
|
||||
# To be defined in subclasses
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -0,0 +1,22 @@
|
||||
# frozen_string_literal: true
|
||||
require_relative 'sorbet/binding_constants_without_type_alias'
|
||||
require_relative 'sorbet/constants_from_strings'
|
||||
require_relative 'sorbet/forbid_superclass_const_literal'
|
||||
require_relative 'sorbet/forbid_include_const_literal'
|
||||
require_relative 'sorbet/forbid_untyped_struct_props'
|
||||
|
||||
require_relative 'sorbet/signatures/allow_incompatible_override'
|
||||
require_relative 'sorbet/signatures/checked_true_in_signature'
|
||||
require_relative 'sorbet/signatures/keyword_argument_ordering'
|
||||
require_relative 'sorbet/signatures/parameters_ordering_in_signature'
|
||||
require_relative 'sorbet/signatures/signature_build_order'
|
||||
require_relative 'sorbet/signatures/enforce_signatures'
|
||||
|
||||
require_relative 'sorbet/sigils/valid_sigil'
|
||||
require_relative 'sorbet/sigils/has_sigil'
|
||||
require_relative 'sorbet/sigils/ignore_sigil'
|
||||
require_relative 'sorbet/sigils/false_sigil'
|
||||
require_relative 'sorbet/sigils/true_sigil'
|
||||
require_relative 'sorbet/sigils/strict_sigil'
|
||||
require_relative 'sorbet/sigils/strong_sigil'
|
||||
require_relative 'sorbet/sigils/enforce_sigil_order'
|
15
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rubocop-sorbet-0.5.1/lib/rubocop/sorbet.rb
vendored
Normal file
15
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rubocop-sorbet-0.5.1/lib/rubocop/sorbet.rb
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
# frozen_string_literal: true
|
||||
require "rubocop/sorbet/version"
|
||||
require "yaml"
|
||||
|
||||
module RuboCop
|
||||
module Sorbet
|
||||
class Error < StandardError; end
|
||||
|
||||
PROJECT_ROOT = Pathname.new(__dir__).parent.parent.expand_path.freeze
|
||||
CONFIG_DEFAULT = PROJECT_ROOT.join('config', 'default.yml').freeze
|
||||
CONFIG = YAML.safe_load(CONFIG_DEFAULT.read).freeze
|
||||
|
||||
private_constant(:CONFIG_DEFAULT, :PROJECT_ROOT)
|
||||
end
|
||||
end
|
20
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rubocop-sorbet-0.5.1/lib/rubocop/sorbet/inject.rb
vendored
Normal file
20
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rubocop-sorbet-0.5.1/lib/rubocop/sorbet/inject.rb
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# The original code is from https://github.com/rubocop-hq/rubocop-rspec/blob/master/lib/rubocop/rspec/inject.rb
|
||||
# See https://github.com/rubocop-hq/rubocop-rspec/blob/master/MIT-LICENSE.md
|
||||
module RuboCop
|
||||
module Sorbet
|
||||
# Because RuboCop doesn't yet support plugins, we have to monkey patch in a
|
||||
# bit of our configuration.
|
||||
module Inject
|
||||
def self.defaults!
|
||||
path = CONFIG_DEFAULT.to_s
|
||||
hash = ConfigLoader.send(:load_yaml_configuration, path)
|
||||
config = Config.new(hash, path).tap(&:make_excludes_absolute)
|
||||
puts "configuration from #{path}" if ConfigLoader.debug?
|
||||
config = ConfigLoader.merge_with_default(config, path)
|
||||
ConfigLoader.instance_variable_set(:@default_configuration, config)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -0,0 +1,6 @@
|
||||
# frozen_string_literal: true
|
||||
module RuboCop
|
||||
module Sorbet
|
||||
VERSION = "0.5.1"
|
||||
end
|
||||
end
|
Loading…
x
Reference in New Issue
Block a user