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/*/lib
|
||||||
!**/vendor/bundle/ruby/*/gems/rubocop-performance-*/config
|
!**/vendor/bundle/ruby/*/gems/rubocop-performance-*/config
|
||||||
!**/vendor/bundle/ruby/*/gems/rubocop-rspec-*/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
|
# Ignore partially included gems where we don't need all files
|
||||||
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support.rb
|
**/vendor/bundle/ruby/*/gems/activesupport-*/lib/active_support.rb
|
||||||
|
@ -25,4 +25,5 @@ gem "patchelf"
|
|||||||
gem "plist"
|
gem "plist"
|
||||||
gem "rubocop-performance"
|
gem "rubocop-performance"
|
||||||
gem "rubocop-rspec"
|
gem "rubocop-rspec"
|
||||||
|
gem "rubocop-sorbet"
|
||||||
gem "ruby-macho"
|
gem "ruby-macho"
|
||||||
|
@ -114,6 +114,8 @@ GEM
|
|||||||
rubocop-ast (>= 0.4.0)
|
rubocop-ast (>= 0.4.0)
|
||||||
rubocop-rspec (1.43.2)
|
rubocop-rspec (1.43.2)
|
||||||
rubocop (~> 0.87)
|
rubocop (~> 0.87)
|
||||||
|
rubocop-sorbet (0.5.1)
|
||||||
|
rubocop
|
||||||
ruby-macho (2.2.0)
|
ruby-macho (2.2.0)
|
||||||
ruby-progressbar (1.10.1)
|
ruby-progressbar (1.10.1)
|
||||||
simplecov (0.19.0)
|
simplecov (0.19.0)
|
||||||
@ -167,6 +169,7 @@ DEPENDENCIES
|
|||||||
rubocop
|
rubocop
|
||||||
rubocop-performance
|
rubocop-performance
|
||||||
rubocop-rspec
|
rubocop-rspec
|
||||||
|
rubocop-sorbet
|
||||||
ruby-macho
|
ruby-macho
|
||||||
simplecov
|
simplecov
|
||||||
sorbet
|
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-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-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-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/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-static-0.5.5942-universal-darwin-19/lib"
|
||||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/sorbet-0.5.5942/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