@@ -27,10 +27,12 @@ def initialize(location, message)
2727 NonConstantClassName = _ = Class . new ( Base )
2828 NonConstantModuleName = _ = Class . new ( Base )
2929 TopLevelMethodDefinition = _ = Class . new ( Base )
30+ TopLevelAttributeDefinition = _ = Class . new ( Base )
3031 UnusedInlineAnnotation = _ = Class . new ( Base )
3132 AnnotationSyntaxError = _ = Class . new ( Base )
3233 MixinMultipleArguments = _ = Class . new ( Base )
3334 MixinNonConstantModule = _ = Class . new ( Base )
35+ AttributeNonSymbolName = _ = Class . new ( Base )
3436 end
3537
3638 def self . parse ( buffer , prism )
@@ -171,7 +173,7 @@ def visit_def_node(node)
171173 end
172174
173175 def visit_call_node ( node )
174- return unless node . receiver . nil? # Only handle top-level calls like include, extend, prepend
176+ return unless node . receiver . nil? # Only handle top-level calls like include, extend, prepend, attr_*
175177
176178 case node . name
177179 when :include , :extend , :prepend
@@ -181,6 +183,19 @@ def visit_call_node(node)
181183 when AST ::Ruby ::Declarations ::ClassDecl , AST ::Ruby ::Declarations ::ModuleDecl
182184 parse_mixin_call ( node )
183185 end
186+ when :attr_reader , :attr_writer , :attr_accessor
187+ return if skip_node? ( node )
188+
189+ case current = current_module
190+ when AST ::Ruby ::Declarations ::ClassDecl , AST ::Ruby ::Declarations ::ModuleDecl
191+ parse_attribute_call ( node )
192+ when nil
193+ # Top-level attribute definition
194+ diagnostics << Diagnostic ::TopLevelAttributeDefinition . new (
195+ rbs_location ( node . message_loc || node . location ) ,
196+ "Top-level attribute definition is not supported"
197+ )
198+ end
184199 else
185200 visit_child_nodes ( node )
186201 end
@@ -244,6 +259,66 @@ def parse_mixin_call(node)
244259 current_module! . members << member
245260 end
246261
262+ def parse_attribute_call ( node )
263+ # Get the name nodes (arguments to attr_*)
264+ unless node . arguments && !node . arguments . arguments . empty?
265+ return # No arguments, nothing to do
266+ end
267+
268+ name_nodes = [ ] #: Array[Prism::SymbolNode]
269+ node . arguments . arguments . each do |arg |
270+ case arg
271+ when Prism ::SymbolNode
272+ name_nodes << arg
273+ else
274+ # Non-symbol argument, report error
275+ diagnostics << Diagnostic ::AttributeNonSymbolName . new (
276+ rbs_location ( arg . location ) ,
277+ "Attribute name must be a symbol"
278+ )
279+ end
280+ end
281+
282+ return if name_nodes . empty?
283+
284+ # Look for leading comment block
285+ leading_block = comments . leading_block! ( node )
286+
287+ # Look for trailing type annotation (#: Type)
288+ trailing_block = comments . trailing_block! ( node . location )
289+ type_annotation = nil
290+
291+ if trailing_block
292+ case annotation = trailing_block . trailing_annotation ( [ ] )
293+ when AST ::Ruby ::Annotations ::NodeTypeAssertion
294+ type_annotation = annotation
295+ when AST ::Ruby ::CommentBlock ::AnnotationSyntaxError
296+ diagnostics << Diagnostic ::AnnotationSyntaxError . new (
297+ annotation . location , "Syntax error: " + annotation . error . error_message
298+ )
299+ end
300+ end
301+
302+ # Report unused leading annotations since @rbs annotations are not used for attributes
303+ if leading_block
304+ report_unused_block ( leading_block )
305+ end
306+
307+ # Create the appropriate member type
308+ member = case node . name
309+ when :attr_reader
310+ AST ::Ruby ::Members ::AttrReaderMember . new ( buffer , node , name_nodes , leading_block , type_annotation )
311+ when :attr_writer
312+ AST ::Ruby ::Members ::AttrWriterMember . new ( buffer , node , name_nodes , leading_block , type_annotation )
313+ when :attr_accessor
314+ AST ::Ruby ::Members ::AttrAccessorMember . new ( buffer , node , name_nodes , leading_block , type_annotation )
315+ else
316+ raise "Unexpected attribute method: #{ node . name } "
317+ end
318+
319+ current_module! . members << member
320+ end
321+
247322 def insert_declaration ( decl )
248323 if current_module
249324 current_module . members << decl
0 commit comments