From f3255f75cc1dc5b213fcff90bff9abbec74df808 Mon Sep 17 00:00:00 2001 From: Philipp Krones Date: Tue, 12 Aug 2025 15:13:17 +0200 Subject: [PATCH] Move config file resolver to library When using stylua as a library, the only formatting interface it provides are the `format_code` and `format_ast` functions, which both already expect a config file. To replicate the config file discovery that the stylua CLI implements, one would have to copy this. My motivation: For one of my projects I'm creating bindings for other languages to the stylua_lib. There it would be really useful, if I could use the config file discovery. --- src/cli/config.rs | 347 ----------------------------------------- src/cli/main.rs | 22 ++- src/cli/opt.rs | 30 +++- src/config_resolver.rs | 299 +++++++++++++++++++++++++++++++++++ src/lib.rs | 4 + 5 files changed, 347 insertions(+), 355 deletions(-) delete mode 100644 src/cli/config.rs create mode 100644 src/config_resolver.rs diff --git a/src/cli/config.rs b/src/cli/config.rs deleted file mode 100644 index 1a76541e..00000000 --- a/src/cli/config.rs +++ /dev/null @@ -1,347 +0,0 @@ -use crate::opt::Opt; -use anyhow::{Context, Result}; -use log::*; -use std::collections::HashMap; -use std::env; -use std::fs; -use std::path::{Path, PathBuf}; -use stylua_lib::Config; -use stylua_lib::SortRequiresConfig; - -#[cfg(feature = "editorconfig")] -use stylua_lib::editorconfig; - -static CONFIG_FILE_NAME: [&str; 2] = ["stylua.toml", ".stylua.toml"]; - -fn read_config_file(path: &Path) -> Result { - let contents = fs::read_to_string(path).context("Failed to read config file")?; - let config = toml::from_str(&contents).context("Config file not in correct format")?; - - Ok(config) -} - -fn read_and_apply_overrides(path: &Path, opt: &Opt) -> Result { - read_config_file(path).map(|config| load_overrides(config, opt)) -} - -pub struct ConfigResolver<'a> { - config_cache: HashMap>, - forced_configuration: Option, - current_directory: PathBuf, - default_configuration: Config, - opt: &'a Opt, -} - -impl ConfigResolver<'_> { - pub fn new(opt: &Opt) -> Result { - let forced_configuration = opt - .config_path - .as_ref() - .map(|config_path| { - debug!( - "config: explicit config path provided at {}", - config_path.display() - ); - read_and_apply_overrides(config_path, opt) - }) - .transpose()?; - - Ok(ConfigResolver { - config_cache: HashMap::new(), - forced_configuration, - current_directory: env::current_dir().context("Could not find current directory")?, - default_configuration: load_overrides(Config::default(), opt), - opt, - }) - } - - /// Returns the root used when searching for configuration - /// If `--search-parent-directories`, then there is no root, and we keep searching - /// Else, the root is the current working directory, and we do not search higher than the cwd - fn get_configuration_search_root(&self) -> Option { - match self.opt.search_parent_directories { - true => None, - false => Some(self.current_directory.to_path_buf()), - } - } - - pub fn load_configuration(&mut self, path: &Path) -> Result { - if let Some(configuration) = self.forced_configuration { - return Ok(configuration); - } - - let root = self.get_configuration_search_root(); - - let absolute_path = self.current_directory.join(path); - let parent_path = &absolute_path - .parent() - .with_context(|| format!("no parent directory found for {}", path.display()))?; - - match self.find_config_file(parent_path, root)? { - Some(config) => Ok(config), - None => { - #[cfg(feature = "editorconfig")] - if self.opt.no_editorconfig { - Ok(self.default_configuration) - } else { - editorconfig::parse(self.default_configuration, path) - .context("could not parse editorconfig") - } - #[cfg(not(feature = "editorconfig"))] - Ok(self.default_configuration) - } - } - } - - pub fn load_configuration_for_stdin(&mut self) -> Result { - if let Some(configuration) = self.forced_configuration { - return Ok(configuration); - } - - let root = self.get_configuration_search_root(); - let my_current_directory = self.current_directory.to_owned(); - - match &self.opt.stdin_filepath { - Some(filepath) => self.load_configuration(filepath), - None => match self.find_config_file(&my_current_directory, root)? { - Some(config) => Ok(config), - None => { - #[cfg(feature = "editorconfig")] - if self.opt.no_editorconfig { - Ok(self.default_configuration) - } else { - editorconfig::parse(self.default_configuration, &PathBuf::from("*.lua")) - .context("could not parse editorconfig") - } - #[cfg(not(feature = "editorconfig"))] - Ok(self.default_configuration) - } - }, - } - } - - fn lookup_config_file_in_directory(&self, directory: &Path) -> Result> { - debug!("config: looking for config in {}", directory.display()); - let config_file = find_toml_file(directory); - match config_file { - Some(file_path) => { - debug!("config: found config at {}", file_path.display()); - let config = read_and_apply_overrides(&file_path, self.opt)?; - debug!("config: {:#?}", config); - Ok(Some(config)) - } - None => Ok(None), - } - } - - /// Looks for a configuration file in the directory provided - /// Keep searching recursively upwards until we hit the root (if provided), then stop - /// When `--search-parent-directories` is enabled, root = None, else root = Some(cwd) - fn find_config_file( - &mut self, - directory: &Path, - root: Option, - ) -> Result> { - if let Some(config) = self.config_cache.get(directory) { - return Ok(*config); - } - - let resolved_configuration = match self.lookup_config_file_in_directory(directory)? { - Some(config) => Some(config), - None => { - let parent_directory = directory.parent(); - let should_stop = Some(directory) == root.as_deref() || parent_directory.is_none(); - - if should_stop { - debug!("config: no configuration file found"); - if self.opt.search_parent_directories { - if let Some(config) = self.search_config_locations()? { - return Ok(Some(config)); - } - } - - debug!("config: falling back to default config"); - None - } else { - self.find_config_file(parent_directory.unwrap(), root)? - } - } - }; - - self.config_cache - .insert(directory.to_path_buf(), resolved_configuration); - Ok(resolved_configuration) - } - - /// Looks for a configuration file at either `$XDG_CONFIG_HOME`, `$XDG_CONFIG_HOME/stylua`, `$HOME/.config` or `$HOME/.config/stylua` - fn search_config_locations(&self) -> Result> { - // Look in `$XDG_CONFIG_HOME` - if let Ok(xdg_config) = std::env::var("XDG_CONFIG_HOME") { - let xdg_config_path = Path::new(&xdg_config); - if xdg_config_path.exists() { - debug!("config: looking in $XDG_CONFIG_HOME"); - - if let Some(config) = self.lookup_config_file_in_directory(xdg_config_path)? { - return Ok(Some(config)); - } - - debug!("config: looking in $XDG_CONFIG_HOME/stylua"); - let xdg_config_path = xdg_config_path.join("stylua"); - if xdg_config_path.exists() { - if let Some(config) = self.lookup_config_file_in_directory(&xdg_config_path)? { - return Ok(Some(config)); - } - } - } - } - - // Look in `$HOME/.config` - if let Ok(home) = std::env::var("HOME") { - let home_config_path = Path::new(&home).join(".config"); - - if home_config_path.exists() { - debug!("config: looking in $HOME/.config"); - - if let Some(config) = self.lookup_config_file_in_directory(&home_config_path)? { - return Ok(Some(config)); - } - - debug!("config: looking in $HOME/.config/stylua"); - let home_config_path = home_config_path.join("stylua"); - if home_config_path.exists() { - if let Some(config) = self.lookup_config_file_in_directory(&home_config_path)? { - return Ok(Some(config)); - } - } - } - } - - Ok(None) - } -} - -/// Searches the directory for the configuration toml file (i.e. `stylua.toml` or `.stylua.toml`) -fn find_toml_file(directory: &Path) -> Option { - for name in &CONFIG_FILE_NAME { - let file_path = directory.join(name); - if file_path.exists() { - return Some(file_path); - } - } - - None -} - -pub fn find_ignore_file_path(mut directory: PathBuf, recursive: bool) -> Option { - debug!("config: looking for ignore file in {}", directory.display()); - let file_path = directory.join(".styluaignore"); - if file_path.is_file() { - debug!("config: resolved ignore file at {}", file_path.display()); - Some(file_path) - } else if recursive && directory.pop() { - find_ignore_file_path(directory, recursive) - } else { - None - } -} - -/// Handles any overrides provided by command line options -fn load_overrides(config: Config, opt: &Opt) -> Config { - let mut new_config = config; - - if let Some(syntax) = opt.format_opts.syntax { - new_config.syntax = syntax.into(); - }; - if let Some(column_width) = opt.format_opts.column_width { - new_config.column_width = column_width; - }; - if let Some(line_endings) = opt.format_opts.line_endings { - new_config.line_endings = line_endings.into(); - }; - if let Some(indent_type) = opt.format_opts.indent_type { - new_config.indent_type = indent_type.into(); - }; - if let Some(indent_width) = opt.format_opts.indent_width { - new_config.indent_width = indent_width; - }; - if let Some(quote_style) = opt.format_opts.quote_style { - new_config.quote_style = quote_style.into(); - }; - if let Some(call_parentheses) = opt.format_opts.call_parentheses { - new_config.call_parentheses = call_parentheses.into(); - }; - if let Some(space_after_function_names) = opt.format_opts.space_after_function_names { - new_config.space_after_function_names = space_after_function_names.into(); - }; - if let Some(collapse_simple_statement) = opt.format_opts.collapse_simple_statement { - new_config.collapse_simple_statement = collapse_simple_statement.into(); - } - if opt.format_opts.sort_requires { - new_config.sort_requires = SortRequiresConfig { enabled: true } - } - - new_config -} - -#[cfg(test)] -mod tests { - use super::*; - use clap::StructOpt; - use stylua_lib::{CallParenType, IndentType, LineEndings, LuaVersion, QuoteStyle}; - - #[test] - fn test_override_syntax() { - let override_opt = Opt::parse_from(vec!["BINARY_NAME", "--syntax", "Lua51"]); - let default_config = Config::new(); - let config = load_overrides(default_config, &override_opt); - assert_eq!(config.syntax, LuaVersion::Lua51); - } - - #[test] - fn test_override_column_width() { - let override_opt = Opt::parse_from(vec!["BINARY_NAME", "--column-width", "80"]); - let default_config = Config::new(); - let config = load_overrides(default_config, &override_opt); - assert_eq!(config.column_width, 80); - } - - #[test] - fn test_override_line_endings() { - let override_opt = Opt::parse_from(vec!["BINARY_NAME", "--line-endings", "Windows"]); - let default_config = Config::new(); - let config = load_overrides(default_config, &override_opt); - assert_eq!(config.line_endings, LineEndings::Windows); - } - - #[test] - fn test_override_indent_type() { - let override_opt = Opt::parse_from(vec!["BINARY_NAME", "--indent-type", "Spaces"]); - let default_config = Config::new(); - let config = load_overrides(default_config, &override_opt); - assert_eq!(config.indent_type, IndentType::Spaces); - } - - #[test] - fn test_override_indent_width() { - let override_opt = Opt::parse_from(vec!["BINARY_NAME", "--indent-width", "2"]); - let default_config = Config::new(); - let config = load_overrides(default_config, &override_opt); - assert_eq!(config.indent_width, 2); - } - - #[test] - fn test_override_quote_style() { - let override_opt = Opt::parse_from(vec!["BINARY_NAME", "--quote-style", "ForceSingle"]); - let default_config = Config::new(); - let config = load_overrides(default_config, &override_opt); - assert_eq!(config.quote_style, QuoteStyle::ForceSingle); - } - - #[test] - fn test_override_call_parentheses() { - let override_opt = Opt::parse_from(vec!["BINARY_NAME", "--call-parentheses", "None"]); - let default_config = Config::new(); - let config = load_overrides(default_config, &override_opt); - assert_eq!(config.call_parentheses, CallParenType::None); - } -} diff --git a/src/cli/main.rs b/src/cli/main.rs index 16eead5c..3df5c2bf 100644 --- a/src/cli/main.rs +++ b/src/cli/main.rs @@ -7,18 +7,15 @@ use serde_json::json; use std::collections::HashSet; use std::fs; use std::io::{stderr, stdin, stdout, Read, Write}; -use std::path::Path; +use std::path::{Path, PathBuf}; use std::sync::atomic::{AtomicI32, AtomicU32, Ordering}; use std::sync::Arc; use std::time::Instant; use thiserror::Error; use threadpool::ThreadPool; -use stylua_lib::{format_code, Config, OutputVerification, Range}; +use stylua_lib::{config_resolver, format_code, Config, OutputVerification, Range}; -use crate::config::find_ignore_file_path; - -mod config; mod opt; mod output_diff; @@ -204,6 +201,19 @@ fn format_string( } } +fn find_ignore_file_path(mut directory: PathBuf, recursive: bool) -> Option { + debug!("config: looking for ignore file in {}", directory.display()); + let file_path = directory.join(".styluaignore"); + if file_path.is_file() { + debug!("config: resolved ignore file at {}", file_path.display()); + Some(file_path) + } else if recursive && directory.pop() { + find_ignore_file_path(directory, recursive) + } else { + None + } +} + fn get_ignore( directory: &Path, search_parent_directories: bool, @@ -281,7 +291,7 @@ fn format(opt: opt::Opt) -> Result { // Load the configuration let opt_for_config_resolver = opt.clone(); - let mut config_resolver = config::ConfigResolver::new(&opt_for_config_resolver)?; + let config_resolver = config_resolver::ConfigResolver::new(opt_for_config_resolver.into())?; // Create range if provided let range = if opt.range_start.is_some() || opt.range_end.is_some() { diff --git a/src/cli/opt.rs b/src/cli/opt.rs index 2e848160..a4f7fc22 100644 --- a/src/cli/opt.rs +++ b/src/cli/opt.rs @@ -1,8 +1,8 @@ use clap::{ArgEnum, StructOpt}; use std::path::PathBuf; use stylua_lib::{ - CallParenType, CollapseSimpleStatement, IndentType, LineEndings, LuaVersion, QuoteStyle, - SpaceAfterFunctionNames, + config_resolver::CliConfig, CallParenType, CollapseSimpleStatement, IndentType, LineEndings, + LuaVersion, QuoteStyle, SpaceAfterFunctionNames, }; lazy_static::lazy_static! { @@ -110,6 +110,32 @@ pub struct Opt { pub respect_ignores: bool, } +impl From for CliConfig { + fn from(value: Opt) -> Self { + CliConfig { + forced_config_path: value.config_path, + stdin_filepath: value.stdin_filepath, + search_parent_directories: value.search_parent_directories, + #[cfg(feature = "editorconfig")] + no_editorconfig: value.no_editorconfig, + + syntax: value.format_opts.syntax.map(Into::into), + column_width: value.format_opts.column_width, + line_endings: value.format_opts.line_endings.map(Into::into), + indent_type: value.format_opts.indent_type.map(Into::into), + indent_width: value.format_opts.indent_width, + quote_style: value.format_opts.quote_style.map(Into::into), + call_parentheses: value.format_opts.call_parentheses.map(Into::into), + collapse_simple_statement: value.format_opts.collapse_simple_statement.map(Into::into), + sort_requires: value.format_opts.sort_requires, + space_after_function_names: value + .format_opts + .space_after_function_names + .map(Into::into), + } + } +} + #[derive(ArgEnum, Clone, Copy, Debug, PartialEq, Eq)] #[clap(rename_all = "PascalCase")] pub enum Color { diff --git a/src/config_resolver.rs b/src/config_resolver.rs new file mode 100644 index 00000000..d89bcc36 --- /dev/null +++ b/src/config_resolver.rs @@ -0,0 +1,299 @@ +use crate::{ + CallParenType, CollapseSimpleStatement, Config, Error, IndentType, LineEndings, LuaVersion, + QuoteStyle, SortRequiresConfig, SpaceAfterFunctionNames, +}; +use log::debug; +use std::env; +use std::fs; +use std::path::{Path, PathBuf}; + +#[cfg(feature = "editorconfig")] +use crate::editorconfig; + +static CONFIG_FILE_NAME: [&str; 2] = ["stylua.toml", ".stylua.toml"]; + +fn read_config_file(path: &Path) -> Result { + let contents = fs::read_to_string(path) + .map_err(|_| Error::ConfigResolutionError("Failed to read config file".to_string()))?; + let config = toml::from_str(&contents).map_err(|_| { + Error::ConfigResolutionError("Config file not in correct format".to_string()) + })?; + + Ok(config) +} + +/// Searches the directory for the configuration toml file (i.e. `stylua.toml` or `.stylua.toml`) +fn find_toml_file(directory: &Path) -> Option { + for name in &CONFIG_FILE_NAME { + let file_path = directory.join(name); + if file_path.exists() { + return Some(file_path); + } + } + + None +} + +/// Those are configuration values forced by CLI arguments. +#[derive(Default, Debug, Clone)] +pub struct CliConfig { + pub forced_config_path: Option, + pub stdin_filepath: Option, + pub search_parent_directories: bool, + #[cfg(feature = "editorconfig")] + pub no_editorconfig: bool, + + // Formatting options + /// The type of Lua syntax to parse + pub syntax: Option, + /// The column width to use to attempt to wrap lines. + pub column_width: Option, + /// The type of line endings to use. + pub line_endings: Option, + /// The type of indents to use. + pub indent_type: Option, + /// The width of a single indentation level. + pub indent_width: Option, + /// The style of quotes to use in string literals. + pub quote_style: Option, + /// Specify whether to apply parentheses on function calls with single string or table arg. + pub call_parentheses: Option, + /// Specify whether to collapse simple statements. + pub collapse_simple_statement: Option, + /// Enable requires sorting + pub sort_requires: bool, + pub space_after_function_names: Option, +} + +impl CliConfig { + fn read_and_apply_overrides(&self, path: &Path) -> Result { + Ok(self.apply_overrides(&read_config_file(path)?)) + } + + fn apply_overrides(&self, config: &Config) -> Config { + let mut new_config = *config; + + if let Some(syntax) = self.syntax { + new_config.syntax = syntax; + } + if let Some(column_width) = self.column_width { + new_config.column_width = column_width; + } + if let Some(line_endings) = self.line_endings { + new_config.line_endings = line_endings; + } + if let Some(indent_type) = self.indent_type { + new_config.indent_type = indent_type; + } + if let Some(indent_width) = self.indent_width { + new_config.indent_width = indent_width; + } + if let Some(quote_style) = self.quote_style { + new_config.quote_style = quote_style; + } + if let Some(call_parentheses) = self.call_parentheses { + new_config.call_parentheses = call_parentheses; + } + if let Some(collapse_simple_statement) = self.collapse_simple_statement { + new_config.collapse_simple_statement = collapse_simple_statement; + } + if self.sort_requires { + new_config.sort_requires = SortRequiresConfig { enabled: true } + } + if let Some(space_after_function_names) = self.space_after_function_names { + new_config.space_after_function_names = space_after_function_names; + }; + + new_config + } +} + +pub struct ConfigResolver { + forced_configuration: Option, + current_directory: PathBuf, + default_configuration: Config, + cli_config: CliConfig, +} + +impl ConfigResolver { + pub fn new(cli_config: CliConfig) -> Result { + Ok(ConfigResolver { + forced_configuration: cli_config + .forced_config_path + .as_ref() + .map(|path| cli_config.read_and_apply_overrides(path)) + .transpose()?, + current_directory: env::current_dir().map_err(|_| { + Error::ConfigResolutionError("Could not find current directory".to_string()) + })?, + default_configuration: cli_config.apply_overrides(&Config::default()), + cli_config, + }) + } + + /// Returns the root used when searching for configuration + /// If `--search-parent-directories`, then there is no root, and we keep searching + /// Else, the root is the current working directory, and we do not search higher than the cwd + fn get_configuration_search_root(&self) -> Option { + match self.cli_config.search_parent_directories { + true => None, + false => Some(self.current_directory.to_path_buf()), + } + } + + pub fn load_configuration(&self, path: &Path) -> Result { + if let Some(configuration) = self.forced_configuration { + return Ok(configuration); + } + + let root = self.get_configuration_search_root(); + + let absolute_path = self.current_directory.join(path); + let parent_path = &absolute_path.parent().ok_or_else(|| { + Error::ConfigResolutionError(format!( + "no parent directory found for {}", + path.display() + )) + })?; + + match self.find_config_file(parent_path, root)? { + Some(config) => Ok(config), + None => { + #[cfg(feature = "editorconfig")] + if self.cli_config.no_editorconfig { + Ok(self.default_configuration) + } else { + editorconfig::parse(self.default_configuration, path).map_err(|_| { + Error::ConfigResolutionError("could not parse editorconfig".to_string()) + }) + } + #[cfg(not(feature = "editorconfig"))] + Ok(self.default_configuration) + } + } + } + + pub fn load_configuration_for_stdin(&self) -> Result { + if let Some(configuration) = self.forced_configuration { + return Ok(configuration); + } + + let root = self.get_configuration_search_root(); + let my_current_directory = self.current_directory.to_owned(); + + match self.cli_config.stdin_filepath.as_ref() { + Some(filepath) => self.load_configuration(filepath), + None => match self.find_config_file(&my_current_directory, root)? { + Some(config) => Ok(config), + None => { + #[cfg(feature = "editorconfig")] + if self.cli_config.no_editorconfig { + Ok(self.default_configuration) + } else { + editorconfig::parse(self.default_configuration, &PathBuf::from("*.lua")) + .map_err(|_| { + Error::ConfigResolutionError( + "could not parse editorconfig".to_string(), + ) + }) + } + #[cfg(not(feature = "editorconfig"))] + Ok(self.default_configuration) + } + }, + } + } + + fn lookup_config_file_in_directory(&self, directory: &Path) -> Result, Error> { + debug!("config: looking for config in {}", directory.display()); + let config_file = find_toml_file(directory); + match config_file { + Some(file_path) => { + debug!("config: found config at {}", file_path.display()); + let config = self.cli_config.read_and_apply_overrides(&file_path)?; + debug!("config: {:#?}", config); + Ok(Some(config)) + } + None => Ok(None), + } + } + + /// Looks for a configuration file in the directory provided + /// Keep searching recursively upwards until we hit the root (if provided), then stop + /// When `--search-parent-directories` is enabled, root = None, else root = Some(cwd) + fn find_config_file( + &self, + directory: &Path, + root: Option, + ) -> Result, Error> { + let resolved_configuration = match self.lookup_config_file_in_directory(directory)? { + Some(config) => Some(config), + None => { + let parent_directory = directory.parent(); + let should_stop = Some(directory) == root.as_deref() || parent_directory.is_none(); + + if should_stop { + debug!("config: no configuration file found"); + if self.cli_config.search_parent_directories { + if let Some(config) = self.search_config_locations()? { + return Ok(Some(config)); + } + } + + debug!("config: falling back to default config"); + None + } else { + self.find_config_file(parent_directory.unwrap(), root)? + } + } + }; + + Ok(resolved_configuration) + } + + /// Looks for a configuration file at either `$XDG_CONFIG_HOME`, `$XDG_CONFIG_HOME/stylua`, `$HOME/.config` or `$HOME/.config/stylua` + fn search_config_locations(&self) -> Result, Error> { + // Look in `$XDG_CONFIG_HOME` + if let Ok(xdg_config) = std::env::var("XDG_CONFIG_HOME") { + let xdg_config_path = Path::new(&xdg_config); + if xdg_config_path.exists() { + debug!("config: looking in $XDG_CONFIG_HOME"); + + if let Some(config) = self.lookup_config_file_in_directory(xdg_config_path)? { + return Ok(Some(config)); + } + + debug!("config: looking in $XDG_CONFIG_HOME/stylua"); + let xdg_config_path = xdg_config_path.join("stylua"); + if xdg_config_path.exists() { + if let Some(config) = self.lookup_config_file_in_directory(&xdg_config_path)? { + return Ok(Some(config)); + } + } + } + } + + // Look in `$HOME/.config` + if let Ok(home) = std::env::var("HOME") { + let home_config_path = Path::new(&home).join(".config"); + + if home_config_path.exists() { + debug!("config: looking in $HOME/.config"); + + if let Some(config) = self.lookup_config_file_in_directory(&home_config_path)? { + return Ok(Some(config)); + } + + debug!("config: looking in $HOME/.config/stylua"); + let home_config_path = home_config_path.join("stylua"); + if home_config_path.exists() { + if let Some(config) = self.lookup_config_file_in_directory(&home_config_path)? { + return Ok(Some(config)); + } + } + } + } + + Ok(None) + } +} diff --git a/src/lib.rs b/src/lib.rs index 9c46e3b4..616d20b6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,6 +9,7 @@ use wasm_bindgen::prelude::*; #[macro_use] mod context; +pub mod config_resolver; #[cfg(feature = "editorconfig")] pub mod editorconfig; mod formatters; @@ -375,6 +376,9 @@ pub enum Error { /// The input AST has a parsing error. #[error("error parsing: {}", print_full_moon_errors(.0))] ParseError(Vec), + /// Resolving the configuration failed. + #[error("Config resolution error: {0}")] + ConfigResolutionError(String), /// The output AST after formatting generated a parse error. This is a definite error. #[error("INTERNAL ERROR: Output AST generated a syntax error. Please report this at https://github.com/johnnymorganz/stylua/issues: {}", print_full_moon_errors(.0))] VerificationAstError(Vec),