Incorporate changes for shadowing outer local var

This commit is contained in:
Vitalii Elenhaupt 2023-02-04 20:40:59 +02:00
parent ddbcf5cb3f
commit 14a9ec3a75
No known key found for this signature in database
GPG key ID: CD0BF17825928BC0
6 changed files with 64 additions and 1 deletions

View file

@ -136,6 +136,19 @@ module Ameba::Rule::Lint
CRYSTAL CRYSTAL
end 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 it "doesn't report if it shadows throwaway arguments" do
expect_no_issues subject, <<-CRYSTAL expect_no_issues subject, <<-CRYSTAL
data = [{1, "a"}, {2, "b"}, {3, "c"}] data = [{1, "a"}, {2, "b"}, {3, "c"}]

View file

@ -19,6 +19,9 @@ module Ameba::AST
# Link to the instance variables used in current scope # Link to the instance variables used in current scope
getter ivariables = [] of InstanceVariable 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 # Link to the outer scope
getter outer_scope : Scope? getter outer_scope : Scope?
@ -74,6 +77,16 @@ module Ameba::AST
ivariables << InstanceVariable.new(node) ivariables << InstanceVariable.new(node)
end 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. # Returns variable by its name or `nil` if it does not exist.
# #
# ``` # ```
@ -126,6 +139,11 @@ module Ameba::AST
ivariables.find(&.name.== "@#{name}") ivariables.find(&.name.== "@#{name}")
end 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 # Returns `true` if and only if current scope represents some
# type definition, for example a class. # type definition, for example a class.
def type_definition? def type_definition?

View file

@ -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

View file

@ -105,13 +105,14 @@ module Ameba::AST
return unless (var = node.var).is_a?(Crystal::Var) return unless (var = node.var).is_a?(Crystal::Var)
@current_scope.add_variable(var) @current_scope.add_variable(var)
@current_scope.add_type_dec_variable(node)
@current_assign = node.value unless node.value.nil? @current_assign = node.value unless node.value.nil?
end end
def end_visit(node : Crystal::TypeDeclaration) def end_visit(node : Crystal::TypeDeclaration)
return unless (var = node.var).is_a?(Crystal::Var) return unless (var = node.var).is_a?(Crystal::Var)
on_assign_end(node.var, node) on_assign_end(var, node)
@current_assign = nil @current_assign = nil
on_scope_end(node) if @current_scope.eql?(node) on_scope_end(node) if @current_scope.eql?(node)
end end

View file

@ -57,6 +57,7 @@ module Ameba::Rule::Lint
next if variable.nil? || !variable.declared_before?(arg) next if variable.nil? || !variable.declared_before?(arg)
next if outer_scope.assigns_ivar?(arg.name) next if outer_scope.assigns_ivar?(arg.name)
next if outer_scope.assigns_type_dec?(arg.name)
issue_for arg.node, MSG % arg.name issue_for arg.node, MSG % arg.name
end end

View file

@ -39,6 +39,7 @@ module Ameba::Rule::Lint
def test(source, node, scope : AST::Scope) def test(source, node, scope : AST::Scope)
scope.variables.each do |var| scope.variables.each do |var|
next if var.ignored? || var.used_in_macro? || var.captured_by_block? 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| var.assignments.each do |assign|
next if assign.referenced? || assign.transformed? next if assign.referenced? || assign.transformed?