-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
309 additions
and
89 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
# This script finds calls to a specific method with a certain keyword parameter | ||
# within a given source file. | ||
|
||
require "prism" | ||
require "pp" | ||
|
||
# For deprecation or refactoring purposes, it's often useful to find all of the | ||
# places that call a specific method with a specific k eyword parameter. This is | ||
# easily accomplished with a visitor such as this one. | ||
class QuxParameterVisitor < Prism::Visitor | ||
def initialize(calls) | ||
@calls = calls | ||
end | ||
|
||
def visit_call_node(node) | ||
@calls << node if qux?(node) | ||
super | ||
end | ||
|
||
private | ||
|
||
def qux?(node) | ||
# All nodes implement pattern matching, so you can use the `in` operator to | ||
# pull out all of their individual fields. As you can see by this extensive | ||
# pattern match, this is quite a powerful feature. | ||
node in { | ||
# This checks that the receiver is the constant Qux or the constant path | ||
# ::Qux. We are assuming relative constants are fine in this case. | ||
receiver: ( | ||
Prism::ConstantReadNode[name: :Qux] | | ||
Prism::ConstantPathNode[parent: nil, name: :Qux] | ||
), | ||
# This checks that the name of the method is qux. We purposefully are not | ||
# checking the call operator (., ::, or &.) because we want all of them. | ||
# In other ASTs, this would be multiple node types, but prism combines | ||
# them all into one for convenience. | ||
name: :qux, | ||
arguments: Prism::ArgumentsNode[ | ||
# Here we're going to use the "find" pattern to find the keyword hash | ||
# node that has the correct key. | ||
arguments: [ | ||
*, | ||
Prism::KeywordHashNode[ | ||
# Here we'll use another "find" pattern to find the key that we are | ||
# specifically looking for. | ||
elements: [ | ||
*, | ||
# Finally, we can assert against the key itself. Note that we are | ||
# not looking at the value of hash pair, because we are only | ||
# specifically looking for a key. | ||
Prism::AssocNode[key: Prism::SymbolNode[unescaped: "qux"]], | ||
* | ||
] | ||
], | ||
* | ||
] | ||
] | ||
} | ||
end | ||
end | ||
|
||
calls = [] | ||
Prism.parse_stream(DATA).value.accept(QuxParameterVisitor.new(calls)) | ||
|
||
calls.each do |call| | ||
print "CallNode " | ||
puts PP.pp(call.location, +"") | ||
print " " | ||
puts call.slice | ||
end | ||
|
||
# => | ||
# CallNode (5,6)-(5,29) | ||
# Qux.qux(222, qux: true) | ||
# CallNode (9,6)-(9,30) | ||
# Qux&.qux(333, qux: true) | ||
# CallNode (20,6)-(20,51) | ||
# Qux::qux(888, qux: ::Qux.qux(999, qux: true)) | ||
# CallNode (20,25)-(20,50) | ||
# ::Qux.qux(999, qux: true) | ||
|
||
__END__ | ||
module Foo | ||
class Bar | ||
def baz1 | ||
Qux.qux(111) | ||
Qux.qux(222, qux: true) | ||
end | ||
|
||
def baz2 | ||
Qux&.qux(333, qux: true) | ||
Qux&.qux(444) | ||
end | ||
|
||
def baz3 | ||
qux(555, qux: false) | ||
666.qux(666) | ||
end | ||
|
||
def baz4 | ||
Qux::qux(777) | ||
Qux::qux(888, qux: ::Qux.qux(999, qux: true)) | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,22 +1,100 @@ | ||
# This script finds all of the comments within a given source file. | ||
# This script finds all of the comments within a given source file for a method. | ||
|
||
require "prism" | ||
|
||
Prism.parse_comments(DATA.read).each do |comment| | ||
class FindMethodComments < Prism::Visitor | ||
def initialize(target, comments, nesting = []) | ||
@target = target | ||
@comments = comments | ||
@nesting = nesting | ||
end | ||
|
||
# These visit methods are specific to each class. Defining a visitor allows | ||
# you to group functionality that applies to all node types into a single | ||
# class. You can find which method corresponds to which node type by looking | ||
# at the class name, calling #type on the node, or by looking at the #accept | ||
# method definition on the node. | ||
def visit_module_node(node) | ||
visitor = FindMethodComments.new(@target, @comments, [*@nesting, node.name]) | ||
node.compact_child_nodes.each { |child| child.accept(visitor) } | ||
end | ||
|
||
def visit_class_node(node) | ||
# We could keep track of an internal state where we push the class name here | ||
# and then pop it after the visit is complete. However, it is often simpler | ||
# and cleaner to generate a new visitor instance when the state changes, | ||
# because then the state is immutable and it's easier to reason about. This | ||
# also provides for more debugging opportunity in the initializer. | ||
visitor = FindMethodComments.new(@target, @comments, [*@nesting, node.name]) | ||
node.compact_child_nodes.each { |child| child.accept(visitor) } | ||
end | ||
|
||
def visit_def_node(node) | ||
if [*@nesting, node.name] == @target | ||
# Comments are always attached to locations (either inner locations on a | ||
# node like the location of a keyword or the location on the node itself). | ||
# Nodes are considered either "leading" or "trailing", which means that | ||
# they occur before or after the location, respectively. In this case of | ||
# documentation, we only want to consider leading comments. You can also | ||
# fetch all of the comments on a location with #comments. | ||
@comments.concat(node.location.leading_comments) | ||
else | ||
super | ||
end | ||
end | ||
end | ||
|
||
# Most of the time, the concept of "finding" something in the AST can be | ||
# accomplished either with a queue or with a visitor. In this case we will use a | ||
# visitor, but a queue would work just as well. | ||
def find_comments(result, path) | ||
target = path.split(/::|#/).map(&:to_sym) | ||
comments = [] | ||
|
||
result.value.accept(FindMethodComments.new(target, comments)) | ||
comments | ||
end | ||
|
||
result = Prism.parse_stream(DATA) | ||
result.attach_comments! | ||
|
||
find_comments(result, "Foo#foo").each do |comment| | ||
puts comment.inspect | ||
puts comment.slice | ||
end | ||
|
||
# => | ||
# #<Prism::InlineComment @location=#<Prism::Location @start_offset=205 @length=27 start_line=13>> | ||
# # This is the documentation | ||
# #<Prism::InlineComment @location=#<Prism::Location @start_offset=235 @length=21 start_line=14>> | ||
# # for the foo method. | ||
|
||
find_comments(result, "Foo::Bar#bar").each do |comment| | ||
puts comment.inspect | ||
puts comment.slice | ||
end | ||
|
||
# => | ||
# #<Prism::InlineComment @location=#<Prism::Location @start_offset=0 @length=42 start_line=1>> | ||
# # This is documentation for the Foo class. | ||
# #<Prism::InlineComment @location=#<Prism::Location @start_offset=55 @length=43 start_line=3>> | ||
# # This is documentation for the bar method. | ||
# #<Prism::InlineComment @location=#<Prism::Location @start_offset=126 @length=23 start_line=7>> | ||
# # This is documentation | ||
# #<Prism::InlineComment @location=#<Prism::Location @start_offset=154 @length=21 start_line=8>> | ||
# # for the bar method. | ||
|
||
__END__ | ||
# This is documentation for the Foo class. | ||
class Foo | ||
# This is documentation for the bar method. | ||
def bar | ||
# This is the documentation | ||
# for the Foo module. | ||
module Foo | ||
# This is documentation | ||
# for the Bar class. | ||
class Bar | ||
# This is documentation | ||
# for the bar method. | ||
def bar | ||
end | ||
end | ||
|
||
# This is the documentation | ||
# for the foo method. | ||
def foo | ||
end | ||
end |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.