diff --git a/lib/tailor/cli/options.rb b/lib/tailor/cli/options.rb index b53035c..b3fe957 100644 --- a/lib/tailor/cli/options.rb +++ b/lib/tailor/cli/options.rb @@ -93,6 +93,11 @@ def self.parse!(args) options.style[:spaces_before_comma] = c end + opt.on('--spaces-after-conditional NUMBER', + 'Spaces to expect after a conditional. (default: 1)') do |c| + options.style[:spaces_after_conditional] = c + end + opt.on('--spaces-after-lbrace NUMBER', 'Spaces to expect after a {. (default: 1)') do |c| options.style[:spaces_after_lbrace] = c diff --git a/lib/tailor/configuration/style.rb b/lib/tailor/configuration/style.rb index 85f2618..873fcbb 100644 --- a/lib/tailor/configuration/style.rb +++ b/lib/tailor/configuration/style.rb @@ -35,6 +35,7 @@ def self.define_property(name) define_property :max_code_lines_in_method define_property :max_line_length define_property :spaces_after_comma + define_property :spaces_after_conditional define_property :spaces_after_lbrace define_property :spaces_after_lbracket define_property :spaces_after_lparen @@ -58,6 +59,7 @@ def initialize max_code_lines_in_method(30, level: :error) max_line_length(80, level: :error) spaces_after_comma(1, level: :error) + spaces_after_conditional(1, level: :error) spaces_after_lbrace(1, level: :error) spaces_after_lbracket(0, level: :error) spaces_after_lparen(0, level: :error) diff --git a/lib/tailor/rulers/spaces_after_conditional_ruler.rb b/lib/tailor/rulers/spaces_after_conditional_ruler.rb new file mode 100644 index 0000000..2493a79 --- /dev/null +++ b/lib/tailor/rulers/spaces_after_conditional_ruler.rb @@ -0,0 +1,51 @@ +require_relative '../ruler' + +class Tailor + module Rulers + class SpacesAfterConditionalRuler < Tailor::Ruler + + def initialize(config, options) + super(config, options) + add_lexer_observers :nl + end + + def nl_update(current_lexed_line, lineno, column) + measure(current_lexed_line, lineno) + end + + # Checks to see if spacing is present after conditionals + # + # @param [Array] lexed_line The lexed line with a conditional + # @param [Fixnum] lineno Line the problem was found on. + # @param [Fixnum] column Column the problem was found on. + def measure(lexed_line, lineno) + + idx = lexed_line.index do |pos, token, name| + token == :on_kw and %w{if unless case}.include?(name) + end + + expected_spaces = @config + spaces = expected_spaces + + if idx + column = lexed_line[idx].first.last + pos, token, name = lexed_line[idx + 1] + spaces = case token + when :on_lparen then 0 + when :on_sp + next_token = lexed_line[idx + 2] + next_token.first.last - pos.last + end + end + + if expected_spaces != spaces + @problems << Problem.new(problem_type, lineno, column, + "#{spaces} spaces after conditional at column #{column}, " + + "expected #{expected_spaces}.", @options[:level]) + end + + end + + end + end +end diff --git a/spec/functional/conditional_spacing_spec.rb b/spec/functional/conditional_spacing_spec.rb new file mode 100644 index 0000000..db82d66 --- /dev/null +++ b/spec/functional/conditional_spacing_spec.rb @@ -0,0 +1,146 @@ +require 'spec_helper' +require_relative '../support/conditional_spacing_cases' +require 'tailor/critic' +require 'tailor/configuration/style' + +describe 'Conditional spacing' do + + def file_name + self.class.description + end + + def contents + CONDITIONAL_SPACING[file_name] || begin + raise "Example not found: #{file_name}" + end + end + + before do + Tailor::Logger.stub(:log) + FakeFS.activate! + FileUtils.touch file_name + File.open(file_name, 'w') { |f| f.write contents } + end + + let(:critic) { Tailor::Critic.new } + + let(:style) do + style = Tailor::Configuration::Style.new + style.trailing_newlines 0, level: :off + style.allow_invalid_ruby true, level: :off + style + end + + context :no_space_after_if do + it 'warns when there is no space after an if statement' do + critic.check_file(file_name, style.to_hash) + expect(critic.problems[file_name]).to eql [{ + :type => 'spaces_after_conditional', + :line => 1, + :column=> 0, + :message=> '0 spaces after conditional at column 0, expected 1.', + :level=> :error + }] + end + it 'warns with the correct number of expected spaces' do + style.spaces_after_conditional 2, level: :error + critic.check_file(file_name, style.to_hash) + expect(critic.problems[file_name]).to eql [{ + :type => 'spaces_after_conditional', + :line => 1, + :column=> 0, + :message=> '0 spaces after conditional at column 0, expected 2.', + :level=> :error + }] + end + it 'does not warn if spaces are set to zero' do + style.spaces_after_conditional 0, level: :error + critic.check_file(file_name, style.to_hash) + expect(critic.problems[file_name]).to be_empty + end + it 'does not warn if spaces are disabled' do + style.spaces_after_conditional 2, level: :off + critic.check_file(file_name, style.to_hash) + expect(critic.problems[file_name]).to be_empty + end + end + + context :space_after_if do + it 'does not warn when there is a space after the if' do + critic.check_file(file_name, style.to_hash) + expect(critic.problems[file_name]).to be_empty + end + it 'warns if spaces has been set to zero' do + style.spaces_after_conditional 0, level: :error + critic.check_file(file_name, style.to_hash) + expect(critic.problems[file_name]).to eql [{ + :type => 'spaces_after_conditional', + :line => 1, + :column=> 0, + :message=> '1 spaces after conditional at column 0, expected 0.', + :level=> :error + }] + end + end + + context :no_parens do + it 'never warns' do + critic.check_file(file_name, style.to_hash) + expect(critic.problems[file_name]).to be_empty + end + end + + context :nested_parens do + it 'warns when there is no space after an if statement' do + critic.check_file(file_name, style.to_hash) + expect(critic.problems[file_name]).to eql [{ + :type => 'spaces_after_conditional', + :line => 1, + :column=> 0, + :message=> '0 spaces after conditional at column 0, expected 1.', + :level=> :error + }] + end + end + + context :no_space_after_unless do + it 'warns when there is no space after an unless statement' do + critic.check_file(file_name, style.to_hash) + expect(critic.problems[file_name]).to eql [{ + :type => 'spaces_after_conditional', + :line => 1, + :column=> 0, + :message=> '0 spaces after conditional at column 0, expected 1.', + :level=> :error + }] + end + end + + context :space_after_unless do + it 'does not warn when there is space after an unless statement' do + critic.check_file(file_name, style.to_hash) + expect(critic.problems[file_name]).to be_empty + end + end + + context :no_space_after_case do + it 'warns when there is no space after a case statement' do + critic.check_file(file_name, style.to_hash) + expect(critic.problems[file_name]).to eql [{ + :type => 'spaces_after_conditional', + :line => 1, + :column=> 5, + :message=> '0 spaces after conditional at column 5, expected 1.', + :level=> :error + }] + end + end + + context :space_after_case do + it 'does not warn when there is space after a case statement' do + critic.check_file(file_name, style.to_hash) + expect(critic.problems[file_name]).to be_empty + end + end + +end diff --git a/spec/support/conditional_spacing_cases.rb b/spec/support/conditional_spacing_cases.rb new file mode 100644 index 0000000..48b3b5a --- /dev/null +++ b/spec/support/conditional_spacing_cases.rb @@ -0,0 +1,37 @@ +CONDITIONAL_SPACING = {} + +CONDITIONAL_SPACING['no_space_after_if'] = + %q{if(foo) +end} + +CONDITIONAL_SPACING['space_after_if'] = + %q{if (foo) +end} + +CONDITIONAL_SPACING['no_parens'] = + %q{if foo +end} + +CONDITIONAL_SPACING['nested_parens'] = + %q{if(foo(bar)) +end} + +CONDITIONAL_SPACING['no_space_after_unless'] = + %q{unless(foo) +end} + +CONDITIONAL_SPACING['space_after_unless'] = + %q{unless (foo) +end} + +CONDITIONAL_SPACING['no_space_after_case'] = + %q{puts case(true) +when true then 'a' +when false then 'b' +end} + +CONDITIONAL_SPACING['space_after_case'] = + %q{puts case (true) +when true then 'a' +when false then 'b' +end} diff --git a/spec/support/good_indentation_cases.rb b/spec/support/good_indentation_cases.rb index e18303d..e973b86 100644 --- a/spec/support/good_indentation_cases.rb +++ b/spec/support/good_indentation_cases.rb @@ -375,7 +375,7 @@ def a_method INDENT_OK['for_with_retry_loop'] = %Q{for i in 1..5 - retry if i > 2 + retry if i > 2 puts "Value of local variable is \#{i}" end} diff --git a/spec/unit/tailor/configuration/style_spec.rb b/spec/unit/tailor/configuration/style_spec.rb index b852a43..1f49c24 100644 --- a/spec/unit/tailor/configuration/style_spec.rb +++ b/spec/unit/tailor/configuration/style_spec.rb @@ -166,6 +166,7 @@ :max_code_lines_in_method => [30, { :level => :error }], :max_line_length => [80, { :level => :error }], :spaces_after_comma => [1, { :level => :error }], + :spaces_after_conditional => [1, { :level=>:error }], :spaces_after_lbrace => [1, { :level => :error }], :spaces_after_lbracket => [0, { :level => :error }], :spaces_after_lparen => [0, { :level => :error }], diff --git a/spec/unit/tailor/configuration_spec.rb b/spec/unit/tailor/configuration_spec.rb index 26cb265..af08b52 100644 --- a/spec/unit/tailor/configuration_spec.rb +++ b/spec/unit/tailor/configuration_spec.rb @@ -49,6 +49,7 @@ max_code_lines_in_method: [30, { level: :error }], max_line_length: [80, { level: :error }], spaces_after_comma: [1, { level: :error }], + spaces_after_conditional: [1, { :level=>:error }], spaces_after_lbrace: [1, { level: :error }], spaces_after_lbracket: [0, { level: :error }], spaces_after_lparen: [0, { level: :error }],