# frozen_string_literal: truerequire"rubocop"moduleRuboCopmoduleCopmoduleSorbet# Disallow using `T::Struct` and `T::Props`.## @example## # bad# class MyStruct < T::Struct# const :foo, String# prop :bar, Integer, default: 0## def some_method; end# end## # good# class MyStruct# extend T::Sig## sig { returns(String) }# attr_reader :foo## sig { returns(Integer) }# attr_accessor :bar## sig { params(foo: String, bar: Integer) }# def initialize(foo:, bar: 0)# @foo = foo# @bar = bar# end## def some_method; end# endclassForbidTStruct<RuboCop::Cop::BaseincludeAlignmentincludeRangeHelpincludeCommentsHelpextendAutoCorrectorRESTRICT_ON_SEND=[:include,:prepend,:extend].freezeMSG_STRUCT="Using `T::Struct` or its variants is deprecated."MSG_PROPS="Using `T::Props` or its variants is deprecated."# This class walks down the class body of a T::Struct and collects all the properties that will need to be# translated into `attr_reader` and `attr_accessor` methods.classTStructWalkerincludeAST::TraversalextendAST::NodePattern::Macrosattr_reader:props,:has_extend_t_sigdefinitialize@props=[]@has_extend_t_sig=falseend# @!method extend_t_sig?(node)def_node_matcher:extend_t_sig?,<<~PATTERN
(send _ :extend (const (const {nil? | cbase} :T) :Sig))
PATTERN# @!method t_struct_prop?(node)def_node_matcher(:t_struct_prop?,<<~PATTERN)
(send nil? {:const :prop} ...)
PATTERNdefon_send(node)ifextend_t_sig?(node)# So we know we won't need to generate again a `extend T::Sig` line in the new class body@has_extend_t_sig=truereturnendreturnunlesst_struct_prop?(node)kind=node.method?(:const)?:attr_reader::attr_accessorname=node.arguments[0].source.delete_prefix(":")type=node.arguments[1].sourcedefault=nilfactory=nilnode.arguments[2..-1].eachdo|arg|nextunlessarg.hash_type?arg.each_pairdo|key,value|casekey.sourcewhen"default"default=value.sourcewhen"factory"factory=value.sourceendendend@props<<Property.new(node,kind,name,type,default: default,factory: factory)endendclassPropertyattr_reader:node,:kind,:name,:type,:default,:factorydefinitialize(node,kind,name,type,default:,factory:)@node=node@kind=kind@name=name@type=type@default=default@factory=factory# A T::Struct should have both a default and a factory, if we find one let's raise an errorraiseif@default&&@factoryenddefattr_sig"sig { returns(#{type}) }"enddefattr_accessor"#{kind} :#{name}"enddefinitialize_sig_param"#{name}: #{type}"enddefinitialize_paramrb=String.newrb<<"#{name}:"ifdefaultrb<<" #{default}"elsiffactoryrb<<" #{factory}"elsifnilable?rb<<" nil"endrbenddefinitialize_assignrb=String.newrb<<"@#{name} = #{name}"rb<<".call"iffactoryrbenddefnilable?type.start_with?("T.nilable(")endend# @!method t_struct?(node)def_node_matcher(:t_struct?,<<~PATTERN)
(const (const {nil? cbase} :T) {:Struct :ImmutableStruct :InexactStruct})
PATTERN# @!method t_props?(node)def_node_matcher(:t_props?,"(send nil? {:include :prepend :extend} `(const (const {nil? cbase} :T) :Props))")defon_class(node)returnunlesst_struct?(node.parent_class)add_offense(node,message: MSG_STRUCT)do|corrector|walker=TStructWalker.newwalker.walk(node.body)range=range_between(node.identifier.source_range.end_pos,node.parent_class.source_range.end_pos)corrector.remove(range)nextifnode.single_line?unlesswalker.has_extend_t_sigindent=offset(node)corrector.insert_after(node.identifier,"\n#{indent} extend T::Sig\n")endfirst_prop=walker.props.firstwalker.props.eachdo|prop|node=prop.nodeindent=offset(node)line_range=range_by_whole_lines(prop.node.source_range)new_line=prop!=first_prop&&!previous_line_blank?(node)trailing_comments=processed_source.each_comment_in_lines(line_range.line..line_range.line)corrector.replace(line_range,"#{new_line?"\n":""}"\"#{trailing_comments.map{|comment|"#{indent}#{comment.text}\n"}.join}"\"#{indent}#{prop.attr_sig}\n#{indent}#{prop.attr_accessor}",)endlast_prop=walker.props.lastiflast_propindent=offset(last_prop.node)line_range=range_by_whole_lines(last_prop.node.source_range,include_final_newline: true)corrector.insert_after(line_range,initialize_method(indent,walker.props))endendenddefon_send(node)returnunlesst_props?(node)add_offense(node,message: MSG_PROPS)endprivatedefinitialize_method(indent,props)# We sort optional keyword arguments after required onessorted_props=props.sort_by{|prop|prop.default||prop.factory||prop.nilable??1:0}string=+"\n"string<<"#{indent}sig { params(#{sorted_props.map(&:initialize_sig_param).join(", ")}).void }\n"string<<"#{indent}def initialize(#{sorted_props.map(&:initialize_param).join(", ")})\n"props.eachdo|prop|string<<"#{indent}#{prop.initialize_assign}\n"endstring<<"#{indent}end\n"enddefprevious_line_blank?(node)processed_source.buffer.source_line(node.source_range.line-1).blank?endendendendend