mirror of
https://github.com/Homebrew/brew.git
synced 2025-07-14 16:09:03 +08:00
163 lines
4.9 KiB
Ruby
163 lines
4.9 KiB
Ruby
# typed: strict
|
|
# frozen_string_literal: true
|
|
|
|
# Parlour type signature generator plugin for Homebrew DSL attributes.
|
|
class Attr < Parlour::Plugin
|
|
sig { override.params(root: Parlour::RbiGenerator::Namespace).void }
|
|
def generate(root)
|
|
tree = T.let([], T::Array[T.untyped])
|
|
Homebrew::Parlour.ast_list.each do |node|
|
|
tree += find_custom_attr(node)
|
|
end
|
|
process_custom_attr(tree, root)
|
|
end
|
|
|
|
sig { override.returns(T.nilable(String)) }
|
|
def strictness
|
|
"strict"
|
|
end
|
|
|
|
private
|
|
|
|
sig { params(node: Parser::AST::Node, list: T::Array[String]).returns(T::Array[String]) }
|
|
def traverse_module_name(node, list = [])
|
|
parent, name = node.children
|
|
list = traverse_module_name(parent, list) if parent
|
|
list << name.to_s
|
|
list
|
|
end
|
|
|
|
sig { params(node: T.nilable(Parser::AST::Node)).returns(T.nilable(String)) }
|
|
def extract_module_name(node)
|
|
return if node.nil?
|
|
|
|
traverse_module_name(node).join("::")
|
|
end
|
|
|
|
sig { params(node: Parser::AST::Node).returns(T::Array[T.untyped]) }
|
|
def find_custom_attr(node)
|
|
tree = T.let([], T::Array[T.untyped])
|
|
children = node.children.dup
|
|
|
|
if node.type == :begin
|
|
children.each do |child|
|
|
subtree = find_custom_attr(child)
|
|
tree += subtree unless subtree.empty?
|
|
end
|
|
elsif node.type == :sclass
|
|
subtree = find_custom_attr(node.children[1])
|
|
return tree if subtree.empty?
|
|
|
|
tree << [:sclass, subtree]
|
|
elsif node.type == :class || node.type == :module
|
|
element = []
|
|
case node.type
|
|
when :class
|
|
element << :class
|
|
element << extract_module_name(children.shift)
|
|
element << extract_module_name(children.shift)
|
|
when :module
|
|
element << :module
|
|
element << extract_module_name(children.shift)
|
|
end
|
|
|
|
body = children.shift
|
|
return tree if body.nil?
|
|
|
|
subtree = find_custom_attr(body)
|
|
return tree if subtree.empty?
|
|
|
|
element << subtree
|
|
tree << element
|
|
elsif node.type == :send && children.shift.nil?
|
|
method_name = children.shift
|
|
|
|
case method_name
|
|
when :attr_rw, :attr_predicate
|
|
children.each do |name_node|
|
|
tree << [method_name, name_node.children.first.to_s]
|
|
end
|
|
when :delegate
|
|
children.each do |name_node|
|
|
name_node.children.each do |pair|
|
|
delegated_method = pair.children.first
|
|
delegated_methods = if delegated_method.type == :array
|
|
delegated_method.children
|
|
else
|
|
[delegated_method]
|
|
end
|
|
|
|
delegated_methods.each do |delegated_method_sym|
|
|
tree << [method_name, delegated_method_sym.children.first.to_s]
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
tree
|
|
end
|
|
|
|
ARRAY_METHODS = T.let(["to_a", "to_ary"].freeze, T::Array[String])
|
|
HASH_METHODS = T.let(["to_h", "to_hash"].freeze, T::Array[String])
|
|
STRING_METHODS = T.let(["to_s", "to_str", "to_json"].freeze, T::Array[String])
|
|
|
|
sig { params(tree: T::Array[T.untyped], namespace: Parlour::RbiGenerator::Namespace, sclass: T::Boolean).void }
|
|
def process_custom_attr(tree, namespace, sclass: false)
|
|
tree.each do |node|
|
|
type = node.shift
|
|
case type
|
|
when :sclass
|
|
process_custom_attr(node.shift, namespace, sclass: true)
|
|
when :class
|
|
class_namespace = namespace.create_class(node.shift, superclass: node.shift)
|
|
process_custom_attr(node.shift, class_namespace)
|
|
when :module
|
|
module_namespace = namespace.create_module(node.shift)
|
|
process_custom_attr(node.shift, module_namespace)
|
|
when :attr_rw
|
|
name = node.shift
|
|
name = "self.#{name}" if sclass
|
|
namespace.create_method(name,
|
|
parameters: [
|
|
Parlour::RbiGenerator::Parameter.new("arg", type: "T.untyped",
|
|
default: "T.unsafe(nil)"),
|
|
],
|
|
return_type: "T.untyped")
|
|
when :attr_predicate
|
|
name = node.shift
|
|
name = "self.#{name}" if sclass
|
|
namespace.create_method(name, return_type: "T::Boolean")
|
|
when :delegate
|
|
name = node.shift
|
|
|
|
return_type = if name.end_with?("?")
|
|
"T::Boolean"
|
|
elsif ARRAY_METHODS.include?(name)
|
|
"Array"
|
|
elsif HASH_METHODS.include?(name)
|
|
"Hash"
|
|
elsif STRING_METHODS.include?(name)
|
|
"String"
|
|
else
|
|
"T.untyped"
|
|
end
|
|
|
|
name = "self.#{name}" if sclass
|
|
|
|
namespace.create_method(
|
|
name,
|
|
parameters: [
|
|
Parlour::RbiGenerator::Parameter.new("*args"),
|
|
Parlour::RbiGenerator::Parameter.new("**options"),
|
|
Parlour::RbiGenerator::Parameter.new("&block"),
|
|
],
|
|
return_type: return_type,
|
|
)
|
|
else
|
|
raise "Malformed tree."
|
|
end
|
|
end
|
|
end
|
|
end
|