Class: RuboCop::Cop::Sorbet::ForbidTStruct::Property
- Inherits:
-
Object
- Object
- RuboCop::Cop::Sorbet::ForbidTStruct::Property
- Defined in:
- lib/rubocop/cop/sorbet/forbid_t_struct.rb
Constant Summary collapse
- GENERIC_BASE_NAMES =
Sorbet
T::X[...]generics that have a direct RBS equivalent. [:Array, :Hash, :Set, :Range].freeze
- T_CONST_MAP =
Sorbet bare
T::Xconstants that map to an RBS built-in. { Boolean: "bool" }.freeze
Instance Attribute Summary collapse
-
#default ⇒ Object
readonly
Returns the value of attribute default.
-
#factory ⇒ Object
readonly
Returns the value of attribute factory.
-
#kind ⇒ Object
readonly
Returns the value of attribute kind.
-
#name ⇒ Object
readonly
Returns the value of attribute name.
-
#node ⇒ Object
readonly
Returns the value of attribute node.
Instance Method Summary collapse
- #attr_accessor ⇒ Object
- #attr_sig ⇒ Object
-
#generic_base(node) ⇒ Object
T::Array[X],T::Hash[K, V], andT::Set[X]become the RBS genericsArray[X],Hash[K, V],Set[X]. -
#initialize(node, kind, name, type, default:, factory:, style: "sig") ⇒ Property
constructor
A new instance of Property.
- #initialize_assign ⇒ Object
- #initialize_param ⇒ Object
- #initialize_sig_param ⇒ Object
- #nilable? ⇒ Boolean
-
#optional? ⇒ Boolean
A prop is optional when it declares a
default:, afactory:, or is nilable (which gives it an implicitnildefault). - #rbs? ⇒ Boolean
- #rbs_type ⇒ Object
-
#sorbet_type_to_rbs(node) ⇒ Object
Translate a Sorbet type expression AST node into RBS syntax.
- #t_const?(node) ⇒ Boolean
-
#translate_const(node) ⇒ Object
Bare class constants are valid RBS class-instance types, except for Sorbet's
T::Boolean(->bool) and otherT::Xconstants which have no RBS equivalent and fall back tountyped. - #translate_send_type(node) ⇒ Object
-
#translate_t_method(method, args) ⇒ Object
Maps
T.xxx(...)type constructors to RBS. - #type ⇒ Object
Constructor Details
#initialize(node, kind, name, type, default:, factory:, style: "sig") ⇒ Property
Returns a new instance of Property.
140 141 142 143 144 145 146 147 148 149 150 151 152 |
# File 'lib/rubocop/cop/sorbet/forbid_t_struct.rb', line 140 def initialize(node, kind, name, type, default:, factory:, style: "sig") @node = node @kind = kind @name = name @type = type @type_node = node.arguments[1] @default = default @factory = factory @style = style # A T::Struct should have both a default and a factory, if we find one let's raise an error raise if @default && @factory end |
Instance Attribute Details
#default ⇒ Object (readonly)
Returns the value of attribute default.
138 139 140 |
# File 'lib/rubocop/cop/sorbet/forbid_t_struct.rb', line 138 def default @default end |
#factory ⇒ Object (readonly)
Returns the value of attribute factory.
138 139 140 |
# File 'lib/rubocop/cop/sorbet/forbid_t_struct.rb', line 138 def factory @factory end |
#kind ⇒ Object (readonly)
Returns the value of attribute kind.
138 139 140 |
# File 'lib/rubocop/cop/sorbet/forbid_t_struct.rb', line 138 def kind @kind end |
#name ⇒ Object (readonly)
Returns the value of attribute name.
138 139 140 |
# File 'lib/rubocop/cop/sorbet/forbid_t_struct.rb', line 138 def name @name end |
#node ⇒ Object (readonly)
Returns the value of attribute node.
138 139 140 |
# File 'lib/rubocop/cop/sorbet/forbid_t_struct.rb', line 138 def node @node end |
Instance Method Details
#attr_accessor ⇒ Object
162 163 164 |
# File 'lib/rubocop/cop/sorbet/forbid_t_struct.rb', line 162 def attr_accessor "#{kind} :#{name}" end |
#attr_sig ⇒ Object
154 155 156 157 158 159 160 |
# File 'lib/rubocop/cop/sorbet/forbid_t_struct.rb', line 154 def attr_sig if rbs? "#: #{rbs_type}" else "sig { returns(#{type}) }" end end |
#generic_base(node) ⇒ Object
T::Array[X], T::Hash[K, V], and T::Set[X] become the RBS
generics Array[X], Hash[K, V], Set[X]. Other const receivers
keep their class name (custom generics). Non-const receivers return
nil so the caller falls back to untyped instead of untyped[X].
295 296 297 298 299 300 301 302 303 |
# File 'lib/rubocop/cop/sorbet/forbid_t_struct.rb', line 295 def generic_base(node) return unless node&.const_type? if t_const?(node.children[0]) && GENERIC_BASE_NAMES.include?(node.children[1]) node.children[1].to_s elsif !t_const?(node.children[0]) node.source end end |
#initialize_assign ⇒ Object
186 187 188 189 190 191 |
# File 'lib/rubocop/cop/sorbet/forbid_t_struct.rb', line 186 def initialize_assign rb = String.new rb << "@#{name} = #{name}" rb << ".call" if factory rb end |
#initialize_param ⇒ Object
173 174 175 176 177 178 179 180 181 182 183 184 |
# File 'lib/rubocop/cop/sorbet/forbid_t_struct.rb', line 173 def initialize_param rb = String.new rb << "#{name}:" if default rb << " #{default}" elsif factory rb << " #{factory}" elsif nilable? rb << " nil" end rb end |
#initialize_sig_param ⇒ Object
166 167 168 169 170 171 |
# File 'lib/rubocop/cop/sorbet/forbid_t_struct.rb', line 166 def initialize_sig_param type_str = rbs? ? rbs_type : type return "?#{name}: #{type_str}" if rbs? && optional? "#{name}: #{type_str}" end |
#nilable? ⇒ Boolean
193 194 195 |
# File 'lib/rubocop/cop/sorbet/forbid_t_struct.rb', line 193 def nilable? type.start_with?("T.nilable(") end |
#optional? ⇒ Boolean
A prop is optional when it declares a default:, a factory:, or
is nilable (which gives it an implicit nil default). RBS marks
optional keyword parameters with ?name:, mirroring the default
value that initialize_param emits.
201 202 203 |
# File 'lib/rubocop/cop/sorbet/forbid_t_struct.rb', line 201 def optional? !!(default || factory || nilable?) end |
#rbs? ⇒ Boolean
210 211 212 |
# File 'lib/rubocop/cop/sorbet/forbid_t_struct.rb', line 210 def rbs? @style == "rbs" end |
#rbs_type ⇒ Object
214 215 216 |
# File 'lib/rubocop/cop/sorbet/forbid_t_struct.rb', line 214 def rbs_type sorbet_type_to_rbs(@type_node) end |
#sorbet_type_to_rbs(node) ⇒ Object
Translate a Sorbet type expression AST node into RBS syntax.
Handles the constructs most commonly found on T::Struct props:
T.nilable, T.any, T.all, T.untyped, T.class_of, and
generics like T::Array[X]. Any unrecognized node falls back to
the valid RBS untyped rather than emitting malformed Sorbet
syntax (e.g. T.proc...) into an RBS annotation.
224 225 226 227 228 229 230 231 232 233 234 235 |
# File 'lib/rubocop/cop/sorbet/forbid_t_struct.rb', line 224 def sorbet_type_to_rbs(node) return "untyped" if node.nil? case node.type when :const translate_const(node) when :send translate_send_type(node) else "untyped" end end |
#t_const?(node) ⇒ Boolean
274 275 276 |
# File 'lib/rubocop/cop/sorbet/forbid_t_struct.rb', line 274 def t_const?(node) node&.const_type? && node.children[1] == :T end |
#translate_const(node) ⇒ Object
Bare class constants are valid RBS class-instance types, except
for Sorbet's T::Boolean (-> bool) and other T::X constants
which have no RBS equivalent and fall back to untyped.
281 282 283 284 285 286 287 288 289 |
# File 'lib/rubocop/cop/sorbet/forbid_t_struct.rb', line 281 def translate_const(node) return "untyped" if t_const?(node) if t_const?(node.children[0]) T_CONST_MAP.fetch(node.children[1]) { "untyped" } else node.source end end |
#translate_send_type(node) ⇒ Object
237 238 239 240 241 242 243 244 245 246 247 248 249 |
# File 'lib/rubocop/cop/sorbet/forbid_t_struct.rb', line 237 def translate_send_type(node) receiver = node.receiver method = node.method_name args = node.arguments if t_const?(receiver) translate_t_method(method, args) elsif method == :[] && (base = generic_base(receiver)) "#{base}[#{args.map { |arg| sorbet_type_to_rbs(arg) }.join(", ")}]" else "untyped" end end |
#translate_t_method(method, args) ⇒ Object
Maps T.xxx(...) type constructors to RBS. Unknown T.xxx sends
become untyped so the annotation stays valid RBS.
253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 |
# File 'lib/rubocop/cop/sorbet/forbid_t_struct.rb', line 253 def translate_t_method(method, args) case method when :nilable inner = sorbet_type_to_rbs(args.first) inner = "(#{inner})" if inner.include?(" | ") || inner.include?(" & ") "#{inner}?" when :any args.map { |arg| sorbet_type_to_rbs(arg) }.join(" | ") when :all args.map { |arg| sorbet_type_to_rbs(arg) }.join(" & ") when :untyped "untyped" when :noreturn "bot" when :class_of "singleton(#{sorbet_type_to_rbs(args.first)})" else "untyped" end end |
#type ⇒ Object
205 206 207 208 |
# File 'lib/rubocop/cop/sorbet/forbid_t_struct.rb', line 205 def type copy = @type.gsub(/[[:space:]]+/, "").strip # Remove newlines and spaces copy.gsub(",", ", ") # Add a space after each comma end |