1## 2# Abstract class representing either a method or an attribute. 3 4class RDoc::MethodAttr < RDoc::CodeObject 5 6 include Comparable 7 8 ## 9 # Name of this method/attribute. 10 11 attr_accessor :name 12 13 ## 14 # public, protected, private 15 16 attr_accessor :visibility 17 18 ## 19 # Is this a singleton method/attribute? 20 21 attr_accessor :singleton 22 23 ## 24 # Source file token stream 25 26 attr_reader :text 27 28 ## 29 # Array of other names for this method/attribute 30 31 attr_reader :aliases 32 33 ## 34 # The method/attribute we're aliasing 35 36 attr_accessor :is_alias_for 37 38 #-- 39 # The attributes below are for AnyMethod only. 40 # They are left here for the time being to 41 # allow ri to operate. 42 # TODO modify ri to avoid calling these on attributes. 43 #++ 44 45 ## 46 # Parameters yielded by the called block 47 48 attr_reader :block_params 49 50 ## 51 # Parameters for this method 52 53 attr_accessor :params 54 55 ## 56 # Different ways to call this method 57 58 attr_accessor :call_seq 59 60 ## 61 # The call_seq or the param_seq with method name, if there is no call_seq. 62 63 attr_reader :arglists 64 65 ## 66 # Pretty parameter list for this method 67 68 attr_reader :param_seq 69 70 71 ## 72 # Creates a new MethodAttr from token stream +text+ and method or attribute 73 # name +name+. 74 # 75 # Usually this is called by super from a subclass. 76 77 def initialize text, name 78 super() 79 80 @text = text 81 @name = name 82 83 @aliases = [] 84 @is_alias_for = nil 85 @parent_name = nil 86 @singleton = nil 87 @visibility = :public 88 @see = false 89 90 @arglists = nil 91 @block_params = nil 92 @call_seq = nil 93 @param_seq = nil 94 @params = nil 95 end 96 97 ## 98 # Order by #singleton then #name 99 100 def <=>(other) 101 [ @singleton ? 0 : 1, name] <=> 102 [other.singleton ? 0 : 1, other.name] 103 end 104 105 def == other # :nodoc: 106 super or self.class == other.class and full_name == other.full_name 107 end 108 109 ## 110 # A method/attribute is documented if any of the following is true: 111 # - it was marked with :nodoc:; 112 # - it has a comment; 113 # - it is an alias for a documented method; 114 # - it has a +#see+ method that is documented. 115 116 def documented? 117 super or 118 (is_alias_for and is_alias_for.documented?) or 119 (see and see.documented?) 120 end 121 122 ## 123 # A method/attribute to look at, 124 # in particular if this method/attribute has no documentation. 125 # 126 # It can be a method/attribute of the superclass or of an included module, 127 # including the Kernel module, which is always appended to the included 128 # modules. 129 # 130 # Returns +nil+ if there is no such method/attribute. 131 # The +#is_alias_for+ method/attribute, if any, is not included. 132 # 133 # Templates may generate a "see also ..." if this method/attribute 134 # has documentation, and "see ..." if it does not. 135 136 def see 137 @see = find_see if @see == false 138 @see 139 end 140 141 ## 142 # Sets the store for this class or module and its contained code objects. 143 144 def store= store 145 super 146 147 @file = @store.add_file @file.full_name if @file 148 end 149 150 def find_see # :nodoc: 151 return nil if singleton || is_alias_for 152 153 # look for the method 154 other = find_method_or_attribute name 155 return other if other 156 157 # if it is a setter, look for a getter 158 return nil unless name =~ /[a-z_]=$/i # avoid == or === 159 return find_method_or_attribute name[0..-2] 160 end 161 162 def find_method_or_attribute name # :nodoc: 163 return nil unless parent.respond_to? :ancestors 164 165 searched = parent.ancestors 166 kernel = @store.modules_hash['Kernel'] 167 168 searched << kernel if kernel && 169 parent != kernel && !searched.include?(kernel) 170 171 searched.each do |ancestor| 172 next if parent == ancestor 173 next if String === ancestor 174 175 other = ancestor.find_method_named('#' << name) || 176 ancestor.find_attribute_named(name) 177 178 return other if other 179 end 180 181 nil 182 end 183 184 ## 185 # Abstract method. Contexts in their building phase call this 186 # to register a new alias for this known method/attribute. 187 # 188 # - creates a new AnyMethod/Attribute named <tt>an_alias.new_name</tt>; 189 # - adds +self+ as an alias for the new method or attribute 190 # - adds the method or attribute to #aliases 191 # - adds the method or attribute to +context+. 192 193 def add_alias(an_alias, context) 194 raise NotImplementedError 195 end 196 197 ## 198 # HTML fragment reference for this method 199 200 def aref 201 type = singleton ? 'c' : 'i' 202 # % characters are not allowed in html names => dash instead 203 "#{aref_prefix}-#{type}-#{html_name}" 204 end 205 206 ## 207 # Prefix for +aref+, defined by subclasses. 208 209 def aref_prefix 210 raise NotImplementedError 211 end 212 213 ## 214 # Attempts to sanitize the content passed by the ruby parser: 215 # remove outer parentheses, etc. 216 217 def block_params=(value) 218 # 'yield.to_s' or 'assert yield, msg' 219 return @block_params = '' if value =~ /^[\.,]/ 220 221 # remove trailing 'if/unless ...' 222 return @block_params = '' if value =~ /^(if|unless)\s/ 223 224 value = $1.strip if value =~ /^(.+)\s(if|unless)\s/ 225 226 # outer parentheses 227 value = $1 if value =~ /^\s*\((.*)\)\s*$/ 228 value = value.strip 229 230 # proc/lambda 231 return @block_params = $1 if value =~ /^(proc|lambda)(\s*\{|\sdo)/ 232 233 # surrounding +...+ or [...] 234 value = $1.strip if value =~ /^\+(.*)\+$/ 235 value = $1.strip if value =~ /^\[(.*)\]$/ 236 237 return @block_params = '' if value.empty? 238 239 # global variable 240 return @block_params = 'str' if value =~ /^\$[&0-9]$/ 241 242 # wipe out array/hash indices 243 value.gsub!(/(\w)\[[^\[]+\]/, '\1') 244 245 # remove @ from class/instance variables 246 value.gsub!(/@@?([a-z0-9_]+)/, '\1') 247 248 # method calls => method name 249 value.gsub!(/([A-Z:a-z0-9_]+)\.([a-z0-9_]+)(\s*\(\s*[a-z0-9_.,\s]*\s*\)\s*)?/) do 250 case $2 251 when 'to_s' then $1 252 when 'const_get' then 'const' 253 when 'new' then 254 $1.split('::').last. # ClassName => class_name 255 gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2'). 256 gsub(/([a-z\d])([A-Z])/,'\1_\2'). 257 downcase 258 else 259 $2 260 end 261 end 262 263 # class prefixes 264 value.gsub!(/[A-Za-z0-9_:]+::/, '') 265 266 # simple expressions 267 value = $1 if value =~ /^([a-z0-9_]+)\s*[-*+\/]/ 268 269 @block_params = value.strip 270 end 271 272 ## 273 # HTML id-friendly method/attribute name 274 275 def html_name 276 require 'cgi' 277 278 CGI.escape(@name.gsub('-', '-2D')).gsub('%','-').sub(/^-/, '') 279 end 280 281 ## 282 # Full method/attribute name including namespace 283 284 def full_name 285 @full_name ||= "#{parent_name}#{pretty_name}" 286 end 287 288 def inspect # :nodoc: 289 alias_for = @is_alias_for ? " (alias for #{@is_alias_for.name})" : nil 290 visibility = self.visibility 291 visibility = "forced #{visibility}" if force_documentation 292 "#<%s:0x%x %s (%s)%s>" % [ 293 self.class, object_id, 294 full_name, 295 visibility, 296 alias_for, 297 ] 298 end 299 300 ## 301 # '::' for a class method/attribute, '#' for an instance method. 302 303 def name_prefix 304 @singleton ? '::' : '#' 305 end 306 307 ## 308 # Name for output to HTML. For class methods the full name with a "." is 309 # used like +SomeClass.method_name+. For instance methods the class name is 310 # used if +context+ does not match the parent. 311 # 312 # This is to help prevent people from using :: to call class methods. 313 314 def output_name context 315 return "#{name_prefix}#{@name}" if context == parent 316 317 "#{parent_name}#{@singleton ? '.' : '#'}#{@name}" 318 end 319 320 ## 321 # Method/attribute name with class/instance indicator 322 323 def pretty_name 324 "#{name_prefix}#{@name}" 325 end 326 327 ## 328 # Type of method/attribute (class or instance) 329 330 def type 331 singleton ? 'class' : 'instance' 332 end 333 334 ## 335 # Path to this method for use with HTML generator output. 336 337 def path 338 "#{@parent.path}##{aref}" 339 end 340 341 ## 342 # Name of our parent with special handling for un-marshaled methods 343 344 def parent_name 345 @parent_name || super 346 end 347 348 def pretty_print q # :nodoc: 349 alias_for = @is_alias_for ? "alias for #{@is_alias_for.name}" : nil 350 351 q.group 2, "[#{self.class.name} #{full_name} #{visibility}", "]" do 352 if alias_for then 353 q.breakable 354 q.text alias_for 355 end 356 357 if text then 358 q.breakable 359 q.text "text:" 360 q.breakable 361 q.pp @text 362 end 363 364 unless comment.empty? then 365 q.breakable 366 q.text "comment:" 367 q.breakable 368 q.pp @comment 369 end 370 end 371 end 372 373 ## 374 # Used by RDoc::Generator::JsonIndex to create a record for the search 375 # engine. 376 377 def search_record 378 [ 379 @name, 380 full_name, 381 @name, 382 @parent.full_name, 383 path, 384 params, 385 snippet(@comment), 386 ] 387 end 388 389 def to_s # :nodoc: 390 if @is_alias_for 391 "#{self.class.name}: #{full_name} -> #{is_alias_for}" 392 else 393 "#{self.class.name}: #{full_name}" 394 end 395 end 396 397end 398 399