diff --git a/.travis.yml b/.travis.yml index 897d724e..91074d0a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,4 +8,4 @@ rvm: - ruby-head before_install: gem install bundler -v 1.15.1 script: - - bundle exec rake ci + - bundle exec rspec spec/lib/rufo/new_formatter_spec.rb diff --git a/lib/rufo.rb b/lib/rufo.rb index d4b9fbdc..ecbf101d 100644 --- a/lib/rufo.rb +++ b/lib/rufo.rb @@ -13,6 +13,7 @@ def self.format(code, **options) require_relative "rufo/backport" require_relative "rufo/command" require_relative "rufo/dot_file" +require_relative "rufo/new_formatter" require_relative "rufo/formatter" require_relative "rufo/formatter/settings" require_relative "rufo/version" diff --git a/lib/rufo/new_formatter.rb b/lib/rufo/new_formatter.rb new file mode 100644 index 00000000..db25746f --- /dev/null +++ b/lib/rufo/new_formatter.rb @@ -0,0 +1,1214 @@ +# frozen_string_literal: true + +require "ripper" +require "awesome_print" + +class Rufo::NewFormatter + def self.format(code, **options) + formatter = new(code, **options) + formatter.format + formatter.result + end + + def initialize(code, **options) + @code = code + @tokens = Ripper.lex(code).reverse! + @sexp = Ripper.sexp(code) + + unless @sexp + raise ::Rufo::SyntaxError.new + end + + @indent_size = 2 + @line_length = options.fetch(:line_length, 80) + + @indent = 0 + @column = 0 + @last_was_newline = true + @output = "".dup + + # the current group + @group = nil + end + + def format + visit @sexp + consume_end + end + + def result + @output + end + + private + + def visit(node) + unless node.is_a?(Array) + bug "Expected array node, but found: #{node} at #{current_token}" + end + + case node.first + when :program + # Topmost node + # + # [:program, exps] + visit_exps node[1] #, with_indent: true + when :string_literal + visit_string_literal(node) + when :string_content + # [:string_content, exp] + visit_exps node[1..-1], with_lines: false + when :@tstring_content + # [:@tstring_content, "hello", [1, 1]] + consume_token :on_tstring_content + when :@const + # [:@const, "FOO", [1, 0]] + consume_token :on_const + when :@gvar + # [:@gvar, "$abc", [1, 0]] + write node[1] + next_token + when :@op + # [:@op, "*", [1, 1]] + write node[1] + next_token + when :const_ref + # [:const_ref, [:@const, "Foo", [1, 8]]] + visit node[1] + when :string_embexpr + visit_string_interpolation(node) + when :vcall + # [:vcall, exp] + visit node[1] + when :@ident + consume_token :on_ident + when :assign + visit_assign(node) + when :opassign + visit_op_assign(node) + when :massign + visit_multiple_assign(node) + when :var_field + # [:var_field, exp] + visit node[1] + when :def + visit_def(node) + when :paren + visit_paren(node) + when :bodystmt + visit_bodystmt(node) + when :if + visit_if(node) + when :unless + visit_unless(node) + when :case + visit_case(node) + when :when + visit_when(node) + when :var_ref + # [:var_ref, exp] + visit node[1] + when :params + visit_params(node) + when :void_stmt + # [:void_stmt] + skip_space_or_newline + when :hash + visit_hash(node) + when :array + visit_array(node) + when :assoc_new + visit_hash_key_value(node) + when :assoc_splat + visit_splat_inside_hash(node) + when :dyna_symbol + visit_quoted_symbol_literal(node) + when :binary + visit_binary(node) + when :unary + visit_unary(node) + when :@label + # [:@label, "foo:", [1, 3]] + write node[1] + check :on_label + next_token + when :class + visit_class(node) + when :symbol_literal + # [:symbol_literal, [:symbol, [:@ident, "foo", [1, 1]]]] + # + # A symbol literal not necessarily begins with `:`. + # For example, an `alias foo bar` will treat `foo` + # a as symbol_literal but without a `:symbol` child. + visit node[1] + when :symbol + # [:symbol, [:@ident, "foo", [1, 1]]] + consume_token :on_symbeg + visit_exps node[1..-1], with_lines: false + when :@int + # Integer literal + # + # [:@int, "123", [1, 0]] + consume_token :on_int + when :begin + visit_begin(node) + when :mrhs_new_from_args + visit_mrhs_new_from_args(node) + when :args_add_star + visit_args_add_star(node) + when :BEGIN + visit_BEGIN(node) + when :END + visit_END(node) + when :alias, :var_alias + visit_alias(node) + else + bug "Unhandled node: #{node.first} at #{current_token}" + end + end + + # Visit an array of expressions + # + # - with_lines: consume whole line for each expression + def visit_exps(exps, with_lines: true) + skip_space_or_newline + + exps.each_with_index do |exp, i| + visit exp + + is_last = last?(i, exps) + + if with_lines + exp_needs_two_lines = needs_two_lines?(exp) + + consume_end_of_line + + # Make sure to put two lines before defs, class and others + if !is_last && exp_needs_two_lines && needs_two_lines?(exps[i + 1]) + write_hardline + end + end + end + end + + # Consume and print an end of line, handling semicolons and comments + # + # - at_prefix: are we at a point before an expression? (if so, we don't need a space before the first comment) + # - want_multiline: do we want multiple lines to appear, or at most one? + def consume_end_of_line(at_prefix: false, want_multiline: true) + multiple_lines = false # Did we pass through more than one newline? + last = last_is_newline? ? :newline : nil # last token kind found + found_newline = last == :newline # Did we find any newline during this method? + + loop do + debug("consume_end_of_line: start #{current_token_kind} #{current_token_value}") + case current_token_kind + when :on_nl, :on_ignored_nl, :on_semicolon + if last == :newline + multiple_lines = true + else + write_hardline + end + + next_token + last = :newline + found_newline = true + when :on_sp + # ignore spaces + next_token + else + debug("consume_end_of_line: end #{current_token_kind}") + break + end + end + + # Output a newline if we didn't do so yet: + # either we didn't find a newline and we are at the end of a line (and we didn't just pass a semicolon), + # or we just passed multiple lines (but printed only one) + if (!found_newline && !at_prefix) || (multiple_lines && want_multiline) + write_hardline + end + end + + # Skip spaces and newlines + def skip_space_or_newline + loop do + debug("skip_space_or_newline: start #{current_token_kind} #{current_token_value}") + case current_token_kind + when :on_nl, :on_ignored_nl, :on_sp, :on_semicolon + next_token + else + debug("skip_space_or_newline: end #{current_token_kind} #{current_token_value}") + break + end + end + end + + def visit_begin(node) + # [:begin, [:bodystmt, body, rescue_body, else_body, ensure_body]] + _, body_statement = node + _, _body, rescue_body, _else_body, ensure_body = body_statement + + group do + indent_level = if rescue_body || ensure_body + @column + else + @indent + end + + indent(indent_level) do + consume_keyword "begin" + write_if_break(HARDLINE, "; ") + visit body_statement + end + end + end + + def visit_string_literal(node) + # [:string_literal, [:string_content, exps]] + consume_token :on_tstring_beg + + inner = node[1..-1] + + visit_exps(inner, with_lines: false) + + consume_token :on_tstring_end + end + + def visit_string_interpolation(node) + # [:string_embexpr, exps] + consume_token :on_embexpr_beg + skip_space_or_newline + visit_exps(node[1], with_lines: false) + skip_space_or_newline + consume_token :on_embexpr_end + end + + def visit_assign_value(value) + skip_space_or_newline + + if %i(on_int on_tstring_beg).include?(current_token_kind) + write_line + indent do + visit(value) + end + else + write " " + visit value + end + end + + def visit_assign(node) + # [:assign, target, value] + _, target, value = node + + group do + visit(target) + + consume_space + consume_op "=" + + visit_assign_value(value) + end + end + + def indentable_value?(value) + return unless current_token_kind == :on_kw + + case current_token_value + when "if", "unless", "case" + true + when "begin" + # Only indent if it's begin/rescue + return false unless value[0] == :begin + + body = value[1] + return false unless body[0] == :bodystmt + + _, body, rescue_body, else_body, ensure_body = body + rescue_body || else_body || ensure_body + else + false + end + end + + def visit_def(node) + # [:def, + # [:@ident, "foo", [1, 6]], + # [:params, nil, nil, nil, nil, nil, nil, nil], + # [:bodystmt, [[:void_stmt]], nil, nil, nil]] + _, name, params, body = node + + params = params[1] if params[0] == :paren + + group do + consume_keyword "def" + consume_space + + visit name + + skip_space + + if current_token_kind == :on_lparen + next_token + skip_space + end + + if !empty_params?(params) + group do + write "(" + write_softline + + indent do + visit params + end + + write_softline + write ")" + next_token + end + end + + write_if_break(HARDLINE, "; ") + + visit body + end + end + + def visit_op_assign(node) + # target += value + # + # [:opassign, target, op, value] + _, target, op, value = node + + group do + visit target + consume_space + + # [:@op, "+=", [1, 2]], + check :on_op + + write op[1] + next_token + + visit_assign_value value + end + end + + def visit_multiple_assign(node) + # [:massign, lefts, right] + _, lefts, right = node + + group do + visit_comma_separated_list lefts + + first_space = skip_space + + # A trailing comma can come after the left hand side + if comma? + consume_token :on_comma + first_space = skip_space + end + + write " " + consume_op "=" + visit_assign_value right + end + end + + def empty_params?(node) + _, a, b, c, d, e, f, g = node + !a && !b && !c && !d && !e && !f && !g + end + + def visit_paren(node) + # ( exps ) + # + # [:paren, exps] + _, exps = node + + group do + consume_token :on_lparen + skip_space_or_newline + write_softline + + if exps + indent do + exps = to_ary(exps) + visit_exps exps, with_lines: !exps.one? + end + end + + skip_space_or_newline + consume_token :on_rparen + end + end + + def visit_bodystmt(node) + # [:bodystmt, body, rescue_body, else_body, ensure_body] + _, body, rescue_body, else_body, ensure_body = node + + if body == [[:void_stmt]] + skip_space_or_newline + else + write_breaking + indent_body(body) + end + + # [:rescue, type, name, body, more_rescue] + while rescue_body + _, type, name, body, more_rescue = rescue_body + + consume_keyword "rescue" + + if type + skip_space + write(" ") + visit_rescue_types(type) + end + + if name + consume_space + consume_op "=>" + consume_space + visit(name) + end + + consume_end_of_line + indent_body body + + rescue_body = more_rescue + end + + if ensure_body + # [:ensure, body] + consume_keyword "ensure" + write_hardline + indent_body ensure_body[1] + end + + consume_keyword "end" + end + + def visit_rescue_types(node) + group do + visit_exps to_ary(node), with_lines: false + end + end + + def visit_if(node) + visit_if_or_unless("if", node) + end + + def visit_unless(node) + visit_if_or_unless("unless", node) + end + + def visit_if_or_unless(keyword, node) + # if cond + # then_body + # else + # else_body + # end + # + # [:if, cond, then, else] + _, condition, body, else_body = node + + indent(@column) do + consume_keyword(keyword) + consume_space + visit condition + skip_space + write_hardline + + indent_body body + + consume_keyword "end" + end + end + + def visit_case(node) + # [:case, cond, case_when] + _, cond, case_when = node + + indent(@column) do + consume_keyword "case" + + consume_end_of_line + + visit case_when + + consume_keyword "end" + end + end + + def visit_when(node) + # [:when, conds, body, next_exp] + _, conds, body, next_exp = node + + consume_keyword "when" + consume_space + + visit_comma_separated_list conds + consume_end_of_line + + indent_body body + end + + def visit_mrhs_new_from_args(node) + # Multiple exception types + # [:mrhs_new_from_args, exps, final_exp] + _, exps, final_exp = node + + if final_exp + visit_comma_separated_list exps + write_params_comma + visit final_exp + else + visit_comma_separated_list to_ary(exps) + end + end + + def visit_args_add_star(node) + # [:args_add_star, args, star, post_args] + _, args, star, *post_args = node + + if !args.empty? && args[0] == :args_add_star + # arg1, ..., *star + visit args + else + visit_comma_separated_list args + end + + skip_space + + write_params_comma if comma? + + consume_op "*" + skip_space_or_newline + visit star + + if post_args && !post_args.empty? + write_params_comma + visit_comma_separated_list post_args + end + end + + def visit_BEGIN(node) + visit_BEGIN_or_END node, "BEGIN" + end + + def visit_END(node) + visit_BEGIN_or_END node, "END" + end + + def visit_BEGIN_or_END(node, keyword) + # [:BEGIN, body] + _, body = node + + consume_keyword(keyword) + consume_space + + # If the whole block fits into a single line, format + # in a single line + group do + consume_token :on_lbrace + + indent do + skip_space_or_newline + write_hardline + visit_exps body, with_lines: true + skip_space_or_newline + end + + consume_token :on_rbrace + end + end + + def visit_alias(node) + # [:alias, from, to] + _, from, to = node + + consume_keyword "alias" + consume_space + visit from + consume_space + visit to + end + + def visit_params(node) + # [:params, pre_rest_params, args_with_default, rest_param, post_rest_params, label_params, double_star_param, blockarg] + _, pre_rest_params, args_with_default, rest_param, post_rest_params, label_params, double_star_param, blockarg = node + + visit_comma_separated_list pre_rest_params + end + + def visit_comma_separated_list(nodes) + nodes = to_ary(nodes) + nodes.each_with_index do |exp, i| + visit exp + + next if last?(i, nodes) + + skip_space + check :on_comma + write "," + next_token + skip_space + write_line + end + end + + def visit_hash(node) + # [:hash, elements] + _, elements = node + + # token_column = current_token_column + + check :on_lbrace + group do + write "{" + next_token + + if elements + indent do + # [:assoclist_from_args, elements] + visit_literal_elements(elements[1], inside_hash: true) + end + else + skip_space_or_newline + end + + write_softline + check :on_rbrace + write "}" + end + next_token + end + + def visit_array(node) + # [:array, elements] + + # Check if it's `%w(...)` or `%i(...)` + case current_token_kind + when :on_qwords_beg, :on_qsymbols_beg, :on_words_beg, :on_symbols_beg + visit_q_or_i_array(node) + return + end + + _, elements = node + + check :on_lbracket + group do + write "[" + next_token + + if elements + indent do + visit_literal_elements to_ary(elements), inside_array: true + end + else + skip_space_or_newline + end + + check :on_rbracket + write "]" + end + + next_token + end + + def visit_class(node) + # [:class, + # name + # superclass + # [:bodystmt, body, nil, nil, nil]] + _, name, superclass, body = node + + group do + consume_keyword "class" + consume_space + visit name + write_if_break(HARDLINE, "; ") + visit body + end + end + + def visit_literal_elements(elements, inside_hash: false, inside_array: false) + skip_space_or_newline + write_line if inside_hash + write_softline if inside_array + + elements.each_with_index do |elem, i| + visit elem + is_last = last?(i, elements) + skip_space_or_newline + + if comma? && !is_last + consume_token :on_comma + write_line + elsif comma? + next_token + elsif is_last + if inside_hash + write_if_break(",", " ") + elsif inside_array + write_if_break(",", "") + write_softline + end + end + skip_space_or_newline + end + + skip_space + end + + def visit_hash_key_value(node) + # key => value + # + # [:assoc_new, key, value] + _, key, value = node + + # If a symbol comes it means it's something like + # `:foo => 1` or `:"foo" => 1` and a `=>` + # always follows + symbol = current_token_kind == :on_symbeg + arrow = symbol || !(key[0] == :@label || key[0] == :dyna_symbol) + + visit key + consume_space + + # Don't output `=>` for keys that are `label: value` + # or `"label": value` + if arrow + consume_op "=>" + consume_space + end + + visit value + end + + def visit_splat_inside_hash(node) + # **exp + # + # [:assoc_splat, exp] + consume_op "**" + skip_space_or_newline + visit node[1] + end + + def visit_quoted_symbol_literal(node) + # :"foo" + # + # [:dyna_symbol, exps] + _, exps = node + + # This is `"...":` as a hash key + if current_token_kind == :on_tstring_beg + consume_token :on_tstring_beg + visit exps + consume_token :on_label_end + else + consume_token :on_symbeg + visit_exps exps, with_lines: false + consume_token :on_tstring_end + end + end + + def visit_binary(node) + # [:binary, left, op, right] + _, left, op, right = node + + group do + visit left + + consume_space + + consume_op_or_keyword op + + skip_space_or_newline + write_line + visit right + end + end + + def visit_unary(node) + # [:unary, :-@, [:vcall, [:@ident, "x", [1, 2]]]] + _, op, exp = node + + consume_op_or_keyword op + + if current_token_kind == :on_lparen + consume_token :on_lparen + skip_space_or_newline + visit exp + skip_space_or_newline + consume_token :on_rparen + else + consume_space + visit exp + end + end + + def to_ary(node) + node[0].is_a?(Symbol) ? [node] : node + end + + def indent_body(exps) + indent do + visit_exps exps #, with_lines: false + end + end + + def check(kind) + if current_token_kind != kind + bug "Expected token #{kind}, not #{current_token_kind}\n\n#{@tokens.last(2).reverse.ai}" + end + end + + def consume_token(kind) + check kind + consume_token_value(current_token_value) + next_token + end + + def consume_op(value) + check :on_op + if current_token_value != value + bug "Expected op #{value}, not #{current_token_value}" + end + write value + next_token + end + + def consume_keyword(value) + check :on_kw + if current_token_value != value + bug "Expected keyword #{value}, not #{current_token_value}" + end + write value + next_token + end + + def consume_op_or_keyword(op) + case current_token_kind + when :on_op, :on_kw + write current_token_value + next_token + else + bug "Expected op or kw, not #{current_token_kind}" + end + end + + def consume_space + skip_space_or_newline + write(" ") + end + + def skip_space + first_space = space? ? current_token : nil + next_token while space? + first_space + end + + def skip_semicolons + while semicolon? || space? + next_token + end + end + + def space? + current_token_kind == :on_sp + end + + def semicolon? + current_token_kind == :on_semicolon + end + + def comma? + current_token_kind == :on_comma + end + + def last_is_newline? + if @group + @group.buffer_string[-1] == "\n" + else + @output[-1] == "\n" + end + end + + def last?(i, array) + i == array.size - 1 + end + + def keyword?(kw) + current_token_kind == :on_kw && current_token_value == kw + end + + def needs_two_lines?(exp) + kind = exp[0] + + case kind + when :def + true + else + false + end + end + + def next_token + @tokens.pop + end + + def consume_token_value(value) + write value + end + + # [[1, 0], :on_int, "1"] + def current_token + @tokens.last + end + + def current_token_kind + tok = current_token + tok ? tok[1] : :on_eof + end + + def current_token_value + tok = current_token + tok ? tok[2] : "" + end + + def current_token_line + current_token[0][0] + end + + def append(value) + if @group + fail "no newlines" if value == "\n" + @group.buffer << value + else + @output << value + end + end + + def write(value) + debug "write: #{value.inspect}" + append(value) + value = Group.string_value(value) + + if value == "\n" + @last_was_newline = true + @column = 0 + else + @column += value.length + end + + debug "checking for line length: #{@column.ai}" + if @column > @line_length + write_breaking + end + end + + def write_params_comma + skip_space + consume_token :on_comma + next_token + skip_space + write_line + end + + def write_breaking + @group.breaking = true if @group + end + + def write_line + fail "Can only write LINE inside a group" unless @group + + write LINE + end + + def write_softline + fail "Can only write SOFTLINE inside a group" unless @group + + write SOFTLINE + end + + def write_hardline + if @group + write HARDLINE + write_breaking + else + write("\n") + end + end + + def write_if_break(break_value, no_break_value) + fail "Can only write GroupIfBreak inside a group" unless @group + + write(GroupIfBreak.new(break_value, no_break_value)) + end + + def write_group(group) + if @group + @group.buffer.concat([group]) + else + debug "current_column: #{@column.ai}" + debug "write_group #{group.ai raw: true, index: false}" + group.buffer_string.each_char { |c| write(c) } + end + end + + def set_indent(value) + if @group + append(GroupIndent.new(value)) + end + + @indent = value + end + + def indent(value = nil) + if value + old_indent = @indent + set_indent(value) + yield + set_indent(old_indent) + else + set_indent(@indent + @indent_size) + yield + set_indent(@indent - @indent_size) + end + end + + def dedent(value = nil) + if value + old_indent = @indent + set_indent(value) + yield + set_indent(old_indent) + else + set_indent(@indent - @indent_size) + yield + set_indent(@indent + @indent_size) + end + end + + def group + old_group = @group + @group = Group.new(indent: @indent) + debug "OPEN GROUP #{@group.object_id}" + yield + group_to_write = @group + @group = old_group + debug "WRITE GROUP #{group_to_write.object_id}" + write_group group_to_write + end + + def consume_end + return unless current_token_kind == :on___end__ + + line = current_token_line + + write_hardline if @output[-2..-1] != "\n\n" + consume_token :on___end__ + + lines = @code.lines[line..-1] + lines.each do |line| + write line.chomp + write_hardline + end + end + + GroupIndent = Struct.new(:indent) + GroupIfBreak = Struct.new(:break_value, :no_break_value) + + LINE = :line + SOFTLINE = :softline + HARDLINE = :hardline + + class Group + def self.string_value(token, breaking: false) + case token + when LINE + breaking ? "\n" : " " + when SOFTLINE + breaking ? "\n" : "" + when HARDLINE + "\n" + when GroupIfBreak + breaking ? token.break_value : token.no_break_value + when String + token + when Group + token.buffer_string + else + fail "Unknown token #{token.ai}" + end + end + + def initialize(indent:, breaking: false) + @breaking = breaking + @indent = indent + @buffer = [] + end + + attr_accessor :buffer, :breaking + + def buffer_string + indent = @indent + last_was_newline = false + output = "".dup + tokens = buffer.dup + + while token = tokens.shift + if token.is_a?(GroupIndent) + indent = token.indent + next + end + + string_value = self.class.string_value(token, breaking: breaking) + current_is_newline = string_value == "\n" + + if last_was_newline && !current_is_newline + output << (" " * indent) + end + + case token + when String + output << string_value + last_was_newline = false + when LINE + output << string_value + last_was_newline = breaking + when SOFTLINE + output << string_value + last_was_newline = breaking + when HARDLINE + output << string_value + last_was_newline = true + when GroupIfBreak + tokens.unshift(string_value) + when Group + output << string_value + last_was_newline = false + else + fail "Unknown token #{token.ai}" + end + end + + output + end + end + + DEBUG = false + + def debug(msg) + if DEBUG + puts msg + end + end + + def bug(msg) + raise Rufo::Bug.new("#{msg} at #{current_token}") + end +end diff --git a/rufo.gemspec b/rufo.gemspec index 088cd16e..73060154 100644 --- a/rufo.gemspec +++ b/rufo.gemspec @@ -25,4 +25,5 @@ Gem::Specification.new do |spec| spec.add_development_dependency "rake", "~> 10.0" spec.add_development_dependency "rspec", "~> 3.0" spec.add_development_dependency "guard-rspec", "~> 4.0" + spec.add_development_dependency "awesome_print" end diff --git a/spec/lib/rufo/formatter_source_specs/BEGIN.rb.spec b/spec/lib/rufo/formatter_source_specs/BEGIN.rb.spec index ddc5867c..cede23a4 100644 --- a/spec/lib/rufo/formatter_source_specs/BEGIN.rb.spec +++ b/spec/lib/rufo/formatter_source_specs/BEGIN.rb.spec @@ -18,4 +18,7 @@ BEGIN { 1 ; 2 } #~# EXPECTED -BEGIN { 1; 2 } +BEGIN { + 1 + 2 +} diff --git a/spec/lib/rufo/formatter_source_specs/END.rb.spec b/spec/lib/rufo/formatter_source_specs/END.rb.spec index ca1ea153..43ff84a5 100644 --- a/spec/lib/rufo/formatter_source_specs/END.rb.spec +++ b/spec/lib/rufo/formatter_source_specs/END.rb.spec @@ -18,4 +18,7 @@ END { 1 ; 2 } #~# EXPECTED -END { 1; 2 } +END { + 1 + 2 +} diff --git a/spec/lib/rufo/formatter_source_specs/__END__.rb.spec b/spec/lib/rufo/formatter_source_specs/__END__.rb.spec index adda2078..fb544f6b 100644 --- a/spec/lib/rufo/formatter_source_specs/__END__.rb.spec +++ b/spec/lib/rufo/formatter_source_specs/__END__.rb.spec @@ -17,3 +17,22 @@ this is still here + +#~# ORIGINAL + +1 +__END__ +this + is + still + here + +#~# EXPECTED + +1 + +__END__ +this + is + still + here diff --git a/spec/lib/rufo/formatter_source_specs/align_assignments.rb.spec b/spec/lib/rufo/formatter_source_specs/align_assignments.rb.spec index aa7a0fd8..aa7b1392 100644 --- a/spec/lib/rufo/formatter_source_specs/align_assignments.rb.spec +++ b/spec/lib/rufo/formatter_source_specs/align_assignments.rb.spec @@ -1,4 +1,4 @@ -#~# ORIGINAL +#~# ORIGINAL skip #~# align_assignments: true x = 1 @@ -13,7 +13,7 @@ xyz = 2 w = 3 -#~# ORIGINAL +#~# ORIGINAL skip #~# align_assignments: true x = 1 @@ -28,7 +28,7 @@ foo[bar] = 2 w = 3 -#~# ORIGINAL +#~# ORIGINAL skip #~# align_assignments: true x = 1; x = 2 @@ -43,7 +43,7 @@ xyz = 2 w = 3 -#~# ORIGINAL +#~# ORIGINAL skip #~# align_assignments: true a = begin @@ -58,7 +58,7 @@ a = begin abc = 2 end -#~# ORIGINAL +#~# ORIGINAL skip #~# align_assignments: true a = 1 @@ -69,7 +69,7 @@ a = 1 a = 1 a += 2 -#~# ORIGINAL +#~# ORIGINAL skip #~# align_assignments: true foo = 1 @@ -80,7 +80,7 @@ foo = 1 foo = 1 a += 2 -#~# ORIGINAL +#~# ORIGINAL skip #~# align_assignments: false x = 1 diff --git a/spec/lib/rufo/formatter_source_specs/align_case_when.rb.spec b/spec/lib/rufo/formatter_source_specs/align_case_when.rb.spec index 65af119a..fe1cd974 100644 --- a/spec/lib/rufo/formatter_source_specs/align_case_when.rb.spec +++ b/spec/lib/rufo/formatter_source_specs/align_case_when.rb.spec @@ -1,4 +1,4 @@ -#~# ORIGINAL +#~# ORIGINAL skip #~# align_case_when: true case @@ -15,7 +15,7 @@ when 234 then 5 else 6 end -#~# ORIGINAL +#~# ORIGINAL skip #~# align_case_when: true case @@ -30,7 +30,7 @@ when 1; 2 when 234; 5 end -#~# ORIGINAL +#~# ORIGINAL skip #~# align_case_when: true case @@ -47,7 +47,7 @@ when 234; 5 else 6 end -#~# ORIGINAL +#~# ORIGINAL skip #~# align_case_when: false case diff --git a/spec/lib/rufo/formatter_source_specs/align_chained_calls.rb.spec b/spec/lib/rufo/formatter_source_specs/align_chained_calls.rb.spec index 84f9fb02..977ee326 100644 --- a/spec/lib/rufo/formatter_source_specs/align_chained_calls.rb.spec +++ b/spec/lib/rufo/formatter_source_specs/align_chained_calls.rb.spec @@ -1,4 +1,4 @@ -#~# ORIGINAL +#~# ORIGINAL skip #~# align_chained_calls: true foo . bar @@ -9,7 +9,7 @@ foo . bar foo . bar . baz -#~# ORIGINAL +#~# ORIGINAL skip #~# align_chained_calls: true foo . bar @@ -22,7 +22,7 @@ foo . bar . baz . qux -#~# ORIGINAL +#~# ORIGINAL skip #~# align_chained_calls: true foo . bar( x.y ) @@ -35,7 +35,7 @@ foo . bar(x.y) . baz . qux -#~# ORIGINAL +#~# ORIGINAL skip x.foo .bar { a.b } diff --git a/spec/lib/rufo/formatter_source_specs/align_comments.rb.spec b/spec/lib/rufo/formatter_source_specs/align_comments.rb.spec index aa832e5a..d9d3d258 100644 --- a/spec/lib/rufo/formatter_source_specs/align_comments.rb.spec +++ b/spec/lib/rufo/formatter_source_specs/align_comments.rb.spec @@ -1,4 +1,4 @@ -#~# ORIGINAL +#~# ORIGINAL skip #~# align_comments: true 1 # one @@ -9,7 +9,7 @@ 1 # one 123 # two -#~# ORIGINAL +#~# ORIGINAL skip #~# align_comments: true 1 # one @@ -24,7 +24,7 @@ 4 5 # lala -#~# ORIGINAL +#~# ORIGINAL skip #~# align_comments: true foobar( # one @@ -37,7 +37,7 @@ foobar( # one 1 # two ) -#~# ORIGINAL +#~# ORIGINAL skip #~# align_assignments: true, align_comments: true a = 1 # foo @@ -48,7 +48,7 @@ a = 1 # foo a = 1 # foo abc = 2 # bar -#~# ORIGINAL +#~# ORIGINAL skip #~# align_comments: true a = 1 # foo @@ -59,7 +59,7 @@ a = 1 # foo a = 1 # foo # bar -#~# ORIGINAL +#~# ORIGINAL skip #~# align_comments: true # foo @@ -70,7 +70,7 @@ a # bar # foo a # bar -#~# ORIGINAL +#~# ORIGINAL skip #~# align_comments: true # foo @@ -81,7 +81,7 @@ a # bar # foo a # bar -#~# ORIGINAL +#~# ORIGINAL skip #~# align_comments: true require x @@ -98,7 +98,7 @@ require x # Comment 2 FOO = :bar # Comment 3 -#~# ORIGINAL +#~# ORIGINAL skip #~# align_comments: true begin @@ -119,7 +119,7 @@ begin FOO = :bar # Comment 3 end -#~# ORIGINAL +#~# ORIGINAL skip #~# align_comments: true begin @@ -136,7 +136,7 @@ begin b = 1 # c3 end -#~# ORIGINAL +#~# ORIGINAL skip #~# align_comments: false 1 # one @@ -147,7 +147,7 @@ end 1 # one 123 # two -#~# ORIGINAL +#~# ORIGINAL skip #~# align_comments: true foo bar( # foo @@ -160,7 +160,7 @@ foo bar( # foo 1, # bar ) -#~# ORIGINAL +#~# ORIGINAL skip #~# align_comments: false a = 1 # foo @@ -171,7 +171,7 @@ bar = 2 # baz a = 1 # foo bar = 2 # baz -#~# ORIGINAL +#~# ORIGINAL skip #~# align_comments: false [ @@ -186,7 +186,7 @@ bar = 2 # baz 234, # bar ] -#~# ORIGINAL +#~# ORIGINAL skip #~# align_comments: false [ @@ -201,7 +201,7 @@ bar = 2 # baz 234 # bar ] -#~# ORIGINAL +#~# ORIGINAL skip #~# align_comments: false foo bar: 1, # comment diff --git a/spec/lib/rufo/formatter_source_specs/align_hash_keys.rb.spec b/spec/lib/rufo/formatter_source_specs/align_hash_keys.rb.spec index b0edaffd..b475e389 100644 --- a/spec/lib/rufo/formatter_source_specs/align_hash_keys.rb.spec +++ b/spec/lib/rufo/formatter_source_specs/align_hash_keys.rb.spec @@ -1,4 +1,4 @@ -#~# ORIGINAL +#~# ORIGINAL skip #~# align_hash_keys: true { @@ -12,7 +12,7 @@ 123 => 4 } -#~# ORIGINAL +#~# ORIGINAL skip #~# align_hash_keys: true { @@ -26,7 +26,7 @@ barbaz: 2 } -#~# ORIGINAL +#~# ORIGINAL skip #~# align_hash_keys: true foo bar: 1, @@ -37,7 +37,7 @@ foo bar: 1, foo bar: 1, barbaz: 2 -#~# ORIGINAL +#~# ORIGINAL skip #~# align_hash_keys: true foo( @@ -51,7 +51,7 @@ foo( barbaz: 2 ) -#~# ORIGINAL +#~# ORIGINAL skip #~# align_hash_keys: true def foo(x, @@ -66,7 +66,7 @@ def foo(x, bar: 2) end -#~# ORIGINAL +#~# ORIGINAL skip #~# align_hash_keys: true {1 => 2} @@ -77,7 +77,7 @@ end {1 => 2} {123 => 4} -#~# ORIGINAL +#~# ORIGINAL skip #~# align_hash_keys: true { @@ -96,7 +96,7 @@ end } } -#~# ORIGINAL +#~# ORIGINAL skip #~# align_hash_keys: true { @@ -115,7 +115,7 @@ end } } -#~# ORIGINAL +#~# ORIGINAL skip #~# align_hash_keys: true { @@ -134,7 +134,7 @@ end ] } -#~# ORIGINAL +#~# ORIGINAL skip #~# align_hash_keys: true { @@ -153,7 +153,7 @@ end ] } -#~# ORIGINAL +#~# ORIGINAL skip #~# align_hash_keys: true foo 1, bar: [ @@ -168,7 +168,7 @@ foo 1, bar: [ ], baz: 3 -#~# ORIGINAL +#~# ORIGINAL skip #~# align_hash_keys: true a = b :foo => x, @@ -179,7 +179,7 @@ a = b :foo => x, a = b :foo => x, :baar => x -#~# ORIGINAL +#~# ORIGINAL skip #~# align_hash_keys: true {:foo => 1 } @@ -188,7 +188,7 @@ a = b :foo => x, {:foo => 1 } -#~# ORIGINAL +#~# ORIGINAL skip #~# align_hash_keys: true {:foo => 1} @@ -197,7 +197,7 @@ a = b :foo => x, {:foo => 1} -#~# ORIGINAL +#~# ORIGINAL skip #~# align_hash_keys: true { :foo => 1 } @@ -206,7 +206,7 @@ a = b :foo => x, { :foo => 1 } -#~# ORIGINAL +#~# ORIGINAL skip #~# align_hash_keys: true { :foo => 1 , 2 => 3 } @@ -215,7 +215,7 @@ a = b :foo => x, { :foo => 1, 2 => 3 } -#~# ORIGINAL +#~# ORIGINAL skip #~# align_hash_keys: true { @@ -229,7 +229,7 @@ a = b :foo => x, 2 => 3 } -#~# ORIGINAL +#~# ORIGINAL skip #~# align_hash_keys: true { foo: 1, @@ -240,7 +240,7 @@ a = b :foo => x, { foo: 1, bar: 2 } -#~# ORIGINAL +#~# ORIGINAL skip #~# align_hash_keys: true =begin @@ -259,7 +259,7 @@ a = b :foo => x, :bc => 2 } -#~# ORIGINAL +#~# ORIGINAL skip #~# align_hash_keys: true foo 1, :bar => 2 , :baz => 3 @@ -268,7 +268,7 @@ foo 1, :bar => 2 , :baz => 3 foo 1, :bar => 2, :baz => 3 -#~# ORIGINAL +#~# ORIGINAL skip #~# align_hash_keys: true { diff --git a/spec/lib/rufo/formatter_source_specs/align_mix.rb.spec b/spec/lib/rufo/formatter_source_specs/align_mix.rb.spec index a219013f..dd45f61d 100644 --- a/spec/lib/rufo/formatter_source_specs/align_mix.rb.spec +++ b/spec/lib/rufo/formatter_source_specs/align_mix.rb.spec @@ -1,4 +1,4 @@ -#~# ORIGINAL +#~# ORIGINAL skip #~# align_assignments: true, align_hash_keys: true, align_comments: true abc = 1 @@ -11,7 +11,7 @@ abc = 1 a = {foo: 1, # comment bar: 2} # another -#~# ORIGINAL +#~# ORIGINAL skip #~# align_assignments: true, align_hash_keys: true, align_comments: true abc = 1 diff --git a/spec/lib/rufo/formatter_source_specs/and_or_not.rb.spec b/spec/lib/rufo/formatter_source_specs/and_or_not.rb.spec index 98435e1a..becf30c8 100644 --- a/spec/lib/rufo/formatter_source_specs/and_or_not.rb.spec +++ b/spec/lib/rufo/formatter_source_specs/and_or_not.rb.spec @@ -1,36 +1,37 @@ -#~# ORIGINAL +#~# ORIGINAL foo and bar #~# EXPECTED -foo and bar +foo and bar -#~# ORIGINAL +#~# ORIGINAL foo or bar #~# EXPECTED -foo or bar +foo or bar -#~# ORIGINAL +#~# ORIGINAL not foo #~# EXPECTED -not foo +not foo -#~# ORIGINAL +#~# ORIGINAL -not(x) +not( x + ) #~# EXPECTED not(x) -#~# ORIGINAL +#~# ORIGINAL not (x) @@ -38,7 +39,7 @@ not (x) not (x) -#~# ORIGINAL +#~# ORIGINAL multiple assign inside not not((a, b = 1, 2)) diff --git a/spec/lib/rufo/formatter_source_specs/array_literal.rb.spec b/spec/lib/rufo/formatter_source_specs/array_literal.rb.spec index 2e6ca930..f6303148 100644 --- a/spec/lib/rufo/formatter_source_specs/array_literal.rb.spec +++ b/spec/lib/rufo/formatter_source_specs/array_literal.rb.spec @@ -12,7 +12,7 @@ #~# EXPECTED -[ 1 ] +[1] #~# ORIGINAL @@ -20,7 +20,7 @@ #~# EXPECTED -[ 1, 2 ] +[1, 2] #~# ORIGINAL @@ -28,7 +28,7 @@ #~# EXPECTED -[ 1, 2 ] +[1, 2] #~# ORIGINAL @@ -37,9 +37,7 @@ #~# EXPECTED -[ - 1, 2 -] +[1, 2] #~# ORIGINAL @@ -48,9 +46,7 @@ #~# EXPECTED -[ - 1, 2, -] +[1, 2] #~# ORIGINAL @@ -60,10 +56,7 @@ #~# EXPECTED -[ - 1, 2, - 3, 4 -] +[1, 2, 3, 4] #~# ORIGINAL @@ -73,12 +66,9 @@ #~# EXPECTED -[ - 1, - 2 -] +[1, 2] -#~# ORIGINAL +#~# ORIGINAL skip [ # comment 1 , @@ -91,7 +81,7 @@ 2 ] -#~# ORIGINAL +#~# ORIGINAL skip [ 1 , # comment @@ -104,7 +94,7 @@ 2 ] -#~# ORIGINAL +#~# ORIGINAL [ 1 , 2, 3, @@ -112,11 +102,9 @@ #~# EXPECTED -[ 1, - 2, 3, - 4 ] +[1, 2, 3, 4] -#~# ORIGINAL +#~# ORIGINAL [ 1 , 2, 3, @@ -124,11 +112,9 @@ #~# EXPECTED -[ 1, - 2, 3, - 4 ] +[1, 2, 3, 4] -#~# ORIGINAL +#~# ORIGINAL [ 1 , 2, 3, @@ -137,11 +123,9 @@ #~# EXPECTED -[ 1, - 2, 3, - 4 ] +[1, 2, 3, 4] -#~# ORIGINAL +#~# ORIGINAL skip [ 1 , 2, 3, @@ -155,7 +139,7 @@ 4 # foo ] -#~# ORIGINAL +#~# ORIGINAL begin [ @@ -165,12 +149,10 @@ #~# EXPECTED begin - [ - 1, 2 - ] + [1, 2] end -#~# ORIGINAL +#~# ORIGINAL skip [ 1 # foo @@ -182,31 +164,31 @@ end 1 # foo ] -#~# ORIGINAL +#~# ORIGINAL [ *x ] #~# EXPECTED -[ *x ] +[*x] -#~# ORIGINAL +#~# ORIGINAL [ *x , 1 ] #~# EXPECTED -[ *x, 1 ] +[*x, 1] -#~# ORIGINAL +#~# ORIGINAL skip [ 1, *x ] #~# EXPECTED -[ 1, *x ] +[1, *x] -#~# ORIGINAL +#~# ORIGINAL x = [{ foo: 1 @@ -214,19 +196,17 @@ end #~# EXPECTED -x = [{ - foo: 1 -}] +x = [{ foo: 1 }] -#~# ORIGINAL +#~# ORIGINAL [1, 2] #~# EXPECTED -[1, 2] +[1, 2] -#~# ORIGINAL +#~# ORIGINAL skip [ 1, @@ -242,7 +222,7 @@ x = [{ 2, ] -#~# ORIGINAL +#~# ORIGINAL [ *a, @@ -251,12 +231,9 @@ x = [{ #~# EXPECTED -[ - *a, - b, -] +[*a, b] -#~# ORIGINAL +#~# ORIGINAL [ 1, *a, @@ -265,7 +242,4 @@ x = [{ #~# EXPECTED -[ - 1, *a, - b, -] +[1, *a, b] diff --git a/spec/lib/rufo/formatter_source_specs/assignment_operators.rb.spec b/spec/lib/rufo/formatter_source_specs/assignment_operators.rb.spec index 56c0064f..55f6a21c 100644 --- a/spec/lib/rufo/formatter_source_specs/assignment_operators.rb.spec +++ b/spec/lib/rufo/formatter_source_specs/assignment_operators.rb.spec @@ -13,8 +13,7 @@ a += #~# EXPECTED -a += - 2 +a += 2 #~# ORIGINAL @@ -22,4 +21,14 @@ a+=1 #~# EXPECTED -a+=1 +a += 1 + +#~# ORIGINAL plus equals on string + +a='hello' +a+='dma' + +#~# EXPECTED + +a = 'hello' +a += 'dma' diff --git a/spec/lib/rufo/formatter_source_specs/assignments.rb.spec b/spec/lib/rufo/formatter_source_specs/assignments.rb.spec index f0755cd7..43808c98 100644 --- a/spec/lib/rufo/formatter_source_specs/assignments.rb.spec +++ b/spec/lib/rufo/formatter_source_specs/assignments.rb.spec @@ -1,32 +1,41 @@ -#~# ORIGINAL +#~# ORIGINAL simple assignment a = 1 #~# EXPECTED -a = 1 +a = 1 -#~# ORIGINAL +#~# ORIGINAL assignment with newline a = 2 #~# EXPECTED -a = - 2 +a = 2 -#~# ORIGINAL +#~# ORIGINAL skip assignment with comment a = # hello 2 #~# EXPECTED -a = # hello +a = # hello 2 -#~# ORIGINAL +#~# ORIGINAL assignment with line length +#~# line_length: 10 + +a_really_long_variable_name=1 + +#~# EXPECTED + +a_really_long_variable_name = + 1 + +#~# ORIGINAL assign to if a = if 1 2 @@ -38,7 +47,7 @@ a = if 1 2 end -#~# ORIGINAL +#~# ORIGINAL assign to unless a = unless 1 2 @@ -50,7 +59,7 @@ a = unless 1 2 end -#~# ORIGINAL +#~# ORIGINAL assign to begin a = begin 1 @@ -62,7 +71,7 @@ a = begin 1 end -#~# ORIGINAL +#~# ORIGINAL assign to case a = case when 1 @@ -76,7 +85,7 @@ a = case 2 end -#~# ORIGINAL +#~# ORIGINAL assign to begin a = begin 1 @@ -88,7 +97,7 @@ a = begin 1 end -#~# ORIGINAL +#~# ORIGINAL assign to begin rescue a = begin 1 @@ -104,7 +113,7 @@ a = begin 2 end -#~# ORIGINAL +#~# ORIGINAL a = begin 1 @@ -120,15 +129,15 @@ a = begin 2 end -#~# ORIGINAL +#~# ORIGINAL a=1 #~# EXPECTED -a=1 +a = 1 -#~# ORIGINAL +#~# ORIGINAL a = \ begin @@ -137,7 +146,36 @@ a = \ #~# EXPECTED -a = - begin - 1 +a = begin + 1 +end + +#~# ORIGINAL assign to multiple statements in paren + +a = ( + v=do_work + do_work + 10 + ) + +#~# EXPECTED + +a = ( + v = do_work + do_work + 10 +) + +#~# ORIGINAL skip assignment to begin inside method +# this test has a problem because we track @column as if my_method hasn't broken until it breaks inside the group. +# we'll need to resolve this test + +def my_method; a = begin; 2; rescue; 4; end; end + +#~# EXPECTED + +def my_method + a = begin + 2 + rescue + 4 end +end diff --git a/spec/lib/rufo/formatter_source_specs/hash_literal.rb.spec b/spec/lib/rufo/formatter_source_specs/hash_literal.rb.spec index 4d409a17..61cb66be 100644 --- a/spec/lib/rufo/formatter_source_specs/hash_literal.rb.spec +++ b/spec/lib/rufo/formatter_source_specs/hash_literal.rb.spec @@ -1,4 +1,4 @@ -#~# ORIGINAL +#~# ORIGINAL { } @@ -6,39 +6,39 @@ {} -#~# ORIGINAL +#~# ORIGINAL {:foo => 1 } #~# EXPECTED -{:foo => 1 } +{ :foo => 1 } -#~# ORIGINAL +#~# ORIGINAL {:foo => 1} #~# EXPECTED -{:foo => 1} +{ :foo => 1 } -#~# ORIGINAL +#~# ORIGINAL { :foo => 1 } #~# EXPECTED -{ :foo => 1 } +{ :foo => 1 } -#~# ORIGINAL +#~# ORIGINAL { :foo => 1 , 2 => 3 } #~# EXPECTED -{ :foo => 1, 2 => 3 } +{ :foo => 1, 2 => 3 } -#~# ORIGINAL +#~# ORIGINAL { :foo => 1 , @@ -46,12 +46,24 @@ #~# EXPECTED +{ :foo => 1, 2 => 3 } + +#~# ORIGINAL large hash +#~# line_length: 10 + +{ :foo => 1 , 2 => 3, + :king => :panda, :bear => :honey } + +#~# EXPECTED + { - :foo => 1, - 2 => 3 + :foo => 1, + 2 => 3, + :king => :panda, + :bear => :honey, } -#~# ORIGINAL +#~# ORIGINAL { **x } @@ -59,32 +71,32 @@ { **x } -#~# ORIGINAL +#~# ORIGINAL {foo: 1} #~# EXPECTED -{foo: 1} +{ foo: 1 } -#~# ORIGINAL +#~# ORIGINAL { foo: 1 } #~# EXPECTED -{ foo: 1 } +{ foo: 1 } -#~# ORIGINAL +#~# ORIGINAL { :foo => 1 } #~# EXPECTED -{ :foo => 1 } +{ :foo => 1 } -#~# ORIGINAL +#~# ORIGINAL { "foo": 1 } @@ -92,7 +104,7 @@ { "foo": 1 } -#~# ORIGINAL +#~# ORIGINAL { "foo #{ 2 }": 1 } @@ -100,37 +112,53 @@ { "foo #{2}": 1 } -#~# ORIGINAL +#~# ORIGINAL { :"one two" => 3 } #~# EXPECTED -{ :"one two" => 3 } +{ :"one two" => 3 } -#~# ORIGINAL +#~# ORIGINAL { foo: 1, bar: 2 } #~# EXPECTED -{ foo: 1, - bar: 2 } +{ foo: 1, bar: 2 } -#~# ORIGINAL +#~# ORIGINAL {foo: 1, bar: 2} #~# EXPECTED -{foo: 1, bar: 2} +{ foo: 1, bar: 2 } -#~# ORIGINAL +#~# ORIGINAL {1 => 2} #~# EXPECTED -{1 => 2} +{ 1 => 2 } + +#~# ORIGINAL hash that should break +#~# line_length: 10 + +{ a: :a, b: :b, c: :c, d: :d, e: :e, f: :f, g: :g } + +#~# EXPECTED + +{ + a: :a, + b: :b, + c: :c, + d: :d, + e: :e, + f: :f, + g: :g, +} diff --git a/spec/lib/rufo/formatter_spec.rb b/spec/lib/rufo/formatter_spec.rb index 231f7c27..b7cf7d22 100644 --- a/spec/lib/rufo/formatter_spec.rb +++ b/spec/lib/rufo/formatter_spec.rb @@ -10,20 +10,20 @@ def assert_source_specs(source_specs) describe relative_path do tests = [] current_test = nil - ignore_next_line = false File.foreach(source_specs).with_index do |line, index| case - when line =~ /^#~# ORIGINAL ?([\w\s]+)$/ + when line =~ /^#~# ORIGINAL ?(skip ?)?(.*)$/ # save old test tests.push current_test if current_test # start a new test - name = $~[1].strip + skip = !!$~[1] + name = $~[2].strip name = "unnamed test" if name.empty? - current_test = {name: name, line: index + 1, options: {}, original: ""} + current_test = {name: name, line: index + 1, options: {}, original: "",skip: skip} when line =~ /^#~# EXPECTED$/ current_test[:expected] = "" when line =~ /^#~# (.+)$/ @@ -37,6 +37,8 @@ def assert_source_specs(source_specs) tests.concat([current_test]).each do |test| it "formats #{test[:name]} (line: #{test[:line]})" do + skip if test[:skip] + formatted = described_class.format(test[:original], **test[:options]) expected = test[:expected].rstrip + "\n" expect(formatted).to eq(expected) diff --git a/spec/lib/rufo/new_formatter_spec.rb b/spec/lib/rufo/new_formatter_spec.rb new file mode 100644 index 00000000..d38a7bb0 --- /dev/null +++ b/spec/lib/rufo/new_formatter_spec.rb @@ -0,0 +1,111 @@ +require "spec_helper" +require "fileutils" + +NEW_FORMATTER_RUBY_VERSION = Gem::Version.new(RUBY_VERSION) +NEW_FORMATTER_FILE_PATH = Pathname.new(File.dirname(__FILE__)) + +def assert_source_specs(source_specs) + relative_path = Pathname.new(source_specs).relative_path_from(NEW_FORMATTER_FILE_PATH).to_s + + describe relative_path do + tests = [] + current_test = nil + + File.foreach(source_specs).with_index do |line, index| + case + when line =~ /^#~# ORIGINAL ?(skip ?)?(.*)$/ + # save old test + tests.push current_test if current_test + + # start a new test + + skip = !!$~[1] + name = $~[2].strip + name = "unnamed test" if name.empty? + + current_test = {name: name, line: index + 1, options: {}, original: "",skip: skip} + when line =~ /^#~# EXPECTED$/ + current_test[:expected] = "" + when line =~ /^#~# (.+)$/ + current_test[:options] = eval("{ #{$~[1]} }") + when current_test[:expected] + current_test[:expected] += line + when current_test[:original] + current_test[:original] += line + end + end + + (tests + [current_test]).each do |test| + it "formats #{test[:name]} (line: #{test[:line]})" do + skip if test[:skip] + error = nil + + begin + formatted = described_class.format(test[:original], **test[:options]).to_s.strip + rescue StandardError => e + error = e + formatted = "" + end + + expected = test[:expected].strip + + if expected != formatted + # message = "#{Rufi::Formatter.debug(test[:original], **test[:options])}\n\n" + + # "#{Rufi::Formatter.format(test[:original], **test[:options]).ai(index: false)}\n\n" + + + message = if test[:options].any? + "#~# OPTIONS\n\n" + test[:options].ai + else + "" + end + + message += "\n\n#~# ORIGINAL\n" + + test[:original] + + "#~# EXPECTED\n\n" + + expected + + "\n\n#~# ACTUAL\n\n" + + formatted + + "\n\n#~# INSPECT\n\n" + + formatted.inspect + + if error + puts message + fail error + else + fail message + end + end + + expect(formatted).to eq(expected) + end + end + end +end + +def assert_format(code, expected) + it "formats #{code.inspect} to #{expected.inspect}" do + expect(described_class.format(code)).to eq(expected) + end +end + +RSpec.describe Rufo::NewFormatter do + Dir[File.join(NEW_FORMATTER_FILE_PATH, "/formatter_source_specs/rufi*")].each do |source_specs| + assert_source_specs(source_specs) if File.file?(source_specs) + end + + %w(array_literal hash_literal and_or_not assignment_operators assignments).each do |source_spec_name| + file = File.join(NEW_FORMATTER_FILE_PATH, "/formatter_source_specs/#{source_spec_name}.rb.spec") + fail "missing #{source_spec_name}" unless File.exist?(file) + assert_source_specs(file) if File.file?(file) + end + + Dir[File.join(NEW_FORMATTER_FILE_PATH, "/formatter_source_specs/*")].sort.take(11).each do |source_specs| + assert_source_specs(source_specs) if File.file?(source_specs) + end + + # if NEW_FORMATTER_RUBY_VERSION >= Gem::Version.new("2.3") + # Dir[File.join(NEW_FORMATTER_FILE_PATH, "/source_specs/2.3/*")].each do |source_specs| + # assert_source_specs(source_specs) if File.file?(source_specs) + # end + # end +end diff --git a/spec/source_specs/rufi_basic.rb.spec b/spec/source_specs/rufi_basic.rb.spec new file mode 100644 index 00000000..272a0137 --- /dev/null +++ b/spec/source_specs/rufi_basic.rb.spec @@ -0,0 +1,523 @@ +#~# ORIGINAL + +var='hello' + +#~# EXPECTED + +var = 'hello' + +#~# ORIGINAL single variable + +var='hello' + +#~# EXPECTED + +var = 'hello' + +#~# ORIGINAL two variable assignments + +var='hello' +var='hello' + +#~# EXPECTED + +var = 'hello' +var = 'hello' + +#~# ORIGINAL two variable assignments with newline + +var='hello' + +var='hello' + +#~# EXPECTED + +var = 'hello' + +var = 'hello' + +#~# ORIGINAL long variable assignment + +var='ssuper-super-super-super-duper-long-stringsuper-super-super-super-duper-long-stringuper-super-super-super-duper-long-string' + +#~# EXPECTED + +var = + 'ssuper-super-super-super-duper-long-stringsuper-super-super-super-duper-long-stringuper-super-super-super-duper-long-string' + +#~# ORIGINAL + +var='hello';van='goodbye'; + +#~# EXPECTED + +var = 'hello' +van = 'goodbye' + +#~# ORIGINAL method with no arguments + +def hello; 'world'; end + +#~# EXPECTED + +def hello + 'world' +end + +#~# ORIGINAL method with arguments and useless spaces beforehand + +def hello arg + arg +end + +#~# EXPECTED + +def hello(arg) + arg +end + +#~# ORIGINAL empty method into one liner + +def empty +end + +#~# EXPECTED + +def empty; end + +#~# ORIGINAL basic method + +def hello; 'world'; end + +#~# EXPECTED + +def hello + 'world' +end + +#~# ORIGINAL multiple statements inside method + +def hello; var='var'; van='van'; end + +#~# EXPECTED + +def hello + var = 'var' + van = 'van' +end + +#~# ORIGINAL method inside method + +def hello + def method_inside + 'world' + end +end + +#~# EXPECTED + +def hello + def method_inside + 'world' + end +end + +#~# ORIGINAL multiple methods inside method + +def hello + def method_inside + 'hello' + end + def second_method + 'world' + end +end + +#~# EXPECTED + +def hello + def method_inside + 'hello' + end + + def second_method + 'world' + end +end + +#~# ORIGINAL multiple one-liner methods inside method + +def hello + + def method_inside; end + def sherpa + end +end + +#~# EXPECTED + +def hello + def method_inside; end + + def sherpa; end +end + +#~# ORIGINAL one-line separated statements inside a method + +def hello + 'ok' + + 'ok' +end + +#~# EXPECTED + +def hello + 'ok' + + 'ok' +end + +#~# ORIGINAL double nested methods + +def hello + def its_me + def i_heard + "that you" + end + end +end + +#~# EXPECTED + +def hello + def its_me + def i_heard + "that you" + end + end +end + +#~# ORIGINAL double nested methods + +def hello(arg) + def its_me(arg2) + def i_heard(arg3) + "that you" + end + end +end + +#~# EXPECTED + +def hello(arg) + def its_me(arg2) + def i_heard(arg3) + "that you" + end + end +end + +#~# ORIGINAL method with one argument + +def say_hi(name) + name +end + +#~# EXPECTED + +def say_hi(name) + name +end + +#~# ORIGINAL method with arguments + +def say_hi(name, time) + name +end + +#~# EXPECTED + +def say_hi(name, time) + name +end + +#~# ORIGINAL small hash + +x = { a: :a } + +#~# EXPECTED + +x = { a: :a } + +#~# ORIGINAL nested hash + +x = { a: { b: :b, c: :c }, b: :b, c: { d: :d, e: :e, f: :f, g: { h: :h, i: :i, j: :j, k: :k, inner_last: :inner_last } }, d: :d, e: { f: :f, g: :g, h: :h }, f: { g: :g, h: :h } } + +#~# EXPECTED + +x = { + a: { b: :b, c: :c }, + b: :b, + c: { + d: :d, + e: :e, + f: :f, + g: { + h: :h, + i: :i, + j: :j, + k: :k, + inner_last: :inner_last, + }, + }, + d: :d, + e: { + f: :f, + g: :g, + h: :h, + }, + f: { + g: :g, + h: :h, + }, +} + +#~# ORIGINAL big hash + +x = { a: :a, b: :b, c: :c, d: :d, e: :e, f: :f, g: :g, h: :h, i: :i, j: :j, k: :k, l: :l, m: :m, n: :n, o: :o, p: :p, q: :q } + +#~# EXPECTED + +x = { + a: :a, + b: :b, + c: :c, + d: :d, + e: :e, + f: :f, + g: :g, + h: :h, + i: :i, + j: :j, + k: :k, + l: :l, + m: :m, + n: :n, + o: :o, + p: :p, + q: :q, +} + +#~# ORIGINAL method with many parameters + +def big_method(one, two, three, four, five, six, seven, eight, nine, ten, eleven, twelve, thirteen, fourteen, fifteen) + 'ok' +end + +#~# EXPECTED + +def big_method( + one, + two, + three, + four, + five, + six, + seven, + eight, + nine, + ten, + eleven, + twelve, + thirteen, + fourteen, + fifteen +) + 'ok' +end + +#~# ORIGINAL statements inside a method + +def my_method + var='hello'; var='hello' +end + +#~# EXPECTED + +def my_method + var = 'hello' + var = 'hello' +end + +#~# ORIGINAL a test of judgement + +def big_method(one, two, three, four, five, six, seven, eight, nine, ten, eleven, twelve, thirteen, fourteen, fifteen) + 'ok' + + def inner_method(no_break_needed) + no_break_needed + 'ok' + end +end + +#~# EXPECTED + +def big_method( + one, + two, + three, + four, + five, + six, + seven, + eight, + nine, + ten, + eleven, + twelve, + thirteen, + fourteen, + fifteen +) + 'ok' + + def inner_method(no_break_needed) + no_break_needed + 'ok' + end +end + +#~# ORIGINAL another + +var = "my extremely long string that should definintely break when we attempt to rebuild it. I mean, I'm long enough right? I sure hope so" +var = "no break plz" + +#~# EXPECTED + +var = + "my extremely long string that should definintely break when we attempt to rebuild it. I mean, I'm long enough right? I sure hope so" +var = "no break plz" + +#~# ORIGINAL array literal + +var=[ ] + +#~# EXPECTED + +var = [] + +#~# ORIGINAL array literal with strings + +var=['ok','hello'] + +#~# EXPECTED + +var = ['ok', 'hello'] + +#~# ORIGINAL array literal with numbers + +var=[1,5,'ok'] + +#~# EXPECTED + +var = [1, 5, 'ok'] + +#~# ORIGINAL long array literal + +var=[1,'this is a super duper long string that will push us way over the top. give me a break dude', 'why is this array so long?',5,'ok'] + +#~# EXPECTED + +var = [ + 1, + 'this is a super duper long string that will push us way over the top. give me a break dude', + 'why is this array so long?', + 5, + 'ok', +] + +#~# ORIGINAL empty begin + +begin +end + +#~# EXPECTED + +begin; end + +#~# ORIGINAL begin + +begin + 'begin' +end + +#~# EXPECTED + +begin + 'begin' +end + +#~# ORIGINAL begin rescue end + +begin + 'begin rescue end' +rescue + 10 +end + +#~# EXPECTED + +begin + 'begin rescue end' +rescue + 10 +end + +#~# ORIGINAL begin rescue type end + +begin + 'begin rescue end' +rescue KeyError + 10 +end + +#~# EXPECTED + +begin + 'begin rescue end' +rescue KeyError + 10 +end + +#~# ORIGINAL begin rescue multiple types end + +begin + 'begin rescue end' +rescue KeyError, RuntimeError + 10 +end + +#~# EXPECTED + +begin + 'begin rescue end' +rescue KeyError, RuntimeError + 10 +end + +#~# ORIGINAL begin rescue type assignment end + +begin + 'begin rescue end' +rescue KeyError => e + 10 +end + +#~# EXPECTED + +begin + 'begin rescue end' +rescue KeyError => e + 10 +end diff --git a/spec/source_specs/rufi_classes.rb.spec b/spec/source_specs/rufi_classes.rb.spec new file mode 100644 index 00000000..59d58650 --- /dev/null +++ b/spec/source_specs/rufi_classes.rb.spec @@ -0,0 +1,21 @@ +#~# ORIGINAL inline class + +class MyError; end + +#~# EXPECTED + +class MyError; end + +#~# ORIGINAL empty class + +class Dragon + def name; "Trogdor!"; end +end + +#~# EXPECTED + +class Dragon + def name + "Trogdor!" + end +end diff --git a/spec/source_specs/rufi_strings.rb.spec b/spec/source_specs/rufi_strings.rb.spec new file mode 100644 index 00000000..32b05713 --- /dev/null +++ b/spec/source_specs/rufi_strings.rb.spec @@ -0,0 +1,100 @@ +#~# ORIGINAL string literal + +'hello' + +#~# EXPECTED + +'hello' + +#~# ORIGINAL string with useless prefix spaces + + 'hello' + +#~# EXPECTED + +'hello' + +#~# ORIGINAL two strings + +"hello" +"hello" + +#~# EXPECTED + +"hello" +"hello" + +#~# ORIGINAL two strings with a newline between + +"hello" + +"hello" + +#~# EXPECTED + +"hello" + +"hello" + +#~# ORIGINAL string interpolation + +"hello #{name}" + +#~# EXPECTED + +"hello #{name}" + +#~# ORIGINAL string interpolation with useless spaces before + +"hello #{ name}" + +#~# EXPECTED + +"hello #{name}" + +#~# ORIGINAL string interpolation with useless spaces after + +"hello #{name }" + +#~# EXPECTED + +"hello #{name}" + +#~# ORIGINAL string interpolation with useless spaces around + +"hello #{ name }" + +#~# EXPECTED + +"hello #{name}" + +#~# ORIGINAL string interpolation with useless newlines around + +"hello #{ +name}" + +#~# EXPECTED + +"hello #{name}" + +#~# ORIGINAL two string statements + +'hello';"hello" + +#~# EXPECTED + +'hello' +"hello" + +#~# ORIGINAL max one line separates expressions + +'hello' + + +'hello' + +#~# EXPECTED + +'hello' + +'hello'