mirror of
				https://gitea.invidious.io/iv-org/shard-ameba.git
				synced 2024-08-15 00:53:29 +00:00 
			
		
		
		
	Merge pull request #291 from crystal-ameba/Sija/lint-not-nil-with-no-bang-rule
Add `Lint/NotNilAfterNoBang` rule
This commit is contained in:
		
						commit
						078c83c2d7
					
				
					 2 changed files with 136 additions and 0 deletions
				
			
		
							
								
								
									
										75
									
								
								spec/ameba/rule/lint/not_nil_after_no_bang_spec.cr
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								spec/ameba/rule/lint/not_nil_after_no_bang_spec.cr
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,75 @@ | ||||||
|  | require "../../../spec_helper" | ||||||
|  | 
 | ||||||
|  | module Ameba::Rule::Lint | ||||||
|  |   subject = NotNilAfterNoBang.new | ||||||
|  | 
 | ||||||
|  |   describe NotNilAfterNoBang do | ||||||
|  |     it "passes for valid cases" do | ||||||
|  |       expect_no_issues subject, <<-CRYSTAL | ||||||
|  |         (1..3).index(1).not_nil!(:foo) | ||||||
|  |         (1..3).index { |i| i > 2 }.not_nil!(:foo) | ||||||
|  |         (1..3).find { |i| i > 2 }.not_nil!(:foo) | ||||||
|  |         CRYSTAL | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     it "reports if there is an `index` call followed by `not_nil!`" do | ||||||
|  |       source = expect_issue subject, <<-CRYSTAL | ||||||
|  |         (1..3).index(1).not_nil! | ||||||
|  |              # ^^^^^^^^^^^^^^^^^ error: Use `index! {...}` instead of `index {...}.not_nil!` | ||||||
|  |         CRYSTAL | ||||||
|  | 
 | ||||||
|  |       expect_correction source, <<-CRYSTAL | ||||||
|  |         (1..3).index!(1) | ||||||
|  |         CRYSTAL | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     it "reports if there is an `index` call with block followed by `not_nil!`" do | ||||||
|  |       source = expect_issue subject, <<-CRYSTAL | ||||||
|  |         (1..3).index { |i| i > 2 }.not_nil! | ||||||
|  |              # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: Use `index! {...}` instead of `index {...}.not_nil!` | ||||||
|  |         CRYSTAL | ||||||
|  | 
 | ||||||
|  |       expect_correction source, <<-CRYSTAL | ||||||
|  |         (1..3).index! { |i| i > 2 } | ||||||
|  |         CRYSTAL | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     it "reports if there is a `find` call with block followed by `not_nil!`" do | ||||||
|  |       source = expect_issue subject, <<-CRYSTAL | ||||||
|  |         (1..3).find { |i| i > 2 }.not_nil! | ||||||
|  |              # ^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: Use `find! {...}` instead of `find {...}.not_nil!` | ||||||
|  |         CRYSTAL | ||||||
|  | 
 | ||||||
|  |       expect_correction source, <<-CRYSTAL | ||||||
|  |         (1..3).find! { |i| i > 2 } | ||||||
|  |         CRYSTAL | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     it "passes if there is a `find` call without block followed by `not_nil!`" do | ||||||
|  |       expect_no_issues subject, <<-CRYSTAL | ||||||
|  |         (1..3).find(1).not_nil! | ||||||
|  |         CRYSTAL | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     context "macro" do | ||||||
|  |       it "doesn't report in macro scope" do | ||||||
|  |         expect_no_issues subject, <<-CRYSTAL | ||||||
|  |           {{ [1, 2, 3].index(1).not_nil! }} | ||||||
|  |           CRYSTAL | ||||||
|  |       end | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     it "reports rule, pos and message" do | ||||||
|  |       s = Source.new %( | ||||||
|  |         (1..3).index(1).not_nil! | ||||||
|  |       ), "source.cr" | ||||||
|  |       subject.catch(s).should_not be_valid | ||||||
|  |       issue = s.issues.first | ||||||
|  | 
 | ||||||
|  |       issue.rule.should_not be_nil | ||||||
|  |       issue.location.to_s.should eq "source.cr:1:8" | ||||||
|  |       issue.end_location.to_s.should eq "source.cr:1:24" | ||||||
|  |       issue.message.should eq "Use `index! {...}` instead of `index {...}.not_nil!`" | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | end | ||||||
							
								
								
									
										61
									
								
								src/ameba/rule/lint/not_nil_after_no_bang.cr
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								src/ameba/rule/lint/not_nil_after_no_bang.cr
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,61 @@ | ||||||
|  | module Ameba::Rule::Lint | ||||||
|  |   # This rule is used to identify usage of `index/find` calls followed by `not_nil!`. | ||||||
|  |   # | ||||||
|  |   # For example, this is considered a code smell: | ||||||
|  |   # | ||||||
|  |   # ``` | ||||||
|  |   # %w[Alice Bob].find(&.match(/^A./)).not_nil! | ||||||
|  |   # ``` | ||||||
|  |   # | ||||||
|  |   # And can be written as this: | ||||||
|  |   # | ||||||
|  |   # ``` | ||||||
|  |   # %w[Alice Bob].find!(&.match(/^A./)) | ||||||
|  |   # ``` | ||||||
|  |   # | ||||||
|  |   # YAML configuration example: | ||||||
|  |   # | ||||||
|  |   # ``` | ||||||
|  |   # Lint/NotNilAfterNoBang: | ||||||
|  |   #   Enabled: true | ||||||
|  |   # ``` | ||||||
|  |   class NotNilAfterNoBang < Base | ||||||
|  |     include AST::Util | ||||||
|  | 
 | ||||||
|  |     properties do | ||||||
|  |       description "Identifies usage of `index/find` calls followed by `not_nil!`" | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     BLOCK_CALL_NAMES = %w(index find) | ||||||
|  |     CALL_NAMES       = %w(index) | ||||||
|  | 
 | ||||||
|  |     NOT_NIL_NAME = "not_nil!" | ||||||
|  |     MSG          = "Use `%s! {...}` instead of `%s {...}.not_nil!`" | ||||||
|  | 
 | ||||||
|  |     def test(source) | ||||||
|  |       AST::NodeVisitor.new self, source, skip: [ | ||||||
|  |         Crystal::Macro, | ||||||
|  |         Crystal::MacroExpression, | ||||||
|  |         Crystal::MacroIf, | ||||||
|  |         Crystal::MacroFor, | ||||||
|  |       ] | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     def test(source, node : Crystal::Call) | ||||||
|  |       return unless node.name == NOT_NIL_NAME && node.args.empty? | ||||||
|  |       return unless (obj = node.obj).is_a?(Crystal::Call) | ||||||
|  |       return unless obj.name.in?(obj.block ? BLOCK_CALL_NAMES : CALL_NAMES) | ||||||
|  | 
 | ||||||
|  |       return unless name_location = obj.name_location | ||||||
|  |       return unless name_location_end = name_end_location(obj) | ||||||
|  |       return unless end_location = name_end_location(node) | ||||||
|  | 
 | ||||||
|  |       msg = MSG % {obj.name, obj.name} | ||||||
|  | 
 | ||||||
|  |       issue_for name_location, end_location, msg do |corrector| | ||||||
|  |         corrector.insert_after(name_location_end, '!') | ||||||
|  |         corrector.remove_trailing(node, ".not_nil!".size) | ||||||
|  |       end | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | end | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue