зеркало из https://github.com/github/ruby.git
[ruby/prism] Add sample for generating tags
https://github.com/ruby/prism/commit/7c9ca47ac5
This commit is contained in:
Родитель
568d7ab7f5
Коммит
d012f6d49f
|
@ -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
|
Загрузка…
Ссылка в новой задаче