[ruby/prism] Add sample for generating tags

https://github.com/ruby/prism/commit/7c9ca47ac5
This commit is contained in:
Kevin Newton 2024-08-12 14:22:21 -04:00 коммит произвёл git
Родитель 568d7ab7f5
Коммит d012f6d49f
1 изменённых файлов: 302 добавлений и 0 удалений

302
sample/prism/make_tags.rb Normal file
Просмотреть файл

@ -0,0 +1,302 @@
# This script generates a tags file using Prism to parse the Ruby files.
require "prism"
# This visitor is responsible for visiting the nodes in the AST and generating
# the appropriate tags. The tags are stored in the entries array as strings.
class TagsVisitor < Prism::Visitor
# This represents an entry in the tags file, which is a tab-separated line. It
# houses the logic for how an entry is constructed.
class Entry
attr_reader :parts
def initialize(name, filepath, pattern, type)
@parts = [name, filepath, pattern, type]
end
def attribute(key, value)
parts << "#{key}:#{value}"
end
def attribute_class(nesting, names)
return if nesting.empty? && names.length == 1
attribute("class", [*nesting, names].flatten.tap(&:pop).join("."))
end
def attribute_inherits(names)
attribute("inherits", names.join(".")) if names
end
def to_line
parts.join("\t")
end
end
private_constant :Entry
attr_reader :entries, :filepath, :lines, :nesting, :singleton
# Initialize the visitor with the given parameters. The first three parameters
# are constant throughout the visit, while the last two are controlled by the
# visitor as it traverses the AST. These are treated as immutable by virtue of
# the visit methods constructing new visitors when they need to change.
def initialize(entries, filepath, lines, nesting = [], singleton = false)
@entries = entries
@filepath = filepath
@lines = lines
@nesting = nesting
@singleton = singleton
end
# Visit a method alias node and generate the appropriate tags.
#
# alias m2 m1
#
def visit_alias_method_node(node)
enter(node.new_name.unescaped.to_sym, node, "a") do |entry|
entry.attribute_class(nesting, [nil])
end
super
end
# Visit a method call to attr_reader, attr_writer, or attr_accessor without a
# receiver and generate the appropriate tags. Note that this ignores the fact
# that these methods could be overridden, which is a limitation of this
# script.
#
# attr_accessor :m1
#
def visit_call_node(node)
if !node.receiver && %i[attr_reader attr_writer attr_accessor].include?(name = node.name)
(node.arguments&.arguments || []).grep(Prism::SymbolNode).each do |argument|
if name != :attr_writer
enter(:"#{argument.unescaped}", argument, singleton ? "F" : "f") do |entry|
entry.attribute_class(nesting, [nil])
end
end
if name != :attr_reader
enter(:"#{argument.unescaped}=", argument, singleton ? "F" : "f") do |entry|
entry.attribute_class(nesting, [nil])
end
end
end
end
super
end
# Visit a class node and generate the appropriate tags.
#
# class C1
# end
#
def visit_class_node(node)
if (names = names_for(node.constant_path))
enter(names.last, node, "c") do |entry|
entry.attribute_class(nesting, names)
entry.attribute_inherits(names_for(node.superclass))
end
node.body&.accept(copy_visitor([*nesting, names], singleton))
end
end
# Visit a constant path write node and generate the appropriate tags.
#
# C1::C2 = 1
#
def visit_constant_path_write_node(node)
if (names = names_for(node.target))
enter(names.last, node, "C") do |entry|
entry.attribute_class(nesting, names)
end
end
super
end
# Visit a constant write node and generate the appropriate tags.
#
# C1 = 1
#
def visit_constant_write_node(node)
enter(node.name, node, "C") do |entry|
entry.attribute_class(nesting, [nil])
end
super
end
# Visit a method definition node and generate the appropriate tags.
#
# def m1; end
#
def visit_def_node(node)
enter(node.name, node, (node.receiver || singleton) ? "F" : "f") do |entry|
entry.attribute_class(nesting, [nil])
end
super
end
# Visit a module node and generate the appropriate tags.
#
# module M1
# end
#
def visit_module_node(node)
if (names = names_for(node.constant_path))
enter(names.last, node, "m") do |entry|
entry.attribute_class(nesting, names)
end
node.body&.accept(copy_visitor([*nesting, names], singleton))
end
end
# Visit a singleton class node and generate the appropriate tags.
#
# class << self
# end
#
def visit_singleton_class_node(node)
case node.expression
when Prism::SelfNode
node.body&.accept(copy_visitor(nesting, true))
when Prism::ConstantReadNode, Prism::ConstantPathNode
if (names = names_for(node.expression))
node.body&.accept(copy_visitor([*nesting, names], true))
end
else
node.body&.accept(copy_visitor([*nesting, nil], true))
end
end
private
# Generate a new visitor with the given dynamic options. The static options
# are copied over automatically.
def copy_visitor(nesting, singleton)
TagsVisitor.new(entries, filepath, lines, nesting, singleton)
end
# Generate a new entry for the given name, node, and type and add it into the
# list of entries. The block is used to add additional attributes to the
# entry.
def enter(name, node, type)
line = lines[node.location.start_line - 1].chomp
pattern = "/^#{line.gsub("\\", "\\\\\\\\").gsub("/", "\\/")}$/;\""
entry = Entry.new(name, filepath, pattern, type)
yield entry
entries << entry.to_line
end
# Retrieve the names for the given node. This is used to construct the class
# attribute for the tags.
def names_for(node)
case node
when Prism::ConstantPathNode
names = names_for(node.parent)
return unless names
names << node.name
when Prism::ConstantReadNode
[node.name]
when Prism::SelfNode
[:self]
else
# dynamic
end
end
end
# Parse the Ruby file and visit all of the nodes in the resulting AST. Once all
# of the nodes have been visited, the entries array should be populated with the
# tags.
result = Prism.parse_stream(DATA)
result.value.accept(TagsVisitor.new(entries = [], __FILE__, result.source.lines))
# Print the tags to STDOUT.
puts "!_TAG_FILE_FORMAT 2 /extended format; --format=1 will not append ;\" to lines/"
puts "!_TAG_FILE_SORTED 1 /0=unsorted, 1=sorted, 2=foldcase/"
puts entries.sort
# =>
# !_TAG_FILE_FORMAT 2 /extended format; --format=1 will not append ;" to lines/
# !_TAG_FILE_SORTED 1 /0=unsorted, 1=sorted, 2=foldcase/
# C1 sample/prism/make_tags.rb /^ class C1$/;" c class:M1.M2
# C2 sample/prism/make_tags.rb /^ class C2 < Object$/;" c class:M1.M2.C1 inherits:Object
# C6 sample/prism/make_tags.rb /^ C6 = 1$/;" C class:M1
# C7 sample/prism/make_tags.rb /^ C7 = 2$/;" C class:M1
# C9 sample/prism/make_tags.rb /^ C8::C9 = 3$/;" C class:M1.C8
# M1 sample/prism/make_tags.rb /^module M1$/;" m
# M2 sample/prism/make_tags.rb /^ module M2$/;" m class:M1
# M4 sample/prism/make_tags.rb /^ module M3::M4$/;" m class:M1.M3
# M5 sample/prism/make_tags.rb /^ module self::M5$/;" m class:M1.self
# m1 sample/prism/make_tags.rb /^ def m1; end$/;" f class:M1.M2.C1.C2
# m10 sample/prism/make_tags.rb /^ attr_accessor :m10, :m11$/;" f class:M1.M3.M4
# m10= sample/prism/make_tags.rb /^ attr_accessor :m10, :m11$/;" f class:M1.M3.M4
# m11 sample/prism/make_tags.rb /^ attr_accessor :m10, :m11$/;" f class:M1.M3.M4
# m11= sample/prism/make_tags.rb /^ attr_accessor :m10, :m11$/;" f class:M1.M3.M4
# m12 sample/prism/make_tags.rb /^ attr_reader :m12, :m13, :m14$/;" f class:M1.M3.M4
# m13 sample/prism/make_tags.rb /^ attr_reader :m12, :m13, :m14$/;" f class:M1.M3.M4
# m14 sample/prism/make_tags.rb /^ attr_reader :m12, :m13, :m14$/;" f class:M1.M3.M4
# m15= sample/prism/make_tags.rb /^ attr_writer :m15$/;" f class:M1.M3.M4
# m2 sample/prism/make_tags.rb /^ def m2; end$/;" f class:M1.M2.C1.C2
# m3 sample/prism/make_tags.rb /^ alias m3 m1$/;" a class:M1.M2.C1.C2
# m4 sample/prism/make_tags.rb /^ alias :m4 :m2$/;" a class:M1.M2.C1.C2
# m5 sample/prism/make_tags.rb /^ def self.m5; end$/;" F class:M1.M2.C1.C2
# m6 sample/prism/make_tags.rb /^ def m6; end$/;" F class:M1.M2.C1.C2
# m7 sample/prism/make_tags.rb /^ def m7; end$/;" F class:M1.M2.C1.C2.C3
# m8 sample/prism/make_tags.rb /^ def m8; end$/;" F class:M1.M2.C1.C2.C4.C5
# m9 sample/prism/make_tags.rb /^ def m9; end$/;" F class:M1.M2.C1.C2.
__END__
module M1
module M2
class C1
class C2 < Object
def m1; end
def m2; end
alias m3 m1
alias :m4 :m2
def self.m5; end
class << self
def m6; end
end
class << C3
def m7; end
end
class << C4::C5
def m8; end
end
class << c
def m9; end
end
end
end
end
module M3::M4
attr_accessor :m10, :m11
attr_reader :m12, :m13, :m14
attr_writer :m15
end
module self::M5
end
C6 = 1
C7 = 2
C8::C9 = 3
end