lib/rubocop/cop/sorbet/signatures/enforce_signatures.rb
# 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