lib/cucumber/core/ast/location.rb
# frozen_string_literal: true require 'forwardable' require 'cucumber/core/platform' module Cucumber module Core module Ast IncompatibleLocations = Class.new(StandardError) module Location def self.of_caller(additional_depth = 0) from_file_colon_line(*caller[1 + additional_depth]) end def self.from_file_colon_line(file_colon_line) file, raw_line = file_colon_line.match(/(.*):(\d+)/)[1..2] from_source_location(file, raw_line.to_i) end def self.from_source_location(file, line) file = File.expand_path(file) pwd = File.expand_path(Dir.pwd) pwd.force_encoding(file.encoding) if file.index(pwd) file = file[pwd.length+1..-1] elsif file =~ /.*\/gems\/(.*\.rb)$/ file = $1 end new(file, line) end def self.new(file, raw_lines=nil) file || raise(ArgumentError, "file is mandatory") if raw_lines Precise.new(file, Lines.new(raw_lines)) else Wildcard.new(file) end end def self.merge(*locations) locations.reduce do |a, b| a + b end end class Wildcard < Struct.new(:file) def to_s file end def match?(other) other.file == file end def include?(lines) true end end class Precise < Struct.new(:file, :lines) def include?(other_lines) lines.include?(other_lines) end def line lines.first end def match?(other) return false unless other.file == file other.include?(lines) end def to_s [file, lines.to_s].join(":") end def hash self.class.hash ^ to_s.hash end def to_str to_s end def on_line(new_line) Location.new(file, new_line) end def +(other) raise IncompatibleLocations if file != other.file Precise.new(file, lines + other.lines) end def inspect "<#{self.class}: #{to_s}>" end end require 'set' class Lines < Struct.new(:data) protected :data def initialize(raw_data) super Array(raw_data).to_set end def first data.first end def include?(other) other.data.subset?(data) || data.subset?(other.data) end def +(more_lines) new_data = data + more_lines.data self.class.new(new_data) end def to_s return first.to_s if data.length == 1 return "#{data.min}..#{data.max}" if range? data.to_a.join(":") end def inspect "<#{self.class}: #{to_s}>" end protected def range? data.size == (data.max - data.min + 1) end end end module HasLocation def file_colon_line location.to_s end def file location.file end def line location.line end def location raise('Please set @location in the constructor') unless defined?(@location) @location end def all_locations @all_locations ||= Location.merge([location] + attributes.map { |node| node.all_locations }.flatten) end def attributes [tags, comments, multiline_arg].flatten end def tags # will be overriden by nodes that actually have tags [] end def comments # will be overriden by nodes that actually have comments [] end def multiline_arg # will be overriden by nodes that actually have a multiline_argument EmptyMultilineArgument.new end end end end end