mirror of
				https://gitea.invidious.io/iv-org/shard-ameba.git
				synced 2024-08-15 00:53:29 +00:00 
			
		
		
		
	Merge pull request #312 from crystal-ameba/Sija/refactors-round-3
This commit is contained in:
		
						commit
						be65ba2a92
					
				
					 26 changed files with 114 additions and 127 deletions
				
			
		|  | @ -59,7 +59,7 @@ module Ameba | ||||||
|         source = Source.new "a = 42", "source.cr" |         source = Source.new "a = 42", "source.cr" | ||||||
|         output = explanation(source) |         output = explanation(source) | ||||||
|         output.should contain "DETAILED DESCRIPTION" |         output.should contain "DETAILED DESCRIPTION" | ||||||
|         output.should contain "TO BE DONE..." |         output.should contain "Rule extended description" | ||||||
|       end |       end | ||||||
| 
 | 
 | ||||||
|       it "writes nothing if location not found" do |       it "writes nothing if location not found" do | ||||||
|  |  | ||||||
|  | @ -24,6 +24,7 @@ module Ameba | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|  |   # Rule extended description | ||||||
|   class ErrorRule < Rule::Base |   class ErrorRule < Rule::Base | ||||||
|     properties do |     properties do | ||||||
|       description "Always adds an error at 1:1" |       description "Always adds an error at 1:1" | ||||||
|  |  | ||||||
|  | @ -175,8 +175,9 @@ module Ameba::AST | ||||||
|         node.accept self |         node.accept self | ||||||
|       end |       end | ||||||
| 
 | 
 | ||||||
|       def references?(node : Crystal::Var) |       # @[AlwaysInline] | ||||||
|         @macro_literals.any?(&.value.includes?(node.name)) |       private def includes_reference?(val) | ||||||
|  |         val.to_s.includes?(@reference) | ||||||
|       end |       end | ||||||
| 
 | 
 | ||||||
|       def visit(node : Crystal::ASTNode) |       def visit(node : Crystal::ASTNode) | ||||||
|  | @ -184,23 +185,22 @@ module Ameba::AST | ||||||
|       end |       end | ||||||
| 
 | 
 | ||||||
|       def visit(node : Crystal::MacroLiteral) |       def visit(node : Crystal::MacroLiteral) | ||||||
|         !(self.references ||= node.value.includes?(@reference)) |         !(@references ||= includes_reference?(node.value)) | ||||||
|       end |       end | ||||||
| 
 | 
 | ||||||
|       def visit(node : Crystal::MacroExpression) |       def visit(node : Crystal::MacroExpression) | ||||||
|         !(self.references ||= node.exp.to_s.includes?(@reference)) |         !(@references ||= includes_reference?(node.exp)) | ||||||
|       end |       end | ||||||
| 
 | 
 | ||||||
|       def visit(node : Crystal::MacroFor) |       def visit(node : Crystal::MacroFor) | ||||||
|         exp, body = node.exp, node.body |         !(@references ||= includes_reference?(node.exp) || | ||||||
|         !(self.references ||= exp.to_s.includes?(@reference) || |                           includes_reference?(node.body)) | ||||||
|                               body.to_s.includes?(@reference)) |  | ||||||
|       end |       end | ||||||
| 
 | 
 | ||||||
|       def visit(node : Crystal::MacroIf) |       def visit(node : Crystal::MacroIf) | ||||||
|         !(self.references ||= node.cond.to_s.includes?(@reference) || |         !(@references ||= includes_reference?(node.cond) || | ||||||
|                               node.then.to_s.includes?(@reference) || |                           includes_reference?(node.then) || | ||||||
|                               node.else.to_s.includes?(@reference)) |                           includes_reference?(node.else)) | ||||||
|       end |       end | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -12,15 +12,17 @@ module Ameba::AST | ||||||
|     # :nodoc: |     # :nodoc: | ||||||
|     def visit(node : Crystal::Require) |     def visit(node : Crystal::Require) | ||||||
|       require_nodes << node |       require_nodes << node | ||||||
|  |       true | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|     # If a top level node is Crystal::Expressions traverse the children. |     # If a top level node is `Crystal::Expressions`, | ||||||
|  |     # then always traverse the children. | ||||||
|     def visit(node : Crystal::Expressions) |     def visit(node : Crystal::Expressions) | ||||||
|       true |       true | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|     # A general visit method for rest of the nodes. |     # A general visit method for rest of the nodes. | ||||||
|     # Returns false meaning all child nodes will not be traversed. |     # Returns `false`, meaning all child nodes will not be traversed. | ||||||
|     def visit(node : Crystal::ASTNode) |     def visit(node : Crystal::ASTNode) | ||||||
|       false |       false | ||||||
|     end |     end | ||||||
|  |  | ||||||
|  | @ -111,7 +111,6 @@ class Ameba::Config | ||||||
|   # config.formatter = custom_formatter |   # config.formatter = custom_formatter | ||||||
|   # config.formatter |   # config.formatter | ||||||
|   # ``` |   # ``` | ||||||
|   # |  | ||||||
|   property formatter : Formatter::BaseFormatter do |   property formatter : Formatter::BaseFormatter do | ||||||
|     Formatter::DotFormatter.new |     Formatter::DotFormatter.new | ||||||
|   end |   end | ||||||
|  | @ -249,17 +248,17 @@ class Ameba::Config | ||||||
|         {% end %} |         {% end %} | ||||||
|       {% end %} |       {% end %} | ||||||
| 
 | 
 | ||||||
|       {% if properties["enabled".id] == nil %} |       {% unless properties["enabled".id] %} | ||||||
|         @[YAML::Field(key: "Enabled")] |         @[YAML::Field(key: "Enabled")] | ||||||
|         property? enabled = true |         property? enabled = true | ||||||
|       {% end %} |       {% end %} | ||||||
| 
 | 
 | ||||||
|       {% if properties["severity".id] == nil %} |       {% unless properties["severity".id] %} | ||||||
|         @[YAML::Field(key: "Severity", converter: Ameba::SeverityYamlConverter)] |         @[YAML::Field(key: "Severity", converter: Ameba::SeverityYamlConverter)] | ||||||
|         property severity = {{ @type }}.default_severity |         property severity = {{ @type }}.default_severity | ||||||
|       {% end %} |       {% end %} | ||||||
| 
 | 
 | ||||||
|       {% if properties["excluded".id] == nil %} |       {% unless properties["excluded".id] %} | ||||||
|         @[YAML::Field(key: "Excluded")] |         @[YAML::Field(key: "Excluded")] | ||||||
|         property excluded : Array(String)? |         property excluded : Array(String)? | ||||||
|       {% end %} |       {% end %} | ||||||
|  |  | ||||||
|  | @ -2,14 +2,14 @@ module Ameba::Formatter | ||||||
|   # A formatter that shows all disabled lines by inline directives. |   # A formatter that shows all disabled lines by inline directives. | ||||||
|   class DisabledFormatter < BaseFormatter |   class DisabledFormatter < BaseFormatter | ||||||
|     def finished(sources) |     def finished(sources) | ||||||
|       output << "Disabled rules using inline directives: \n\n" |       output << "Disabled rules using inline directives:\n\n" | ||||||
| 
 | 
 | ||||||
|       sources.each do |source| |       sources.each do |source| | ||||||
|         source.issues.select(&.disabled?).each do |e| |         source.issues.select(&.disabled?).each do |issue| | ||||||
|           next unless loc = e.location |           next unless loc = issue.location | ||||||
| 
 | 
 | ||||||
|           output << "#{source.path}:#{loc.line_number}".colorize(:cyan) |           output << "#{source.path}:#{loc.line_number}".colorize(:cyan) | ||||||
|           output << " #{e.rule.name}\n" |           output << " #{issue.rule.name}\n" | ||||||
|         end |         end | ||||||
|       end |       end | ||||||
|     end |     end | ||||||
|  |  | ||||||
|  | @ -4,8 +4,7 @@ module Ameba::Formatter | ||||||
|   # A formatter that shows the detailed explanation of the issue at |   # A formatter that shows the detailed explanation of the issue at | ||||||
|   # a specific location. |   # a specific location. | ||||||
|   class ExplainFormatter |   class ExplainFormatter | ||||||
|     HEADING = "## " |     HEADING_MARKER = "## " | ||||||
|     PREFIX  = " " |  | ||||||
| 
 | 
 | ||||||
|     include Util |     include Util | ||||||
| 
 | 
 | ||||||
|  | @ -18,13 +17,18 @@ module Ameba::Formatter | ||||||
|     # Second argument is *location* which indicates the location to explain. |     # Second argument is *location* which indicates the location to explain. | ||||||
|     # |     # | ||||||
|     # ``` |     # ``` | ||||||
|     # ExplainFormatter.new output, |     # ExplainFormatter.new output, { | ||||||
|     #   file: path, |     #   file:   path, | ||||||
|     #   line: line_number, |     #   line:   line_number, | ||||||
|     #   column: column_number |     #   column: column_number, | ||||||
|  |     # } | ||||||
|     # ``` |     # ``` | ||||||
|     def initialize(@output, location) |     def initialize(@output, location) | ||||||
|       @location = Crystal::Location.new(location[:file], location[:line], location[:column]) |       @location = Crystal::Location.new( | ||||||
|  |         location[:file], | ||||||
|  |         location[:line], | ||||||
|  |         location[:column] | ||||||
|  |       ) | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|     # Reports the explanations at the *@location*. |     # Reports the explanations at the *@location*. | ||||||
|  | @ -39,42 +43,59 @@ module Ameba::Formatter | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|     private def explain(source, issue) |     private def explain(source, issue) | ||||||
|       rule = issue.rule |  | ||||||
| 
 |  | ||||||
|       return unless location = issue.location |       return unless location = issue.location | ||||||
| 
 | 
 | ||||||
|       output_title "ISSUE INFO" |       output << '\n' | ||||||
|  |       output_title "Issue info" | ||||||
|       output_paragraph [ |       output_paragraph [ | ||||||
|         issue.message.colorize(:red).to_s, |         issue.message.colorize(:red), | ||||||
|         location.to_s.colorize(:cyan).to_s, |         location.to_s.colorize(:cyan), | ||||||
|       ] |       ] | ||||||
| 
 | 
 | ||||||
|       if affected_code = affected_code(issue, context_lines: 3) |       if affected_code = affected_code(issue, context_lines: 3) | ||||||
|         output_title "AFFECTED CODE" |         output_title "Affected code" | ||||||
|         output_paragraph affected_code |         output_paragraph affected_code | ||||||
|       end |       end | ||||||
| 
 | 
 | ||||||
|  |       rule = issue.rule | ||||||
|  | 
 | ||||||
|  |       output_title "Rule info" | ||||||
|  |       output_paragraph "%s of a %s severity" % { | ||||||
|  |         rule.name.colorize(:magenta), | ||||||
|  |         rule.severity.to_s.colorize(rule.severity.color), | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|       if rule.responds_to?(:description) |       if rule.responds_to?(:description) | ||||||
|         output_title "RULE INFO" |         output_paragraph rule.description | ||||||
|         output_paragraph [rule.severity.to_s, rule.name, rule.description] |  | ||||||
|       end |       end | ||||||
| 
 | 
 | ||||||
|       output_title "DETAILED DESCRIPTION" |       rule_doc = colorize_code_fences(rule.class.parsed_doc) | ||||||
|       output_paragraph(rule.class.parsed_doc || "TO BE DONE...") |       return unless rule_doc | ||||||
|  | 
 | ||||||
|  |       output_title "Detailed description" | ||||||
|  |       output_paragraph rule_doc | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     private def colorize_code_fences(string) | ||||||
|  |       return unless string | ||||||
|  |       string | ||||||
|  |         .gsub(/```(.+?)```/m, &.colorize(:dark_gray)) | ||||||
|  |         .gsub(/`(?!`)(.+?)`/, &.colorize(:dark_gray)) | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|     private def output_title(title) |     private def output_title(title) | ||||||
|       output << HEADING.colorize(:yellow) << title.colorize(:yellow) << '\n' |       output << HEADING_MARKER.colorize(:yellow) | ||||||
|       output << '\n' |       output << title.upcase.colorize(:yellow) | ||||||
|  |       output << "\n\n" | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|     private def output_paragraph(paragraph : String) |     private def output_paragraph(paragraph : String) | ||||||
|       output_paragraph(paragraph.lines) |       output_paragraph(paragraph.lines) | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|     private def output_paragraph(paragraph : Array(String)) |     private def output_paragraph(paragraph : Array) | ||||||
|       paragraph.each do |line| |       paragraph.each do |line| | ||||||
|         output << PREFIX << line << '\n' |         output << ' ' << line << '\n' | ||||||
|       end |       end | ||||||
|       output << '\n' |       output << '\n' | ||||||
|     end |     end | ||||||
|  |  | ||||||
|  | @ -3,16 +3,16 @@ module Ameba::Formatter | ||||||
|     @mutex = Mutex.new |     @mutex = Mutex.new | ||||||
| 
 | 
 | ||||||
|     def source_finished(source : Source) |     def source_finished(source : Source) | ||||||
|       source.issues.each do |e| |       source.issues.each do |issue| | ||||||
|         next if e.disabled? |         next if issue.disabled? | ||||||
|         next if e.correctable? && config[:autocorrect]? |         next if issue.correctable? && config[:autocorrect]? | ||||||
| 
 | 
 | ||||||
|         next unless loc = e.location |         next unless loc = issue.location | ||||||
| 
 | 
 | ||||||
|         @mutex.synchronize do |         @mutex.synchronize do | ||||||
|           output.printf "%s:%d:%d: %s: [%s] %s\n", |           output.printf "%s:%d:%d: %s: [%s] %s\n", | ||||||
|             source.path, loc.line_number, loc.column_number, e.rule.severity.symbol, |             source.path, loc.line_number, loc.column_number, issue.rule.severity.symbol, | ||||||
|             e.rule.name, e.message.gsub('\n', " ") |             issue.rule.name, issue.message.gsub('\n', " ") | ||||||
|         end |         end | ||||||
|       end |       end | ||||||
|     end |     end | ||||||
|  |  | ||||||
|  | @ -24,7 +24,9 @@ module Ameba::Rule::Layout | ||||||
|       return if source_lines_size == 1 && last_source_line.empty? |       return if source_lines_size == 1 && last_source_line.empty? | ||||||
| 
 | 
 | ||||||
|       last_line_empty = last_source_line.empty? |       last_line_empty = last_source_line.empty? | ||||||
|       return if source_lines_size.zero? || (source_lines.last(2).join.presence && last_line_empty) |       return if source_lines_size.zero? || | ||||||
|  |                 (source_lines.last(2).join.presence && last_line_empty) | ||||||
|  | 
 | ||||||
|       if last_line_empty |       if last_line_empty | ||||||
|         issue_for({source_lines_size, 1}, MSG) |         issue_for({source_lines_size, 1}, MSG) | ||||||
|       else |       else | ||||||
|  |  | ||||||
|  | @ -41,6 +41,7 @@ module Ameba::Rule::Lint | ||||||
|         op_end_location.column_number - 1 |         op_end_location.column_number - 1 | ||||||
|       ) |       ) | ||||||
|       op_text = source_between(op_location, op_end_location, source.lines) |       op_text = source_between(op_location, op_end_location, source.lines) | ||||||
|  | 
 | ||||||
|       return unless op_text |       return unless op_text | ||||||
|       return unless MISTAKES.has_key?(op_text) |       return unless MISTAKES.has_key?(op_text) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -52,11 +52,7 @@ module Ameba::Rule::Lint | ||||||
|       check_node(source, node, node.block) |       check_node(source, node, node.block) | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|     def test(source, node : Crystal::While) |     def test(source, node : Crystal::While | Crystal::Until) | ||||||
|       check_node(source, node, node.body) if literal?(node.cond) |  | ||||||
|     end |  | ||||||
| 
 |  | ||||||
|     def test(source, node : Crystal::Until) |  | ||||||
|       check_node(source, node, node.body) if literal?(node.cond) |       check_node(source, node, node.body) if literal?(node.cond) | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -35,7 +35,7 @@ module Ameba::Rule::Lint | ||||||
|     private def duplicated_keys(entries) |     private def duplicated_keys(entries) | ||||||
|       entries.map(&.key) |       entries.map(&.key) | ||||||
|         .group_by(&.itself) |         .group_by(&.itself) | ||||||
|         .tap(&.select! { |_, v| v.size > 1 }) |         .select! { |_, v| v.size > 1 } | ||||||
|         .map { |k, _| k } |         .map { |k, _| k } | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
|  |  | ||||||
|  | @ -33,15 +33,7 @@ module Ameba::Rule::Lint | ||||||
|       issue_for node, MSG if static_literal?(node.cond) |       issue_for node, MSG if static_literal?(node.cond) | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|     def test(source, node : Crystal::If) |     def test(source, node : Crystal::If | Crystal::Unless | Crystal::Case) | ||||||
|       check_node source, node |  | ||||||
|     end |  | ||||||
| 
 |  | ||||||
|     def test(source, node : Crystal::Unless) |  | ||||||
|       check_node source, node |  | ||||||
|     end |  | ||||||
| 
 |  | ||||||
|     def test(source, node : Crystal::Case) |  | ||||||
|       check_node source, node |       check_node source, node | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
|  |  | ||||||
|  | @ -26,8 +26,8 @@ module Ameba::Rule::Lint | ||||||
| 
 | 
 | ||||||
|     def test(source, node : Crystal::StringInterpolation) |     def test(source, node : Crystal::StringInterpolation) | ||||||
|       node.expressions |       node.expressions | ||||||
|         .select { |e| !e.is_a?(Crystal::StringLiteral) && literal?(e) } |         .select { |exp| !exp.is_a?(Crystal::StringLiteral) && literal?(exp) } | ||||||
|         .each { |n| issue_for n, MSG } |         .each { |exp| issue_for exp, MSG } | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
| end | end | ||||||
|  |  | ||||||
|  | @ -36,12 +36,12 @@ module Ameba::Rule::Lint | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|     private def string_coercion_nodes(node) |     private def string_coercion_nodes(node) | ||||||
|       node.expressions.select do |e| |       node.expressions.select do |exp| | ||||||
|         e.is_a?(Crystal::Call) && |         exp.is_a?(Crystal::Call) && | ||||||
|           e.name == "to_s" && |           exp.name == "to_s" && | ||||||
|           e.args.size.zero? && |           exp.args.size.zero? && | ||||||
|           e.named_args.nil? && |           exp.named_args.nil? && | ||||||
|           e.obj |           exp.obj | ||||||
|       end |       end | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
|  |  | ||||||
|  | @ -45,13 +45,16 @@ module Ameba::Rule::Lint | ||||||
| 
 | 
 | ||||||
|       return if rescues.nil? |       return if rescues.nil? | ||||||
| 
 | 
 | ||||||
|       shadowed(rescues).each { |tp| issue_for tp, MSG % tp.names.join("::") } |       shadowed(rescues).each do |path| | ||||||
|  |         issue_for path, MSG % path.names.join("::") | ||||||
|  |       end | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|     private def shadowed(rescues, catch_all = false) |     private def shadowed(rescues, catch_all = false) | ||||||
|       traversed_types = Set(String).new |       traversed_types = Set(String).new | ||||||
| 
 | 
 | ||||||
|       filter_rescues(rescues).each_with_object([] of Crystal::Path) do |types, shadowed| |       rescues = filter_rescues(rescues) | ||||||
|  |       rescues.each_with_object([] of Crystal::Path) do |types, shadowed| | ||||||
|         case |         case | ||||||
|         when catch_all |         when catch_all | ||||||
|           shadowed.concat(types) |           shadowed.concat(types) | ||||||
|  | @ -62,7 +65,7 @@ module Ameba::Rule::Lint | ||||||
|           catch_all = true |           catch_all = true | ||||||
|           next |           next | ||||||
|         else |         else | ||||||
|           nodes = types.select { |tp| traverse(tp.to_s, traversed_types) } |           nodes = types.select { |path| traverse(path.to_s, traversed_types) } | ||||||
|           shadowed.concat(nodes) unless nodes.empty? |           shadowed.concat(nodes) unless nodes.empty? | ||||||
|         end |         end | ||||||
|       end |       end | ||||||
|  |  | ||||||
|  | @ -45,11 +45,7 @@ module Ameba::Rule::Lint | ||||||
|       ] |       ] | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|     def test(source, node : Crystal::ProcLiteral, scope : AST::Scope) |     def test(source, node : Crystal::ProcLiteral | Crystal::Block, scope : AST::Scope) | ||||||
|       find_shadowing source, scope |  | ||||||
|     end |  | ||||||
| 
 |  | ||||||
|     def test(source, node : Crystal::Block, scope : AST::Scope) |  | ||||||
|       find_shadowing source, scope |       find_shadowing source, scope | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -21,7 +21,7 @@ module Ameba::Rule::Lint | ||||||
|   class Syntax < Base |   class Syntax < Base | ||||||
|     properties do |     properties do | ||||||
|       description "Reports invalid Crystal syntax" |       description "Reports invalid Crystal syntax" | ||||||
|       severity Ameba::Severity::Error |       severity :error | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|     def test(source) |     def test(source) | ||||||
|  |  | ||||||
|  | @ -61,12 +61,7 @@ module Ameba::Rule::Lint | ||||||
|         @parent.accept self |         @parent.accept self | ||||||
|       end |       end | ||||||
| 
 | 
 | ||||||
|       def visit(node : Crystal::If) |       def visit(node : Crystal::If | Crystal::Unless) | ||||||
|         @rule.check_node(@source, @parent, node.cond) |  | ||||||
|         true |  | ||||||
|       end |  | ||||||
| 
 |  | ||||||
|       def visit(node : Crystal::Unless) |  | ||||||
|         @rule.check_node(@source, @parent, node.cond) |         @rule.check_node(@source, @parent, node.cond) | ||||||
|         true |         true | ||||||
|       end |       end | ||||||
|  |  | ||||||
|  | @ -20,10 +20,12 @@ module Ameba::Rule::Metrics | ||||||
| 
 | 
 | ||||||
|     def test(source, node : Crystal::Def) |     def test(source, node : Crystal::Def) | ||||||
|       complexity = AST::CountingVisitor.new(node).count |       complexity = AST::CountingVisitor.new(node).count | ||||||
|  |       return unless complexity > max_complexity | ||||||
| 
 | 
 | ||||||
|       return unless complexity > max_complexity && (location = node.name_location) |       return unless location = node.name_location | ||||||
|       issue_for location, name_end_location(node), |       end_location = name_end_location(node) | ||||||
|         MSG % {complexity, max_complexity} | 
 | ||||||
|  |       issue_for location, end_location, MSG % {complexity, max_complexity} | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
| end | end | ||||||
|  |  | ||||||
|  | @ -79,6 +79,7 @@ module Ameba::Rule::Style | ||||||
|       old = OLD % node.name |       old = OLD % node.name | ||||||
|       new = NEW % {node.name, name} |       new = NEW % {node.name, name} | ||||||
|       msg = MSG % {new, old} |       msg = MSG % {new, old} | ||||||
|  | 
 | ||||||
|       if end_location |       if end_location | ||||||
|         issue_for(filter_location, end_location, msg) do |corrector| |         issue_for(filter_location, end_location, msg) do |corrector| | ||||||
|           corrector.replace(filter_location, end_location, new) |           corrector.replace(filter_location, end_location, new) | ||||||
|  |  | ||||||
|  | @ -39,11 +39,12 @@ module Ameba::Rule::Style | ||||||
|       Tokenizer.new(source).run do |token| |       Tokenizer.new(source).run do |token| | ||||||
|         next unless token.type.number? && decimal?(token.raw) |         next unless token.type.number? && decimal?(token.raw) | ||||||
| 
 | 
 | ||||||
|         parsed = parse_number token.raw |         parsed = parse_number(token.raw) | ||||||
| 
 | 
 | ||||||
|         if allowed?(*parsed) && (expected = underscored *parsed) != token.raw |         if allowed?(*parsed) && (expected = underscored *parsed) != token.raw | ||||||
|           location = token.location |           location = token.location | ||||||
|           end_location = location.adjust(column_number: token.raw.size - 1) |           end_location = location.adjust(column_number: token.raw.size - 1) | ||||||
|  | 
 | ||||||
|           issue_for location, end_location, MSG % expected do |corrector| |           issue_for location, end_location, MSG % expected do |corrector| | ||||||
|             corrector.replace(location, end_location, expected) |             corrector.replace(location, end_location, expected) | ||||||
|           end |           end | ||||||
|  | @ -58,21 +59,21 @@ module Ameba::Rule::Style | ||||||
|     private def allowed?(_sign, value, fraction, _suffix) |     private def allowed?(_sign, value, fraction, _suffix) | ||||||
|       return true if fraction && fraction.size > 3 |       return true if fraction && fraction.size > 3 | ||||||
| 
 | 
 | ||||||
|       digits = value.chars.select &.to_s.=~ /[0-9]/ |       digits = value.chars.select(&.number?) | ||||||
|       digits.size >= int_min_digits |       digits.size >= int_min_digits | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|     private def underscored(sign, value, fraction, suffix) |     private def underscored(sign, value, fraction, suffix) | ||||||
|       value = slice_digits(value.reverse) { |slice| slice }.reverse |       value = slice_digits(value.reverse).reverse | ||||||
|       fraction = "." + slice_digits(fraction) { |slice| slice } if fraction |       fraction = "." + slice_digits(fraction) if fraction | ||||||
| 
 | 
 | ||||||
|       "#{sign}#{value}#{fraction}#{suffix}" |       "#{sign}#{value}#{fraction}#{suffix}" | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|     private def slice_digits(value, by = 3) |     private def slice_digits(value, by = 3) | ||||||
|       ([] of String).tap do |slices| |       %w[].tap do |slices| | ||||||
|         value.chars.reject(&.== '_').each_slice(by) do |slice| |         value.chars.reject(&.== '_').each_slice(by) do |slice| | ||||||
|           slices << (yield slice).join |           slices << slice.join | ||||||
|         end |         end | ||||||
|       end.join('_') |       end.join('_') | ||||||
|     end |     end | ||||||
|  |  | ||||||
|  | @ -42,7 +42,7 @@ module Ameba::Rule::Style | ||||||
|       when Crystal::BinaryOp |       when Crystal::BinaryOp | ||||||
|         negated_condition?(node.left) || negated_condition?(node.right) |         negated_condition?(node.left) || negated_condition?(node.right) | ||||||
|       when Crystal::Expressions |       when Crystal::Expressions | ||||||
|         node.expressions.any? { |e| negated_condition?(e) } |         node.expressions.any? { |exp| negated_condition?(exp) } | ||||||
|       when Crystal::Not |       when Crystal::Not | ||||||
|         true |         true | ||||||
|       else |       else | ||||||
|  |  | ||||||
|  | @ -66,23 +66,7 @@ module Ameba::Rule::Style | ||||||
|       issue_for node, MSG % {expected, name} |       issue_for node, MSG % {expected, name} | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|     def test(source, node : Crystal::ClassDef) |     def test(source, node : Crystal::Alias | Crystal::ClassDef | Crystal::ModuleDef | Crystal::LibDef | Crystal::EnumDef) | ||||||
|       check_node(source, node) |  | ||||||
|     end |  | ||||||
| 
 |  | ||||||
|     def test(source, node : Crystal::Alias) |  | ||||||
|       check_node(source, node) |  | ||||||
|     end |  | ||||||
| 
 |  | ||||||
|     def test(source, node : Crystal::LibDef) |  | ||||||
|       check_node(source, node) |  | ||||||
|     end |  | ||||||
| 
 |  | ||||||
|     def test(source, node : Crystal::EnumDef) |  | ||||||
|       check_node(source, node) |  | ||||||
|     end |  | ||||||
| 
 |  | ||||||
|     def test(source, node : Crystal::ModuleDef) |  | ||||||
|       check_node(source, node) |       check_node(source, node) | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
|  |  | ||||||
|  | @ -39,15 +39,7 @@ module Ameba::Rule::Style | ||||||
|       VarVisitor.new self, source |       VarVisitor.new self, source | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|     def test(source, node : Crystal::Var) |     def test(source, node : Crystal::Var | Crystal::InstanceVar | Crystal::ClassVar) | ||||||
|       check_node source, node |  | ||||||
|     end |  | ||||||
| 
 |  | ||||||
|     def test(source, node : Crystal::InstanceVar) |  | ||||||
|       check_node source, node |  | ||||||
|     end |  | ||||||
| 
 |  | ||||||
|     def test(source, node : Crystal::ClassVar) |  | ||||||
|       check_node source, node |       check_node source, node | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -33,6 +33,7 @@ module Ameba | ||||||
|       @code = corrected_code |       @code = corrected_code | ||||||
|       @lines = nil |       @lines = nil | ||||||
|       @ast = nil |       @ast = nil | ||||||
|  | 
 | ||||||
|       true |       true | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|  | @ -46,7 +47,6 @@ module Ameba | ||||||
|     # source = Ameba::Source.new "a = 1\nb = 2", path |     # source = Ameba::Source.new "a = 1\nb = 2", path | ||||||
|     # source.lines # => ["a = 1", "b = 2"] |     # source.lines # => ["a = 1", "b = 2"] | ||||||
|     # ``` |     # ``` | ||||||
|     # |  | ||||||
|     getter lines : Array(String) { code.split('\n') } |     getter lines : Array(String) { code.split('\n') } | ||||||
| 
 | 
 | ||||||
|     # Returns AST nodes constructed by `Crystal::Parser`. |     # Returns AST nodes constructed by `Crystal::Parser`. | ||||||
|  | @ -55,7 +55,6 @@ module Ameba | ||||||
|     # source = Ameba::Source.new code, path |     # source = Ameba::Source.new code, path | ||||||
|     # source.ast |     # source.ast | ||||||
|     # ``` |     # ``` | ||||||
|     # |  | ||||||
|     getter ast : Crystal::ASTNode do |     getter ast : Crystal::ASTNode do | ||||||
|       Crystal::Parser.new(code) |       Crystal::Parser.new(code) | ||||||
|         .tap(&.wants_doc = true) |         .tap(&.wants_doc = true) | ||||||
|  | @ -72,9 +71,9 @@ module Ameba | ||||||
|       path.ends_with?("_spec.cr") |       path.ends_with?("_spec.cr") | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|     # Returns `true` if *filepath* matches the source's path, `false` if it does not. |     # Returns `true` if *filepath* matches the source's path, `false` otherwise. | ||||||
|     def matches_path?(filepath) |     def matches_path?(filepath) | ||||||
|       path == filepath || path == File.expand_path(filepath) |       path.in?(filepath, File.expand_path(filepath)) | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
| end | end | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue