1use context::Context;
2use full_moon::ast::Ast;
3use serde::Deserialize;
4use thiserror::Error;
5#[cfg(all(target_arch = "wasm32", feature = "wasm-bindgen"))]
6use wasm_bindgen::prelude::*;
7
8#[macro_use]
9mod context;
10#[cfg(feature = "editorconfig")]
11pub mod editorconfig;
12mod formatters;
13mod shape;
14mod sort_requires;
15mod verify_ast;
16
17#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Deserialize)]
19#[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen"), wasm_bindgen)]
20#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
21#[cfg_attr(feature = "fromstr", derive(strum::EnumString))]
22pub enum LuaVersion {
23 #[default]
27 All,
28 Lua51,
30 #[cfg(feature = "lua52")]
32 Lua52,
33 #[cfg(feature = "lua53")]
35 Lua53,
36 #[cfg(feature = "lua54")]
38 Lua54,
39 #[cfg(feature = "luau")]
41 Luau,
42 #[cfg(feature = "luajit")]
44 LuaJIT,
45}
46
47impl From<LuaVersion> for full_moon::LuaVersion {
48 fn from(val: LuaVersion) -> Self {
49 match val {
50 LuaVersion::All => full_moon::LuaVersion::new(),
51 LuaVersion::Lua51 => full_moon::LuaVersion::lua51(),
52 #[cfg(feature = "lua52")]
53 LuaVersion::Lua52 => full_moon::LuaVersion::lua52(),
54 #[cfg(feature = "lua53")]
55 LuaVersion::Lua53 => full_moon::LuaVersion::lua53(),
56 #[cfg(feature = "lua54")]
57 LuaVersion::Lua54 => full_moon::LuaVersion::lua54(),
58 #[cfg(feature = "luau")]
59 LuaVersion::Luau => full_moon::LuaVersion::luau(),
60 #[cfg(feature = "luajit")]
61 LuaVersion::LuaJIT => full_moon::LuaVersion::luajit(),
62 }
63 }
64}
65
66#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Deserialize)]
68#[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen"), wasm_bindgen)]
69#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
70#[cfg_attr(feature = "fromstr", derive(strum::EnumString))]
71pub enum IndentType {
72 #[default]
74 Tabs,
75 Spaces,
77}
78
79#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Deserialize)]
81#[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen"), wasm_bindgen)]
82#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
83#[cfg_attr(feature = "fromstr", derive(strum::EnumString))]
84pub enum LineEndings {
85 #[default]
88 Unix,
89 Windows,
91}
92
93#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Deserialize)]
95#[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen"), wasm_bindgen)]
96#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
97#[cfg_attr(feature = "fromstr", derive(strum::EnumString))]
98pub enum QuoteStyle {
99 #[default]
101 AutoPreferDouble,
102 AutoPreferSingle,
104 ForceDouble,
106 ForceSingle,
108}
109
110#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Deserialize)]
112#[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen"), wasm_bindgen)]
113#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
114#[cfg_attr(feature = "fromstr", derive(strum::EnumString))]
115pub enum CallParenType {
116 #[default]
118 Always,
119 NoSingleString,
121 NoSingleTable,
123 None,
125 Input,
127}
128
129#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Deserialize)]
131#[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen"), wasm_bindgen)]
132#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
133#[cfg_attr(feature = "fromstr", derive(strum::EnumString))]
134pub enum CollapseSimpleStatement {
135 #[default]
137 Never,
138 FunctionOnly,
140 ConditionalOnly,
142 Always,
144}
145
146#[derive(Debug, Copy, Clone, Deserialize)]
150#[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen"), wasm_bindgen)]
151pub struct Range {
152 pub start: Option<usize>,
153 pub end: Option<usize>,
154}
155
156#[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen"), wasm_bindgen)]
157impl Range {
158 pub fn from_values(start: Option<usize>, end: Option<usize>) -> Self {
161 Self { start, end }
162 }
163}
164
165#[derive(Copy, Clone, Debug, Default, Deserialize)]
167#[serde(default, deny_unknown_fields)]
168#[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen"), wasm_bindgen)]
169#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
170pub struct SortRequiresConfig {
171 pub enabled: bool,
173}
174
175#[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen"), wasm_bindgen)]
176impl SortRequiresConfig {
177 pub fn new() -> Self {
178 SortRequiresConfig::default()
179 }
180 #[deprecated(since = "0.19.0", note = "access `.enabled` directly instead")]
181 #[cfg(not(all(target_arch = "wasm32", feature = "wasm-bindgen")))]
182 pub fn enabled(&self) -> bool {
183 self.enabled
184 }
185 #[deprecated(since = "0.19.0", note = "modify `.enabled` directly instead")]
186 pub fn set_enabled(&self, enabled: bool) -> Self {
187 Self { enabled }
188 }
189}
190
191#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Deserialize)]
193#[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen"), wasm_bindgen)]
194#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
195#[cfg_attr(feature = "fromstr", derive(strum::EnumString))]
196pub enum SpaceAfterFunctionNames {
197 #[default]
199 Never,
200 Definitions,
202 Calls,
204 Always,
206}
207
208#[derive(Copy, Clone, Debug, Deserialize)]
210#[serde(default, deny_unknown_fields)]
211#[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen"), wasm_bindgen)]
212#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
213pub struct Config {
214 pub syntax: LuaVersion,
216 pub column_width: usize,
220 pub line_endings: LineEndings,
222 pub indent_type: IndentType,
224 pub indent_width: usize,
228 pub quote_style: QuoteStyle,
230 #[deprecated(note = "use `call_parentheses` instead")]
233 pub no_call_parentheses: bool,
234 pub call_parentheses: CallParenType,
243 pub collapse_simple_statement: CollapseSimpleStatement,
247 pub sort_requires: SortRequiresConfig,
249 pub space_after_function_names: SpaceAfterFunctionNames,
255}
256
257#[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen"), wasm_bindgen)]
258impl Config {
259 pub fn new() -> Self {
261 Config::default()
262 }
263}
264
265impl Default for Config {
266 fn default() -> Self {
267 #[allow(deprecated)]
268 Self {
269 syntax: LuaVersion::default(),
270 column_width: 120,
271 line_endings: LineEndings::default(),
272 indent_type: IndentType::default(),
273 indent_width: 4,
274 quote_style: QuoteStyle::default(),
275 no_call_parentheses: false,
276 call_parentheses: CallParenType::default(),
277 collapse_simple_statement: CollapseSimpleStatement::default(),
278 sort_requires: SortRequiresConfig::default(),
279 space_after_function_names: SpaceAfterFunctionNames::default(),
280 }
281 }
282}
283
284#[derive(Debug, Copy, Clone, Deserialize)]
286#[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen"), wasm_bindgen)]
287pub enum OutputVerification {
288 Full,
290 None,
292}
293
294fn print_full_moon_error(error: &full_moon::Error) -> String {
295 match error {
296 full_moon::Error::AstError(ast_error) => format!(
297 "unexpected token `{}` ({}:{} to {}:{}), {}",
298 ast_error.token(),
299 ast_error.range().0.line(),
300 ast_error.range().0.character(),
301 ast_error.range().1.line(),
302 ast_error.range().1.character(),
303 ast_error.error_message()
304 ),
305 full_moon::Error::TokenizerError(tokenizer_error) => tokenizer_error.to_string(),
306 }
307}
308
309fn print_full_moon_errors(errors: &[full_moon::Error]) -> String {
310 if errors.len() == 1 {
311 print_full_moon_error(errors.first().unwrap())
312 } else {
313 errors
314 .iter()
315 .map(|err| "\n - ".to_string() + &print_full_moon_error(err))
316 .collect::<String>()
317 }
318}
319
320#[derive(Clone, Debug, Error)]
322pub enum Error {
323 #[error("error parsing: {}", print_full_moon_errors(.0))]
325 ParseError(Vec<full_moon::Error>),
326 #[error("INTERNAL ERROR: Output AST generated a syntax error. Please report this at https://github.com/johnnymorganz/stylua/issues: {}", print_full_moon_errors(.0))]
328 VerificationAstError(Vec<full_moon::Error>),
329 #[error("INTERNAL WARNING: Output AST may be different to input AST. Code correctness may have changed. Please examine the formatting diff and report any issues at https://github.com/johnnymorganz/stylua/issues")]
331 VerificationAstDifference,
332}
333
334#[allow(clippy::result_large_err)]
336pub fn format_ast(
337 input_ast: Ast,
338 config: Config,
339 range: Option<Range>,
340 verify_output: OutputVerification,
341) -> Result<Ast, Error> {
342 let input_ast_for_verification = if let OutputVerification::Full = verify_output {
344 Some(input_ast.to_owned())
345 } else {
346 None
347 };
348
349 let ctx = Context::new(config, range);
350
351 let input_ast = match config.sort_requires.enabled {
353 true => sort_requires::sort_requires(&ctx, input_ast),
354 false => input_ast,
355 };
356
357 let code_formatter = formatters::CodeFormatter::new(ctx);
358 let ast = code_formatter.format(input_ast);
359
360 if let Some(input_ast) = input_ast_for_verification {
362 let output = ast.to_string();
363 let reparsed_output =
364 match full_moon::parse_fallible(&output, config.syntax.into()).into_result() {
365 Ok(ast) => ast,
366 Err(error) => {
367 return Err(Error::VerificationAstError(error));
368 }
369 };
370
371 let mut ast_verifier = verify_ast::AstVerifier::new();
372 if !ast_verifier.compare(input_ast, reparsed_output) {
373 return Err(Error::VerificationAstDifference);
374 }
375 }
376
377 Ok(ast)
378}
379
380#[allow(clippy::result_large_err)]
382pub fn format_code(
383 code: &str,
384 config: Config,
385 range: Option<Range>,
386 verify_output: OutputVerification,
387) -> Result<String, Error> {
388 let input_ast = match full_moon::parse_fallible(code, config.syntax.into()).into_result() {
389 Ok(ast) => ast,
390 Err(error) => {
391 return Err(Error::ParseError(error));
392 }
393 };
394
395 let ast = format_ast(input_ast, config, range, verify_output)?;
396 let output = ast.to_string();
397
398 Ok(output)
399}
400
401#[cfg(all(target_arch = "wasm32", feature = "wasm-bindgen"))]
402#[wasm_bindgen(js_name = formatCode)]
403pub fn format_code_wasm(
404 code: &str,
405 config: Config,
406 range: Option<Range>,
407 verify_output: OutputVerification,
408) -> Result<String, String> {
409 format_code(code, config, range, verify_output).map_err(|err| err.to_string())
410}
411
412#[cfg(test)]
413mod tests {
414 use super::*;
415
416 #[test]
417 fn test_entry_point() {
418 let output = format_code(
419 "local x = 1",
420 Config::default(),
421 None,
422 OutputVerification::None,
423 )
424 .unwrap();
425 assert_eq!(output, "local x = 1\n");
426 }
427
428 #[test]
429 fn test_invalid_input() {
430 let output = format_code(
431 "local x = ",
432 Config::default(),
433 None,
434 OutputVerification::None,
435 );
436 assert!(matches!(output, Err(Error::ParseError(_))))
437 }
438
439 #[test]
440 fn test_with_ast_verification() {
441 let output = format_code(
442 "local x = 1",
443 Config::default(),
444 None,
445 OutputVerification::Full,
446 )
447 .unwrap();
448 assert_eq!(output, "local x = 1\n");
449 }
450}