# encoding: utf-8 require 'astrolabe/builder' require 'digest/md5' module RuboCop # ProcessedSource contains objects which are generated by Parser # and other information such as disabled lines for cops. # It also provides a convenient way to access source lines. class ProcessedSource STRING_SOURCE_NAME = '(string)' attr_reader :path, :buffer, :ast, :comments, :tokens, :diagnostics, :parser_error, :raw_source def self.from_file(path) new(File.read(path), path) end def initialize(source, path = nil) # In Ruby 2, source code encoding defaults to UTF-8. We follow the same # principle regardless of which Ruby version we're running under. # Encoding comments will override this setting. source.force_encoding(Encoding::UTF_8) @raw_source = source @path = path @diagnostics = [] parse(source) end def comment_config @comment_config ||= CommentConfig.new(self) end def disabled_line_ranges comment_config.cop_disabled_line_ranges end # Returns the source lines, line break characters removed, excluding a # possible __END__ and everything that comes after. def lines @lines ||= begin all_lines = raw_source.lines.map(&:chomp) last_token_line = tokens.any? ? tokens.last.pos.line : all_lines.count result = [] all_lines.each_with_index do |line, ix| break if ix >= last_token_line && line == '__END__' result << line end result end end def [](*args) lines[*args] end def valid_syntax? return false if @parser_error @diagnostics.none? { |d| [:error, :fatal].include?(d.level) } end # Raw source checksum for tracking infinite loops. def checksum Digest::MD5.hexdigest(@raw_source) end private def parse(source) buffer_name = @path || STRING_SOURCE_NAME @buffer = Parser::Source::Buffer.new(buffer_name, 1) begin @buffer.source = source rescue EncodingError => error @parser_error = error return end parser = create_parser begin @ast, @comments, tokens = parser.tokenize(@buffer) rescue Parser::SyntaxError # rubocop:disable Lint/HandleExceptions # All errors are in diagnostics. No need to handle exception. end @tokens = tokens.map { |t| Token.from_parser_token(t) } if tokens end def create_parser builder = Astrolabe::Builder.new Parser::CurrentRuby.new(builder).tap do |parser| # On JRuby and Rubinius, there's a risk that we hang in tokenize() if we # don't set the all errors as fatal flag. The problem is caused by a bug # in Racc that is discussed in issue #93 of the whitequark/parser # project on GitHub. parser.diagnostics.all_errors_are_fatal = (RUBY_ENGINE != 'ruby') parser.diagnostics.ignore_warnings = false parser.diagnostics.consumer = lambda do |diagnostic| @diagnostics << diagnostic end end end end end