diff --git a/spec/ameba/rule/lint/shadowing_outer_local_var_spec.cr b/spec/ameba/rule/lint/shadowing_outer_local_var_spec.cr index ba44bc11..22027c0d 100644 --- a/spec/ameba/rule/lint/shadowing_outer_local_var_spec.cr +++ b/spec/ameba/rule/lint/shadowing_outer_local_var_spec.cr @@ -136,6 +136,19 @@ module Ameba::Rule::Lint CRYSTAL end + it "doesn't report if it shadows type definition" do + expect_no_issues subject, <<-CRYSTAL + class FooBar + getter index : String + + def bar + 3.times do |index| + end + end + end + CRYSTAL + end + it "doesn't report if it shadows throwaway arguments" do expect_no_issues subject, <<-CRYSTAL data = [{1, "a"}, {2, "b"}, {3, "c"}] diff --git a/src/ameba/ast/scope.cr b/src/ameba/ast/scope.cr index ff488ca4..59c7f3de 100644 --- a/src/ameba/ast/scope.cr +++ b/src/ameba/ast/scope.cr @@ -19,6 +19,9 @@ module Ameba::AST # Link to the instance variables used in current scope getter ivariables = [] of InstanceVariable + # Link to the type declaration variables used in current scope + getter type_dec_variables = [] of TypeDecVariable + # Link to the outer scope getter outer_scope : Scope? @@ -74,6 +77,16 @@ module Ameba::AST ivariables << InstanceVariable.new(node) end + # Adds a new type declaration variable to the current scope. + # + # ``` + # scope = Scope.new(class_node, nil) + # scope.add_type_dec_variable(node) + # ``` + def add_type_dec_variable(node) + type_dec_variables << TypeDecVariable.new(node) + end + # Returns variable by its name or `nil` if it does not exist. # # ``` @@ -126,6 +139,11 @@ module Ameba::AST ivariables.find(&.name.== "@#{name}") end + # Returns `true` if type declaration variable is assigned in this scope. + def assigns_type_dec?(name) + type_dec_variables.find(&.name.== name) || outer_scope.try(&.assigns_type_dec?(name)) + end + # Returns `true` if and only if current scope represents some # type definition, for example a class. def type_definition? diff --git a/src/ameba/ast/variabling/type_def_variable.cr b/src/ameba/ast/variabling/type_def_variable.cr new file mode 100644 index 00000000..9509eec4 --- /dev/null +++ b/src/ameba/ast/variabling/type_def_variable.cr @@ -0,0 +1,29 @@ +module Ameba::AST + class TypeDecVariable + getter node : Crystal::TypeDeclaration + + delegate location, to: @node + delegate end_location, to: @node + delegate to_s, to: @node + + def initialize(@node) + end + + def name + var = @node.var + + case var + when Crystal::Var + var.name + when Crystal::InstanceVar + var.name + when Crystal::ClassVar + var.name + when Crystal::Global + var.name + else + raise "unsupported type declaration var node" + end + end + end +end diff --git a/src/ameba/ast/visitors/scope_visitor.cr b/src/ameba/ast/visitors/scope_visitor.cr index 16868da6..a5c578ae 100644 --- a/src/ameba/ast/visitors/scope_visitor.cr +++ b/src/ameba/ast/visitors/scope_visitor.cr @@ -105,13 +105,14 @@ module Ameba::AST return unless (var = node.var).is_a?(Crystal::Var) @current_scope.add_variable(var) + @current_scope.add_type_dec_variable(node) @current_assign = node.value unless node.value.nil? end def end_visit(node : Crystal::TypeDeclaration) return unless (var = node.var).is_a?(Crystal::Var) - on_assign_end(node.var, node) + on_assign_end(var, node) @current_assign = nil on_scope_end(node) if @current_scope.eql?(node) end diff --git a/src/ameba/rule/lint/shadowing_outer_local_var.cr b/src/ameba/rule/lint/shadowing_outer_local_var.cr index d61221ec..9a1f8c84 100644 --- a/src/ameba/rule/lint/shadowing_outer_local_var.cr +++ b/src/ameba/rule/lint/shadowing_outer_local_var.cr @@ -57,6 +57,7 @@ module Ameba::Rule::Lint next if variable.nil? || !variable.declared_before?(arg) next if outer_scope.assigns_ivar?(arg.name) + next if outer_scope.assigns_type_dec?(arg.name) issue_for arg.node, MSG % arg.name end diff --git a/src/ameba/rule/lint/useless_assign.cr b/src/ameba/rule/lint/useless_assign.cr index e09df45e..f0319329 100644 --- a/src/ameba/rule/lint/useless_assign.cr +++ b/src/ameba/rule/lint/useless_assign.cr @@ -39,6 +39,7 @@ module Ameba::Rule::Lint def test(source, node, scope : AST::Scope) scope.variables.each do |var| next if var.ignored? || var.used_in_macro? || var.captured_by_block? + next if scope.assigns_type_dec?(var.name) var.assignments.each do |assign| next if assign.referenced? || assign.transformed?