1use crate::{
2 shape::Shape, BlockNewlineGaps, CallParenType, CollapseSimpleStatement, Config, IndentType,
3 LineEndings, Range as FormatRange, SpaceAfterFunctionNames,
4};
5use full_moon::{
6 node::Node,
7 tokenizer::{Token, TokenType},
8};
9
10#[derive(Debug, PartialEq, Eq)]
11pub enum FormatNode {
12 Skip,
14 NotInRange,
16 Normal,
18}
19
20#[derive(Debug, Clone, Copy)]
21pub struct Context {
22 config: Config,
24 range: Option<FormatRange>,
26 formatting_disabled: bool,
28}
29
30impl Context {
31 pub fn new(config: Config, range: Option<FormatRange>) -> Self {
33 Self {
34 config,
35 range,
36 formatting_disabled: false,
37 }
38 }
39
40 pub fn config(&self) -> Config {
42 self.config
43 }
44
45 pub fn check_toggle_formatting(&self, node: &impl Node) -> Self {
50 let leading_trivia = node.surrounding_trivia().0;
52 let comment_lines = leading_trivia
53 .iter()
54 .filter_map(|trivia| {
55 match trivia.token_type() {
56 TokenType::SingleLineComment { comment } => Some(comment),
57 TokenType::MultiLineComment { comment, .. } => Some(comment),
58 _ => None,
59 }
60 .map(|comment| comment.lines().map(|line| line.trim()))
61 })
62 .flatten();
63
64 let mut formatting_disabled = self.formatting_disabled;
66
67 for line in comment_lines {
69 if line == "stylua: ignore start" {
70 formatting_disabled = true;
71 } else if line == "stylua: ignore end" {
72 formatting_disabled = false;
73 }
74 }
75
76 Self {
77 formatting_disabled,
78 ..*self
79 }
80 }
81
82 pub fn should_format_node(&self, node: &impl Node) -> FormatNode {
88 if self.formatting_disabled {
90 return FormatNode::Skip;
91 }
92
93 let leading_trivia = node.surrounding_trivia().0;
95 for trivia in leading_trivia {
96 let comment_lines = match trivia.token_type() {
97 TokenType::SingleLineComment { comment } => comment,
98 TokenType::MultiLineComment { comment, .. } => comment,
99 _ => continue,
100 }
101 .lines()
102 .map(|line| line.trim());
103
104 for line in comment_lines {
105 if line == "stylua: ignore" {
106 return FormatNode::Skip;
107 }
108 }
109 }
110
111 if let Some(range) = self.range {
112 match (range.start, node.start_position()) {
113 (Some(start_bound), Some(node_start)) if node_start.bytes() < start_bound => {
114 return FormatNode::NotInRange
115 }
116 _ => (),
117 };
118
119 match (range.end, node.end_position()) {
120 (Some(end_bound), Some(node_end)) if node_end.bytes() > end_bound => {
121 return FormatNode::NotInRange
122 }
123 _ => (),
124 }
125 }
126
127 FormatNode::Normal
128 }
129
130 #[allow(deprecated)]
131 pub fn should_omit_string_parens(&self) -> bool {
132 self.config().no_call_parentheses
133 || self.config().call_parentheses == CallParenType::None
134 || self.config().call_parentheses == CallParenType::NoSingleString
135 }
136
137 #[allow(deprecated)]
138 pub fn should_omit_table_parens(&self) -> bool {
139 self.config().no_call_parentheses
140 || self.config().call_parentheses == CallParenType::None
141 || self.config().call_parentheses == CallParenType::NoSingleTable
142 }
143
144 pub fn should_collapse_simple_functions(&self) -> bool {
145 matches!(
146 self.config().collapse_simple_statement,
147 CollapseSimpleStatement::FunctionOnly | CollapseSimpleStatement::Always
148 )
149 }
150
151 pub fn should_collapse_simple_conditionals(&self) -> bool {
152 matches!(
153 self.config().collapse_simple_statement,
154 CollapseSimpleStatement::ConditionalOnly | CollapseSimpleStatement::Always
155 )
156 }
157
158 pub fn should_preserve_leading_block_newline_gaps(&self) -> bool {
159 matches!(self.config().block_newline_gaps, BlockNewlineGaps::Preserve)
160 }
161
162 pub fn should_preserve_trailing_block_newline_gaps(&self) -> bool {
163 matches!(self.config().block_newline_gaps, BlockNewlineGaps::Preserve)
164 }
165}
166
167pub fn line_ending_character(line_endings: LineEndings) -> String {
169 match line_endings {
170 LineEndings::Unix => String::from("\n"),
171 LineEndings::Windows => String::from("\r\n"),
172 }
173}
174
175pub fn create_indent_trivia(ctx: &Context, shape: Shape) -> Token {
177 let indent_level = shape.indent().block_indent() + shape.indent().additional_indent();
178 create_plain_indent_trivia(ctx, indent_level)
179}
180
181pub fn create_plain_indent_trivia(ctx: &Context, indent_level: usize) -> Token {
184 match ctx.config().indent_type {
185 IndentType::Tabs => Token::new(TokenType::tabs(indent_level)),
186 IndentType::Spaces => {
187 Token::new(TokenType::spaces(indent_level * ctx.config().indent_width))
188 }
189 }
190}
191
192pub fn create_function_definition_trivia(ctx: &Context) -> Token {
194 match ctx.config().space_after_function_names {
195 SpaceAfterFunctionNames::Always | SpaceAfterFunctionNames::Definitions => {
196 Token::new(TokenType::spaces(1))
197 }
198 SpaceAfterFunctionNames::Never | SpaceAfterFunctionNames::Calls => {
199 Token::new(TokenType::spaces(0))
200 }
201 }
202}
203
204pub fn create_function_call_trivia(ctx: &Context) -> Token {
206 match ctx.config().space_after_function_names {
207 SpaceAfterFunctionNames::Always | SpaceAfterFunctionNames::Calls => {
208 Token::new(TokenType::spaces(1))
209 }
210 SpaceAfterFunctionNames::Never | SpaceAfterFunctionNames::Definitions => {
211 Token::new(TokenType::spaces(0))
212 }
213 }
214}
215
216pub fn create_newline_trivia(ctx: &Context) -> Token {
218 Token::new(TokenType::Whitespace {
219 characters: line_ending_character(ctx.config().line_endings).into(),
220 })
221}