diff --git a/config/ameba.yml b/config/ameba.yml index 66043bf8..89cb381a 100644 --- a/config/ameba.yml +++ b/config/ameba.yml @@ -46,6 +46,12 @@ NegatedConditionsInUnless: # Disallows negated conditions in unless. Enabled: true +PercentArrays: + # Disallows unwanted symbols in percent array literals. + Enabled: true + StringArrayUnwantedSymbols: ',"' + SymbolArrayUnwantedSymbols: ',:' + PredicateName: # Disallows tautological predicate names, meaning those that start with # the prefix `has_` or the prefix `is_`. diff --git a/spec/ameba/rule/percent_arrays_spec.cr b/spec/ameba/rule/percent_arrays_spec.cr new file mode 100644 index 00000000..184d1a57 --- /dev/null +++ b/spec/ameba/rule/percent_arrays_spec.cr @@ -0,0 +1,69 @@ +require "../../spec_helper" + +module Ameba::Rule + describe PercentArrays do + subject = PercentArrays.new + + it "passes if percent arrays are written correctly" do + s = Source.new %q( + %i(one two three) + %w(one two three) + + %i(1 2 3) + %w(1 2 3) + + %i() + %w() + ) + subject.catch(s).should be_valid + end + + it "fails if string percent array has commas" do + s = Source.new %( %w(one, two) ) + subject.catch(s).should_not be_valid + end + + it "fails if string percent array has quotes" do + s = Source.new %( %w("one" "two") ) + subject.catch(s).should_not be_valid + end + + it "fails if symbols percent array has commas" do + s = Source.new %( %i(one, two) ) + subject.catch(s).should_not be_valid + end + + it "fails if symbols percent array has a colon" do + s = Source.new %( %i(:one :two) ) + subject.catch(s).should_not be_valid + end + + it "reports rule, location and message for %i" do + s = Source.new %( + %i(:one) + ), "source.cr" + + subject.catch(s).should_not be_valid + error = s.errors.first + error.rule.should_not be_nil + error.location.to_s.should eq "source.cr:2:9" + error.message.should eq( + "Symbols `,:` may be unwanted in %i array literals" + ) + end + + it "reports rule, location and message for %w" do + s = Source.new %( + %w("one") + ), "source.cr" + + subject.catch(s).should_not be_valid + error = s.errors.first + error.rule.should_not be_nil + error.location.to_s.should eq "source.cr:2:9" + error.message.should eq( + "Symbols `,\"` may be unwanted in %w array literals" + ) + end + end +end diff --git a/src/ameba/rule/percent_arrays.cr b/src/ameba/rule/percent_arrays.cr new file mode 100644 index 00000000..f967bfee --- /dev/null +++ b/src/ameba/rule/percent_arrays.cr @@ -0,0 +1,65 @@ +module Ameba::Rule + # A rule that disallows some unwanted symbols in percent array literals. + # + # For example, this is usually written by mistake: + # + # ``` + # %i(:one, :two) + # %w("one", "two") + # ``` + # + # And the expected example is: + # + # ``` + # %i(one two) + # %w(one two) + # ``` + # + # YAML configuration example: + # + # ``` + # PercentArrays: + # Enabled: true + # StringArrayUnwantedSymbols: ',"' + # SymbolArrayUnwantedSymbols: ',:' + # ``` + # + struct PercentArrays < Base + prop string_array_unwanted_symbols = ",\"" + prop symbol_array_unwanted_symbols = ",:" + + def test(source) + error = start_token = nil + + Tokenizer.new(source).run do |token| + case token.type + when :STRING_ARRAY_START, :SYMBOL_ARRAY_START + start_token = token.dup + when :STRING + if start_token && error.nil? + error = array_entry_invalid?(token.value, start_token.not_nil!.raw) + end + when :STRING_ARRAY_END, :SYMBOL_ARRAY_END + if error + source.error(self, start_token.try &.location, error.not_nil!) + end + error = start_token = nil + end + end + end + + private def array_entry_invalid?(entry, array_type) + case array_type + when .starts_with? "%w" + check_array_entry entry, string_array_unwanted_symbols, "%w" + when .starts_with? "%i" + check_array_entry entry, symbol_array_unwanted_symbols, "%i" + end + end + + private def check_array_entry(entry, symbols, literal) + return unless entry =~ /[#{symbols}]/ + "Symbols `#{symbols}` may be unwanted in #{literal} array literals" + end + end +end