Skip to content

Commit

Permalink
Migrate to RubyVM::AbstractSyntaxTree parsing
Browse files Browse the repository at this point in the history
- Should improve the performance
  (castwide/solargraph#522 (comment))
- Reuse existing AST tree from solargraph and avoid redundant parsing
- Required for making type inference work

Squashed commit of the following:

commit 42f0ace
Author: Lekë Mula <[email protected]>
Date:   Sun May 19 20:59:02 2024 +0200

    Remove redundant code Parsing

commit 35ca92f
Author: Lekë Mula <[email protected]>
Date:   Sun May 19 20:47:35 2024 +0200

    Suppress SyntaxError from RubyVM in SpecWalker

commit 99a4736
Author: Lekë Mula <[email protected]>
Date:   Sun May 19 19:57:25 2024 +0200

    Refactor specs and add more unhappy paths

commit f33e74e
Author: Lekë Mula <[email protected]>
Date:   Sun May 19 19:39:39 2024 +0200

    Refactor SpecWalker - extract sub-classes into own files

commit daf2cd6
Author: Lekë Mula <[email protected]>
Date:   Sun May 19 19:32:04 2024 +0200

    Rename RubyVMSpecWalker to SpecWalker

commit 555ad9d
Author: Lekë Mula <[email protected]>
Date:   Sun May 19 19:23:05 2024 +0200

    Replace Util with PinFactory

commit e55ce29
Author: Lekë Mula <[email protected]>
Date:   Sun May 19 18:35:29 2024 +0200

    Don't pass AST nodes into correctors

commit 102ac76
Author: Lekë Mula <[email protected]>
Date:   Sun May 19 18:13:30 2024 +0200

    Remove old SpecWalker

commit 3235e89
Author: Lekë Mula <[email protected]>
Date:   Sun May 19 18:10:04 2024 +0200

    Adapt ExampleAndHookBlocksBindingCorrector with RubyVM

commit e359861
Author: Lekë Mula <[email protected]>
Date:   Sun May 19 17:44:23 2024 +0200

    Adapt SubjectMethodCorrector with RubyVM

commit cddb93f
Author: Lekë Mula <[email protected]>
Date:   Sun May 19 17:19:15 2024 +0200

    Adapt LetMethodsCorrector with RubyVM

commit 541f4df
Author: Lekë Mula <[email protected]>
Date:   Sun May 19 16:43:15 2024 +0200

    Adapt DescribedClassCorrector with RubyVM

commit 66ff1ca
Author: Lekë Mula <[email protected]>
Date:   Sun May 19 15:53:20 2024 +0200

    Adapt ContextBlockNamespaceCorrector with RubyVM

commit f5e6046
Author: Lekë Mula <[email protected]>
Date:   Thu May 16 17:56:02 2024 +0200

    Implement RubyVMSpecWalker#on_described_class

    Last one for REAL!

commit 982914b
Author: Lekë Mula <[email protected]>
Date:   Thu May 16 17:38:35 2024 +0200

    Implement RubyVMSpecWalker#on_blocks_in_examples

    Last hook!

commit 080f9e6
Author: Lekë Mula <[email protected]>
Date:   Thu May 16 17:22:05 2024 +0200

    Implement RubyVMSpecWalker#on_hook_block

commit 4e8e662
Author: Lekë Mula <[email protected]>
Date:   Thu May 16 16:57:16 2024 +0200

    Implement RubyVMSpecWalker#on_example_block

commit badd097
Author: Lekë Mula <[email protected]>
Date:   Thu May 16 16:34:32 2024 +0200

    Implement RubyVMSpecWalker#on_let_method

commit 74ba354
Author: Lekë Mula <[email protected]>
Date:   Thu May 16 16:10:40 2024 +0200

    Implement RubyVMSpecWalker#on_subject

commit e264bd1
Author: Lekë Mula <[email protected]>
Date:   Thu May 16 15:18:40 2024 +0200

    Implement RubyVMSpecWalker#on_each_context_block

commit f8a4d6a
Author: Lekë Mula <[email protected]>
Date:   Thu May 16 11:50:13 2024 +0200

    Add walker specs
  • Loading branch information
lekemula committed May 20, 2024
1 parent 3a213fd commit d20f3e9
Show file tree
Hide file tree
Showing 21 changed files with 770 additions and 268 deletions.
21 changes: 15 additions & 6 deletions lib/solargraph/rspec/convention.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
require_relative 'correctors/context_block_methods_corrector'
require_relative 'correctors/implicit_subject_method_corrector'
require_relative 'correctors/dsl_methods_corrector'
require_relative 'util'
require_relative 'pin_factory'

module Solargraph
module Rspec
Expand Down Expand Up @@ -121,22 +121,31 @@ def local(source_map)

# @type [Pin::Method, nil]
described_class_pin = nil
Correctors::DescribedClassCorrector.new(namespace_pins: namespace_pins, rspec_walker: rspec_walker).correct(
Correctors::DescribedClassCorrector.new(
namespace_pins: namespace_pins,
rspec_walker: rspec_walker
).correct(
source_map
) do |pins_to_add|
described_class_pin = pins_to_add.first
pins += pins_to_add
end

Correctors::LetMethodsCorrector.new(namespace_pins: namespace_pins, rspec_walker: rspec_walker).correct(
Correctors::LetMethodsCorrector.new(
namespace_pins: namespace_pins,
rspec_walker: rspec_walker
).correct(
source_map
) do |pins_to_add|
pins += pins_to_add
end

# @type [Pin::Method, nil]
subject_pin = nil
Correctors::SubjectMethodCorrector.new(namespace_pins: namespace_pins, rspec_walker: rspec_walker).correct(
Correctors::SubjectMethodCorrector.new(
namespace_pins: namespace_pins,
rspec_walker: rspec_walker
).correct(
source_map
) do |pins_to_add|
subject_pin = pins_to_add.first
Expand Down Expand Up @@ -199,7 +208,7 @@ def local(source_map)
# @return [Array<Pin::Base>]
def include_helper_pins(helper_modules: HELPER_MODULES)
helper_modules.map do |helper_module|
Util.build_module_include(
PinFactory.build_module_include(
root_example_group_namespace_pin,
helper_module,
root_example_group_namespace_pin.location
Expand All @@ -216,7 +225,7 @@ def config
def root_example_group_namespace_pin
Solargraph::Pin::Namespace.new(
name: ROOT_NAMESPACE,
location: Util.dummy_location('lib/rspec/core/example_group.rb')
location: PinFactory.dummy_location('lib/rspec/core/example_group.rb')
)
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/solargraph/rspec/correctors/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def correct(_source_map)
def root_example_group_namespace_pin
Solargraph::Pin::Namespace.new(
name: ROOT_NAMESPACE,
location: Util.dummy_location('lib/rspec/core/example_group.rb')
location: PinFactory.dummy_location('lib/rspec/core/example_group.rb')
)
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ module Correctors
class ContextBlockNamespaceCorrector < WalkerBase
# @param source_map [Solargraph::SourceMap]
def correct(source_map)
rspec_walker.on_each_context_block do |namespace_name, ast|
original_block_pin = source_map.locate_block_pin(ast.location.begin.line, ast.location.begin.column)
# @param location_range [Solargraph::Range]
rspec_walker.on_each_context_block do |namespace_name, location_range|
original_block_pin = source_map.locate_block_pin(location_range.start.line, location_range.start.column)
original_block_pin_index = source_map.pins.index(original_block_pin)
location = Util.build_location(ast, source_map.filename)
location = PinFactory.build_location(location_range, source_map.filename)

# Define a dynamic module for the example group block
# Example:
Expand All @@ -36,15 +37,15 @@ def correct(source_map)

# Include DSL methods in the example group block
# TOOD: This does not work on solagraph! Class methods are not included from parent class.
namespace_extend_pin = Util.build_module_extend(
namespace_extend_pin = PinFactory.build_module_extend(
namespace_pin,
root_example_group_namespace_pin.name,
location
)

# Include parent example groups to share let definitions
parent_namespace_name = namespace_name.split('::')[0..-2].join('::')
namespace_include_pin = Util.build_module_include(
namespace_include_pin = PinFactory.build_module_include(
namespace_pin,
parent_namespace_name,
location
Expand Down
14 changes: 7 additions & 7 deletions lib/solargraph/rspec/correctors/described_class_corrector.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,27 @@ class DescribedClassCorrector < WalkerBase
# @param source_map [Solargraph::SourceMap]
# @return [void]
def correct(_source_map)
rspec_walker.on_described_class do |ast, described_class_name|
namespace_pin = closest_namespace_pin(namespace_pins, ast.loc.line)
rspec_walker.on_described_class do |described_class_name, location_range|
namespace_pin = closest_namespace_pin(namespace_pins, location_range.start.line)
next unless namespace_pin

described_class_pin = rspec_described_class_method(namespace_pin, ast, described_class_name)
described_class_pin = rspec_described_class_method(namespace_pin, location_range, described_class_name)
yield [described_class_pin].compact if block_given?
end
end

private

# @param namespace [Pin::Namespace]
# @param ast [Parser::AST::Node]
# @param location_range [Solargraph::Range]
# @param described_class_name [String]
# @return [Pin::Method, nil]
def rspec_described_class_method(namespace, ast, described_class_name)
Util.build_public_method(
def rspec_described_class_method(namespace, location_range, described_class_name)
PinFactory.build_public_method(
namespace,
'described_class',
types: ["Class<#{described_class_name}>"],
location: Util.build_location(ast, namespace.filename),
location: PinFactory.build_location(location_range, namespace.filename),
scope: :instance
)
end
Expand Down
4 changes: 2 additions & 2 deletions lib/solargraph/rspec/correctors/dsl_methods_corrector.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def correct(_source_map)
# @return [Array<Solargraph::Pin::Method>]
def add_methods_with_example_binding(namespace_pin)
rspec_context_block_methods.map do |method|
Util.build_public_method(
PinFactory.build_public_method(
namespace_pin,
method.to_s,
comments: ["@yieldself [#{namespace_pin.path}]"], # Fixes the binding of the block to the correct class
Expand All @@ -50,7 +50,7 @@ def add_methods_with_example_binding(namespace_pin)
# @return [Array<Solargraph::Pin::Base>]
def add_context_dsl_methods(namespace_pin)
Rspec::CONTEXT_METHODS.map do |method|
Util.build_public_method(
PinFactory.build_public_method(
namespace_pin,
method.to_s,
scope: :class
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,48 +11,48 @@ class ExampleAndHookBlocksBindingCorrector < WalkerBase
# @param source_map [Solargraph::SourceMap]
# @return [void]
def correct(source_map)
rspec_walker.on_example_block do |block_ast|
bind_closest_namespace(block_ast, source_map)
rspec_walker.on_example_block do |location_range|
bind_closest_namespace(location_range, source_map)

yield [] if block_given?
end

rspec_walker.on_hook_block do |block_ast|
bind_closest_namespace(block_ast, source_map)
rspec_walker.on_hook_block do |location_range|
bind_closest_namespace(location_range, source_map)

yield [] if block_given?
end

rspec_walker.on_let_method do |let_method_ast|
bind_closest_namespace(let_method_ast, source_map)
rspec_walker.on_let_method do |_method_name, location_range|
bind_closest_namespace(location_range, source_map)

yield [] if block_given?
end

rspec_walker.on_blocks_in_examples do |block_ast|
bind_closest_namespace(block_ast, source_map)
rspec_walker.on_blocks_in_examples do |location_range|
bind_closest_namespace(location_range, source_map)

yield [] if block_given?
end

rspec_walker.on_subject do |_subject_ast, block_ast|
bind_closest_namespace(block_ast, source_map)
rspec_walker.on_subject do |_method_name, location_range|
bind_closest_namespace(location_range, source_map)

yield [] if block_given?
end
end

private

# @param block_ast [Parser::AST::Node]
# @param location_range [Solargraph::Range]
# @param source_map [Solargraph::SourceMap]
# @return [void]
def bind_closest_namespace(block_ast, source_map)
namespace_pin = closest_namespace_pin(namespace_pins, block_ast.loc.line)
def bind_closest_namespace(location_range, source_map)
namespace_pin = closest_namespace_pin(namespace_pins, location_range.start.line)
return unless namespace_pin

original_block_pin = source_map.locate_block_pin(block_ast.location.begin.line,
block_ast.location.begin.column)
original_block_pin = source_map.locate_block_pin(location_range.start.line,
location_range.start.column)
original_block_pin_index = source_map.pins.index(original_block_pin)
fixed_namespace_block_pin = Solargraph::Pin::Block.new(
closure: example_run_method(namespace_pin),
Expand All @@ -67,7 +67,7 @@ def bind_closest_namespace(block_ast, source_map)
# @param namespace_pin [Solargraph::Pin::Namespace]
# @return [Solargraph::Pin::Method]
def example_run_method(namespace_pin)
Util.build_public_method(
PinFactory.build_public_method(
namespace_pin,
'run',
# https://github.com/rspec/rspec-core/blob/main/lib/rspec/core/example.rb#L246
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def correct(_source_map)
def implicit_subject_pin(described_class_pin, namespace_pin)
described_class = described_class_pin.return_type.first.subtypes.first.name

Util.build_public_method(
PinFactory.build_public_method(
namespace_pin,
'subject',
types: [described_class],
Expand Down
18 changes: 7 additions & 11 deletions lib/solargraph/rspec/correctors/let_methods_corrector.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,31 +10,27 @@ class LetMethodsCorrector < WalkerBase
# @param source_map [Solargraph::SourceMap]
# @return [void]
def correct(_source_map)
rspec_walker.on_let_method do |ast|
namespace_pin = closest_namespace_pin(namespace_pins, ast.loc.line)
rspec_walker.on_let_method do |let_name, location_range|
namespace_pin = closest_namespace_pin(namespace_pins, location_range.start.line)
next unless namespace_pin

pin = rspec_let_method(namespace_pin, ast)
pin = rspec_let_method(namespace_pin, let_name, location_range)
yield [pin] if block_given?
end
end

private

# @param namespace [Pin::Namespace]
# @param ast [Parser::AST::Node]
# @param method_name [String]
# @param types [Array<String>, nil]
# @return [Pin::Method, nil]
def rspec_let_method(namespace, ast, types: nil)
return unless ast.children
return unless ast.children[2]&.children

method_name = ast.children[2].children[0]&.to_s or return
Util.build_public_method(
def rspec_let_method(namespace, method_name, location_range, types: nil)
PinFactory.build_public_method(
namespace,
method_name,
types: types,
location: Util.build_location(ast, namespace.filename),
location: PinFactory.build_location(location_range, namespace.filename),
scope: :instance
)
end
Expand Down
8 changes: 5 additions & 3 deletions lib/solargraph/rspec/correctors/subject_method_corrector.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ class SubjectMethodCorrector < LetMethodsCorrector
# @param source_map [Solargraph::SourceMap]
# @return [void]
def correct(_source_map)
rspec_walker.on_subject do |ast|
namespace_pin = closest_namespace_pin(namespace_pins, ast.loc.line)
rspec_walker.on_subject do |subject_name, location_range|
next unless subject_name

namespace_pin = closest_namespace_pin(namespace_pins, location_range.start.line)
next unless namespace_pin

subject_pin = rspec_let_method(namespace_pin, ast)
subject_pin = rspec_let_method(namespace_pin, subject_name, location_range)
yield [subject_pin].compact if block_given?
end
end
Expand Down
98 changes: 98 additions & 0 deletions lib/solargraph/rspec/pin_factory.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# frozen_string_literal: true

# Credits: This file is a copy of the file from the solargraph-rspec gem

module Solargraph
module Rspec
# Factory class for building pins and references.
module PinFactory
# @param namespace [Solargraph::Pin::Namespace]
# @param name [String]
# @param types [Array<String>]
# @param location [Solargraph::Location]
# @param comments [Array<String>]
# @param attribute [Boolean]
# @param scope [:instance, :class]
# @return [Solargraph::Pin::Method]
def self.build_public_method(
namespace,
name,
types: nil,
location: nil,
comments: [],
attribute: false,
scope: :instance
)
opts = {
name: name,
location: location,
closure: namespace,
scope: scope,
attribute: attribute,
comments: []
}

comments << "@return [#{types.join(",")}]" if types

opts[:comments] = comments.join("\n")

Solargraph::Pin::Method.new(**opts)
end

# @param namespace [Solargraph::Pin::Namespace]
# @param name [String]
# @param location [Solargraph::Location]
# @return [Solargraph::Pin::Reference::Include]
def self.build_module_include(namespace, module_name, location)
Solargraph::Pin::Reference::Include.new(
closure: namespace,
name: module_name,
location: location
)
end

# @param namespace [Solargraph::Pin::Namespace]
# @param module_name [String]
# @param location [Solargraph::Location]
# @return [Solargraph::Pin::Reference::Extend]
def self.build_module_extend(namespace, module_name, location)
Solargraph::Pin::Reference::Extend.new(
closure: namespace,
name: module_name,
location: location
)
end

# @param path [String]
# @return [Solargraph::Location]
def self.dummy_location(path)
Solargraph::Location.new(
File.expand_path(path),
Solargraph::Range.from_to(0, 0, 0, 0)
)
end

# @param ast [RubyVM::AbstractSyntaxTree::Node]
# @see [RubyVM::AbstractSyntaxTree::NodeWrapper] - for why we need -1 for lineno
# @return [Solargraph::Range]
def self.build_location_range(ast)
Solargraph::Range.from_to(
ast.first_lineno - 1,
ast.first_column,
ast.last_lineno - 1,
ast.last_column
)
end

# @param location_range [Solargraph::Range]
# @param path [String]
# @return [Solargraph::Location]
def self.build_location(location_range, path)
Solargraph::Location.new(
File.expand_path(path),
location_range
)
end
end
end
end
Loading

0 comments on commit d20f3e9

Please sign in to comment.