use full_moon::ast::Ast;
use serde::Deserialize;
use thiserror::Error;
#[cfg(all(target_arch = "wasm32", feature = "wasm-bindgen"))]
use wasm_bindgen::prelude::*;
#[macro_use]
mod context;
mod formatters;
mod shape;
mod verify_ast;
#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize)]
#[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen"), wasm_bindgen)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
#[cfg_attr(feature = "fromstr", derive(strum::EnumString))]
pub enum IndentType {
Tabs,
Spaces,
}
impl Default for IndentType {
fn default() -> Self {
IndentType::Tabs
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize)]
#[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen"), wasm_bindgen)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
#[cfg_attr(feature = "fromstr", derive(strum::EnumString))]
pub enum LineEndings {
Unix,
Windows,
}
impl Default for LineEndings {
fn default() -> Self {
LineEndings::Unix
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize)]
#[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen"), wasm_bindgen)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
#[cfg_attr(feature = "fromstr", derive(strum::EnumString))]
pub enum QuoteStyle {
AutoPreferDouble,
AutoPreferSingle,
ForceDouble,
ForceSingle,
}
impl Default for QuoteStyle {
fn default() -> Self {
QuoteStyle::AutoPreferDouble
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize)]
#[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen"), wasm_bindgen)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
#[cfg_attr(feature = "fromstr", derive(strum::EnumString))]
pub enum CallParenType {
Always,
NoSingleString,
NoSingleTable,
None,
}
impl Default for CallParenType {
fn default() -> Self {
CallParenType::Always
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize)]
#[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen"), wasm_bindgen)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
#[cfg_attr(feature = "fromstr", derive(strum::EnumString))]
pub enum CollapseSimpleStatement {
Never,
FunctionOnly,
ConditionalOnly,
Always,
}
impl Default for CollapseSimpleStatement {
fn default() -> Self {
CollapseSimpleStatement::Never
}
}
#[derive(Debug, Copy, Clone, Deserialize)]
#[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen"), wasm_bindgen)]
pub struct Range {
start: Option<usize>,
end: Option<usize>,
}
#[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen"), wasm_bindgen)]
impl Range {
pub fn from_values(start: Option<usize>, end: Option<usize>) -> Self {
Self { start, end }
}
}
#[derive(Copy, Clone, Debug, Deserialize)]
#[serde(default, deny_unknown_fields)]
#[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen"), wasm_bindgen)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
pub struct Config {
column_width: usize,
line_endings: LineEndings,
indent_type: IndentType,
indent_width: usize,
quote_style: QuoteStyle,
no_call_parentheses: bool,
call_parentheses: CallParenType,
collapse_simple_statement: CollapseSimpleStatement,
}
#[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen"), wasm_bindgen)]
impl Config {
pub fn new() -> Self {
Config::default()
}
pub fn column_width(&self) -> usize {
self.column_width
}
pub fn line_endings(&self) -> LineEndings {
self.line_endings
}
pub fn indent_type(&self) -> IndentType {
self.indent_type
}
pub fn indent_width(&self) -> usize {
self.indent_width
}
pub fn quote_style(&self) -> QuoteStyle {
self.quote_style
}
pub fn call_parentheses(&self) -> CallParenType {
self.call_parentheses
}
pub fn collapse_simple_statement(&self) -> CollapseSimpleStatement {
self.collapse_simple_statement
}
pub fn with_column_width(self, column_width: usize) -> Self {
Self {
column_width,
..self
}
}
pub fn with_line_endings(self, line_endings: LineEndings) -> Self {
Self {
line_endings,
..self
}
}
pub fn with_indent_type(self, indent_type: IndentType) -> Self {
Self {
indent_type,
..self
}
}
pub fn with_indent_width(self, indent_width: usize) -> Self {
Self {
indent_width,
..self
}
}
pub fn with_quote_style(self, quote_style: QuoteStyle) -> Self {
Self {
quote_style,
..self
}
}
pub fn with_no_call_parentheses(self, no_call_parentheses: bool) -> Self {
Self {
no_call_parentheses,
..self
}
}
pub fn with_call_parentheses(self, call_parentheses: CallParenType) -> Self {
Self {
call_parentheses,
..self
}
}
pub fn with_collapse_simple_statement(
self,
collapse_simple_statement: CollapseSimpleStatement,
) -> Self {
Self {
collapse_simple_statement,
..self
}
}
}
impl Default for Config {
fn default() -> Self {
Self {
column_width: 120,
line_endings: LineEndings::default(),
indent_type: IndentType::default(),
indent_width: 4,
quote_style: QuoteStyle::default(),
no_call_parentheses: false,
call_parentheses: CallParenType::default(),
collapse_simple_statement: CollapseSimpleStatement::default(),
}
}
}
#[derive(Debug, Copy, Clone, Deserialize)]
#[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen"), wasm_bindgen)]
pub enum OutputVerification {
Full,
None,
}
#[derive(Clone, Debug, Error)]
pub enum Error {
#[error("error parsing: {0}")]
ParseError(full_moon::Error),
#[error("INTERNAL ERROR: Output AST generated a syntax error. Please report this at https://github.com/johnnymorganz/stylua/issues\n{0}")]
VerificationAstError(full_moon::Error),
#[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")]
VerificationAstDifference,
}
#[allow(clippy::result_large_err)]
pub fn format_ast(
input_ast: Ast,
config: Config,
range: Option<Range>,
verify_output: OutputVerification,
) -> Result<Ast, Error> {
let input_ast_for_verification = if let OutputVerification::Full = verify_output {
Some(input_ast.to_owned())
} else {
None
};
let code_formatter = formatters::CodeFormatter::new(config, range);
let ast = code_formatter.format(input_ast);
if let Some(input_ast) = input_ast_for_verification {
let output = full_moon::print(&ast);
let reparsed_output = match full_moon::parse(&output) {
Ok(ast) => ast,
Err(error) => {
return Err(Error::VerificationAstError(error));
}
};
let mut ast_verifier = verify_ast::AstVerifier::new();
if !ast_verifier.compare(input_ast, reparsed_output) {
return Err(Error::VerificationAstDifference);
}
}
Ok(ast)
}
#[allow(clippy::result_large_err)]
pub fn format_code(
code: &str,
config: Config,
range: Option<Range>,
verify_output: OutputVerification,
) -> Result<String, Error> {
let input_ast = match full_moon::parse(code) {
Ok(ast) => ast,
Err(error) => {
return Err(Error::ParseError(error));
}
};
let ast = format_ast(input_ast, config, range, verify_output)?;
let output = full_moon::print(&ast);
Ok(output)
}
#[cfg(all(target_arch = "wasm32", feature = "wasm-bindgen"))]
#[wasm_bindgen(js_name = formatCode)]
pub fn format_code_wasm(
code: &str,
config: Config,
range: Option<Range>,
verify_output: OutputVerification,
) -> Result<String, String> {
format_code(code, config, range, verify_output).map_err(|err| err.to_string())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_entry_point() {
let output = format_code(
"local x = 1",
Config::default(),
None,
OutputVerification::None,
)
.unwrap();
assert_eq!(output, "local x = 1\n");
}
#[test]
fn test_invalid_input() {
let output = format_code(
"local x = ",
Config::default(),
None,
OutputVerification::None,
);
assert!(matches!(output, Err(Error::ParseError(_))))
}
#[test]
fn test_with_ast_verification() {
let output = format_code(
"local x = 1",
Config::default(),
None,
OutputVerification::Full,
)
.unwrap();
assert_eq!(output, "local x = 1\n");
}
#[test]
fn test_config_column_width() {
let new_config = Config::new().with_column_width(80);
assert_eq!(new_config.column_width(), 80);
}
#[test]
fn test_config_line_endings() {
let new_config = Config::new().with_line_endings(LineEndings::Windows);
assert_eq!(new_config.line_endings(), LineEndings::Windows);
}
#[test]
fn test_config_indent_type() {
let new_config = Config::new().with_indent_type(IndentType::Spaces);
assert_eq!(new_config.indent_type(), IndentType::Spaces);
}
#[test]
fn test_config_indent_width() {
let new_config = Config::new().with_indent_width(2);
assert_eq!(new_config.indent_width(), 2);
}
#[test]
fn test_config_quote_style() {
let new_config = Config::new().with_quote_style(QuoteStyle::ForceDouble);
assert_eq!(new_config.quote_style(), QuoteStyle::ForceDouble);
}
#[test]
fn test_config_call_parentheses() {
let new_config = Config::new().with_call_parentheses(CallParenType::None);
assert_eq!(new_config.call_parentheses(), CallParenType::None);
}
}