1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187
use crate::{
shape::Shape, CallParenType, CollapseSimpleStatement, Config, IndentType, LineEndings,
Range as FormatRange,
};
use full_moon::{
node::Node,
tokenizer::{Token, TokenType},
};
#[derive(Debug, PartialEq, Eq)]
pub enum FormatNode {
/// The formatting is completely blocked via an ignore comment, so this node should be skipped
Skip,
/// This node is outside the range, but we should still look to format internally to find items within the range
NotInRange,
/// There is no restriction, the node should be formatted normally
Normal,
}
#[derive(Debug, Clone, Copy)]
pub struct Context {
/// The configuration passed to the formatter
config: Config,
/// An optional range of values to format within the file.
range: Option<FormatRange>,
/// Whether the formatting has currently been disabled. This should occur when we see the relevant comment.
formatting_disabled: bool,
}
impl Context {
/// Creates a new Context, with the given configuration
pub fn new(config: Config, range: Option<FormatRange>) -> Self {
Self {
config,
range,
formatting_disabled: false,
}
}
/// Get the configuration for this context
pub fn config(&self) -> Config {
self.config
}
/// Determines whether we need to toggle whether formatting is enabled or disabled.
/// Formatting is toggled on/off whenever we see a `-- stylua: ignore start` or `-- stylua: ignore end` comment respectively.
// To preserve immutability of Context, we return a new Context with the `formatting_disabled` field toggled or left the same
// where necessary. Context is cheap so this is reasonable to do.
pub fn check_toggle_formatting(&self, node: &impl Node) -> Self {
// Load all the leading comments from the token
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();
// Load the current formatting disabled state
let mut formatting_disabled = self.formatting_disabled;
// Work through all the lines and update the state as necessary
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
}
}
/// Checks whether we should format the given node.
/// Firstly determine if formatting is disabled (due to the relevant comment)
/// If not, determine whether the node has an ignore comment present.
/// If not, checks whether the provided node is outside the formatting range.
/// If not, the node should be formatted.
pub fn should_format_node(&self, node: &impl Node) -> FormatNode {
// If formatting is disabled we should immediately bailed out.
if self.formatting_disabled {
return FormatNode::Skip;
}
// Check comments
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
}
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
}
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
)
}
}
/// Returns the relevant line ending string from the [`LineEndings`] enum
pub fn line_ending_character(line_endings: LineEndings) -> String {
match line_endings {
LineEndings::Unix => String::from("\n"),
LineEndings::Windows => String::from("\r\n"),
}
}
/// Creates a new Token containing whitespace for indents, used for trivia
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)
}
/// Creates indent trivia without including `ctx.indent_level()`.
/// You should pass the exact amount of indent you require to this function
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))
}
}
}
/// Creates a new Token containing new line whitespace, used for trivia
pub fn create_newline_trivia(ctx: &Context) -> Token {
Token::new(TokenType::Whitespace {
characters: line_ending_character(ctx.config().line_endings).into(),
})
}