Implement `Scope#visibility`

This commit is contained in:
Sijawusz Pur Rahnama 2023-06-08 01:58:58 +02:00
parent 85c3db4d74
commit 4c740f394a
4 changed files with 62 additions and 1 deletions

View File

@ -2,6 +2,17 @@ require "../../../spec_helper"
module Ameba::AST
describe ScopeVisitor do
{% for type in %w[class module enum].map(&.id) %}
it "creates a scope for the {{ type }} def" do
rule = ScopeRule.new
ScopeVisitor.new rule, Source.new <<-CRYSTAL
{{ type }} Foo
end
CRYSTAL
rule.scopes.size.should eq 1
end
{% end %}
it "creates a scope for the def" do
rule = ScopeRule.new
ScopeVisitor.new rule, Source.new <<-CRYSTAL
@ -54,5 +65,31 @@ module Ameba::AST
outer_block.outer_scope.should be_nil
end
end
context "#visibility" do
it "is being properly set" do
rule = ScopeRule.new
ScopeVisitor.new rule, Source.new <<-CRYSTAL
private class Foo
end
CRYSTAL
rule.scopes.size.should eq 1
rule.scopes.first.visibility.should eq Crystal::Visibility::Private
end
it "is being inherited from the outer scope(s)" do
rule = ScopeRule.new
ScopeVisitor.new rule, Source.new <<-CRYSTAL
private class Foo
class Bar
def baz
end
end
end
CRYSTAL
rule.scopes.size.should eq 3
rule.scopes.each &.visibility.should eq Crystal::Visibility::Private
end
end
end
end

View File

@ -43,6 +43,9 @@ module Ameba
description "Internal rule to test scopes"
end
def test(source, node : Crystal::VisibilityModifier, scope : AST::Scope)
end
def test(source, node : Crystal::ASTNode, scope : AST::Scope)
@scopes << scope
end

View File

@ -7,6 +7,9 @@ module Ameba::AST
# Whether the scope yields.
setter yields = false
# Scope visibility level
setter visibility : Crystal::Visibility?
# Link to local variables
getter variables = [] of Variable
@ -172,6 +175,11 @@ module Ameba::AST
false
end
# Returns visibility of the current scope (could be inherited from the outer scope).
def visibility
@visibility || outer_scope.try(&.visibility)
end
# Returns `true` if current scope is a def, `false` otherwise.
def def?
node.is_a?(Crystal::Def)

View File

@ -25,6 +25,7 @@ module Ameba::AST
@scope_queue = [] of Scope
@current_scope : Scope
@current_assign : Crystal::ASTNode?
@visibility_modifier : Crystal::Visibility?
@skip : Array(Crystal::ASTNode.class)?
def initialize(@rule, @source, skip = nil)
@ -36,12 +37,18 @@ module Ameba::AST
private def on_scope_enter(node)
return if skip?(node)
@current_scope = Scope.new(node, @current_scope)
scope = Scope.new(node, @current_scope)
scope.visibility = @visibility_modifier
@current_scope = scope
end
private def on_scope_end(node)
@scope_queue << @current_scope
@visibility_modifier = nil
# go up if this is not a top level scope
return unless outer_scope = @current_scope.outer_scope
@current_scope = outer_scope
@ -64,6 +71,12 @@ module Ameba::AST
end
{% end %}
# :nodoc:
def visit(node : Crystal::VisibilityModifier)
@visibility_modifier = node.modifier
true
end
# :nodoc:
def visit(node : Crystal::Yield)
@current_scope.yields = true