class RuboCop::AST::ProcessedSource

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.

Constants

INVALID_LEVELS
STRING_SOURCE_NAME

@api private

Attributes

ast[R]
buffer[R]
comments[R]
diagnostics[R]
parser_error[R]
path[R]
raw_source[R]
ruby_version[R]
tokens[R]

Public Class Methods

from_file(path, ruby_version) click to toggle source
# File lib/rubocop/ast/processed_source.rb, line 18
def self.from_file(path, ruby_version)
  file = File.read(path, mode: 'rb')
  new(file, ruby_version, path)
end
new(source, ruby_version, path = nil) click to toggle source
# File lib/rubocop/ast/processed_source.rb, line 26
def initialize(source, ruby_version, path = nil)
  # Defaults source encoding to UTF-8, regardless of the encoding it has
  # been read with, which could be non-utf8 depending on the default
  # external encoding.
  source.force_encoding(Encoding::UTF_8) unless source.encoding == Encoding::UTF_8

  @raw_source = source
  @path = path
  @diagnostics = []
  @ruby_version = ruby_version
  @parser_error = nil

  parse(source, ruby_version)
end

Public Instance Methods

[](*args) click to toggle source
# File lib/rubocop/ast/processed_source.rb, line 63
def [](*args)
  lines[*args]
end
ast_with_comments() click to toggle source
# File lib/rubocop/ast/processed_source.rb, line 41
def ast_with_comments
  return if !ast || !comments

  @ast_with_comments ||= Parser::Source::Comment.associate_by_identity(ast, comments)
end
blank?() click to toggle source
# File lib/rubocop/ast/processed_source.rb, line 102
def blank?
  ast.nil?
end
checksum() click to toggle source

Raw source checksum for tracking infinite loops.

# File lib/rubocop/ast/processed_source.rb, line 74
def checksum
  Digest::SHA1.hexdigest(@raw_source)
end
comment_at_line(line) click to toggle source

@return [Comment, nil] the comment at that line, if any.

# File lib/rubocop/ast/processed_source.rb, line 107
def comment_at_line(line)
  comment_index[line]
end
commented?(source_range)

@deprecated use contains_comment?

Alias for: contains_comment?
comments_before_line(line) click to toggle source

@deprecated Use `each_comment_in_lines` Should have been called `comments_before_or_at_line`. Doubtful it has of any valid use.

# File lib/rubocop/ast/processed_source.rb, line 137
def comments_before_line(line)
  each_comment_in_lines(0..line).to_a
end
contains_comment?(source_range) click to toggle source

@return [Boolean] if any of the lines in the given `source_range` has a comment. Consider using `each_comment_in_lines` instead

# File lib/rubocop/ast/processed_source.rb, line 129
def contains_comment?(source_range)
  each_comment_in_lines(source_range.line..source_range.last_line).any?
end
Also aliased as: commented?
current_line(token) click to toggle source
# File lib/rubocop/ast/processed_source.rb, line 151
def current_line(token)
  lines[token.line - 1]
end
each_comment(&block) click to toggle source

@deprecated Use `comments.each`

# File lib/rubocop/ast/processed_source.rb, line 79
def each_comment(&block)
  comments.each(&block)
end
each_comment_in_lines(line_range) { |comment| ... } click to toggle source

Enumerates on the comments contained with the given `line_range`

# File lib/rubocop/ast/processed_source.rb, line 117
def each_comment_in_lines(line_range)
  return to_enum(:each_comment_in_lines, line_range) unless block_given?

  line_range.each do |line|
    if (comment = comment_index[line])
      yield comment
    end
  end
end
each_token(&block) click to toggle source

@deprecated Use `tokens.each`

# File lib/rubocop/ast/processed_source.rb, line 89
def each_token(&block)
  tokens.each(&block)
end
file_path() click to toggle source
# File lib/rubocop/ast/processed_source.rb, line 98
def file_path
  buffer.name
end
find_comment(&block) click to toggle source

@deprecated Use `comment_at_line`, `each_comment_in_lines`, or `comments.find`

# File lib/rubocop/ast/processed_source.rb, line 84
def find_comment(&block)
  comments.find(&block)
end
find_token(&block) click to toggle source

@deprecated Use `tokens.find`

# File lib/rubocop/ast/processed_source.rb, line 94
def find_token(&block)
  tokens.find(&block)
end
first_token_of(range_or_node) click to toggle source
# File lib/rubocop/ast/processed_source.rb, line 172
def first_token_of(range_or_node)
  sorted_tokens[first_token_index(range_or_node)]
end
following_line(token) click to toggle source
# File lib/rubocop/ast/processed_source.rb, line 155
def following_line(token)
  lines[token.line]
end
last_token_of(range_or_node) click to toggle source
# File lib/rubocop/ast/processed_source.rb, line 176
def last_token_of(range_or_node)
  sorted_tokens[last_token_index(range_or_node)]
end
line_indentation(line_number) click to toggle source
# File lib/rubocop/ast/processed_source.rb, line 159
def line_indentation(line_number)
  lines[line_number - 1]
    .match(/^(\s*)/)[1]
    .to_s
    .length
end
line_with_comment?(line) click to toggle source

@return [Boolean] if the given line number has a comment.

# File lib/rubocop/ast/processed_source.rb, line 112
def line_with_comment?(line)
  comment_index.include?(line)
end
lines() click to toggle source

Returns the source lines, line break characters removed, excluding a possible __END__ and everything that comes after.

# File lib/rubocop/ast/processed_source.rb, line 49
def lines
  @lines ||= begin
    all_lines = @buffer.source_lines
    last_token_line = tokens.any? ? tokens.last.line : all_lines.size
    result = []
    all_lines.each_with_index do |line, ix|
      break if ix >= last_token_line && line == '__END__'

      result << line
    end
    result
  end
end
preceding_line(token) click to toggle source
# File lib/rubocop/ast/processed_source.rb, line 147
def preceding_line(token)
  lines[token.line - 2]
end
sorted_tokens() click to toggle source

The tokens list is always sorted by token position, except for cases when heredoc is passed as a method argument. In this case tokens are interleaved by heredoc contents' tokens.

# File lib/rubocop/ast/processed_source.rb, line 183
def sorted_tokens
  # Use stable sort.
  @sorted_tokens ||= tokens.sort_by.with_index { |token, i| [token.begin_pos, i] }
end
start_with?(string) click to toggle source
# File lib/rubocop/ast/processed_source.rb, line 141
def start_with?(string)
  return false if self[0].nil?

  self[0].start_with?(string)
end
tokens_within(range_or_node) click to toggle source
# File lib/rubocop/ast/processed_source.rb, line 166
def tokens_within(range_or_node)
  begin_index = first_token_index(range_or_node)
  end_index = last_token_index(range_or_node)
  sorted_tokens[begin_index..end_index]
end
valid_syntax?() click to toggle source
# File lib/rubocop/ast/processed_source.rb, line 67
def valid_syntax?
  return false if @parser_error

  @diagnostics.none? { |d| INVALID_LEVELS.include?(d.level) }
end

Private Instance Methods

comment_index() click to toggle source
# File lib/rubocop/ast/processed_source.rb, line 190
def comment_index
  @comment_index ||= {}.tap do |hash|
    comments.each { |c| hash[c.location.line] = c }
  end
end
create_parser(ruby_version) click to toggle source

rubocop:enable Metrics/MethodLength

# File lib/rubocop/ast/processed_source.rb, line 257
def create_parser(ruby_version)
  builder = RuboCop::AST::Builder.new

  parser_class(ruby_version).new(builder).tap do |parser|
    # On JRuby 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
first_token_index(range_or_node) click to toggle source
# File lib/rubocop/ast/processed_source.rb, line 273
def first_token_index(range_or_node)
  begin_pos = source_range(range_or_node).begin_pos
  sorted_tokens.bsearch_index { |token| token.begin_pos >= begin_pos }
end
last_token_index(range_or_node) click to toggle source
# File lib/rubocop/ast/processed_source.rb, line 278
def last_token_index(range_or_node)
  end_pos = source_range(range_or_node).end_pos
  sorted_tokens.bsearch_index { |token| token.end_pos >= end_pos }
end
parse(source, ruby_version) click to toggle source
# File lib/rubocop/ast/processed_source.rb, line 196
def parse(source, ruby_version)
  buffer_name = @path || STRING_SOURCE_NAME
  @buffer = Parser::Source::Buffer.new(buffer_name, 1)

  begin
    @buffer.source = source
  rescue EncodingError => e
    @parser_error = e
    @ast = nil
    @comments = []
    @tokens = []
    return
  end

  @ast, @comments, @tokens = tokenize(create_parser(ruby_version))
end
parser_class(ruby_version) click to toggle source

rubocop:disable Metrics/MethodLength

# File lib/rubocop/ast/processed_source.rb, line 230
def parser_class(ruby_version)
  case ruby_version
  when 2.4
    require 'parser/ruby24'
    Parser::Ruby24
  when 2.5
    require 'parser/ruby25'
    Parser::Ruby25
  when 2.6
    require 'parser/ruby26'
    Parser::Ruby26
  when 2.7
    require 'parser/ruby27'
    Parser::Ruby27
  when 2.8, 3.0
    require 'parser/ruby30'
    Parser::Ruby30
  when 3.1
    require 'parser/ruby31'
    Parser::Ruby31
  else
    raise ArgumentError,
          "RuboCop found unknown Ruby version: #{ruby_version.inspect}"
  end
end
source_range(range_or_node) click to toggle source
# File lib/rubocop/ast/processed_source.rb, line 283
def source_range(range_or_node)
  if range_or_node.respond_to?(:source_range)
    range_or_node.source_range
  else
    range_or_node
  end
end
tokenize(parser) click to toggle source
# File lib/rubocop/ast/processed_source.rb, line 213
def tokenize(parser)
  begin
    ast, comments, tokens = parser.tokenize(@buffer)
    ast ||= nil # force `false` to `nil`, see https://github.com/whitequark/parser/pull/722
  rescue Parser::SyntaxError
    # All errors are in diagnostics. No need to handle exception.
    comments = []
    tokens = []
  end

  ast&.complete!
  tokens.map! { |t| Token.from_parser_token(t) }

  [ast, comments, tokens]
end