use crate::{
shape::Shape, BlockNewlineGaps, CallParenType, CollapseSimpleStatement, Config, IndentType,
LineEndings, Range as FormatRange, SpaceAfterFunctionNames,
};
use full_moon::{
node::Node,
tokenizer::{Token, TokenType},
};
#[derive(Debug, PartialEq, Eq)]
pub enum FormatNode {
Skip,
NotInRange,
Normal,
}
#[derive(Debug, Clone, Copy)]
pub struct Context {
config: Config,
range: Option<FormatRange>,
formatting_disabled: bool,
}
impl Context {
pub fn new(config: Config, range: Option<FormatRange>) -> Self {
Self {
config,
range,
formatting_disabled: false,
}
}
pub fn config(&self) -> Config {
self.config
}
pub fn check_toggle_formatting(&self, node: &impl Node) -> Self {
let leading_trivia = node.surrounding_trivia().0;
let comment_lines = leading_trivia
.iter()
.filter_map(|trivia| {
match trivia.token_type() {
TokenType::SingleLineComment { comment } => Some(comment),
TokenType::MultiLineComment { comment, .. } => Some(comment),
_ => None,
}
.map(|comment| comment.lines().map(|line| line.trim()))
})
.flatten();
let mut formatting_disabled = self.formatting_disabled;
for line in comment_lines {
if line == "stylua: ignore start" {
formatting_disabled = true;
} else if line == "stylua: ignore end" {
formatting_disabled = false;
}
}
Self {
formatting_disabled,
..*self
}
}
pub fn should_format_node(&self, node: &impl Node) -> FormatNode {
if self.formatting_disabled {
return FormatNode::Skip;
}
let leading_trivia = node.surrounding_trivia().0;
for trivia in leading_trivia {
let comment_lines = match trivia.token_type() {
TokenType::SingleLineComment { comment } => comment,
TokenType::MultiLineComment { comment, .. } => comment,
_ => continue,
}
.lines()
.map(|line| line.trim());
for line in comment_lines {
if line == "stylua: ignore" {
return FormatNode::Skip;
}
}
}
if let Some(range) = self.range {
match (range.start, node.start_position()) {
(Some(start_bound), Some(node_start)) if node_start.bytes() < start_bound => {
return FormatNode::NotInRange
}
_ => (),
};
match (range.end, node.end_position()) {
(Some(end_bound), Some(node_end)) if node_end.bytes() > end_bound => {
return FormatNode::NotInRange
}
_ => (),
}
}
FormatNode::Normal
}
#[allow(deprecated)]
pub fn should_omit_string_parens(&self) -> bool {
self.config().no_call_parentheses
|| self.config().call_parentheses == CallParenType::None
|| self.config().call_parentheses == CallParenType::NoSingleString
}
#[allow(deprecated)]
pub fn should_omit_table_parens(&self) -> bool {
self.config().no_call_parentheses
|| self.config().call_parentheses == CallParenType::None
|| self.config().call_parentheses == CallParenType::NoSingleTable
}
pub fn should_collapse_simple_functions(&self) -> bool {
matches!(
self.config().collapse_simple_statement,
CollapseSimpleStatement::FunctionOnly | CollapseSimpleStatement::Always
)
}
pub fn should_collapse_simple_conditionals(&self) -> bool {
matches!(
self.config().collapse_simple_statement,
CollapseSimpleStatement::ConditionalOnly | CollapseSimpleStatement::Always
)
}
pub fn should_preserve_leading_block_newline_gaps(&self) -> bool {
matches!(self.config().block_newline_gaps, BlockNewlineGaps::Preserve)
}
pub fn should_preserve_trailing_block_newline_gaps(&self) -> bool {
matches!(self.config().block_newline_gaps, BlockNewlineGaps::Preserve)
}
}
pub fn line_ending_character(line_endings: LineEndings) -> String {
match line_endings {
LineEndings::Unix => String::from("\n"),
LineEndings::Windows => String::from("\r\n"),
}
}
pub fn create_indent_trivia(ctx: &Context, shape: Shape) -> Token {
let indent_level = shape.indent().block_indent() + shape.indent().additional_indent();
create_plain_indent_trivia(ctx, indent_level)
}
pub fn create_plain_indent_trivia(ctx: &Context, indent_level: usize) -> Token {
match ctx.config().indent_type {
IndentType::Tabs => Token::new(TokenType::tabs(indent_level)),
IndentType::Spaces => {
Token::new(TokenType::spaces(indent_level * ctx.config().indent_width))
}
}
}
pub fn create_function_definition_trivia(ctx: &Context) -> Token {
match ctx.config().space_after_function_names {
SpaceAfterFunctionNames::Always | SpaceAfterFunctionNames::Definitions => {
Token::new(TokenType::spaces(1))
}
SpaceAfterFunctionNames::Never | SpaceAfterFunctionNames::Calls => {
Token::new(TokenType::spaces(0))
}
}
}
pub fn create_function_call_trivia(ctx: &Context) -> Token {
match ctx.config().space_after_function_names {
SpaceAfterFunctionNames::Always | SpaceAfterFunctionNames::Calls => {
Token::new(TokenType::spaces(1))
}
SpaceAfterFunctionNames::Never | SpaceAfterFunctionNames::Definitions => {
Token::new(TokenType::spaces(0))
}
}
}
pub fn create_newline_trivia(ctx: &Context) -> Token {
Token::new(TokenType::Whitespace {
characters: line_ending_character(ctx.config().line_endings).into(),
})
}