# frozen_string_literal: true require "ripper" module SmartTodo module Parser # A MethodNode represent an event associated to a TODO. class MethodNode attr_reader :method_name, :arguments # @param method_name [Symbol] # @param arguments [Array] def initialize(method_name, arguments) @arguments = arguments @method_name = method_name end end # This class is used to parse the ruby TODO() comment. class MetadataParser < Ripper class << self # @param source [String] the actual Ruby code def parse(source) sexp = new(source).parse Visitor.new.tap { |v| v.process(sexp) } end end # @return [Array] an Array of Array # the first element from each inner array is a token def on_stmts_add(_, data) data end # @param method [String] the name of the method # when the parser hits one. # @param args [Array] # @return [Array, MethodNode] def on_method_add_arg(method, args) if method.start_with?(/TODO\W?/) args else MethodNode.new(method, args) end end # @param list [nil, Array] # @param arg [String] # @return [Array] def on_args_add(list, arg) Array(list) << arg end # @param string_content [String] # @return [String] def on_string_add(_, string_content) string_content end # @param key [String] # @param value [String, Integer, MethodNode] def on_assoc_new(key, value) key.tr!(":", "") case key when "on" [:on_todo_event, value] when "to" [:on_todo_assignee, value] else [:unknown, value] end end # @param data [Hash] # @return [Hash] def on_bare_assoc_hash(data) data end end class Visitor attr_reader :events, :assignees, :errors def initialize @events = [] @assignees = [] @errors = [] end # Iterate over each tokens returned from the parser and call # the corresponding method # # @param sexp [Array] # @return [void] def process(sexp) return unless sexp if sexp[0].is_a?(Array) sexp.each { |node| process(node) } else method, *args = sexp send(method, *args) if method.is_a?(Symbol) && respond_to?(method) end end # @param method_node [MethodNode] # @return [void] def on_todo_event(method_node) if method_node.is_a?(MethodNode) events << method_node else errors << "Incorrect `:on` event format: #{method_node}" end end # @param assignee [String] # @return [void] def on_todo_assignee(assignee) @assignees << assignee end end end end