# frozen_string_literal: true module SyntaxTree # A slightly enhanced PP that knows how to format recursively including # comments. class Formatter < PrettierPrint # We want to minimize as much as possible the number of options that are # available in syntax tree. For the most part, if users want non-default # formatting, they should override the format methods on the specific nodes # themselves. However, because of some history with prettier and the fact # that folks have become entrenched in their ways, we decided to provide a # small amount of configurability. # # Note that we're keeping this in a global-ish hash instead of just # overriding methods on classes so that other plugins can reference this if # necessary. For example, the RBS plugin references the quote style. OPTIONS = { quote: "\"", trailing_comma: false } COMMENT_PRIORITY = 1 HEREDOC_PRIORITY = 2 attr_reader :source, :stack # These options are overridden in plugins to we need to make sure they are # available here. attr_reader :quote, :trailing_comma alias trailing_comma? trailing_comma def initialize( source, *args, quote: OPTIONS[:quote], trailing_comma: OPTIONS[:trailing_comma] ) super(*args) @source = source @stack = [] # Memoizing these values per formatter to make access faster. @quote = quote @trailing_comma = trailing_comma end def self.format(source, node) formatter = new(source, []) node.format(formatter) formatter.flush formatter.output.join end def format(node, stackable: true) stack << node if stackable doc = nil # If there are comments, then we're going to format them around the node # so that they get printed properly. if node.comments.any? leading, trailing = node.comments.partition(&:leading?) # Print all comments that were found before the node. leading.each do |comment| comment.format(self) breakable(force: true) end # If the node has a stree-ignore comment right before it, then we're # going to just print out the node as it was seen in the source. doc = if leading.last&.ignore? range = source[node.location.start_char...node.location.end_char] separator = -> { breakable(indent: false, force: true) } seplist(range.split(/\r?\n/, -1), separator) { |line| text(line) } else node.format(self) end # Print all comments that were found after the node. trailing.each do |comment| line_suffix(priority: COMMENT_PRIORITY) do comment.inline? ? text(" ") : breakable comment.format(self) break_parent end end else doc = node.format(self) end stack.pop if stackable doc end def format_each(nodes) nodes.each { |node| format(node) } end def parent stack[-2] end def parents stack[0...-1].reverse_each end end end