stylua 0.1.0-alpha.3

A code formatter for Lua
Documentation
use full_moon::ast::{
    punctuated::{Pair, Punctuated},
    span::ContainedSpan,
    Call, Expression, FunctionArgs, FunctionBody, FunctionCall, FunctionDeclaration, FunctionName,
    LocalFunction, MethodCall, Parameter, Value,
};
use full_moon::tokenizer::{Symbol, Token, TokenReference, TokenType};
use std::borrow::Cow;
use std::boxed::Box;

use crate::formatters::{
    get_line_ending_character,
    trivia_formatter::{self, FormatTriviaType},
    CodeFormatter,
};

impl CodeFormatter {
    /// Formats an Anonymous Function
    /// This doesn't have its own struct, but it is part of Value::Function
    pub fn format_anonymous_function<'ast>(
        &mut self,
        function_token: Cow<'ast, TokenReference<'ast>>,
        function_body: FunctionBody<'ast>,
    ) -> (Cow<'ast, TokenReference<'ast>>, FunctionBody<'ast>) {
        let function_token_range = CodeFormatter::get_token_range(function_token.token());
        let additional_indent_level = self.get_range_indent_increase(function_token_range); //code_formatter.get_token_indent_increase(function_token.token());

        let function_token = crate::fmt_symbol!(self, function_token.into_owned(), "function");
        let function_body = self.format_function_body(function_body);

        // Need to insert any additional trivia, as it isn't being inserted elsewhere
        let parameters_parentheses = trivia_formatter::contained_span_add_trivia(
            function_body.parameters_parentheses().to_owned(),
            FormatTriviaType::NoChange,
            FormatTriviaType::Append(vec![self.create_newline_trivia()]),
        );

        let end_token = Cow::Owned(trivia_formatter::token_reference_add_trivia(
            function_body.end_token().to_owned(),
            FormatTriviaType::Append(vec![self.create_indent_trivia(additional_indent_level)]),
            FormatTriviaType::NoChange,
        ));

        (
            function_token,
            function_body
                .with_parameters_parentheses(parameters_parentheses)
                .with_end_token(end_token),
        )
    }

    /// Formats a Call node
    pub fn format_call<'ast>(&mut self, call: Call<'ast>) -> Call<'ast> {
        match call {
            Call::AnonymousCall(function_args) => {
                Call::AnonymousCall(self.format_function_args(function_args))
            }
            Call::MethodCall(method_call) => Call::MethodCall(self.format_method_call(method_call)),
        }
    }

    /// Formats a FunctionArgs node
    pub fn format_function_args<'ast>(
        &mut self,
        function_args: FunctionArgs<'ast>,
    ) -> FunctionArgs<'ast> {
        match function_args {
            FunctionArgs::Parentheses {
                parentheses,
                arguments,
            } => {
                let (start_parens, end_parens) = parentheses.tokens();
                // Find the range of the function arguments
                let function_call_range = (
                    start_parens.end_position().bytes(),
                    end_parens.start_position().bytes(),
                );
                let mut is_multiline = (function_call_range.1 - function_call_range.0) > 80; // TODO: Properly determine this arbitrary number, and see if other factors should come into play
                let mut current_arguments = arguments.iter().peekable();

                // If we only have one argument then we will not make it multi line. TODO: this is subject to change
                if arguments.len() == 1 {
                    is_multiline = false;
                }

                if is_multiline {
                    // TODO: This is similar to multiline in TableConstructor, can we resolve?
                    // Format start and end brace properly with correct trivia

                    // Calculate to see if the end parentheses requires any additional indentation
                    let end_parens_additional_indent_level = self.get_range_indent_increase((
                        end_parens.start_position().bytes(),
                        end_parens.end_position().bytes(),
                    ));
                    let end_parens_leading_trivia =
                        vec![self.create_indent_trivia(end_parens_additional_indent_level)];

                    // Add new_line trivia to start_brace
                    let start_parens_token = TokenReference::symbol(
                        &(String::from("(")
                            + &get_line_ending_character(&self.config.line_endings)),
                    )
                    .unwrap();
                    let end_parens_token = TokenReference::new(
                        end_parens_leading_trivia,
                        Token::new(TokenType::Symbol {
                            symbol: Symbol::RightParen,
                        }),
                        vec![],
                    );
                    let parentheses = ContainedSpan::new(
                        self.format_symbol(start_parens.to_owned(), start_parens_token),
                        self.format_symbol(end_parens.to_owned(), end_parens_token),
                    );

                    let mut formatted_arguments = Punctuated::new();

                    self.add_indent_range(function_call_range);

                    while let Some(argument) = current_arguments.next() {
                        let argument_range = CodeFormatter::get_range_in_expression(argument);
                        let additional_indent_level =
                            self.get_range_indent_increase(argument_range);

                        let formatted_argument = trivia_formatter::expression_add_leading_trivia(
                            self.format_expression(argument.to_owned()),
                            FormatTriviaType::Append(vec![
                                self.create_indent_trivia(additional_indent_level)
                            ]),
                        );

                        let punctuation = match current_arguments.peek() {
                            Some(_) => {
                                let symbol = String::from(",")
                                    + &get_line_ending_character(&self.config.line_endings);
                                Some(Cow::Owned(TokenReference::symbol(&symbol).unwrap()))
                            }
                            None => Some(Cow::Owned(TokenReference::new(
                                vec![],
                                Token::new(TokenType::Whitespace {
                                    characters: Cow::Owned(get_line_ending_character(
                                        &self.config.line_endings,
                                    )),
                                }),
                                vec![],
                            ))),
                        };

                        formatted_arguments.push(Pair::new(formatted_argument, punctuation))
                    }

                    FunctionArgs::Parentheses {
                        parentheses,
                        arguments: formatted_arguments,
                    }
                } else {
                    let formatted_arguments =
                        self.format_punctuated(arguments, &CodeFormatter::format_expression);

                    FunctionArgs::Parentheses {
                        parentheses: self.format_contained_span(parentheses),
                        arguments: formatted_arguments,
                    }
                }
            }

            FunctionArgs::String(token_reference) => {
                let mut arguments = Punctuated::new();
                arguments.push(Pair::new(
                    self.format_expression(Expression::Value {
                        value: Box::new(Value::String(token_reference)),
                        binop: None,
                        #[cfg(feature = "luau")]
                        as_assertion: None,
                    }),
                    None, // Only single argument, so no trailing comma
                ));

                FunctionArgs::Parentheses {
                    parentheses: ContainedSpan::new(
                        Cow::Owned(TokenReference::symbol("(").unwrap()),
                        Cow::Owned(TokenReference::symbol(")").unwrap()),
                    ),
                    arguments,
                }
            }

            FunctionArgs::TableConstructor(table_constructor) => {
                let mut arguments = Punctuated::new();
                arguments.push(Pair::new(
                    self.format_expression(Expression::Value {
                        value: Box::new(Value::TableConstructor(table_constructor)),
                        binop: None,
                        #[cfg(feature = "luau")]
                        as_assertion: None,
                    }),
                    None,
                ));

                FunctionArgs::Parentheses {
                    parentheses: ContainedSpan::new(
                        Cow::Owned(TokenReference::symbol("(").unwrap()),
                        Cow::Owned(TokenReference::symbol(")").unwrap()),
                    ),
                    arguments,
                }
            }
        }
    }

    /// Formats a FunctionBody node
    pub fn format_function_body<'ast>(
        &mut self,
        function_body: FunctionBody<'ast>,
    ) -> FunctionBody<'ast> {
        let parameters_parentheses =
            self.format_contained_span(function_body.parameters_parentheses().to_owned());
        let formatted_parameters = self.format_parameters(function_body.to_owned());

        #[cfg(feature = "luau")]
        let mut type_specifiers;
        #[cfg(feature = "luau")]
        let return_type;

        #[cfg(feature = "luau")]
        {
            type_specifiers = Vec::new();
            for specifier in function_body.type_specifiers() {
                let formatted_specifier = match specifier {
                    Some(specifier) => Some(self.format_type_specifier(specifier.to_owned())),
                    None => None,
                };
                type_specifiers.push(formatted_specifier);
            }

            return_type = match function_body.return_type() {
                Some(return_type) => Some(self.format_type_specifier(return_type.to_owned())),
                None => None,
            };
        }

        let end_token = self.format_end_token(function_body.end_token().to_owned());

        #[cfg(feature = "luau")]
        let function_body = function_body
            .with_type_specifiers(type_specifiers)
            .with_return_type(return_type);

        function_body
            .with_parameters_parentheses(parameters_parentheses)
            .with_parameters(formatted_parameters)
            .with_end_token(end_token)
    }

    /// Formats a FunctionCall node
    pub fn format_function_call<'ast>(
        &mut self,
        function_call: FunctionCall<'ast>,
    ) -> FunctionCall<'ast> {
        let formatted_prefix = self.format_prefix(function_call.prefix().to_owned());
        let formatted_suffixes = function_call
            .iter_suffixes()
            .map(|x| self.format_suffix(x.to_owned()))
            .collect();
        function_call
            .with_prefix(formatted_prefix)
            .with_suffixes(formatted_suffixes)
    }

    /// Formats a FunctionName node
    pub fn format_function_name<'ast>(
        &mut self,
        function_name: FunctionName<'ast>,
    ) -> FunctionName<'ast> {
        // TODO: This is based off formatters::format_punctuated - can we merge them into one?
        let mut formatted_names = Punctuated::new();
        for pair in function_name.names().to_owned().into_pairs() {
            // Format Punctuation
            match pair {
                Pair::Punctuated(value, punctuation) => {
                    let formatted_punctuation = self.format_symbol(
                        punctuation.into_owned(),
                        TokenReference::symbol(".").unwrap(),
                    );
                    let formatted_value = self.format_token_reference(value);
                    formatted_names.push(Pair::new(formatted_value, Some(formatted_punctuation)));
                }
                Pair::End(value) => {
                    let formatted_value = self.format_token_reference(value);
                    formatted_names.push(Pair::new(formatted_value, None));
                }
            }
        }

        let mut formatted_method: Option<(
            Cow<'ast, TokenReference<'ast>>,
            Cow<'ast, TokenReference<'ast>>,
        )> = None;

        if let Some(method_colon) = function_name.method_colon() {
            if let Some(token_reference) = function_name.method_name() {
                formatted_method = Some((
                    crate::fmt_symbol!(self, method_colon.to_owned(), ":"),
                    Cow::Owned(self.format_plain_token_reference(token_reference.to_owned())),
                ));
            }
        };

        function_name
            .with_names(formatted_names)
            .with_method(formatted_method)
    }

    /// Formats a FunctionDeclaration node
    pub fn format_function_declaration<'ast>(
        &mut self,
        function_declaration: FunctionDeclaration<'ast>,
    ) -> FunctionDeclaration<'ast> {
        let function_token = crate::fmt_symbol!(
            self,
            function_declaration.function_token().to_owned(),
            "function "
        );
        let formatted_function_name =
            self.format_function_name(function_declaration.name().to_owned());
        let formatted_function_body =
            self.format_function_body(function_declaration.body().to_owned());

        function_declaration
            .with_function_token(function_token)
            .with_name(formatted_function_name)
            .with_body(formatted_function_body)
    }

    /// Formats a LocalFunction node
    pub fn format_local_function<'ast>(
        &mut self,
        local_function: LocalFunction<'ast>,
    ) -> LocalFunction<'ast> {
        let local_token =
            crate::fmt_symbol!(self, local_function.local_token().to_owned(), "local ");
        let function_token = crate::fmt_symbol!(
            self,
            local_function.function_token().to_owned(),
            "function "
        );
        let formatted_name =
            Cow::Owned(self.format_plain_token_reference(local_function.name().to_owned()));
        let formatted_function_body =
            self.format_function_body(local_function.func_body().to_owned());

        local_function
            .with_local_token(local_token)
            .with_function_token(function_token)
            .with_name(formatted_name)
            .with_func_body(formatted_function_body)
    }

    /// Formats a MethodCall node
    pub fn format_method_call<'ast>(&mut self, method_call: MethodCall<'ast>) -> MethodCall<'ast> {
        let formatted_colon_token =
            self.format_plain_token_reference(method_call.colon_token().to_owned());
        let formatted_name = self.format_plain_token_reference(method_call.name().to_owned());
        let formatted_function_args = self.format_function_args(method_call.args().to_owned());
        method_call
            .with_colon_token(Cow::Owned(formatted_colon_token))
            .with_name(Cow::Owned(formatted_name))
            .with_args(formatted_function_args)
    }

    /// Formats a single Parameter node
    pub fn format_parameter<'ast>(&mut self, parameter: Parameter<'ast>) -> Parameter<'ast> {
        match parameter {
            Parameter::Ellipse(token) => {
                Parameter::Ellipse(crate::fmt_symbol!(self, token.into_owned(), "..."))
            }
            Parameter::Name(token_reference) => {
                Parameter::Name(self.format_token_reference(token_reference))
            }
        }
    }

    /// Utilises the FunctionBody iterator to format a list of Parameter nodes
    fn format_parameters<'ast>(
        &mut self,
        function_body: FunctionBody<'ast>,
    ) -> Punctuated<'ast, Parameter<'ast>> {
        let mut formatted_parameters = Punctuated::new();
        let mut parameters_iterator = function_body.parameters().iter().peekable();
        while let Some(parameter) = parameters_iterator.next() {
            let formatted_parameter = self.format_parameter(parameter.to_owned());
            let mut punctuation = None;

            if parameters_iterator.peek().is_some() {
                punctuation = Some(Cow::Owned(TokenReference::symbol(", ").unwrap()));
            }

            formatted_parameters.push(Pair::new(formatted_parameter, punctuation));
        }
        formatted_parameters
    }
}