mirror of
https://gitea.invidious.io/iv-org/shard-ameba.git
synced 2024-08-15 00:53:29 +00:00
Merge pull request #300 from crystal-ameba/Sija/some-refactors
Misc refactors
This commit is contained in:
commit
04b19a60db
33 changed files with 119 additions and 87 deletions
|
@ -191,7 +191,7 @@ module Ameba::AST
|
||||||
it "returns true if node is nested to Crystal::Macro" do
|
it "returns true if node is nested to Crystal::Macro" do
|
||||||
nodes = as_nodes %(
|
nodes = as_nodes %(
|
||||||
macro included
|
macro included
|
||||||
{{@type.each do |type| a = type end}}
|
{{ @type.each do |type| a = type end }}
|
||||||
end
|
end
|
||||||
)
|
)
|
||||||
outer_scope = Scope.new nodes.macro_nodes.first
|
outer_scope = Scope.new nodes.macro_nodes.first
|
||||||
|
|
|
@ -40,7 +40,9 @@ module Ameba
|
||||||
---
|
---
|
||||||
Globs: 100
|
Globs: 100
|
||||||
CONFIG
|
CONFIG
|
||||||
expect_raises(Exception, "incorrect 'Globs' section in a config file") { Config.new(yml) }
|
expect_raises(Exception, "Incorrect 'Globs' section in a config file") do
|
||||||
|
Config.new(yml)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it "initializes excluded as string" do
|
it "initializes excluded as string" do
|
||||||
|
@ -68,7 +70,9 @@ module Ameba
|
||||||
---
|
---
|
||||||
Excluded: true
|
Excluded: true
|
||||||
CONFIG
|
CONFIG
|
||||||
expect_raises(Exception, "incorrect 'Excluded' section in a config file") { Config.new(yml) }
|
expect_raises(Exception, "Incorrect 'Excluded' section in a config file") do
|
||||||
|
Config.new(yml)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -211,11 +211,13 @@ module Ameba::Rule::Lint
|
||||||
end
|
end
|
||||||
|
|
||||||
def bar
|
def bar
|
||||||
{{@type.instance_vars.map do |ivar|
|
{{
|
||||||
|
@type.instance_vars.map do |ivar|
|
||||||
ivar.annotations(Name).each do |ann|
|
ivar.annotations(Name).each do |ann|
|
||||||
puts ann.args
|
puts ann.args
|
||||||
end
|
end
|
||||||
end}}
|
end
|
||||||
|
}}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -218,11 +218,11 @@ module Ameba::Rule::Lint
|
||||||
s = Source.new %(
|
s = Source.new %(
|
||||||
record X do
|
record X do
|
||||||
macro foo(a, b)
|
macro foo(a, b)
|
||||||
{{a}} + {{b}}
|
{{ a }} + {{ b }}
|
||||||
end
|
end
|
||||||
|
|
||||||
macro bar(a, b, c)
|
macro bar(a, b, c)
|
||||||
{{a}} + {{b}} + {{c}}
|
{{ a }} + {{ b }} + {{ c }}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
)
|
)
|
||||||
|
|
|
@ -949,7 +949,7 @@ module Ameba::Rule::Lint
|
||||||
foo = 22
|
foo = 22
|
||||||
|
|
||||||
{% for x in %w(foo) %}
|
{% for x in %w(foo) %}
|
||||||
add({{x.id}})
|
add({{ x.id }})
|
||||||
{% end %}
|
{% end %}
|
||||||
)
|
)
|
||||||
subject.catch(s).should be_valid
|
subject.catch(s).should be_valid
|
||||||
|
|
|
@ -190,7 +190,7 @@ module Ameba
|
||||||
def test(source, node : Crystal::ClassDef)
|
def test(source, node : Crystal::ClassDef)
|
||||||
return unless location = node.location
|
return unless location = node.location
|
||||||
|
|
||||||
end_location = location.adjust(column_number: {{"class".size - 1}})
|
end_location = location.adjust(column_number: {{ "class".size - 1 }})
|
||||||
|
|
||||||
issue_for(location, end_location, message: "class to module") do |corrector|
|
issue_for(location, end_location, message: "class to module") do |corrector|
|
||||||
corrector.replace(location, end_location, "module")
|
corrector.replace(location, end_location, "module")
|
||||||
|
@ -208,7 +208,7 @@ module Ameba
|
||||||
def test(source, node : Crystal::ModuleDef)
|
def test(source, node : Crystal::ModuleDef)
|
||||||
return unless location = node.location
|
return unless location = node.location
|
||||||
|
|
||||||
end_location = location.adjust(column_number: {{"module".size - 1}})
|
end_location = location.adjust(column_number: {{ "module".size - 1 }})
|
||||||
|
|
||||||
issue_for(location, end_location, message: "module to class") do |corrector|
|
issue_for(location, end_location, message: "module to class") do |corrector|
|
||||||
corrector.replace(location, end_location, "class")
|
corrector.replace(location, end_location, "class")
|
||||||
|
@ -265,12 +265,12 @@ module Ameba
|
||||||
end
|
end
|
||||||
|
|
||||||
{% for node in NODES %}
|
{% for node in NODES %}
|
||||||
{{getter_name = node.stringify.split("::").last.underscore + "_nodes"}}
|
{{ getter_name = node.stringify.split("::").last.underscore + "_nodes" }}
|
||||||
|
|
||||||
getter {{getter_name.id}} = [] of {{node}}
|
getter {{ getter_name.id }} = [] of {{ node }}
|
||||||
|
|
||||||
def visit(node : {{node}})
|
def visit(node : {{ node }})
|
||||||
{{getter_name.id}} << node
|
{{ getter_name.id }} << node
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
{% end %}
|
{% end %}
|
||||||
|
|
|
@ -58,7 +58,9 @@ module Ameba::AST
|
||||||
control_flow_found ||= !loop?(exp) && flow_expression?(exp, in_loop?)
|
control_flow_found ||= !loop?(exp) && flow_expression?(exp, in_loop?)
|
||||||
end
|
end
|
||||||
when Crystal::BinaryOp
|
when Crystal::BinaryOp
|
||||||
unreachable_nodes << current_node.right if flow_expression?(current_node.left, in_loop?)
|
if flow_expression?(current_node.left, in_loop?)
|
||||||
|
unreachable_nodes << current_node.right
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
unreachable_nodes
|
unreachable_nodes
|
||||||
|
|
|
@ -106,6 +106,7 @@ module Ameba::AST
|
||||||
# ```
|
# ```
|
||||||
def spawn_block?
|
def spawn_block?
|
||||||
return false unless node.is_a?(Crystal::Block)
|
return false unless node.is_a?(Crystal::Block)
|
||||||
|
|
||||||
call = node.as(Crystal::Block).call
|
call = node.as(Crystal::Block).call
|
||||||
call.try(&.name) == "spawn"
|
call.try(&.name) == "spawn"
|
||||||
end
|
end
|
||||||
|
|
|
@ -32,7 +32,6 @@ module Ameba::AST::Util
|
||||||
# to determine and cut a piece of source of the node.
|
# to determine and cut a piece of source of the node.
|
||||||
def node_source(node, code_lines)
|
def node_source(node, code_lines)
|
||||||
loc, end_loc = node.location, node.end_location
|
loc, end_loc = node.location, node.end_location
|
||||||
|
|
||||||
return unless loc && end_loc
|
return unless loc && end_loc
|
||||||
|
|
||||||
source_between(loc, end_loc, code_lines)
|
source_between(loc, end_loc, code_lines)
|
||||||
|
@ -46,7 +45,7 @@ module Ameba::AST::Util
|
||||||
first_line, last_line = node_lines[0]?, node_lines[-1]?
|
first_line, last_line = node_lines[0]?, node_lines[-1]?
|
||||||
|
|
||||||
return if first_line.nil? || last_line.nil?
|
return if first_line.nil? || last_line.nil?
|
||||||
return if first_line.size < column # compiler reports incorrection location
|
return if first_line.size < column # compiler reports incorrect location
|
||||||
|
|
||||||
node_lines[0] = first_line.sub(0...column, "")
|
node_lines[0] = first_line.sub(0...column, "")
|
||||||
|
|
||||||
|
@ -162,11 +161,11 @@ module Ameba::AST::Util
|
||||||
# Returns the exp code of a control expression.
|
# Returns the exp code of a control expression.
|
||||||
# Wraps implicit tuple literal with curly brackets (e.g. multi-return).
|
# Wraps implicit tuple literal with curly brackets (e.g. multi-return).
|
||||||
def control_exp_code(node : Crystal::ControlExpression, code_lines)
|
def control_exp_code(node : Crystal::ControlExpression, code_lines)
|
||||||
return unless (exp = node.exp)
|
return unless exp = node.exp
|
||||||
return unless (exp_code = node_source(exp, code_lines))
|
return unless exp_code = node_source(exp, code_lines)
|
||||||
return exp_code unless exp.is_a?(Crystal::TupleLiteral) && exp_code[0] != '{'
|
return exp_code unless exp.is_a?(Crystal::TupleLiteral) && exp_code[0] != '{'
|
||||||
return unless (exp_start = exp.elements.first.location)
|
return unless exp_start = exp.elements.first.location
|
||||||
return unless (exp_end = exp.end_location)
|
return unless exp_end = exp.end_location
|
||||||
|
|
||||||
"{#{source_between(exp_start, exp_end, code_lines)}}"
|
"{#{source_between(exp_start, exp_end, code_lines)}}"
|
||||||
end
|
end
|
||||||
|
|
|
@ -39,10 +39,10 @@ module Ameba::AST
|
||||||
# Name of the argument.
|
# Name of the argument.
|
||||||
def name
|
def name
|
||||||
case current_node = node
|
case current_node = node
|
||||||
when Crystal::Var then current_node.name
|
when Crystal::Var, Crystal::Arg
|
||||||
when Crystal::Arg then current_node.name
|
current_node.name
|
||||||
else
|
else
|
||||||
raise ArgumentError.new "invalid node"
|
raise ArgumentError.new "Invalid node"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -97,6 +97,7 @@ module Ameba::AST
|
||||||
return false unless (assign = node).is_a?(Crystal::Assign)
|
return false unless (assign = node).is_a?(Crystal::Assign)
|
||||||
return false unless (value = assign.value).is_a?(Crystal::Call)
|
return false unless (value = assign.value).is_a?(Crystal::Call)
|
||||||
return false unless (obj = value.obj).is_a?(Crystal::Var)
|
return false unless (obj = value.obj).is_a?(Crystal::Var)
|
||||||
|
|
||||||
obj.name.starts_with? "__arg"
|
obj.name.starts_with? "__arg"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
module Ameba::AST
|
module Ameba::AST
|
||||||
# Represents the existence of the local variable.
|
# Represents the existence of the local variable.
|
||||||
# Holds the var node and variable assigments.
|
# Holds the var node and variable assignments.
|
||||||
class Variable
|
class Variable
|
||||||
# List of the assigments of this variable.
|
# List of the assignments of this variable.
|
||||||
getter assignments = [] of Assignment
|
getter assignments = [] of Assignment
|
||||||
|
|
||||||
# List of the references of this variable.
|
# List of the references of this variable.
|
||||||
|
@ -30,7 +30,7 @@ module Ameba::AST
|
||||||
def initialize(@node, @scope)
|
def initialize(@node, @scope)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns true if it is a special variable, i.e `$?`.
|
# Returns `true` if it is a special variable, i.e `$?`.
|
||||||
def special?
|
def special?
|
||||||
@node.special_var?
|
@node.special_var?
|
||||||
end
|
end
|
||||||
|
@ -50,7 +50,7 @@ module Ameba::AST
|
||||||
update_assign_reference!
|
update_assign_reference!
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns true if variable has any reference.
|
# Returns `true` if variable has any reference.
|
||||||
#
|
#
|
||||||
# ```
|
# ```
|
||||||
# variable = Variable.new(node, scope)
|
# variable = Variable.new(node, scope)
|
||||||
|
@ -85,7 +85,7 @@ module Ameba::AST
|
||||||
consumed_branches = Set(Branch).new
|
consumed_branches = Set(Branch).new
|
||||||
|
|
||||||
assignments.reverse_each do |assignment|
|
assignments.reverse_each do |assignment|
|
||||||
next if consumed_branches.includes?(assignment.branch)
|
next if assignment.branch.in?(consumed_branches)
|
||||||
assignment.referenced = true
|
assignment.referenced = true
|
||||||
|
|
||||||
break unless branch = assignment.branch
|
break unless branch = assignment.branch
|
||||||
|
@ -93,7 +93,7 @@ module Ameba::AST
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns true if the current var is referenced in
|
# Returns `true` if the current var is referenced in
|
||||||
# in the block. For example this variable is captured
|
# in the block. For example this variable is captured
|
||||||
# by block:
|
# by block:
|
||||||
#
|
#
|
||||||
|
@ -110,26 +110,29 @@ module Ameba::AST
|
||||||
# ```
|
# ```
|
||||||
def captured_by_block?(scope = @scope)
|
def captured_by_block?(scope = @scope)
|
||||||
scope.inner_scopes.each do |inner_scope|
|
scope.inner_scopes.each do |inner_scope|
|
||||||
return true if inner_scope.block? && inner_scope.references?(self, check_inner_scopes: false)
|
return true if inner_scope.block? &&
|
||||||
|
inner_scope.references?(self, check_inner_scopes: false)
|
||||||
return true if captured_by_block?(inner_scope)
|
return true if captured_by_block?(inner_scope)
|
||||||
end
|
end
|
||||||
|
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns true if current variable potentially referenced in a macro,
|
# Returns `true` if current variable potentially referenced in a macro,
|
||||||
# false if not.
|
# `false` if not.
|
||||||
def used_in_macro?(scope = @scope)
|
def used_in_macro?(scope = @scope)
|
||||||
scope.inner_scopes.each do |inner_scope|
|
scope.inner_scopes.each do |inner_scope|
|
||||||
return true if MacroReferenceFinder.new(inner_scope.node, node.name).references
|
return true if MacroReferenceFinder.new(inner_scope.node, node.name).references
|
||||||
end
|
end
|
||||||
return true if MacroReferenceFinder.new(scope.node, node.name).references
|
return true if MacroReferenceFinder.new(scope.node, node.name).references
|
||||||
return true if (outer_scope = scope.outer_scope) && used_in_macro?(outer_scope)
|
return true if (outer_scope = scope.outer_scope) &&
|
||||||
|
used_in_macro?(outer_scope)
|
||||||
|
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns true if the variable is a target (on the left) of the assignment,
|
# Returns `true` if the variable is a target (on the left) of the assignment,
|
||||||
# false otherwise.
|
# `false` otherwise.
|
||||||
def target_of?(assign)
|
def target_of?(assign)
|
||||||
case assign
|
case assign
|
||||||
when Crystal::Assign then eql?(assign.target)
|
when Crystal::Assign then eql?(assign.target)
|
||||||
|
@ -141,12 +144,12 @@ module Ameba::AST
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns true if the name starts with '_', false if not.
|
# Returns `true` if the name starts with '_', `false` if not.
|
||||||
def ignored?
|
def ignored?
|
||||||
name.starts_with? '_'
|
name.starts_with? '_'
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns true if the `node` represents exactly
|
# Returns `true` if the `node` represents exactly
|
||||||
# the same Crystal node as `@node`.
|
# the same Crystal node as `@node`.
|
||||||
def eql?(node)
|
def eql?(node)
|
||||||
node.is_a?(Crystal::Var) &&
|
node.is_a?(Crystal::Var) &&
|
||||||
|
@ -154,7 +157,7 @@ module Ameba::AST
|
||||||
node.location == @node.location
|
node.location == @node.location
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns true if the variable is declared before the `node`.
|
# Returns `true` if the variable is declared before the `node`.
|
||||||
def declared_before?(node)
|
def declared_before?(node)
|
||||||
var_location, node_location = location, node.location
|
var_location, node_location = location, node.location
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ module Ameba::AST
|
||||||
# AST Visitor that counts occurrences of certain keywords
|
# AST Visitor that counts occurrences of certain keywords
|
||||||
class CountingVisitor < Crystal::Visitor
|
class CountingVisitor < Crystal::Visitor
|
||||||
DEFAULT_COMPLEXITY = 1
|
DEFAULT_COMPLEXITY = 1
|
||||||
|
|
||||||
getter macro_condition = false
|
getter macro_condition = false
|
||||||
|
|
||||||
# Creates a new counting visitor
|
# Creates a new counting visitor
|
||||||
|
@ -44,6 +45,7 @@ module Ameba::AST
|
||||||
def visit(node : Crystal::MacroIf | Crystal::MacroFor)
|
def visit(node : Crystal::MacroIf | Crystal::MacroFor)
|
||||||
@macro_condition = true
|
@macro_condition = true
|
||||||
@complexity = DEFAULT_COMPLEXITY
|
@complexity = DEFAULT_COMPLEXITY
|
||||||
|
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -45,11 +45,11 @@ module Ameba::AST
|
||||||
end
|
end
|
||||||
|
|
||||||
{% for name in NODES %}
|
{% for name in NODES %}
|
||||||
# A visit callback for `Crystal::{{name}}` node.
|
# A visit callback for `Crystal::{{ name }}` node.
|
||||||
#
|
#
|
||||||
# Returns `true` if the child nodes should be traversed as well,
|
# Returns `true` if the child nodes should be traversed as well,
|
||||||
# `false` otherwise.
|
# `false` otherwise.
|
||||||
def visit(node : Crystal::{{name}})
|
def visit(node : Crystal::{{ name }})
|
||||||
return false if skip?(node)
|
return false if skip?(node)
|
||||||
|
|
||||||
@rule.test @source, node
|
@rule.test @source, node
|
||||||
|
|
|
@ -48,7 +48,8 @@ module Ameba::AST
|
||||||
end
|
end
|
||||||
|
|
||||||
private def on_assign_end(target, node)
|
private def on_assign_end(target, node)
|
||||||
target.is_a?(Crystal::Var) && @current_scope.assign_variable(target.name, node)
|
target.is_a?(Crystal::Var) &&
|
||||||
|
@current_scope.assign_variable(target.name, node)
|
||||||
end
|
end
|
||||||
|
|
||||||
# :nodoc:
|
# :nodoc:
|
||||||
|
@ -58,7 +59,7 @@ module Ameba::AST
|
||||||
|
|
||||||
{% for name in NODES %}
|
{% for name in NODES %}
|
||||||
# :nodoc:
|
# :nodoc:
|
||||||
def visit(node : Crystal::{{name}})
|
def visit(node : Crystal::{{ name }})
|
||||||
on_scope_enter(node)
|
on_scope_enter(node)
|
||||||
end
|
end
|
||||||
{% end %}
|
{% end %}
|
||||||
|
@ -96,13 +97,15 @@ module Ameba::AST
|
||||||
|
|
||||||
# :nodoc:
|
# :nodoc:
|
||||||
def visit(node : Crystal::TypeDeclaration)
|
def visit(node : Crystal::TypeDeclaration)
|
||||||
return if @current_scope.type_definition? || !(var = node.var).is_a?(Crystal::Var)
|
return if @current_scope.type_definition?
|
||||||
@current_scope.add_variable var
|
return if !(var = node.var).is_a?(Crystal::Var)
|
||||||
|
|
||||||
|
@current_scope.add_variable(var)
|
||||||
end
|
end
|
||||||
|
|
||||||
# :nodoc:
|
# :nodoc:
|
||||||
def visit(node : Crystal::Arg)
|
def visit(node : Crystal::Arg)
|
||||||
@current_scope.add_argument node
|
@current_scope.add_argument(node)
|
||||||
end
|
end
|
||||||
|
|
||||||
# :nodoc:
|
# :nodoc:
|
||||||
|
@ -114,11 +117,12 @@ module Ameba::AST
|
||||||
def visit(node : Crystal::Var)
|
def visit(node : Crystal::Var)
|
||||||
variable = @current_scope.find_variable node.name
|
variable = @current_scope.find_variable node.name
|
||||||
|
|
||||||
if @current_scope.arg?(node) # node is an argument
|
case
|
||||||
|
when @current_scope.arg?(node) # node is an argument
|
||||||
@current_scope.add_argument(node)
|
@current_scope.add_argument(node)
|
||||||
elsif variable.nil? && @current_assign # node is a variable
|
when variable.nil? && @current_assign # node is a variable
|
||||||
@current_scope.add_variable(node)
|
@current_scope.add_variable(node)
|
||||||
elsif variable # node is a reference
|
when variable # node is a reference
|
||||||
reference = variable.reference node, @current_scope
|
reference = variable.reference node, @current_scope
|
||||||
if @current_assign.is_a?(Crystal::OpAssign) || !reference.target_of?(@current_assign)
|
if @current_assign.is_a?(Crystal::OpAssign) || !reference.target_of?(@current_assign)
|
||||||
variable.reference_assignments!
|
variable.reference_assignments!
|
||||||
|
|
|
@ -123,10 +123,10 @@ class Ameba::Config
|
||||||
# config.formatter = :progress
|
# config.formatter = :progress
|
||||||
# ```
|
# ```
|
||||||
def formatter=(name : String | Symbol)
|
def formatter=(name : String | Symbol)
|
||||||
unless f = AVAILABLE_FORMATTERS[name]?
|
unless formatter = AVAILABLE_FORMATTERS[name]?
|
||||||
raise "Unknown formatter `#{name}`. Use one of #{Config.formatter_names}."
|
raise "Unknown formatter `#{name}`. Use one of #{Config.formatter_names}."
|
||||||
end
|
end
|
||||||
@formatter = f.new
|
@formatter = formatter.new
|
||||||
end
|
end
|
||||||
|
|
||||||
# Updates rule properties.
|
# Updates rule properties.
|
||||||
|
@ -180,7 +180,7 @@ class Ameba::Config
|
||||||
when .as_s? then [value.to_s]
|
when .as_s? then [value.to_s]
|
||||||
when .as_a? then value.as_a.map(&.as_s)
|
when .as_a? then value.as_a.map(&.as_s)
|
||||||
else
|
else
|
||||||
raise "incorrect '#{section_name}' section in a config files"
|
raise "Incorrect '#{section_name}' section in a config files"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -241,8 +241,8 @@ class Ameba::Config
|
||||||
|
|
||||||
{% properties[name] = {key: key, default: value, type: type, converter: converter} %}
|
{% properties[name] = {key: key, default: value, type: type, converter: converter} %}
|
||||||
|
|
||||||
@[YAML::Field(key: {{key}}, converter: {{converter}}, type: {{type}})]
|
@[YAML::Field(key: {{ key }}, converter: {{ converter }}, type: {{ type }})]
|
||||||
property {{name}} : {{type}} = {{value}}
|
property {{ name }} : {{ type }} = {{ value }}
|
||||||
{% end %}
|
{% end %}
|
||||||
|
|
||||||
{% if properties["enabled".id] == nil %}
|
{% if properties["enabled".id] == nil %}
|
||||||
|
@ -253,7 +253,7 @@ class Ameba::Config
|
||||||
{% if properties["severity".id] == nil %}
|
{% if properties["severity".id] == nil %}
|
||||||
{% default = @type.name.starts_with?("Ameba::Rule::Lint") ? "Ameba::Severity::Warning".id : "Ameba::Severity::Convention".id %}
|
{% default = @type.name.starts_with?("Ameba::Rule::Lint") ? "Ameba::Severity::Warning".id : "Ameba::Severity::Convention".id %}
|
||||||
@[YAML::Field(key: "Severity", converter: Ameba::SeverityYamlConverter)]
|
@[YAML::Field(key: "Severity", converter: Ameba::SeverityYamlConverter)]
|
||||||
property severity = {{default}}
|
property severity = {{ default }}
|
||||||
{% end %}
|
{% end %}
|
||||||
|
|
||||||
{% if properties["excluded".id] == nil %}
|
{% if properties["excluded".id] == nil %}
|
||||||
|
|
|
@ -38,7 +38,7 @@ module Ameba::Formatter
|
||||||
private def explain(source, issue)
|
private def explain(source, issue)
|
||||||
rule = issue.rule
|
rule = issue.rule
|
||||||
|
|
||||||
return unless (location = issue.location)
|
return unless location = issue.location
|
||||||
|
|
||||||
output_title "ISSUE INFO"
|
output_title "ISSUE INFO"
|
||||||
output_paragraph [
|
output_paragraph [
|
||||||
|
|
|
@ -41,7 +41,7 @@ module Ameba::Formatter
|
||||||
end
|
end
|
||||||
|
|
||||||
def affected_code(issue : Issue, context_lines = 0, max_length = 120, ellipsis = " ...", prompt = "> ")
|
def affected_code(issue : Issue, context_lines = 0, max_length = 120, ellipsis = " ...", prompt = "> ")
|
||||||
return unless (location = issue.location)
|
return unless location = issue.location
|
||||||
|
|
||||||
affected_code(issue.code, location, issue.end_location, context_lines, max_length, ellipsis, prompt)
|
affected_code(issue.code, location, issue.end_location, context_lines, max_length, ellipsis, prompt)
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
module Ameba
|
module Ameba
|
||||||
# A module that utilizes inline comments parsing and processing logic.
|
# A module that utilizes inline comments parsing and processing logic.
|
||||||
module InlineComments
|
module InlineComments
|
||||||
COMMENT_DIRECTIVE_REGEX = /# ameba:(?<action>\w+) (?<rules>\w+(?:\/\w+)?(?:,? \w+(?:\/\w+)?)*)/
|
COMMENT_DIRECTIVE_REGEX =
|
||||||
|
/# ameba:(?<action>\w+) (?<rules>\w+(?:\/\w+)?(?:,? \w+(?:\/\w+)?)*)/
|
||||||
|
|
||||||
# Available actions in the inline comments
|
# Available actions in the inline comments
|
||||||
enum Action
|
enum Action
|
||||||
|
@ -87,15 +88,17 @@ module Ameba
|
||||||
return false unless directive = parse_inline_directive(line)
|
return false unless directive = parse_inline_directive(line)
|
||||||
return false unless Action.parse?(directive[:action]).try(&.disable?)
|
return false unless Action.parse?(directive[:action]).try(&.disable?)
|
||||||
|
|
||||||
directive[:rules].includes?(rule.name) ||
|
rules = directive[:rules]
|
||||||
directive[:rules].includes?(rule.group)
|
rules.includes?(rule.name) || rules.includes?(rule.group)
|
||||||
end
|
end
|
||||||
|
|
||||||
private def commented_out?(line)
|
private def commented_out?(line)
|
||||||
commented = false
|
commented = false
|
||||||
|
|
||||||
lexer = Crystal::Lexer.new(line).tap(&.comments_enabled = true)
|
lexer = Crystal::Lexer.new(line).tap(&.comments_enabled = true)
|
||||||
Tokenizer.new(lexer).run { |t| commented = true if t.type.comment? }
|
Tokenizer.new(lexer).run do |token|
|
||||||
|
commented = true if token.type.comment?
|
||||||
|
end
|
||||||
commented
|
commented
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -33,7 +33,7 @@ module Ameba::Rule
|
||||||
# that are tested by this rule, it should add an issue.
|
# that are tested by this rule, it should add an issue.
|
||||||
#
|
#
|
||||||
# Be default it uses a node visitor to traverse all the nodes in the source.
|
# Be default it uses a node visitor to traverse all the nodes in the source.
|
||||||
# Must be overriten for other type of rules.
|
# NOTE: Must be overridden for other type of rules.
|
||||||
def test(source : Source)
|
def test(source : Source)
|
||||||
AST::NodeVisitor.new self, source
|
AST::NodeVisitor.new self, source
|
||||||
end
|
end
|
||||||
|
@ -64,7 +64,7 @@ module Ameba::Rule
|
||||||
# MyRule.new.name # => "MyRule"
|
# MyRule.new.name # => "MyRule"
|
||||||
# ```
|
# ```
|
||||||
def name
|
def name
|
||||||
{{@type}}.rule_name
|
{{ @type }}.rule_name
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns a group this rule belong to.
|
# Returns a group this rule belong to.
|
||||||
|
@ -77,7 +77,7 @@ module Ameba::Rule
|
||||||
# MyGroup::MyRule.new.group # => "MyGroup"
|
# MyGroup::MyRule.new.group # => "MyGroup"
|
||||||
# ```
|
# ```
|
||||||
def group
|
def group
|
||||||
{{@type}}.group_name
|
{{ @type }}.group_name
|
||||||
end
|
end
|
||||||
|
|
||||||
# Checks whether the source is excluded from this rule.
|
# Checks whether the source is excluded from this rule.
|
||||||
|
@ -113,7 +113,7 @@ module Ameba::Rule
|
||||||
end
|
end
|
||||||
|
|
||||||
macro issue_for(*args, **kwargs, &block)
|
macro issue_for(*args, **kwargs, &block)
|
||||||
source.add_issue(self, {{*args}}, {{**kwargs}}) {{block}}
|
source.add_issue(self, {{ *args }}, {{ **kwargs }}) {{ block }}
|
||||||
end
|
end
|
||||||
|
|
||||||
protected def self.rule_name
|
protected def self.rule_name
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
module Ameba::Rule::Layout
|
module Ameba::Rule::Layout
|
||||||
# A rule that disallows trailing whitespaces.
|
# A rule that disallows trailing whitespace.
|
||||||
#
|
#
|
||||||
# YAML configuration example:
|
# YAML configuration example:
|
||||||
#
|
#
|
||||||
|
|
|
@ -23,7 +23,7 @@ module Ameba::Rule::Lint
|
||||||
def test(source)
|
def test(source)
|
||||||
nodes = AST::TopLevelNodesVisitor.new(source.ast).require_nodes
|
nodes = AST::TopLevelNodesVisitor.new(source.ast).require_nodes
|
||||||
nodes.each_with_object([] of String) do |node, processed_require_strings|
|
nodes.each_with_object([] of String) do |node, processed_require_strings|
|
||||||
issue_for(node, MSG % node.string) if processed_require_strings.includes?(node.string)
|
issue_for(node, MSG % node.string) if node.string.in?(processed_require_strings)
|
||||||
processed_require_strings << node.string
|
processed_require_strings << node.string
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -54,7 +54,7 @@ module Ameba::Rule::Lint
|
||||||
|
|
||||||
issue_for name_location, end_location, msg do |corrector|
|
issue_for name_location, end_location, msg do |corrector|
|
||||||
corrector.insert_after(name_location_end, '!')
|
corrector.insert_after(name_location_end, '!')
|
||||||
corrector.remove_trailing(node, ".not_nil!".size)
|
corrector.remove_trailing(node, {{ ".not_nil!".size }})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -75,8 +75,8 @@ module Ameba::Rule::Style
|
||||||
end
|
end
|
||||||
|
|
||||||
case final_expression
|
case final_expression
|
||||||
when Crystal::If then check_ending_if(source, final_expression)
|
when Crystal::If, Crystal::Unless
|
||||||
when Crystal::Unless then check_ending_if(source, final_expression)
|
check_ending_if(source, final_expression)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -92,7 +92,8 @@ module Ameba::Rule::Style
|
||||||
|
|
||||||
return unless guard_clause && parent && conditional_keyword
|
return unless guard_clause && parent && conditional_keyword
|
||||||
|
|
||||||
report_issue(source, node, guard_clause_source(source, guard_clause, parent), conditional_keyword)
|
guard_clause_source = guard_clause_source(source, guard_clause, parent)
|
||||||
|
report_issue(source, node, guard_clause_source, conditional_keyword)
|
||||||
end
|
end
|
||||||
|
|
||||||
private def check_ending_if(source, node)
|
private def check_ending_if(source, node)
|
||||||
|
@ -113,10 +114,11 @@ module Ameba::Rule::Style
|
||||||
if node.else.is_a?(Crystal::Nop)
|
if node.else.is_a?(Crystal::Nop)
|
||||||
return unless end_end_loc = node.end_location
|
return unless end_end_loc = node.end_location
|
||||||
|
|
||||||
end_loc = end_end_loc.adjust(column_number: {{1 - "end".size}})
|
end_loc = end_end_loc.adjust(column_number: {{ 1 - "end".size }})
|
||||||
|
|
||||||
issue_for keyword_loc, keyword_end_loc, MSG % example do |corrector|
|
issue_for keyword_loc, keyword_end_loc, MSG % example do |corrector|
|
||||||
corrector.replace(keyword_loc, keyword_end_loc, "#{scope_exiting_keyword} #{conditional_keyword}")
|
replacement = "#{scope_exiting_keyword} #{conditional_keyword}"
|
||||||
|
corrector.replace(keyword_loc, keyword_end_loc, replacement)
|
||||||
corrector.remove(end_loc, end_end_loc)
|
corrector.remove(end_loc, end_end_loc)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
|
|
|
@ -59,8 +59,8 @@ module Ameba::Rule::Style
|
||||||
|
|
||||||
def test(source, node : Crystal::Call)
|
def test(source, node : Crystal::Call)
|
||||||
return unless node.name.in?(filter_names)
|
return unless node.name.in?(filter_names)
|
||||||
return unless (filter_location = node.name_location)
|
return unless filter_location = node.name_location
|
||||||
return unless (block = node.block)
|
return unless block = node.block
|
||||||
return unless (body = block.body).is_a?(Crystal::IsA)
|
return unless (body = block.body).is_a?(Crystal::IsA)
|
||||||
return unless (path = body.const).is_a?(Crystal::Path)
|
return unless (path = body.const).is_a?(Crystal::Path)
|
||||||
return unless body.obj.is_a?(Crystal::Var)
|
return unless body.obj.is_a?(Crystal::Var)
|
||||||
|
|
|
@ -116,7 +116,7 @@ module Ameba::Rule::Style
|
||||||
return if allow_multi_next && node.exp.is_a?(Crystal::TupleLiteral)
|
return if allow_multi_next && node.exp.is_a?(Crystal::TupleLiteral)
|
||||||
return if allow_empty_next && (node.exp.nil? || node.exp.try(&.nop?))
|
return if allow_empty_next && (node.exp.nil? || node.exp.try(&.nop?))
|
||||||
|
|
||||||
if (exp_code = control_exp_code(node, source.lines))
|
if exp_code = control_exp_code(node, source.lines)
|
||||||
issue_for node, MSG do |corrector|
|
issue_for node, MSG do |corrector|
|
||||||
corrector.replace(node, exp_code)
|
corrector.replace(node, exp_code)
|
||||||
end
|
end
|
||||||
|
|
|
@ -113,7 +113,7 @@ module Ameba::Rule::Style
|
||||||
return if allow_multi_return && node.exp.is_a?(Crystal::TupleLiteral)
|
return if allow_multi_return && node.exp.is_a?(Crystal::TupleLiteral)
|
||||||
return if allow_empty_return && (node.exp.nil? || node.exp.try(&.nop?))
|
return if allow_empty_return && (node.exp.nil? || node.exp.try(&.nop?))
|
||||||
|
|
||||||
if (exp_code = control_exp_code(node, source.lines))
|
if exp_code = control_exp_code(node, source.lines)
|
||||||
issue_for node, MSG do |corrector|
|
issue_for node, MSG do |corrector|
|
||||||
corrector.replace(node, exp_code)
|
corrector.replace(node, exp_code)
|
||||||
end
|
end
|
||||||
|
|
|
@ -55,11 +55,11 @@ module Ameba::Rule::Style
|
||||||
private getter var_locations = [] of Crystal::Location
|
private getter var_locations = [] of Crystal::Location
|
||||||
|
|
||||||
def visit(node : Crystal::Var)
|
def visit(node : Crystal::Var)
|
||||||
!var_locations.includes?(node.location) && super
|
!node.location.in?(var_locations) && super
|
||||||
end
|
end
|
||||||
|
|
||||||
def visit(node : Crystal::InstanceVar | Crystal::ClassVar)
|
def visit(node : Crystal::InstanceVar | Crystal::ClassVar)
|
||||||
if (location = node.location)
|
if location = node.location
|
||||||
var_locations << location
|
var_locations << location
|
||||||
end
|
end
|
||||||
super
|
super
|
||||||
|
|
|
@ -34,8 +34,8 @@ module Ameba::Rule::Style
|
||||||
|
|
||||||
def test(source, node : Crystal::While)
|
def test(source, node : Crystal::While)
|
||||||
return unless node.cond.true_literal?
|
return unless node.cond.true_literal?
|
||||||
return unless (location = node.location)
|
return unless location = node.location
|
||||||
return unless (end_location = node.cond.end_location)
|
return unless end_location = node.cond.end_location
|
||||||
|
|
||||||
issue_for node, MSG do |corrector|
|
issue_for node, MSG do |corrector|
|
||||||
corrector.replace(location, end_location, "loop do")
|
corrector.replace(location, end_location, "loop do")
|
||||||
|
|
|
@ -87,6 +87,7 @@ module Ameba
|
||||||
# ```
|
# ```
|
||||||
def run
|
def run
|
||||||
@formatter.started @sources
|
@formatter.started @sources
|
||||||
|
|
||||||
channels = @sources.map { Channel(Exception?).new }
|
channels = @sources.map { Channel(Exception?).new }
|
||||||
@sources.each_with_index do |source, idx|
|
@sources.each_with_index do |source, idx|
|
||||||
channel = channels[idx]
|
channel = channels[idx]
|
||||||
|
@ -219,7 +220,9 @@ module Ameba
|
||||||
end
|
end
|
||||||
|
|
||||||
private def check_unneeded_directives(source)
|
private def check_unneeded_directives(source)
|
||||||
return unless (rule = @unneeded_disable_directive_rule) && rule.enabled
|
return unless rule = @unneeded_disable_directive_rule
|
||||||
|
return unless rule.enabled
|
||||||
|
|
||||||
rule.test(source)
|
rule.test(source)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -36,7 +36,7 @@ module Ameba
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns lines of code splitted by new line character.
|
# Returns lines of code split by new line character.
|
||||||
# Since `code` is immutable and can't be changed, this
|
# Since `code` is immutable and can't be changed, this
|
||||||
# method caches lines in an instance variable, so calling
|
# method caches lines in an instance variable, so calling
|
||||||
# it second time will not perform a split, but will return
|
# it second time will not perform a split, but will return
|
||||||
|
|
|
@ -32,10 +32,12 @@ class Ameba::Source::Rewriter
|
||||||
|
|
||||||
def empty?
|
def empty?
|
||||||
replacement = @replacement
|
replacement = @replacement
|
||||||
|
|
||||||
@insert_before.empty? &&
|
@insert_before.empty? &&
|
||||||
@insert_after.empty? &&
|
@insert_after.empty? &&
|
||||||
@children.empty? &&
|
@children.empty? &&
|
||||||
(replacement.nil? || (replacement.empty? && @begin_pos == @end_pos))
|
(replacement.nil? ||
|
||||||
|
(replacement.empty? && @begin_pos == @end_pos))
|
||||||
end
|
end
|
||||||
|
|
||||||
def ordered_replacements
|
def ordered_replacements
|
||||||
|
@ -50,7 +52,10 @@ class Ameba::Source::Rewriter
|
||||||
|
|
||||||
def insertion?
|
def insertion?
|
||||||
replacement = @replacement
|
replacement = @replacement
|
||||||
!@insert_before.empty? || !@insert_after.empty? || (replacement && !replacement.empty?)
|
|
||||||
|
!@insert_before.empty? ||
|
||||||
|
!@insert_after.empty? ||
|
||||||
|
(replacement && !replacement.empty?)
|
||||||
end
|
end
|
||||||
|
|
||||||
protected def with(*,
|
protected def with(*,
|
||||||
|
|
|
@ -17,12 +17,13 @@ class Ameba::Spec::AnnotatedSource
|
||||||
annotations = [] of {Int32, String, String}
|
annotations = [] of {Int32, String, String}
|
||||||
code_lines = annotated_code.split('\n') # must preserve trailing newline
|
code_lines = annotated_code.split('\n') # must preserve trailing newline
|
||||||
code_lines.each do |code_line|
|
code_lines.each do |code_line|
|
||||||
if (annotation_match = ANNOTATION_PATTERN_1.match(code_line))
|
case
|
||||||
|
when annotation_match = ANNOTATION_PATTERN_1.match(code_line)
|
||||||
message_index = annotation_match.end
|
message_index = annotation_match.end
|
||||||
prefix = code_line[0...message_index]
|
prefix = code_line[0...message_index]
|
||||||
message = code_line[message_index...]
|
message = code_line[message_index...]
|
||||||
annotations << {lines.size, prefix, message}
|
annotations << {lines.size, prefix, message}
|
||||||
elsif (annotation_index = code_line.index(ANNOTATION_PATTERN_2))
|
when annotation_index = code_line.index(ANNOTATION_PATTERN_2)
|
||||||
lines << code_line[...annotation_index]
|
lines << code_line[...annotation_index]
|
||||||
message_index = annotation_index + ANNOTATION_PATTERN_2.size
|
message_index = annotation_index + ANNOTATION_PATTERN_2.size
|
||||||
message = code_line[message_index...]
|
message = code_line[message_index...]
|
||||||
|
|
Loading…
Reference in a new issue