Skip to content
This repository has been archived by the owner on Mar 16, 2023. It is now read-only.

Commit

Permalink
Merge pull request #188 from mvidner/absolute-constant-lookup
Browse files Browse the repository at this point in the history
Absolute constant lookup (::Foo)
  • Loading branch information
Yorick Peterse committed Jun 28, 2016
2 parents d893905 + 6b8d972 commit 9a349f7
Show file tree
Hide file tree
Showing 6 changed files with 117 additions and 25 deletions.
20 changes: 12 additions & 8 deletions lib/ruby-lint/constant_loader.rb
Expand Up @@ -102,7 +102,7 @@ def after_class(_node)
# @param [RubyLint::Node] node
#
def on_const(node)
load_nested_constant(ConstantPath.new(node).root_node[1])
load_nested_constant(ConstantPath.new(node).to_s)
end

##
Expand All @@ -129,13 +129,17 @@ def registry
# @param [String] constant name, possibly unqualified
#
def load_nested_constant(constant)
# ["A", "B", "C"] -> ["A::B::C", "A::B", "A"]
namespaces = module_nesting.size.downto(1).map do |n|
module_nesting.take(n).join("::")
end

namespaces.each do |ns|
load_constant("#{ns}::#{constant}")
if constant.start_with?("::")
constant = constant.sub(/^::/, "")
else
# ["A", "B", "C"] -> ["A::B::C", "A::B", "A"]
namespaces = module_nesting.size.downto(1).map do |n|
module_nesting.take(n).join("::")
end

namespaces.each do |ns|
load_constant("#{ns}::#{constant}")
end
end
load_constant(constant)
end
Expand Down
11 changes: 1 addition & 10 deletions lib/ruby-lint/constant_path.rb
Expand Up @@ -40,7 +40,7 @@ def resolve(scope)
type = REMAP_TYPES.fetch(type, type)
found = current.lookup(type, name, index == 0)

if found and found.const?
if found and (found.const? or found.type == :root)
current = found

# Local variables and the likes.
Expand All @@ -55,15 +55,6 @@ def resolve(scope)
return current
end

##
# Returns the very first segment of the constant path as an AST node.
#
# @return [RubyLint::AST::Node]
#
def root_node
return constant_segments.first
end

##
# Returns a String containing the full constant path, e.g.
# "RubyLint::Runner".
Expand Down
8 changes: 8 additions & 0 deletions lib/ruby-lint/definition/ruby_object.rb
Expand Up @@ -278,6 +278,8 @@ def lookup(type, name, lookup_parent = true, exclude = [])
if defines?(type, name)
found = definitions[type][name]

elsif type == :cbase
found = top_scope
# Look up the definition in the parent scope(s) (if any are set). This
# takes the parents themselves also into account.
elsif lookup_parent?(type) and lookup_parent
Expand Down Expand Up @@ -677,6 +679,12 @@ def inspect
return %Q(#<#{self.class}:0x#{address} #{attributes.join(' ')}>)
end

def top_scope
return self if type == :root
scope = parents.last # the enclosing scope
scope ? scope.top_scope : self
end

private

##
Expand Down
42 changes: 42 additions & 0 deletions spec/ruby-lint/analysis/undefined_variables_spec.rb
Expand Up @@ -67,6 +67,48 @@ module A
entry.message.should == 'undefined constant A::B'
end

it 'does not add errors for absolute constants' do
code = <<-CODE
module Project
class File
def self.Exists(filename)
::File.exist?(filename)
end
end
end
fn = "foo"
puts File.exist?(fn)
puts ::File.exist?(fn)
puts Project::File.Exists(fn)
CODE
report = build_report(code, RubyLint::Analysis::UndefinedVariables)

report.entries.empty?.should == true
end

it 'does not add errors for absolute constants, with multiple parents' do
code = <<-CODE
module Project
include Comparable
class File < Enumerable
include Comparable
def self.Exists(filename)
::File.exist?(filename)
end
end
end
fn = "foo"
puts File.exist?(fn)
puts ::File.exist?(fn)
puts Project::File.Exists(fn)
CODE
report = build_report(code, RubyLint::Analysis::UndefinedVariables)

report.entries.empty?.should == true
end

it 'does not depend on the order of variable definitions' do
code = <<-CODE
class Person
Expand Down
37 changes: 37 additions & 0 deletions spec/ruby-lint/constant_loader_spec.rb
Expand Up @@ -84,6 +84,43 @@
end
end

context 'iterating over an AST with PP::ObjectMixin' do
before do
@ast = s(:root, s(:const, s(:const, nil, 'PP'), 'ObjectMixin'))
end

it 'loads a constant' do
@loader.run([@ast])
@loader.loaded?('PP').should == true
end

it 'calls the correct callbacks' do
@loader.should_receive(:on_const)
.with(an_instance_of(RubyLint::AST::Node)).twice

@loader.run([@ast])
end
end


context 'iterating over an AST with ::PP' do
before do
@ast = s(:root, s(:const, s(:cbase), 'PP'))
end

it 'loads a constant' do
@loader.run([@ast])
@loader.loaded?('PP').should == true
end

it 'calls the correct callbacks' do
@loader.should_receive(:on_const)
.with(an_instance_of(RubyLint::AST::Node))

@loader.run([@ast])
end
end

context 'loading scoped constants' do
before do
@registry.register('Foo') do |defs|
Expand Down
24 changes: 17 additions & 7 deletions spec/ruby-lint/constant_path_spec.rb
@@ -1,23 +1,23 @@
require 'spec_helper'

describe RubyLint::ConstantPath do
example 'return the root node of a constant path' do
example 'generate the name of a constant path' do
node = s(:const, s(:const, nil, :Foo), :Bar)
root = RubyLint::ConstantPath.new(node).root_node
name = RubyLint::ConstantPath.new(node).to_s

root.should == [:const, 'Foo']
name.should == 'Foo::Bar'
end

example 'generate the name of a constant path' do
node = s(:const, s(:const, nil, :Foo), :Bar)
example 'generate the name of an absolute constant path' do
node = s(:const, s(:cbase), :Foo)
name = RubyLint::ConstantPath.new(node).to_s

name.should == 'Foo::Bar'
name.should == '::Foo'
end

context 'resolving definitions' do
before :all do
@scope = ruby_object.new(:type => :const, :name => 'Example')
@scope = ruby_object.new(:type => :root, :name => 'root')
@foo = @scope.define_constant('Foo')
@bar = @foo.define_constant('Bar')

Expand All @@ -43,6 +43,16 @@
defs.type.should == :const
end

example 'resolve a path of an absolute constant' do
node = s(:const, s(:const, s(:cbase), :Foo), :Bar)
defs = RubyLint::ConstantPath.new(node).resolve(@scope)

defs.is_a?(ruby_object).should == true

defs.name.should == 'Bar'
defs.type.should == :const
end

example 'resolve a path containing a variable' do
node = s(:const, s(:lvar, :example), :Bar)
defs = RubyLint::ConstantPath.new(node).resolve(@scope)
Expand Down

0 comments on commit 9a349f7

Please sign in to comment.