diff --git a/apps/oxfmt/src/cli/walk.rs b/apps/oxfmt/src/cli/walk.rs index 7b3449f059a4d..373a9a1961546 100644 --- a/apps/oxfmt/src/cli/walk.rs +++ b/apps/oxfmt/src/cli/walk.rs @@ -228,22 +228,22 @@ impl ignore::ParallelVisitor for WalkVisitor { if file_type.is_file() { // Determine this file should be handled or NOT // Tier 1 = `.js`, `.tsx`, etc: JS/TS files supported by `oxc_formatter` - // Tier 2 = `.html`, `.json`, etc: Other files supported by Prettier - // (Tier 3 = `.astro`, `.svelte`, etc: Other files supported by Prettier plugins) - // Tier 4 = everything else: Not handled - let Ok(format_file_source) = FormatFileStrategy::try_from(entry.into_path()) - else { + // Tier 2 = `.toml`, etc: Some files supported by `oxfmt` directly + // Tier 3 = `.html`, `.json`, etc: Other files supported by Prettier + // (Tier 4 = `.astro`, `.svelte`, etc: Other files supported by Prettier plugins) + // Everything else: Ignored + let Ok(strategy) = FormatFileStrategy::try_from(entry.into_path()) else { return ignore::WalkState::Continue; }; #[cfg(not(feature = "napi"))] - if !matches!(format_file_source, FormatFileStrategy::OxcFormatter { .. }) { + if !strategy.can_format_without_external() { return ignore::WalkState::Continue; } // Send each entry immediately through the channel // If send fails, the receiver has been dropped, so stop walking - if self.sender.send(format_file_source).is_err() { + if self.sender.send(strategy).is_err() { return ignore::WalkState::Quit; } } diff --git a/apps/oxfmt/src/core/support.rs b/apps/oxfmt/src/core/support.rs index 660c251c2c891..9f2f3447a217b 100644 --- a/apps/oxfmt/src/core/support.rs +++ b/apps/oxfmt/src/core/support.rs @@ -27,13 +27,31 @@ impl TryFrom for FormatFileStrategy { type Error = (); fn try_from(path: PathBuf) -> Result { + // Check JS/TS files first // TODO: This logic should(can) move to this file, after LSP support is also moved here. if let Some(source_type) = get_supported_source_type(&path) { return Ok(Self::OxcFormatter { path, source_type }); } - if let Some(source) = get_external_format_source(path) { - return Ok(source); + // Extract file_name and extension once for all subsequent checks + let Some(file_name) = path.file_name().and_then(|f| f.to_str()) else { + return Err(()); + }; + + // Excluded files like lock files + if EXCLUDE_FILENAMES.contains(file_name) { + return Err(()); + } + + // Then external formatter files + // `package.json` is special: sorted then formatted + if file_name == "package.json" { + return Ok(Self::ExternalFormatterPackageJson { path, parser_name: "json-stringify" }); + } + + let extension = path.extension().and_then(|ext| ext.to_str()); + if let Some(parser_name) = get_external_parser_name(file_name, extension) { + return Ok(Self::ExternalFormatter { path, parser_name }); } Err(()) @@ -41,6 +59,11 @@ impl TryFrom for FormatFileStrategy { } impl FormatFileStrategy { + #[cfg(not(feature = "napi"))] + pub fn can_format_without_external(&self) -> bool { + matches!(self, Self::OxcFormatter { .. }) + } + pub fn path(&self) -> &Path { match self { Self::OxcFormatter { path, .. } @@ -50,132 +73,118 @@ impl FormatFileStrategy { } } +static EXCLUDE_FILENAMES: phf::Set<&'static str> = phf_set! { + // JSON, YAML lock files + "package-lock.json", + "pnpm-lock.yaml", + "yarn.lock", + "MODULE.bazel.lock", + "bun.lock", + "deno.lock", + "composer.lock", + "Package.resolved", + "Pipfile.lock", + "flake.lock", + "mcmod.info", +}; + // --- -/// Returns `FormatFileSource` for external formatter, if supported. +/// Returns parser name for external formatter, if supported. +/// NOTE: `package.json` is handled separately in `TryFrom`. /// See also `prettier --support-info | jq '.languages[]'` -/// NOTE: The order matters: more specific matches (like `package.json`) must come before generic ones. -fn get_external_format_source(path: PathBuf) -> Option { - let file_name = path.file_name()?.to_str()?; - let extension = path.extension().and_then(|ext| ext.to_str()); - - // Excluded files like lock files - if EXCLUDE_FILENAMES.contains(file_name) { - return None; - } - +fn get_external_parser_name(file_name: &str, extension: Option<&str>) -> Option<&'static str> { // JSON and variants - // `package.json` is special case - if file_name == "package.json" { - return Some(FormatFileStrategy::ExternalFormatterPackageJson { - path, - parser_name: "json-stringify", - }); - } if JSON_STRINGIFY_FILENAMES.contains(file_name) || extension == Some("importmap") { - return Some(FormatFileStrategy::ExternalFormatter { path, parser_name: "json-stringify" }); + return Some("json-stringify"); } if JSON_FILENAMES.contains(file_name) { - return Some(FormatFileStrategy::ExternalFormatter { path, parser_name: "json" }); + return Some("json"); } if let Some(ext) = extension && JSON_EXTENSIONS.contains(ext) { - return Some(FormatFileStrategy::ExternalFormatter { path, parser_name: "json" }); + return Some("json"); } if let Some(ext) = extension && JSONC_EXTENSIONS.contains(ext) { - return Some(FormatFileStrategy::ExternalFormatter { path, parser_name: "jsonc" }); + return Some("jsonc"); } if extension == Some("json5") { - return Some(FormatFileStrategy::ExternalFormatter { path, parser_name: "json5" }); + return Some("json5"); } // YAML if YAML_FILENAMES.contains(file_name) { - return Some(FormatFileStrategy::ExternalFormatter { path, parser_name: "yaml" }); + return Some("yaml"); } if let Some(ext) = extension && YAML_EXTENSIONS.contains(ext) { - return Some(FormatFileStrategy::ExternalFormatter { path, parser_name: "yaml" }); + return Some("yaml"); } // Markdown and variants if MARKDOWN_FILENAMES.contains(file_name) { - return Some(FormatFileStrategy::ExternalFormatter { path, parser_name: "markdown" }); + return Some("markdown"); } if let Some(ext) = extension && MARKDOWN_EXTENSIONS.contains(ext) { - return Some(FormatFileStrategy::ExternalFormatter { path, parser_name: "markdown" }); + return Some("markdown"); } if extension == Some("mdx") { - return Some(FormatFileStrategy::ExternalFormatter { path, parser_name: "mdx" }); + return Some("mdx"); } // HTML and variants // Must be checked before generic HTML if file_name.ends_with(".component.html") { - return Some(FormatFileStrategy::ExternalFormatter { path, parser_name: "angular" }); + return Some("angular"); } if let Some(ext) = extension && HTML_EXTENSIONS.contains(ext) { - return Some(FormatFileStrategy::ExternalFormatter { path, parser_name: "html" }); + return Some("html"); } if extension == Some("vue") { - return Some(FormatFileStrategy::ExternalFormatter { path, parser_name: "vue" }); + return Some("vue"); } if extension == Some("mjml") { - return Some(FormatFileStrategy::ExternalFormatter { path, parser_name: "mjml" }); + return Some("mjml"); } // CSS and variants if let Some(ext) = extension && CSS_EXTENSIONS.contains(ext) { - return Some(FormatFileStrategy::ExternalFormatter { path, parser_name: "css" }); + return Some("css"); } if extension == Some("less") { - return Some(FormatFileStrategy::ExternalFormatter { path, parser_name: "less" }); + return Some("less"); } if extension == Some("scss") { - return Some(FormatFileStrategy::ExternalFormatter { path, parser_name: "scss" }); + return Some("scss"); } // GraphQL if let Some(ext) = extension && GRAPHQL_EXTENSIONS.contains(ext) { - return Some(FormatFileStrategy::ExternalFormatter { path, parser_name: "graphql" }); + return Some("graphql"); } // Handlebars if let Some(ext) = extension && HANDLEBARS_EXTENSIONS.contains(ext) { - return Some(FormatFileStrategy::ExternalFormatter { path, parser_name: "glimmer" }); + return Some("glimmer"); } None } -static EXCLUDE_FILENAMES: phf::Set<&'static str> = phf_set! { - "package-lock.json", - "pnpm-lock.yaml", - "yarn.lock", - "MODULE.bazel.lock", - "bun.lock", - "deno.lock", - "composer.lock", - "Package.resolved", - "Pipfile.lock", - "flake.lock", - "mcmod.info", -}; - static JSON_STRINGIFY_FILENAMES: phf::Set<&'static str> = phf_set! { // NOTE: `package.json` is handled separately as `ExternalFormatterPackageJson` "composer.json", @@ -319,20 +328,15 @@ mod tests { use super::*; fn get_parser_name(file_name: &str) -> Option<&'static str> { - match get_external_format_source(PathBuf::from(file_name)) { - Some( - FormatFileStrategy::ExternalFormatter { parser_name, .. } - | FormatFileStrategy::ExternalFormatterPackageJson { parser_name, .. }, - ) => Some(parser_name), - _ => None, - } + let path = Path::new(file_name); + let extension = path.extension().and_then(|ext| ext.to_str()); + get_external_parser_name(file_name, extension) } #[test] - fn test_get_external_format_source() { + fn test_get_external_parser_name() { let test_cases = vec![ - // JSON - ("package.json", Some("json-stringify")), + // JSON (NOTE: `package.json` is handled in TryFrom, not here) ("config.importmap", Some("json-stringify")), ("data.json", Some("json")), ("schema.avsc", Some("json")), @@ -375,10 +379,6 @@ mod tests { ("config.yml", Some("yaml")), ("settings.yaml", Some("yaml")), ("grammar.sublime-syntax", Some("yaml")), - // Excluded lock files - ("package-lock.json", None), - ("pnpm-lock.yaml", None), - ("yarn.lock", None), // Unknown ("unknown.txt", None), ("prof.png", None), @@ -393,10 +393,10 @@ mod tests { #[test] fn test_package_json_is_special() { - let source = get_external_format_source(PathBuf::from("package.json")).unwrap(); + let source = FormatFileStrategy::try_from(PathBuf::from("package.json")).unwrap(); assert!(matches!(source, FormatFileStrategy::ExternalFormatterPackageJson { .. })); - let source = get_external_format_source(PathBuf::from("composer.json")).unwrap(); + let source = FormatFileStrategy::try_from(PathBuf::from("composer.json")).unwrap(); assert!(matches!(source, FormatFileStrategy::ExternalFormatter { .. })); } }