mirror of
				https://gitea.invidious.io/iv-org/shard-ameba.git
				synced 2024-08-15 00:53:29 +00:00 
			
		
		
		
	Merge pull request #317 from crystal-ameba/Sija/further-refinements
This commit is contained in:
		
						commit
						4533e52aa5
					
				
					 40 changed files with 130 additions and 131 deletions
				
			
		
							
								
								
									
										43
									
								
								README.md
									
										
									
									
									
								
							
							
						
						
									
										43
									
								
								README.md
									
										
									
									
									
								
							|  | @ -19,21 +19,21 @@ | |||
| 
 | ||||
| - [About](#about) | ||||
| - [Usage](#usage) | ||||
|   * [Watch a tutorial](#watch-a-tutorial) | ||||
|   * [Autocorrection](#autocorrection) | ||||
|   * [Explain issues](#explain-issues) | ||||
|   * [Run in parallel](#run-in-parallel) | ||||
|   - [Watch a tutorial](#watch-a-tutorial) | ||||
|   - [Autocorrection](#autocorrection) | ||||
|   - [Explain issues](#explain-issues) | ||||
|   - [Run in parallel](#run-in-parallel) | ||||
| - [Installation](#installation) | ||||
|   * [As a project dependency:](#as-a-project-dependency) | ||||
|   * [OS X](#os-x) | ||||
|   * [Docker](#docker) | ||||
|   * [From sources](#from-sources) | ||||
|   - [As a project dependency:](#as-a-project-dependency) | ||||
|   - [OS X](#os-x) | ||||
|   - [Docker](#docker) | ||||
|   - [From sources](#from-sources) | ||||
| - [Configuration](#configuration) | ||||
|   * [Sources](#sources) | ||||
|   * [Rules](#rules) | ||||
|   * [Inline disabling](#inline-disabling) | ||||
| - [Editors & integrations](#editors--integrations) | ||||
| - [Credits & inspirations](#credits--inspirations) | ||||
|   - [Sources](#sources) | ||||
|   - [Rules](#rules) | ||||
|   - [Inline disabling](#inline-disabling) | ||||
| - [Editors \& integrations](#editors--integrations) | ||||
| - [Credits \& inspirations](#credits--inspirations) | ||||
| - [Contributors](#contributors) | ||||
| 
 | ||||
| ## About | ||||
|  | @ -201,8 +201,8 @@ In this example we define default globs and exclude `src/compiler` folder: | |||
| 
 | ||||
| ``` yaml | ||||
| Globs: | ||||
|   - **/*.cr | ||||
|   - !lib | ||||
|   - "**/*.cr" | ||||
|   - "!lib" | ||||
| 
 | ||||
| Excluded: | ||||
|   - src/compiler | ||||
|  | @ -245,18 +245,17 @@ One or more rules or one or more group of rules can be disabled using inline dir | |||
| time = Time.epoch(1483859302) | ||||
| 
 | ||||
| time = Time.epoch(1483859302) # ameba:disable Style/LargeNumbers, Lint/UselessAssign | ||||
| 
 | ||||
| time = Time.epoch(1483859302) # ameba:disable Style, Lint | ||||
| ``` | ||||
| 
 | ||||
| ## Editors & integrations | ||||
| 
 | ||||
|  * Vim: [vim-crystal](https://github.com/rhysd/vim-crystal), [Ale](https://github.com/w0rp/ale) | ||||
|  * Emacs: [ameba.el](https://github.com/crystal-ameba/ameba.el) | ||||
|  * Sublime Text: [Sublime Linter Ameba](https://github.com/epergo/SublimeLinter-contrib-ameba) | ||||
|  * VSCode: [vscode-crystal-ameba](https://github.com/crystal-ameba/vscode-crystal-ameba) | ||||
|  * Codacy: [codacy-ameba](https://github.com/codacy/codacy-ameba) | ||||
|  * GitHub Actions: [github-action](https://github.com/crystal-ameba/github-action) | ||||
| - Vim: [vim-crystal](https://github.com/rhysd/vim-crystal), [Ale](https://github.com/w0rp/ale) | ||||
| - Emacs: [ameba.el](https://github.com/crystal-ameba/ameba.el) | ||||
| - Sublime Text: [Sublime Linter Ameba](https://github.com/epergo/SublimeLinter-contrib-ameba) | ||||
| - VSCode: [vscode-crystal-ameba](https://github.com/crystal-ameba/vscode-crystal-ameba) | ||||
| - Codacy: [codacy-ameba](https://github.com/codacy/codacy-ameba) | ||||
| - GitHub Actions: [github-action](https://github.com/crystal-ameba/github-action) | ||||
| 
 | ||||
| ## Credits & inspirations | ||||
| 
 | ||||
|  |  | |||
|  | @ -39,7 +39,7 @@ module Ameba::Formatter | |||
| 
 | ||||
|     describe "#context" do | ||||
|       it "returns correct pre/post context lines" do | ||||
|         source = Source.new <<-EOF | ||||
|         source = Source.new <<-CRYSTAL | ||||
|           # pre:1 | ||||
|             # pre:2 | ||||
|               # pre:3 | ||||
|  | @ -51,7 +51,7 @@ module Ameba::Formatter | |||
|               # post:3 | ||||
|             # post:4 | ||||
|           # post:5 | ||||
|           EOF | ||||
|           CRYSTAL | ||||
| 
 | ||||
|         subject.context(source.lines, lineno: 6, context_lines: 3) | ||||
|           .should eq({<<-PRE.lines, <<-POST.lines | ||||
|  | @ -69,24 +69,24 @@ module Ameba::Formatter | |||
| 
 | ||||
|     describe "#affected_code" do | ||||
|       it "returns nil if there is no such a line number" do | ||||
|         code = <<-EOF | ||||
|         code = <<-CRYSTAL | ||||
|           a = 1 | ||||
|           EOF | ||||
|           CRYSTAL | ||||
|         location = Crystal::Location.new("filename", 2, 1) | ||||
|         subject.affected_code(code, location).should be_nil | ||||
|       end | ||||
| 
 | ||||
|       it "returns correct line if it is found" do | ||||
|         code = <<-EOF | ||||
|         code = <<-CRYSTAL | ||||
|           a = 1 | ||||
|           EOF | ||||
|           CRYSTAL | ||||
|         location = Crystal::Location.new("filename", 1, 1) | ||||
|         subject.deansify(subject.affected_code(code, location)) | ||||
|           .should eq "> a = 1\n  ^\n" | ||||
|       end | ||||
| 
 | ||||
|       it "returns correct line if it is found" do | ||||
|         code = <<-EOF | ||||
|         code = <<-CRYSTAL | ||||
|           # pre:1 | ||||
|             # pre:2 | ||||
|               # pre:3 | ||||
|  | @ -98,7 +98,7 @@ module Ameba::Formatter | |||
|               # post:3 | ||||
|             # post:4 | ||||
|           # post:5 | ||||
|           EOF | ||||
|           CRYSTAL | ||||
| 
 | ||||
|         location = Crystal::Location.new("filename", 6, 1) | ||||
|         subject.deansify(subject.affected_code(code, location, context_lines: 3)) | ||||
|  |  | |||
|  | @ -41,24 +41,20 @@ module Ameba::Rule::Lint | |||
|         CRYSTAL | ||||
|     end | ||||
| 
 | ||||
|     it "reports if there is a static path comparison evaluating to false" do | ||||
|       expect_issue subject, <<-CRYSTAL | ||||
|         String == Nil | ||||
|         # ^^^^^^^^^^^ error: Comparison always evaluates to false | ||||
|         CRYSTAL | ||||
|     end | ||||
| 
 | ||||
|     context "macro" do | ||||
|       pending "reports in macro scope" do | ||||
|       it "reports in macro scope" do | ||||
|         expect_issue subject, <<-CRYSTAL | ||||
|           {{ "foo" == "foo" }} | ||||
|            # ^^^^^^^^^^^^^^ error: Comparison always evaluates to true | ||||
|           CRYSTAL | ||||
|       end | ||||
| 
 | ||||
|       it "passes for free variables comparisons in macro scope" do | ||||
|       it "passes for valid cases" do | ||||
|         expect_no_issues subject, <<-CRYSTAL | ||||
|           {{ T == Nil }} | ||||
|           {{ "foo" == foo }} | ||||
|           {{ "foo" != foo }} | ||||
|           {% foo == "foo" %} | ||||
|           {% foo != "foo" %} | ||||
|           CRYSTAL | ||||
|       end | ||||
|     end | ||||
|  |  | |||
|  | @ -18,6 +18,13 @@ module Ameba::Rule::Lint | |||
|         CRYSTAL | ||||
|     end | ||||
| 
 | ||||
|     it "reports if there is a `not_nil!` call in the middle of the call-chain" do | ||||
|       expect_issue subject, <<-CRYSTAL | ||||
|         (1..3).first?.not_nil!.to_s | ||||
|                     # ^^^^^^^^ error: Avoid using `not_nil!` | ||||
|         CRYSTAL | ||||
|     end | ||||
| 
 | ||||
|     context "macro" do | ||||
|       it "doesn't report in macro scope" do | ||||
|         expect_no_issues subject, <<-CRYSTAL | ||||
|  |  | |||
|  | @ -2,7 +2,7 @@ require "../../../spec_helper" | |||
| 
 | ||||
| module Ameba::Rule::Metrics | ||||
|   subject = CyclomaticComplexity.new | ||||
|   complex_method = <<-CODE | ||||
|   complex_method = <<-CRYSTAL | ||||
|     def hello(a, b, c) | ||||
|       if a && b && c | ||||
|         begin | ||||
|  | @ -15,7 +15,7 @@ module Ameba::Rule::Metrics | |||
|         end | ||||
|       end | ||||
|     end | ||||
|     CODE | ||||
|     CRYSTAL | ||||
| 
 | ||||
|   describe CyclomaticComplexity do | ||||
|     it "passes for empty methods" do | ||||
|  |  | |||
|  | @ -14,17 +14,25 @@ module Ameba::Rule::Style | |||
|     end | ||||
| 
 | ||||
|     it "reports if there is a call to is_a?(Nil) without receiver" do | ||||
|       expect_issue subject, <<-CRYSTAL | ||||
|       source = expect_issue subject, <<-CRYSTAL | ||||
|         a = is_a?(Nil) | ||||
|                 # ^^^ error: Use `nil?` instead of `is_a?(Nil)` | ||||
|         CRYSTAL | ||||
| 
 | ||||
|       expect_correction source, <<-CRYSTAL | ||||
|         a = self.nil? | ||||
|         CRYSTAL | ||||
|     end | ||||
| 
 | ||||
|     it "reports if there is a call to is_a?(Nil) with receiver" do | ||||
|       expect_issue subject, <<-CRYSTAL | ||||
|       source = expect_issue subject, <<-CRYSTAL | ||||
|         a.is_a?(Nil) | ||||
|               # ^^^ error: Use `nil?` instead of `is_a?(Nil)` | ||||
|         CRYSTAL | ||||
| 
 | ||||
|       expect_correction source, <<-CRYSTAL | ||||
|         a.nil? | ||||
|         CRYSTAL | ||||
|     end | ||||
| 
 | ||||
|     it "reports rule, location and message" do | ||||
|  |  | |||
|  | @ -32,7 +32,7 @@ module Ameba::AST | |||
|     def initialize(@node, @parent) | ||||
|     end | ||||
| 
 | ||||
|     # Returns true if current branch is in a loop, false - otherwise. | ||||
|     # Returns `true` if current branch is in a loop, `false` - otherwise. | ||||
|     # For example, this branch is in a loop: | ||||
|     # | ||||
|     # ``` | ||||
|  |  | |||
|  | @ -35,7 +35,8 @@ module Ameba::AST | |||
|     def initialize(@node, @parent = nil) | ||||
|     end | ||||
| 
 | ||||
|     # Returns true if this node or one of the parent branchables is a loop, false otherwise. | ||||
|     # Returns `true` if this node or one of the parent branchables is a loop, | ||||
|     # `false` otherwise. | ||||
|     def loop? | ||||
|       loop?(node) || !!parent.try(&.loop?) | ||||
|     end | ||||
|  |  | |||
|  | @ -134,7 +134,7 @@ module Ameba::AST | |||
|         node.is_a?(Crystal::CStructOrUnionDef) | ||||
|     end | ||||
| 
 | ||||
|     # Returns true if current scope (or any of inner scopes) references variable, | ||||
|     # Returns `true` if current scope (or any of inner scopes) references variable, | ||||
|     # `false` otherwise. | ||||
|     def references?(variable : Variable, check_inner_scopes = true) | ||||
|       variable.references.any? do |reference| | ||||
|  | @ -153,7 +153,7 @@ module Ameba::AST | |||
|       outer_scope.nil? | ||||
|     end | ||||
| 
 | ||||
|     # Returns true if var is an argument in current scope, `false` otherwise. | ||||
|     # Returns `true` if var is an argument in current scope, `false` otherwise. | ||||
|     def arg?(var) | ||||
|       case current_node = node | ||||
|       when Crystal::Def | ||||
|  |  | |||
|  | @ -4,7 +4,7 @@ module Ameba::AST::Util | |||
|   # | ||||
|   # 1. is *node* a literal? | ||||
|   # 2. can *node* be proven static? | ||||
|   protected def literal_kind?(node, include_paths = false) : {Bool, Bool} | ||||
|   protected def literal_kind?(node) : {Bool, Bool} | ||||
|     case node | ||||
|     when Crystal::NilLiteral, | ||||
|          Crystal::BoolLiteral, | ||||
|  | @ -17,44 +17,42 @@ module Ameba::AST::Util | |||
|          Crystal::MacroLiteral | ||||
|       {true, true} | ||||
|     when Crystal::RangeLiteral | ||||
|       {true, static_literal?(node.from, include_paths) && | ||||
|         static_literal?(node.to, include_paths)} | ||||
|       {true, static_literal?(node.from) && | ||||
|         static_literal?(node.to)} | ||||
|     when Crystal::ArrayLiteral, | ||||
|          Crystal::TupleLiteral | ||||
|       {true, node.elements.all? do |el| | ||||
|         static_literal?(el, include_paths) | ||||
|         static_literal?(el) | ||||
|       end} | ||||
|     when Crystal::HashLiteral | ||||
|       {true, node.entries.all? do |entry| | ||||
|         static_literal?(entry.key, include_paths) && | ||||
|           static_literal?(entry.value, include_paths) | ||||
|         static_literal?(entry.key) && | ||||
|           static_literal?(entry.value) | ||||
|       end} | ||||
|     when Crystal::NamedTupleLiteral | ||||
|       {true, node.entries.all? do |entry| | ||||
|         static_literal?(entry.value, include_paths) | ||||
|         static_literal?(entry.value) | ||||
|       end} | ||||
|     when Crystal::Path | ||||
|       {include_paths, true} | ||||
|     else | ||||
|       {false, false} | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   # Returns `true` if current `node` is a static literal, `false` otherwise. | ||||
|   def static_literal?(node, include_paths = false) : Bool | ||||
|     is_literal, is_static = literal_kind?(node, include_paths) | ||||
|   def static_literal?(node) : Bool | ||||
|     is_literal, is_static = literal_kind?(node) | ||||
|     is_literal && is_static | ||||
|   end | ||||
| 
 | ||||
|   # Returns `true` if current `node` is a dynamic literal, `false` otherwise. | ||||
|   def dynamic_literal?(node, include_paths = false) : Bool | ||||
|     is_literal, is_static = literal_kind?(node, include_paths) | ||||
|   def dynamic_literal?(node) : Bool | ||||
|     is_literal, is_static = literal_kind?(node) | ||||
|     is_literal && !is_static | ||||
|   end | ||||
| 
 | ||||
|   # Returns `true` if current `node` is a literal, `false` otherwise. | ||||
|   def literal?(node, include_paths = false) : Bool | ||||
|     is_literal, _ = literal_kind?(node, include_paths) | ||||
|   def literal?(node) : Bool | ||||
|     is_literal, _ = literal_kind?(node) | ||||
|     is_literal | ||||
|   end | ||||
| 
 | ||||
|  | @ -93,8 +91,8 @@ module Ameba::AST::Util | |||
|     end | ||||
| 
 | ||||
|     return if last_line.size < end_column + 1 | ||||
|     node_lines[-1] = last_line.sub(end_column + 1...last_line.size, "") | ||||
| 
 | ||||
|     node_lines[-1] = last_line.sub(end_column + 1...last_line.size, "") | ||||
|     node_lines.join('\n') | ||||
|   end | ||||
| 
 | ||||
|  |  | |||
|  | @ -31,7 +31,7 @@ module Ameba::AST | |||
|     def initialize(@node, @variable) | ||||
|     end | ||||
| 
 | ||||
|     # Returns true if the name starts with '_', false if not. | ||||
|     # Returns `true` if the name starts with '_', `false` if not. | ||||
|     def ignored? | ||||
|       name.starts_with? '_' | ||||
|     end | ||||
|  |  | |||
|  | @ -41,7 +41,7 @@ module Ameba::AST | |||
|       @variable.referenced? && !!@branch.try(&.in_loop?) | ||||
|     end | ||||
| 
 | ||||
|     # Returns true if this assignment is an op assign, false if not. | ||||
|     # Returns `true` if this assignment is an op assign, `false` if not. | ||||
|     # For example, this is an op assign: | ||||
|     # | ||||
|     # ``` | ||||
|  | @ -51,7 +51,7 @@ module Ameba::AST | |||
|       node.is_a?(Crystal::OpAssign) | ||||
|     end | ||||
| 
 | ||||
|     # Returns true if this assignment is in a branch, false if not. | ||||
|     # Returns `true` if this assignment is in a branch, `false` if not. | ||||
|     # For example, this assignment is in a branch: | ||||
|     # | ||||
|     # ``` | ||||
|  |  | |||
|  | @ -174,7 +174,7 @@ module Ameba::AST | |||
|         node.accept self | ||||
|       end | ||||
| 
 | ||||
|       # @[AlwaysInline] | ||||
|       @[AlwaysInline] | ||||
|       private def includes_reference?(val) | ||||
|         val.to_s.includes?(@reference) | ||||
|       end | ||||
|  |  | |||
|  | @ -20,7 +20,7 @@ module Ameba::AST | |||
|     end | ||||
| 
 | ||||
|     # A main visit method that accepts `Crystal::ASTNode`. | ||||
|     # Returns true meaning all child nodes will be traversed. | ||||
|     # Returns `true`, meaning all child nodes will be traversed. | ||||
|     def visit(node : Crystal::ASTNode) | ||||
|       true | ||||
|     end | ||||
|  |  | |||
|  | @ -54,7 +54,7 @@ class Ameba::Config | |||
|   # ``` | ||||
|   property excluded : Array(String) | ||||
| 
 | ||||
|   # Returns true if correctable issues should be autocorrected. | ||||
|   # Returns `true` if correctable issues should be autocorrected. | ||||
|   property? autocorrect = false | ||||
| 
 | ||||
|   @rule_groups : Hash(String, Array(Rule::Base)) | ||||
|  |  | |||
|  | @ -10,8 +10,8 @@ module Ameba | |||
|       Enable | ||||
|     end | ||||
| 
 | ||||
|     # Returns true if current location is disabled for a particular rule, | ||||
|     # false otherwise. | ||||
|     # Returns `true` if current location is disabled for a particular rule, | ||||
|     # `false` otherwise. | ||||
|     # | ||||
|     # Location is disabled in two cases: | ||||
|     #   1. The line of the location ends with a comment directive. | ||||
|  | @ -74,7 +74,7 @@ module Ameba | |||
|       } | ||||
|     end | ||||
| 
 | ||||
|     # Returns true if the line at the given `line_number` is a comment. | ||||
|     # Returns `true` if the line at the given `line_number` is a comment. | ||||
|     def comment?(line_number : Int32) | ||||
|       return unless line = lines[line_number]? | ||||
|       comment?(line) | ||||
|  |  | |||
|  | @ -21,8 +21,8 @@ module Ameba | |||
| 
 | ||||
|     # :ditto: | ||||
|     def add_issue(rule, | ||||
|                   location : Crystal::Location, | ||||
|                   end_location : Crystal::Location, | ||||
|                   location : Crystal::Location?, | ||||
|                   end_location : Crystal::Location?, | ||||
|                   message : String, | ||||
|                   status : Issue::Status? = nil, | ||||
|                   &block : Source::Corrector ->) : Issue | ||||
|  |  | |||
|  | @ -94,7 +94,7 @@ module Ameba::Rule | |||
|       end | ||||
|     end | ||||
| 
 | ||||
|     # Returns true if this rule is special and behaves differently than | ||||
|     # Returns `true` if this rule is special and behaves differently than | ||||
|     # usual rules. | ||||
|     # | ||||
|     # ``` | ||||
|  |  | |||
|  | @ -17,7 +17,6 @@ module Ameba::Rule::Lint | |||
|   # | ||||
|   # And it should be written as this: | ||||
|   # | ||||
|   # | ||||
|   # ``` | ||||
|   # def some_method | ||||
|   #   do_some_stuff | ||||
|  |  | |||
|  | @ -47,9 +47,7 @@ module Ameba::Rule::Lint | |||
|     MSG = "Empty loop detected" | ||||
| 
 | ||||
|     def test(source, node : Crystal::Call) | ||||
|       return unless loop?(node) | ||||
| 
 | ||||
|       check_node(source, node, node.block) | ||||
|       check_node(source, node, node.block) if loop?(node) | ||||
|     end | ||||
| 
 | ||||
|     def test(source, node : Crystal::While | Crystal::Until) | ||||
|  | @ -58,7 +56,9 @@ module Ameba::Rule::Lint | |||
| 
 | ||||
|     private def check_node(source, node, loop_body) | ||||
|       body = loop_body.is_a?(Crystal::Block) ? loop_body.body : loop_body | ||||
|       issue_for node, MSG if body.nil? || body.nop? | ||||
|       return unless body.nil? || body.nop? | ||||
| 
 | ||||
|       issue_for node, MSG | ||||
|     end | ||||
|   end | ||||
| end | ||||
|  |  | |||
|  | @ -35,7 +35,7 @@ module Ameba::Rule::Lint | |||
| 
 | ||||
|     def test(source, node : Crystal::If | Crystal::Unless | Crystal::Case | Crystal::While | Crystal::Until) | ||||
|       return unless (cond = node.cond).is_a?(Crystal::Assign) | ||||
|       return unless literal?(cond.value, include_paths: true) | ||||
|       return unless literal?(cond.value) | ||||
| 
 | ||||
|       issue_for cond, MSG | ||||
|     end | ||||
|  |  | |||
|  | @ -7,6 +7,7 @@ module Ameba::Rule::Lint | |||
|   # replaced with either the body of the construct, or deleted entirely. | ||||
|   # | ||||
|   # This is considered invalid: | ||||
|   # | ||||
|   # ``` | ||||
|   # if "something" | ||||
|   #   :ok | ||||
|  | @ -29,12 +30,8 @@ module Ameba::Rule::Lint | |||
| 
 | ||||
|     MSG = "Literal value found in conditional" | ||||
| 
 | ||||
|     def check_node(source, node) | ||||
|       issue_for node, MSG if static_literal?(node.cond) | ||||
|     end | ||||
| 
 | ||||
|     def test(source, node : Crystal::If | Crystal::Unless | Crystal::Case) | ||||
|       check_node source, node | ||||
|       issue_for node, MSG if static_literal?(node.cond) | ||||
|     end | ||||
|   end | ||||
| end | ||||
|  |  | |||
|  | @ -4,7 +4,6 @@ module Ameba::Rule::Lint | |||
|   # They usually have the same result - except for non-primitive | ||||
|   # types like containers, range or regex. | ||||
|   # | ||||
|   # | ||||
|   # For example, this will be always false: | ||||
|   # | ||||
|   # ``` | ||||
|  | @ -29,28 +28,12 @@ module Ameba::Rule::Lint | |||
|     MSG        = "Comparison always evaluates to %s" | ||||
|     MSG_LIKELY = "Comparison most likely evaluates to %s" | ||||
| 
 | ||||
|     # Edge-case: `{{ T == Nil }}` | ||||
|     # | ||||
|     # Current implementation just skips all macro contexts, | ||||
|     # regardless of the free variable being present. | ||||
|     # | ||||
|     # Ideally we should only check whether either of the sides | ||||
|     # is a free var | ||||
|     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.in?(OP_NAMES) | ||||
|       return unless (obj = node.obj) && (arg = node.args.first?) | ||||
| 
 | ||||
|       obj_is_literal, obj_is_static = literal_kind?(obj, include_paths: true) | ||||
|       arg_is_literal, arg_is_static = literal_kind?(arg, include_paths: true) | ||||
|       obj_is_literal, obj_is_static = literal_kind?(obj) | ||||
|       arg_is_literal, arg_is_static = literal_kind?(arg) | ||||
| 
 | ||||
|       return unless obj_is_literal && arg_is_literal | ||||
| 
 | ||||
|  |  | |||
|  | @ -32,10 +32,8 @@ module Ameba::Rule::Lint | |||
|     def test(source, node : Crystal::Call) | ||||
|       return unless node.name == "rand" && | ||||
|                     node.args.size == 1 && | ||||
|                     (arg = node.args.first) && | ||||
|                     arg.is_a?(Crystal::NumberLiteral) && | ||||
|                     (value = arg.value) && | ||||
|                     value.in?("0", "1") | ||||
|                     (arg = node.args.first).is_a?(Crystal::NumberLiteral) && | ||||
|                     arg.value.in?("0", "1") | ||||
| 
 | ||||
|       issue_for node, MSG % node | ||||
|     end | ||||
|  |  | |||
|  | @ -17,7 +17,7 @@ module Ameba::Rule::Lint | |||
|   # YAML configuration example: | ||||
|   # | ||||
|   # ``` | ||||
|   # Lint/RedundantStringCoersion | ||||
|   # Lint/RedundantStringCoercion | ||||
|   #   Enabled: true | ||||
|   # ``` | ||||
|   class RedundantStringCoercion < Base | ||||
|  |  | |||
|  | @ -42,7 +42,6 @@ module Ameba::Rule::Lint | |||
| 
 | ||||
|     def test(source, node : Crystal::ExceptionHandler) | ||||
|       rescues = node.rescues | ||||
| 
 | ||||
|       return if rescues.nil? | ||||
| 
 | ||||
|       shadowed(rescues).each do |path| | ||||
|  |  | |||
|  | @ -63,8 +63,9 @@ module Ameba::Rule::Lint | |||
|       return unless node.block | ||||
| 
 | ||||
|       arg = node.named_args.try &.find(&.name.== "focus") | ||||
|       return unless arg | ||||
| 
 | ||||
|       issue_for arg, MSG if arg | ||||
|       issue_for arg, MSG | ||||
|     end | ||||
|   end | ||||
| end | ||||
|  |  | |||
|  | @ -7,6 +7,7 @@ module Ameba::Rule::Lint | |||
|   #   a + b | ||||
|   # end | ||||
|   # ``` | ||||
|   # | ||||
|   # and should be written as: | ||||
|   # | ||||
|   # ``` | ||||
|  |  | |||
|  | @ -41,6 +41,7 @@ module Ameba::Rule::Performance | |||
|       return unless node.name == ANY_NAME | ||||
|       return unless node.block.nil? && node.args.empty? | ||||
|       return unless node.obj | ||||
| 
 | ||||
|       return unless location = node.location | ||||
|       return unless name_location = node.name_location | ||||
|       return unless end_location = name_end_location(node) | ||||
|  |  | |||
|  | @ -68,12 +68,13 @@ module Ameba::Rule::Performance | |||
|     end | ||||
| 
 | ||||
|     def test(source, node : Crystal::Call) | ||||
|       return unless location = node.name_location | ||||
|       return unless end_location = name_end_location(node) | ||||
|       return unless (obj = node.obj).is_a?(Crystal::Call) | ||||
|       return unless node.name.in?(call_names) | ||||
|       return unless obj.name.in?(call_names) || obj.name.in?(ALLOCATING_METHOD_NAMES) | ||||
| 
 | ||||
|       return unless location = node.name_location | ||||
|       return unless end_location = name_end_location(node) | ||||
| 
 | ||||
|       issue_for location, end_location, MSG % {node.name, obj.name} do |corrector| | ||||
|         corrector.insert_after(end_location, '!') | ||||
|       end | ||||
|  |  | |||
|  | @ -51,7 +51,9 @@ module Ameba::Rule::Performance | |||
|       return unless obj.name.in?(filter_names) | ||||
| 
 | ||||
|       message = node.name.includes?(CALL_NAMES.first) ? MSG : MSG_REVERSE | ||||
|       issue_for obj.name_location, node.name_end_location, message % {obj.name, node.name} | ||||
| 
 | ||||
|       issue_for obj.name_location, node.name_end_location, | ||||
|         message % {obj.name, node.name} | ||||
|     end | ||||
|   end | ||||
| end | ||||
|  |  | |||
|  | @ -34,7 +34,9 @@ module Ameba::Rule::Style | |||
|       const = node.const | ||||
|       return unless path_named?(const, "Nil") | ||||
| 
 | ||||
|       issue_for const, MSG | ||||
|       issue_for const, MSG do |corrector| | ||||
|         corrector.replace(node, "#{node.obj}.nil?") | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| end | ||||
|  |  | |||
|  | @ -59,20 +59,20 @@ module Ameba::Rule::Style | |||
|     private def allowed?(_sign, value, fraction, _suffix) | ||||
|       return true if fraction && fraction.size > 3 | ||||
| 
 | ||||
|       digits = value.chars.select(&.number?) | ||||
|       digits = value.chars.select!(&.number?) | ||||
|       digits.size >= int_min_digits | ||||
|     end | ||||
| 
 | ||||
|     private def underscored(sign, value, fraction, suffix) | ||||
|       value = slice_digits(value.reverse).reverse | ||||
|       fraction = "." + slice_digits(fraction) if fraction | ||||
|       fraction = ".#{slice_digits(fraction)}" if fraction | ||||
| 
 | ||||
|       "#{sign}#{value}#{fraction}#{suffix}" | ||||
|     end | ||||
| 
 | ||||
|     private def slice_digits(value, by = 3) | ||||
|       %w[].tap do |slices| | ||||
|         value.chars.reject(&.== '_').each_slice(by) do |slice| | ||||
|         value.chars.reject!(&.== '_').each_slice(by) do |slice| | ||||
|           slices << slice.join | ||||
|         end | ||||
|       end.join('_') | ||||
|  |  | |||
|  | @ -48,6 +48,7 @@ module Ameba::Rule::Style | |||
| 
 | ||||
|     def test(source, node : Crystal::Def) | ||||
|       return if (expected = node.name.underscore) == node.name | ||||
| 
 | ||||
|       return unless location = name_location(node) | ||||
|       return unless end_location = name_end_location(node) | ||||
| 
 | ||||
|  |  | |||
|  | @ -188,9 +188,6 @@ module Ameba::Rule::Style | |||
| 
 | ||||
|     # ameba:disable Metrics/CyclomaticComplexity | ||||
|     protected def issue_for_valid(source, call : Crystal::Call, block : Crystal::Block, body : Crystal::Call) | ||||
|       return unless location = call.name_location | ||||
|       return unless end_location = block.end_location | ||||
| 
 | ||||
|       return if exclude_calls_with_block? && body.block | ||||
|       return if exclude_multiple_line_blocks? && !same_location_lines?(call, body) | ||||
|       return if exclude_prefix_operators? && prefix_operator?(body) | ||||
|  | @ -203,6 +200,9 @@ module Ameba::Rule::Style | |||
|       return unless valid_line_length?(call, call_code) | ||||
|       return unless valid_length?(call_code) | ||||
| 
 | ||||
|       return unless location = call.name_location | ||||
|       return unless end_location = block.end_location | ||||
| 
 | ||||
|       if call_code.includes?("{...}") | ||||
|         issue_for location, end_location, MSG % call_code | ||||
|       else | ||||
|  |  | |||
|  | @ -34,6 +34,7 @@ module Ameba::Rule::Style | |||
| 
 | ||||
|     def test(source, node : Crystal::While) | ||||
|       return unless node.cond.true_literal? | ||||
| 
 | ||||
|       return unless location = node.location | ||||
|       return unless end_location = node.cond.end_location | ||||
| 
 | ||||
|  |  | |||
|  | @ -46,7 +46,7 @@ module Ameba | |||
|     # Checks for unneeded disable directives. Always inspects a source last | ||||
|     @unneeded_disable_directive_rule : Rule::Base? | ||||
| 
 | ||||
|     # Returns true if correctable issues should be autocorrected. | ||||
|     # Returns `true` if correctable issues should be autocorrected. | ||||
|     private getter? autocorrect : Bool | ||||
| 
 | ||||
|     # Instantiates a runner using a `config`. | ||||
|  | @ -162,7 +162,7 @@ module Ameba | |||
|     end | ||||
| 
 | ||||
|     # Indicates whether the last inspection successful or not. | ||||
|     # It returns true if no issues matching severity in sources found, false otherwise. | ||||
|     # It returns `true` if no issues matching severity in sources found, `false` otherwise. | ||||
|     # | ||||
|     # ``` | ||||
|     # runner = Ameba::Runner.new config | ||||
|  |  | |||
|  | @ -65,7 +65,7 @@ class Ameba::Source | |||
|       @action_root = Rewriter::Action.new(0, code.size) | ||||
|     end | ||||
| 
 | ||||
|     # Returns true if no (non trivial) update has been recorded | ||||
|     # Returns `true` if no (non trivial) update has been recorded | ||||
|     def empty? | ||||
|       @action_root.empty? | ||||
|     end | ||||
|  |  | |||
|  | @ -70,7 +70,7 @@ class Ameba::Source::Rewriter | |||
|     end | ||||
| 
 | ||||
|     protected def place_in_hierarchy(action) | ||||
|       family = analyse_hierarchy(action) | ||||
|       family = analyze_hierarchy(action) | ||||
|       sibling_left, sibling_right = family[:sibling_left], family[:sibling_right] | ||||
| 
 | ||||
|       if fusible = family[:fusible] | ||||
|  | @ -126,7 +126,7 @@ class Ameba::Source::Rewriter | |||
|     # In case a child has equal range to *action*, it is returned as `:parent` | ||||
|     # | ||||
|     # Reminder: an empty range 1...1 is considered disjoint from 1...10 | ||||
|     protected def analyse_hierarchy(action) # ameba:disable Metrics/CyclomaticComplexity | ||||
|     protected def analyze_hierarchy(action) # ameba:disable Metrics/CyclomaticComplexity | ||||
|       # left_index is the index of the first child that isn't completely to the left of action | ||||
|       left_index = bsearch_child_index { |child| child.end_pos > action.begin_pos } | ||||
|       # right_index is the index of the first child that is completely on the right of action | ||||
|  | @ -147,6 +147,7 @@ class Ameba::Source::Rewriter | |||
|       else | ||||
|         overlap_left = @children[left_index].begin_pos <=> action.begin_pos | ||||
|         overlap_right = @children[right_index - 1].end_pos <=> action.end_pos | ||||
| 
 | ||||
|         raise "Unable to compare begin pos" if overlap_left.nil? | ||||
|         raise "Unable to compare end pos" if overlap_right.nil? | ||||
| 
 | ||||
|  |  | |||
|  | @ -160,8 +160,10 @@ module Ameba::Spec::ExpectIssue | |||
|                        file = __FILE__, | ||||
|                        line = __LINE__) | ||||
|     lines = code.split('\n') # must preserve trailing newline | ||||
| 
 | ||||
|     _, actual_annotations = actual_annotations(rules, code, path, lines) | ||||
|     return if actual_annotations.to_s == code | ||||
| 
 | ||||
|     fail <<-MSG, file, line | ||||
|       Expected no issues, but got: | ||||
| 
 | ||||
|  | @ -182,9 +184,10 @@ module Ameba::Spec::ExpectIssue | |||
|   private def format_issue(code, **replacements) | ||||
|     replacements.each do |keyword, value| | ||||
|       value = value.to_s | ||||
|       code = code.gsub("%{#{keyword}}", value) | ||||
|       code = code.gsub("^{#{keyword}}", "^" * value.size) | ||||
|       code = code.gsub("_{#{keyword}}", " " * value.size) | ||||
|       code = code | ||||
|         .gsub("%{#{keyword}}", value) | ||||
|         .gsub("^{#{keyword}}", "^" * value.size) | ||||
|         .gsub("_{#{keyword}}", " " * value.size) | ||||
|     end | ||||
|     code | ||||
|   end | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue