lib/yard/handlers/ruby/legacy/base.rb in yard-0.9.18 vs lib/yard/handlers/ruby/legacy/base.rb in yard-0.9.19

- old
+ new

@@ -1,245 +1,245 @@ -# frozen_string_literal: true -module YARD - module Handlers - module Ruby::Legacy - # This is the base handler for the legacy parser. To implement a legacy - # handler, subclass this class. - # - # @abstract (see Ruby::Base) - class Base < Handlers::Base - # For tokens like TkDEF, TkCLASS, etc. - include YARD::Parser::Ruby::Legacy::RubyToken - - # @return [Boolean] whether or not a {Parser::Ruby::Legacy::Statement} object should be handled - # by this handler. - def self.handles?(stmt) - handlers.any? do |a_handler| - case a_handler - when String - stmt.tokens.first.text == a_handler - when Regexp - stmt.tokens.to_s =~ a_handler - else - a_handler == stmt.tokens.first.class - end - end - end - - # Parses a statement's block with a set of state values. If the - # statement has no block, nothing happens. A description of state - # values can be found at {Handlers::Base#push_state} - # - # @param [Hash] opts State options - # @option opts (see Handlers::Base#push_state) - # @see Handlers::Base#push_state #push_state - def parse_block(opts = {}) - push_state(opts) do - if statement.block - blk = Parser::Ruby::Legacy::StatementList.new(statement.block) - parser.process(blk) - end - end - end - - def call_params - if statement.tokens.first.is_a?(TkDEF) - extract_method_details.last.map(&:first) - else - tokens = statement.tokens[1..-1] - tokval_list(tokens, :attr, :identifier, TkId).map(&:to_s) - end - end - - def caller_method - if statement.tokens.first.is_a?(TkIDENTIFIER) - statement.tokens.first.text - elsif statement.tokens.first.is_a?(TkDEF) - extract_method_details.first - end - end - - private - - # Extracts method information for macro expansion only - # - # @todo This is a duplicate implementation of {MethodHandler}. Refactor. - # @return [Array<String,Array<Array<String>>>] the method name followed by method - # arguments (name and optional value) - def extract_method_details - if statement.tokens.to_s =~ /^def\s+(#{METHODMATCH})(?:(?:\s+|\s*\()(.*)(?:\)\s*$)?)?/m - meth = $1 - args = $2 - meth.gsub!(/\s+/, '') - args = tokval_list(Parser::Ruby::Legacy::TokenList.new(args), :all) - args.map! {|a| k, v = *a.split('=', 2); [k.strip, (v ? v.strip : nil)] } if args - meth = $` if meth =~ /(?:#{NSEPQ}|#{CSEPQ})([^#{NSEP}#{CSEPQ}]+)$/ - [meth, args] - end - end - - # The string value of a token. For example, the return value for the symbol :sym - # would be :sym. The return value for a string +"foo #{ bar}"+ would be the literal - # +"foo #{ bar}"+ without any interpolation. The return value of the identifier - # 'test' would be the same value: 'test'. Here is a list of common types and - # their return values: - # - # @example - # tokval(TokenList.new('"foo"').first) => "foo" - # tokval(TokenList.new(':foo').first) => :foo - # tokval(TokenList.new('CONSTANT').first, RubyToken::TkId) => "CONSTANT" - # tokval(TokenList.new('identifier').first, RubyToken::TkId) => "identifier" - # tokval(TokenList.new('3.25').first) => 3.25 - # tokval(TokenList.new('/xyz/i').first) => /xyz/i - # - # @param [Token] token The token of the class - # - # @param [Array<Class<Token>>, Symbol] accepted_types - # The allowed token types that this token can be. Defaults to [{TkVal}]. - # A list of types would be, for example, [+TkSTRING+, +TkSYMBOL+], to return - # the token's value if it is either of those types. If +TkVal+ is accepted, - # +TkNode+ is also accepted. - # - # Certain symbol keys are allowed to specify multiple types in one fell swoop. - # These symbols are: - # :string => +TkSTRING+, +TkDSTRING+, +TkDXSTRING+ and +TkXSTRING+ - # :attr => +TkSYMBOL+ and +TkSTRING+ - # :identifier => +TkIDENTIFIER, +TkFID+ and +TkGVAR+. - # :number => +TkFLOAT+, +TkINTEGER+ - # - # @return [Object] if the token is one of the accepted types, in its real value form. - # It should be noted that identifiers and constants are kept in String form. - # @return [nil] if the token is not any of the specified accepted types - def tokval(token, *accepted_types) - accepted_types = [TkVal] if accepted_types.empty? - accepted_types.push(TkNode) if accepted_types.include? TkVal - - if accepted_types.include?(:attr) - accepted_types.push(TkSTRING, TkSYMBOL) - end - - if accepted_types.include?(:string) - accepted_types.push(TkSTRING, TkDSTRING, TkXSTRING, TkDXSTRING) - end - - if accepted_types.include?(:identifier) - accepted_types.push(TkIDENTIFIER, TkFID, TkGVAR) - end - - if accepted_types.include?(:number) - accepted_types.push(TkFLOAT, TkINTEGER) - end - - return unless accepted_types.any? {|t| t === token } - - case token - when TkSTRING, TkDSTRING, TkXSTRING, TkDXSTRING - token.text[1..-2] - when TkSYMBOL - token.text[1..-1].to_sym - when TkFLOAT - token.text.to_f - when TkINTEGER - token.text.to_i - when TkREGEXP - token.text =~ %r{\A/(.+)/([^/])\Z} - Regexp.new($1, $2) - when TkTRUE - true - when TkFALSE - false - when TkNIL - nil - else - token.text - end - end - - # Returns a list of symbols or string values from a statement. - # The list must be a valid comma delimited list, and values - # will only be returned to the end of the list only. - # - # Example: - # attr_accessor :a, 'b', :c, :d => ['a', 'b', 'c', 'd'] - # attr_accessor 'a', UNACCEPTED_TYPE, 'c' => ['a', 'c'] - # - # The tokval list of a {Parser::Ruby::Legacy::TokenList} of the above - # code would be the {#tokval} value of :a, 'b', - # :c and :d. - # - # It should also be noted that this function stops immediately at - # any ruby keyword encountered: - # "attr_accessor :a, :b, :c if x == 5" => ['a', 'b', 'c'] - # - # @param [TokenList] tokenlist The list of tokens to process. - # @param [Array<Class<Token>>] accepted_types passed to {#tokval} - # @return [Array<String>] the list of tokvalues in the list. - # @return [Array<EMPTY>] if there are no symbols or Strings in the list - # @see #tokval - def tokval_list(tokenlist, *accepted_types) - return [] unless tokenlist - out = [[]] - parencount = 0 - beforeparen = 0 - needcomma = false - seen_comma = true - tokenlist.each do |token| - tokval = accepted_types == [:all] ? token.text : tokval(token, *accepted_types) - parencond = !out.last.empty? && !tokval.nil? - # puts "#{seen_comma.inspect} #{parencount} #{token.class.class_name} #{out.inspect}" - case token - when TkCOMMA - if parencount == 0 - out << [] unless out.last.empty? - needcomma = false - seen_comma = true - elsif parencond - out.last << token.text - end - when TkLPAREN - if seen_comma - beforeparen += 1 - else - parencount += 1 - out.last << token.text if parencond - end - when TkRPAREN - if beforeparen > 0 - beforeparen -= 1 - else - out.last << token.text if parencount > 0 && !tokval.nil? - parencount -= 1 - end - when TkLBRACE, TkLBRACK, TkDO - parencount += 1 - out.last << token.text unless tokval.nil? - when TkRBRACE, TkRBRACK, TkEND - out.last << token.text unless tokval.nil? - parencount -= 1 - else - break if TkKW === token && ![TkTRUE, TkFALSE, TkSUPER, TkSELF, TkNIL].include?(token.class) - - seen_comma = false unless TkWhitespace === token - if parencount == 0 - next if needcomma - next if TkWhitespace === token - if !tokval.nil? - out.last << tokval - else - out.last.clear - needcomma = true - end - elsif parencond - needcomma = true - out.last << token.text - end - end - - break if beforeparen == 0 && parencount < 0 - end - # Flatten any single element lists - out.map {|e| e.empty? ? nil : (e.size == 1 ? e.pop : e.flatten.join) }.compact - end - end - end - end -end +# frozen_string_literal: true +module YARD + module Handlers + module Ruby::Legacy + # This is the base handler for the legacy parser. To implement a legacy + # handler, subclass this class. + # + # @abstract (see Ruby::Base) + class Base < Handlers::Base + # For tokens like TkDEF, TkCLASS, etc. + include YARD::Parser::Ruby::Legacy::RubyToken + + # @return [Boolean] whether or not a {Parser::Ruby::Legacy::Statement} object should be handled + # by this handler. + def self.handles?(stmt) + handlers.any? do |a_handler| + case a_handler + when String + stmt.tokens.first.text == a_handler + when Regexp + stmt.tokens.to_s =~ a_handler + else + a_handler == stmt.tokens.first.class + end + end + end + + # Parses a statement's block with a set of state values. If the + # statement has no block, nothing happens. A description of state + # values can be found at {Handlers::Base#push_state} + # + # @param [Hash] opts State options + # @option opts (see Handlers::Base#push_state) + # @see Handlers::Base#push_state #push_state + def parse_block(opts = {}) + push_state(opts) do + if statement.block + blk = Parser::Ruby::Legacy::StatementList.new(statement.block) + parser.process(blk) + end + end + end + + def call_params + if statement.tokens.first.is_a?(TkDEF) + extract_method_details.last.map(&:first) + else + tokens = statement.tokens[1..-1] + tokval_list(tokens, :attr, :identifier, TkId).map(&:to_s) + end + end + + def caller_method + if statement.tokens.first.is_a?(TkIDENTIFIER) + statement.tokens.first.text + elsif statement.tokens.first.is_a?(TkDEF) + extract_method_details.first + end + end + + private + + # Extracts method information for macro expansion only + # + # @todo This is a duplicate implementation of {MethodHandler}. Refactor. + # @return [Array<String,Array<Array<String>>>] the method name followed by method + # arguments (name and optional value) + def extract_method_details + if statement.tokens.to_s =~ /^def\s+(#{METHODMATCH})(?:(?:\s+|\s*\()(.*)(?:\)\s*$)?)?/m + meth = $1 + args = $2 + meth.gsub!(/\s+/, '') + args = tokval_list(Parser::Ruby::Legacy::TokenList.new(args), :all) + args.map! {|a| k, v = *a.split('=', 2); [k.strip, (v ? v.strip : nil)] } if args + meth = $` if meth =~ /(?:#{NSEPQ}|#{CSEPQ})([^#{NSEP}#{CSEPQ}]+)$/ + [meth, args] + end + end + + # The string value of a token. For example, the return value for the symbol :sym + # would be :sym. The return value for a string +"foo #{ bar}"+ would be the literal + # +"foo #{ bar}"+ without any interpolation. The return value of the identifier + # 'test' would be the same value: 'test'. Here is a list of common types and + # their return values: + # + # @example + # tokval(TokenList.new('"foo"').first) => "foo" + # tokval(TokenList.new(':foo').first) => :foo + # tokval(TokenList.new('CONSTANT').first, RubyToken::TkId) => "CONSTANT" + # tokval(TokenList.new('identifier').first, RubyToken::TkId) => "identifier" + # tokval(TokenList.new('3.25').first) => 3.25 + # tokval(TokenList.new('/xyz/i').first) => /xyz/i + # + # @param [Token] token The token of the class + # + # @param [Array<Class<Token>>, Symbol] accepted_types + # The allowed token types that this token can be. Defaults to [{TkVal}]. + # A list of types would be, for example, [+TkSTRING+, +TkSYMBOL+], to return + # the token's value if it is either of those types. If +TkVal+ is accepted, + # +TkNode+ is also accepted. + # + # Certain symbol keys are allowed to specify multiple types in one fell swoop. + # These symbols are: + # :string => +TkSTRING+, +TkDSTRING+, +TkDXSTRING+ and +TkXSTRING+ + # :attr => +TkSYMBOL+ and +TkSTRING+ + # :identifier => +TkIDENTIFIER, +TkFID+ and +TkGVAR+. + # :number => +TkFLOAT+, +TkINTEGER+ + # + # @return [Object] if the token is one of the accepted types, in its real value form. + # It should be noted that identifiers and constants are kept in String form. + # @return [nil] if the token is not any of the specified accepted types + def tokval(token, *accepted_types) + accepted_types = [TkVal] if accepted_types.empty? + accepted_types.push(TkNode) if accepted_types.include? TkVal + + if accepted_types.include?(:attr) + accepted_types.push(TkSTRING, TkSYMBOL) + end + + if accepted_types.include?(:string) + accepted_types.push(TkSTRING, TkDSTRING, TkXSTRING, TkDXSTRING) + end + + if accepted_types.include?(:identifier) + accepted_types.push(TkIDENTIFIER, TkFID, TkGVAR) + end + + if accepted_types.include?(:number) + accepted_types.push(TkFLOAT, TkINTEGER) + end + + return unless accepted_types.any? {|t| t === token } + + case token + when TkSTRING, TkDSTRING, TkXSTRING, TkDXSTRING + token.text[1..-2] + when TkSYMBOL + token.text[1..-1].to_sym + when TkFLOAT + token.text.to_f + when TkINTEGER + token.text.to_i + when TkREGEXP + token.text =~ %r{\A/(.+)/([^/])\Z} + Regexp.new($1, $2) + when TkTRUE + true + when TkFALSE + false + when TkNIL + nil + else + token.text + end + end + + # Returns a list of symbols or string values from a statement. + # The list must be a valid comma delimited list, and values + # will only be returned to the end of the list only. + # + # Example: + # attr_accessor :a, 'b', :c, :d => ['a', 'b', 'c', 'd'] + # attr_accessor 'a', UNACCEPTED_TYPE, 'c' => ['a', 'c'] + # + # The tokval list of a {Parser::Ruby::Legacy::TokenList} of the above + # code would be the {#tokval} value of :a, 'b', + # :c and :d. + # + # It should also be noted that this function stops immediately at + # any ruby keyword encountered: + # "attr_accessor :a, :b, :c if x == 5" => ['a', 'b', 'c'] + # + # @param [TokenList] tokenlist The list of tokens to process. + # @param [Array<Class<Token>>] accepted_types passed to {#tokval} + # @return [Array<String>] the list of tokvalues in the list. + # @return [Array<EMPTY>] if there are no symbols or Strings in the list + # @see #tokval + def tokval_list(tokenlist, *accepted_types) + return [] unless tokenlist + out = [[]] + parencount = 0 + beforeparen = 0 + needcomma = false + seen_comma = true + tokenlist.each do |token| + tokval = accepted_types == [:all] ? token.text : tokval(token, *accepted_types) + parencond = !out.last.empty? && !tokval.nil? + # puts "#{seen_comma.inspect} #{parencount} #{token.class.class_name} #{out.inspect}" + case token + when TkCOMMA + if parencount == 0 + out << [] unless out.last.empty? + needcomma = false + seen_comma = true + elsif parencond + out.last << token.text + end + when TkLPAREN + if seen_comma + beforeparen += 1 + else + parencount += 1 + out.last << token.text if parencond + end + when TkRPAREN + if beforeparen > 0 + beforeparen -= 1 + else + out.last << token.text if parencount > 0 && !tokval.nil? + parencount -= 1 + end + when TkLBRACE, TkLBRACK, TkDO + parencount += 1 + out.last << token.text unless tokval.nil? + when TkRBRACE, TkRBRACK, TkEND + out.last << token.text unless tokval.nil? + parencount -= 1 + else + break if TkKW === token && ![TkTRUE, TkFALSE, TkSUPER, TkSELF, TkNIL].include?(token.class) + + seen_comma = false unless TkWhitespace === token + if parencount == 0 + next if needcomma + next if TkWhitespace === token + if !tokval.nil? + out.last << tokval + else + out.last.clear + needcomma = true + end + elsif parencond + needcomma = true + out.last << token.text + end + end + + break if beforeparen == 0 && parencount < 0 + end + # Flatten any single element lists + out.map {|e| e.empty? ? nil : (e.size == 1 ? e.pop : e.flatten.join) }.compact + end + end + end + end +end