diff options
Diffstat (limited to 'lib/prism/node_ext.rb')
| -rw-r--r-- | lib/prism/node_ext.rb | 275 |
1 files changed, 237 insertions, 38 deletions
diff --git a/lib/prism/node_ext.rb b/lib/prism/node_ext.rb index 8c09632345..8a6624e76d 100644 --- a/lib/prism/node_ext.rb +++ b/lib/prism/node_ext.rb @@ -1,13 +1,37 @@ # frozen_string_literal: true +# :markup: markdown +#-- +# rbs_inline: enabled +#-- # Here we are reopening the prism module to provide methods on nodes that aren't # templated and are meant as convenience methods. +#++ module Prism + class Node + #: (*String replacements) -> void + def deprecated(*replacements) # :nodoc: + location = caller_locations(1, 1)&.[](0)&.label + suggest = replacements.map { |replacement| "#{self.class}##{replacement}" } + + warn(<<~MSG, uplevel: 1, category: :deprecated) + [deprecation]: #{self.class}##{location} is deprecated and will be \ + removed in the next major version. Use #{suggest.join("/")} instead. + #{(caller(1, 3) || []).join("\n")} + MSG + end + end + module RegularExpressionOptions # :nodoc: # Returns a numeric value that represents the flags that were used to create # the regular expression. - def options - o = flags & (RegularExpressionFlags::IGNORE_CASE | RegularExpressionFlags::EXTENDED | RegularExpressionFlags::MULTI_LINE) + #-- + #: (Integer flags) -> Integer + def self.options(flags) + o = 0 + o |= Regexp::IGNORECASE if flags.anybits?(RegularExpressionFlags::IGNORE_CASE) + o |= Regexp::EXTENDED if flags.anybits?(RegularExpressionFlags::EXTENDED) + o |= Regexp::MULTILINE if flags.anybits?(RegularExpressionFlags::MULTI_LINE) o |= Regexp::FIXEDENCODING if flags.anybits?(RegularExpressionFlags::EUC_JP | RegularExpressionFlags::WINDOWS_31J | RegularExpressionFlags::UTF_8) o |= Regexp::NOENCODING if flags.anybits?(RegularExpressionFlags::ASCII_8BIT) o @@ -15,86 +39,176 @@ module Prism end class InterpolatedMatchLastLineNode < Node - include RegularExpressionOptions + # Returns a numeric value that represents the flags that were used to create + # the regular expression. + #-- + #: () -> Integer + def options + RegularExpressionOptions.options(flags) + end end class InterpolatedRegularExpressionNode < Node - include RegularExpressionOptions + # Returns a numeric value that represents the flags that were used to create + # the regular expression. + #-- + #: () -> Integer + def options + RegularExpressionOptions.options(flags) + end end class MatchLastLineNode < Node - include RegularExpressionOptions + # Returns a numeric value that represents the flags that were used to create + # the regular expression. + #-- + #: () -> Integer + def options + RegularExpressionOptions.options(flags) + end end class RegularExpressionNode < Node - include RegularExpressionOptions + # Returns a numeric value that represents the flags that were used to create + # the regular expression. + #-- + #: () -> Integer + def options + RegularExpressionOptions.options(flags) + end end private_constant :RegularExpressionOptions module HeredocQuery # :nodoc: # Returns true if this node was represented as a heredoc in the source code. - def heredoc? + #-- + #: (String? opening) -> bool? + def self.heredoc?(opening) + # @type self: InterpolatedStringNode | InterpolatedXStringNode | StringNode | XStringNode opening&.start_with?("<<") end end class InterpolatedStringNode < Node - include HeredocQuery + # Returns true if this node was represented as a heredoc in the source code. + #-- + #: () -> bool? + def heredoc? + HeredocQuery.heredoc?(opening) + end end class InterpolatedXStringNode < Node - include HeredocQuery + # Returns true if this node was represented as a heredoc in the source code. + #-- + #: () -> bool? + def heredoc? + HeredocQuery.heredoc?(opening) + end end class StringNode < Node - include HeredocQuery - end + # Returns true if this node was represented as a heredoc in the source code. + #-- + #: () -> bool? + def heredoc? + HeredocQuery.heredoc?(opening) + end - class XStringNode < Node - include HeredocQuery + # Occasionally it's helpful to treat a string as if it were interpolated so + # that there's a consistent interface for working with strings. + #-- + #: () -> InterpolatedStringNode + def to_interpolated + InterpolatedStringNode.new( + source, + -1, + location, + frozen? ? InterpolatedStringNodeFlags::FROZEN : 0, + opening_loc, + [copy(location: content_loc, opening_loc: nil, closing_loc: nil)], + closing_loc + ) + end end - private_constant :HeredocQuery + class XStringNode < Node + # Returns true if this node was represented as a heredoc in the source code. + #-- + #: () -> bool? + def heredoc? + HeredocQuery.heredoc?(opening) + end - class FloatNode < Node - # Returns the value of the node as a Ruby Float. - def value - Float(slice) + # Occasionally it's helpful to treat a string as if it were interpolated so + # that there's a consistent interface for working with strings. + #-- + #: () -> InterpolatedXStringNode + def to_interpolated + InterpolatedXStringNode.new( + source, + -1, + location, + flags, + opening_loc, + [StringNode.new(source, node_id, content_loc, 0, nil, content_loc, nil, unescaped)], + closing_loc + ) end end + private_constant :HeredocQuery + class ImaginaryNode < Node # Returns the value of the node as a Ruby Complex. + #-- + #: () -> Complex def value Complex(0, numeric.value) end end - class IntegerNode < Node - # Returns the value of the node as a Ruby Integer. - def value - Integer(slice) - end - end - class RationalNode < Node # Returns the value of the node as a Ruby Rational. + #-- + #: () -> Rational def value - Rational(slice.chomp("r")) + Rational(numerator, denominator) end end class ConstantReadNode < Node # Returns the list of parts for the full name of this constant. # For example: [:Foo] + #-- + #: () -> Array[Symbol] + def full_name_parts + [name] + end + + # Returns the full name of this constant. For example: "Foo" + #-- + #: () -> String + def full_name + name.to_s + end + end + + class ConstantWriteNode < Node + # Returns the list of parts for the full name of this constant. + # For example: [:Foo] + #-- + #: () -> Array[Symbol] def full_name_parts [name] end # Returns the full name of this constant. For example: "Foo" + #-- + #: () -> String def full_name - name.name + name.to_s end end @@ -107,18 +221,30 @@ module Prism # local variable class DynamicPartsInConstantPathError < StandardError; end + # An error class raised when error recovery nodes are found while computing a + # constant path's full name. For example: + # Foo:: -> raises because the constant path is missing the last part + class ErrorRecoveryNodesInConstantPathError < StandardError; end + # Returns the list of parts for the full name of this constant path. # For example: [:Foo, :Bar] + #-- + #: () -> Array[Symbol] def full_name_parts - parts = [child.name] - current = parent + parts = [] #: Array[Symbol] + current = self #: node? while current.is_a?(ConstantPathNode) - parts.unshift(current.child.name) + name = current.name + if name.nil? + raise ErrorRecoveryNodesInConstantPathError, "Constant path contains error recovery nodes. Cannot compute full name" + end + + parts.unshift(name) current = current.parent end - unless current.is_a?(ConstantReadNode) + if !current.is_a?(ConstantReadNode) && !current.nil? raise DynamicPartsInConstantPathError, "Constant path contains dynamic parts. Cannot compute full name" end @@ -126,6 +252,8 @@ module Prism end # Returns the full name of this constant path. For example: "Foo::Bar" + #-- + #: () -> String def full_name full_name_parts.join("::") end @@ -134,35 +262,83 @@ module Prism class ConstantPathTargetNode < Node # Returns the list of parts for the full name of this constant path. # For example: [:Foo, :Bar] + #-- + #: () -> Array[Symbol] def full_name_parts - (parent&.full_name_parts || [:""]).push(child.name) + parts = + case (parent = self.parent) + when ConstantPathNode, ConstantReadNode + parent.full_name_parts + when nil + [:""] + else + # e.g. self::Foo, (var)::Bar = baz + raise ConstantPathNode::DynamicPartsInConstantPathError, "Constant target path contains dynamic parts. Cannot compute full name" + end + + if (name = self.name).nil? + raise ConstantPathNode::ErrorRecoveryNodesInConstantPathError, "Constant target path contains error recovery nodes. Cannot compute full name" + end + + parts.push(name) end # Returns the full name of this constant path. For example: "Foo::Bar" + #-- + #: () -> String def full_name full_name_parts.join("::") end end + class ConstantTargetNode < Node + # Returns the list of parts for the full name of this constant. + # For example: [:Foo] + #-- + #: () -> Array[Symbol] + def full_name_parts + [name] + end + + # Returns the full name of this constant. For example: "Foo" + #-- + #: () -> String + def full_name + name.to_s + end + end + class ParametersNode < Node # Mirrors the Method#parameters method. + #-- + #: () -> Array[[Symbol, Symbol] | [Symbol]] def signature - names = [] + names = [] #: Array[[Symbol, Symbol] | [Symbol]] requireds.each do |param| names << (param.is_a?(MultiTargetNode) ? [:req] : [:req, param.name]) end optionals.each { |param| names << [:opt, param.name] } - names << [:rest, rest.name || :*] if rest + + if (rest = self.rest).is_a?(RestParameterNode) + names << [:rest, rest.name || :*] + end posts.each do |param| - names << (param.is_a?(MultiTargetNode) ? [:req] : [:req, param.name]) + case param + when MultiTargetNode + names << [:req] + when ErrorRecoveryNode + raise "Invalid syntax" + else + names << [:req, param.name] + end end # Regardless of the order in which the keywords were defined, the required # keywords always come first followed by the optional keywords. - keyopt = [] + keyopt = [] #: Array[OptionalKeywordParameterNode] keywords.each do |param| if param.is_a?(OptionalKeywordParameterNode) keyopt << param @@ -173,7 +349,7 @@ module Prism keyopt.each { |param| names << [:key, param.name] } - case keyword_rest + case (keyword_rest = self.keyword_rest) when ForwardingParameterNode names.concat([[:rest, :*], [:keyrest, :**], [:block, :&]]) when KeywordRestParameterNode @@ -182,8 +358,31 @@ module Prism names << [:nokey] end - names << [:block, block.name || :&] if block + case (block = self.block) + when BlockParameterNode + names << [:block, block.name || :&] + when NoBlockParameterNode + names << [:noblock] + end + names end end + + class CallNode < Node + # When a call node has the attribute_write flag set, it means that the call + # is using the attribute write syntax. This is either a method call to []= + # or a method call to a method that ends with =. Either way, the = sign is + # present in the source. + # + # Prism returns the message_loc _without_ the = sign attached, because there + # can be any amount of space between the message and the = sign. However, + # sometimes you want the location of the full message including the inner + # space and the = sign. This method provides that. + #-- + #: () -> Location? + def full_message_loc + attribute_write? ? message_loc&.adjoin("=") : message_loc + end + end end |
