class Rufo::Formatter
def initialize(code, **options)
def initialize(code, **options) @code = code @tokens = Rufo::Parser.lex(code).reverse! @sexp = Rufo::Parser.sexp(code) # sexp being nil means that the code is not valid. # Parse the code so we get better error messages. if @sexp.nil? Rufo::Parser.parse(code) raise Rufo::UnknownSyntaxError # Sometimes parsing does not raise an error end @indent = 0 @line = 0 @column = 0 @last_was_newline = true @output = +"" # The column of a `obj.method` call, so we can align # calls to that dot @dot_column = nil # Same as above, but the column of the original dot, not # the one we finally wrote @original_dot_column = nil # Did this line already set the `@dot_column` variable? @line_has_dot_column = nil # The column of a `obj.method` call, but only the name part, # so we can also align arguments accordingly @name_dot_column = nil # Heredocs list, associated with calls ([heredoc, tilde]) @heredocs = [] # Current node, to be able to associate it to heredocs @current_node = nil # The current heredoc being printed @current_heredoc = nil # The current hash or call or method that has hash-like parameters @current_hash = nil @current_type = nil # Are we inside a type body? @inside_type_body = false # Map lines to commands that start at the begining of a line with the following info: # - line indent # - first param indent # - first line ends with '(', '[' or '{'? # - line of matching pair of the previous item # - last line of that call # # This is needed to dedent some calls that look like this: # # foo bar( # 2, # ) # # Without the dedent it would normally look like this: # # foo bar( # 2, # ) # # Because the formatter aligns this to the first parameter in the call. # However, for these cases it's better to not align it like that. @line_to_call_info = {} # Lists [first_line, last_line, indent] of lines that need an indent because # of alignment of literals. For example this:# # # foo [ # 1, # ] # # is normally formatted to: # # foo [ # 1, # ] # # However, if it's already formatted like the above we preserve it. @literal_indents = [] # First non-space token in this line @first_token_in_line = nil # Do we want to compute the above? @want_first_token_in_line = false # Each line that belongs to a string literal besides the first # go here, so we don't break them when indenting/dedenting stuff @unmodifiable_string_lines = {} # Position of comments that occur at the end of a line @comments_positions = [] # Token for the last comment found @last_comment = nil # Actual column of the last comment written @last_comment_column = nil # Associate lines to alignments # Associate a line to an index inside @comments_position # becuase when aligning something to the left of a comment # we need to adjust the relative comment @line_to_alignments_positions = Hash.new { |h, k| h[k] = [] } # Position of assignments @assignments_positions = [] # Range of assignment (line => end_line) # # We need this because when we have to format: # # ``` # abc = 1 # a = foo bar: 2 # baz: # # ``` # # Because we'll insert two spaces after `a`, this will # result in a mis-alignment for baz (and possibly other lines # below it). So, we remember the line ranges of an assignment, # and once we align the first one we fix the other ones. @assignments_ranges = {} # Case when positions @case_when_positions = [] # Declarations that are written in a single line, like: # # def foo; 1; end # # We want to track these because we allow consecutive inline defs # to be together (without an empty line between them) # # This is [[line, original_line], ...] @inline_declarations = [] # This is used to track how far deep we are in the AST. # This is useful as it allows you to check if you are inside an array # when dealing with heredocs. @node_level = 0 # This represents the node level of the most recent literal elements list. # It is used to track if we are in a list of elements so that commas # can be added appropriately for heredocs for example. @literal_elements_level = nil init_settings(options) end