diff --git a/Cargo.lock b/Cargo.lock index c95e8f3c647b4..ebd226857621e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -44,6 +44,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "arc-swap" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" + [[package]] name = "arraydeque" version = "0.5.1" @@ -78,6 +84,12 @@ dependencies = [ "vsimd", ] +[[package]] +name = "beef" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1" + [[package]] name = "bincode" version = "1.3.3" @@ -382,6 +394,12 @@ dependencies = [ "libm", ] +[[package]] +name = "countme" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7704b5fdd17b18ae31c4c1da5a2e0305a2bf17b5249300a9ee9ed7b72114c636" + [[package]] name = "cow-utils" version = "0.1.3" @@ -1400,6 +1418,40 @@ version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +[[package]] +name = "logos" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a790d11254054e5dc83902dba85d253ff06ceb0cfafb12be8773435cb9dfb4f4" +dependencies = [ + "logos-derive", +] + +[[package]] +name = "logos-codegen" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60337c43a38313b58871f8d5d76872b8e17aa9d51fad494b5e76092c0ce05f5" +dependencies = [ + "beef", + "fnv", + "proc-macro2", + "quote", + "regex-automata", + "regex-syntax", + "rustc_version", + "syn", +] + +[[package]] +name = "logos-derive" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d151b2ae667f69e10b8738f5cac0c746faa22b2e15ea7e83b55476afec3767dc" +dependencies = [ + "logos-codegen", +] + [[package]] name = "ls-types" version = "0.0.2" @@ -1459,7 +1511,7 @@ dependencies = [ "napi-build", "napi-sys", "nohash-hasher", - "rustc-hash", + "rustc-hash 2.1.1", "serde", "serde_json", "tokio", @@ -1668,7 +1720,7 @@ dependencies = [ "bincode 2.0.1", "flate2", "nom", - "rustc-hash", + "rustc-hash 2.1.1", "serde", "serde_json", "thiserror", @@ -1715,6 +1767,24 @@ dependencies = [ "serde_json", ] +[[package]] +name = "oxc-toml" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a0c30de15256c25716ecf850c55fd993e0ab42514b708e9cf8bd07d426cc0f2" +dependencies = [ + "arc-swap", + "globset", + "itertools", + "logos", + "rowan", + "rustc-hash 2.1.1", + "serde", + "thiserror", + "time", + "tracing", +] + [[package]] name = "oxc_allocator" version = "0.104.0" @@ -1725,7 +1795,7 @@ dependencies = [ "oxc_ast_macros", "oxc_data_structures", "oxc_estree", - "rustc-hash", + "rustc-hash 2.1.1", "serde", "serde_json", ] @@ -1782,7 +1852,7 @@ dependencies = [ "proc-macro2", "quote", "rayon", - "rustc-hash", + "rustc-hash 2.1.1", "serde", "serde_json", "syn", @@ -1819,7 +1889,7 @@ dependencies = [ "oxc_span", "oxc_tasks_common", "oxc_transformer", - "rustc-hash", + "rustc-hash 2.1.1", "serde", "serde_json", ] @@ -1833,7 +1903,7 @@ dependencies = [ "oxc_index", "oxc_syntax", "petgraph", - "rustc-hash", + "rustc-hash 2.1.1", ] [[package]] @@ -1855,7 +1925,7 @@ dependencies = [ "oxc_span", "oxc_syntax", "pico-args", - "rustc-hash", + "rustc-hash 2.1.1", ] [[package]] @@ -1865,7 +1935,7 @@ dependencies = [ "cow-utils", "oxc-browserslist", "oxc_syntax", - "rustc-hash", + "rustc-hash 2.1.1", "serde", ] @@ -1902,7 +1972,7 @@ dependencies = [ "phf", "pico-args", "rayon", - "rustc-hash", + "rustc-hash 2.1.1", "saphyr", "serde", "serde_json", @@ -1967,7 +2037,7 @@ dependencies = [ "phf", "pico-args", "project-root", - "rustc-hash", + "rustc-hash 2.1.1", "serde", "serde_json", "unicode-width", @@ -1998,7 +2068,7 @@ dependencies = [ "oxc_parser", "oxc_span", "oxc_syntax", - "rustc-hash", + "rustc-hash 2.1.1", ] [[package]] @@ -2018,7 +2088,7 @@ dependencies = [ "oxc_linter", "oxc_parser", "papaya", - "rustc-hash", + "rustc-hash 2.1.1", "serde", "serde_json", "tokio", @@ -2068,7 +2138,7 @@ dependencies = [ "project-root", "rayon", "rust-lapper", - "rustc-hash", + "rustc-hash 2.1.1", "self_cell", "serde", "serde_json", @@ -2083,7 +2153,7 @@ version = "0.0.0" dependencies = [ "convert_case", "project-root", - "rustc-hash", + "rustc-hash 2.1.1", "syn", ] @@ -2111,7 +2181,7 @@ dependencies = [ "oxc_semantic", "oxc_span", "oxc_syntax", - "rustc-hash", + "rustc-hash 2.1.1", ] [[package]] @@ -2138,7 +2208,7 @@ dependencies = [ "oxc_syntax", "oxc_traverse", "pico-args", - "rustc-hash", + "rustc-hash 2.1.1", ] [[package]] @@ -2176,7 +2246,7 @@ dependencies = [ "oxc_tasks_common", "oxc_transformer_plugins", "pico-args", - "rustc-hash", + "rustc-hash 2.1.1", "similar-asserts", ] @@ -2213,7 +2283,7 @@ dependencies = [ "oxc_span", "oxc_syntax", "pico-args", - "rustc-hash", + "rustc-hash 2.1.1", "seq-macro", ] @@ -2229,7 +2299,7 @@ dependencies = [ "oxc_ast_macros", "oxc_estree", "oxc_napi", - "rustc-hash", + "rustc-hash 2.1.1", ] [[package]] @@ -2245,7 +2315,7 @@ dependencies = [ "oxc_linter", "oxc_napi", "oxc_transformer_plugins", - "rustc-hash", + "rustc-hash 2.1.1", "serde", "serde_json", ] @@ -2263,7 +2333,7 @@ dependencies = [ "oxc_span", "oxc_tasks_common", "pico-args", - "rustc-hash", + "rustc-hash 2.1.1", "similar", "walkdir", ] @@ -2279,7 +2349,7 @@ dependencies = [ "oxc_diagnostics", "oxc_span", "phf", - "rustc-hash", + "rustc-hash 2.1.1", "unicode-id-start", ] @@ -2296,7 +2366,7 @@ dependencies = [ "once_cell", "papaya", "parking_lot", - "rustc-hash", + "rustc-hash 2.1.1", "rustix", "self_cell", "serde", @@ -2339,7 +2409,7 @@ dependencies = [ "oxc_span", "oxc_syntax", "phf", - "rustc-hash", + "rustc-hash 2.1.1", "self_cell", "serde_json", ] @@ -2355,7 +2425,7 @@ dependencies = [ "napi", "napi-build", "napi-derive", - "rustc-hash", + "rustc-hash 2.1.1", "serde", "serde_json", ] @@ -2415,7 +2485,7 @@ dependencies = [ "oxc_semantic", "oxc_span", "oxc_syntax", - "rustc-hash", + "rustc-hash 2.1.1", ] [[package]] @@ -2457,7 +2527,7 @@ dependencies = [ "oxc", "oxc_napi", "oxc_sourcemap", - "rustc-hash", + "rustc-hash 2.1.1", ] [[package]] @@ -2485,7 +2555,7 @@ dependencies = [ "oxc_syntax", "oxc_traverse", "pico-args", - "rustc-hash", + "rustc-hash 2.1.1", "serde", "serde_json", "sha1", @@ -2514,7 +2584,7 @@ dependencies = [ "oxc_transformer", "oxc_traverse", "pico-args", - "rustc-hash", + "rustc-hash 2.1.1", "similar", ] @@ -2531,7 +2601,7 @@ dependencies = [ "oxc_semantic", "oxc_span", "oxc_syntax", - "rustc-hash", + "rustc-hash 2.1.1", ] [[package]] @@ -2548,6 +2618,7 @@ dependencies = [ "napi-build", "napi-derive", "oxc-miette", + "oxc-toml", "oxc_allocator", "oxc_diagnostics", "oxc_formatter", @@ -2589,7 +2660,7 @@ dependencies = [ "oxc_semantic", "oxc_span", "rayon", - "rustc-hash", + "rustc-hash 2.1.1", "serde", "serde_json", "simdutf8", @@ -2937,6 +3008,18 @@ dependencies = [ "str_indices", ] +[[package]] +name = "rowan" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "417a3a9f582e349834051b8a10c8d71ca88da4211e4093528e36b9845f6b5f21" +dependencies = [ + "countme", + "hashbrown 0.14.5", + "rustc-hash 1.1.0", + "text-size", +] + [[package]] name = "rulegen" version = "0.0.0" @@ -2951,7 +3034,7 @@ dependencies = [ "oxc_parser", "oxc_span", "oxc_tasks_common", - "rustc-hash", + "rustc-hash 2.1.1", "serde", ] @@ -2964,12 +3047,27 @@ dependencies = [ "num-traits", ] +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustc-hash" version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "1.1.2" @@ -3367,6 +3465,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "text-size" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f18aa187839b2bdb1ad2fa35ead8c4c2976b64e4363c386d45ac0f7ee85c9233" + [[package]] name = "textwrap" version = "0.16.2" @@ -3571,9 +3675,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.43" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d15d90a0b5c19378952d479dc858407149d7bb45a14de0142f6c534b16fc647" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "pin-project-lite", "tracing-attributes", @@ -3593,9 +3697,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.35" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a04e24fab5c89c6a36eb8558c9656f30d81de51dfa4d3b45f26b21d61fa0a6c" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", "valuable", diff --git a/Cargo.toml b/Cargo.toml index d91e4a05959f6..99244892b0e62 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -233,6 +233,13 @@ tracing-subscriber = "0.3.22" # Tracing implementation ureq = { version = "3.1.4", default-features = false } # HTTP client url = { version = "2.5.7" } # URL parsing walkdir = "2.5.0" # Directory traversal +editorconfig-parser = "0.0.3" +natord = "1.0.9" +oxfmt = { path = "apps/oxfmt" } +sort-package-json = "0.0.5" +oxc-toml = "0.14.1" +unicode-width = "0.2" +website_common = { path = "tasks/website_common" } [workspace.metadata.cargo-shear] ignored = ["oxc_transform_napi", "oxc_parser_napi", "oxc_minify_napi"] diff --git a/apps/oxfmt/Cargo.toml b/apps/oxfmt/Cargo.toml index 876afd41532aa..42ec6aced8032 100644 --- a/apps/oxfmt/Cargo.toml +++ b/apps/oxfmt/Cargo.toml @@ -37,7 +37,7 @@ oxc_span = { workspace = true } bpaf = { workspace = true, features = ["autocomplete", "bright-color", "derive"] } cow-utils = { workspace = true } -editorconfig-parser = "0.0.3" +editorconfig-parser = { workspace = true } ignore = { workspace = true, features = ["simd-accel"] } json-strip-comments = { workspace = true } miette = { workspace = true } @@ -45,7 +45,8 @@ phf = { workspace = true, features = ["macros"] } rayon = { workspace = true } serde_json = { workspace = true } simdutf8 = { workspace = true } -sort-package-json = "0.0.5" +sort-package-json = { workspace = true } +oxc-toml = { workspace = true } tokio = { workspace = true, features = ["rt-multi-thread", "io-std", "macros"] } tracing = { workspace = true } tracing-subscriber = { workspace = true, features = [] } # Omit the `regex` feature diff --git a/apps/oxfmt/src/core/config.rs b/apps/oxfmt/src/core/config.rs index 92c4345de8220..4333be7f72566 100644 --- a/apps/oxfmt/src/core/config.rs +++ b/apps/oxfmt/src/core/config.rs @@ -4,6 +4,7 @@ use editorconfig_parser::{ EditorConfig, EditorConfigProperties, EditorConfigProperty, EndOfLine, IndentStyle, MaxLineLength, }; +use oxc_toml::formatter::Options as TomlFormatterOptions; use serde_json::Value; use oxc_formatter::{ @@ -53,6 +54,8 @@ pub enum ResolvedOptions { /// For embedded language formatting (e.g., CSS in template literals) external_options: Value, }, + /// For TOML files. + OxfmtToml { toml_options: TomlFormatterOptions }, /// For non-JS files formatted by external formatter (Prettier). #[cfg(feature = "napi")] ExternalFormatter { external_options: Value }, @@ -189,6 +192,9 @@ impl ConfigResolver { FormatFileStrategy::OxcFormatter { .. } => { ResolvedOptions::OxcFormatter { format_options, external_options } } + FormatFileStrategy::OxfmtToml { .. } => { + ResolvedOptions::OxfmtToml { toml_options: build_toml_options(&format_options) } + } #[cfg(feature = "napi")] FormatFileStrategy::ExternalFormatter { .. } => { ResolvedOptions::ExternalFormatter { external_options } @@ -319,3 +325,23 @@ fn apply_editorconfig(oxfmtrc: &mut Oxfmtrc, props: &EditorConfigProperties) { oxfmtrc.tab_width = Some(size as u8); } } + +// --- + +/// Build `toml` formatter options. +/// The same as `prettier-plugin-toml`. +/// +fn build_toml_options(format_options: &FormatOptions) -> TomlFormatterOptions { + TomlFormatterOptions { + column_width: format_options.line_width.value() as usize, + indent_string: if format_options.indent_style.is_tab() { + "\t".to_string() + } else { + " ".repeat(format_options.indent_width.value() as usize) + }, + trailing_newline: true, + array_trailing_comma: !format_options.trailing_commas.is_none(), + crlf: format_options.line_ending.is_carriage_return_line_feed(), + ..Default::default() + } +} diff --git a/apps/oxfmt/src/core/format.rs b/apps/oxfmt/src/core/format.rs index f795ba5aa50ca..7ecd0bad69b16 100644 --- a/apps/oxfmt/src/core/format.rs +++ b/apps/oxfmt/src/core/format.rs @@ -59,6 +59,9 @@ impl SourceFormatter { format_options, external_options, ), + (FormatFileStrategy::OxfmtToml { .. }, ResolvedOptions::OxfmtToml { toml_options }) => { + Ok(Self::format_by_toml(source_text, toml_options)) + } #[cfg(feature = "napi")] ( FormatFileStrategy::ExternalFormatter { path, parser_name }, @@ -149,6 +152,11 @@ impl SourceFormatter { Ok(code.into_code()) } + /// Format TOML file using `toml`. + fn format_by_toml(source_text: &str, options: &oxc_toml::formatter::Options) -> String { + oxc_toml::formatter::format(source_text, options.clone()) + } + /// Format non-JS/TS file using external formatter (Prettier). #[cfg(feature = "napi")] fn format_by_external_formatter( diff --git a/apps/oxfmt/src/core/support.rs b/apps/oxfmt/src/core/support.rs index 9f2f3447a217b..c6eb3c81930dc 100644 --- a/apps/oxfmt/src/core/support.rs +++ b/apps/oxfmt/src/core/support.rs @@ -10,6 +10,10 @@ pub enum FormatFileStrategy { path: PathBuf, source_type: SourceType, }, + /// TOML files formatted by taplo (Pure Rust). + OxfmtToml { + path: PathBuf, + }, ExternalFormatter { path: PathBuf, #[cfg_attr(not(feature = "napi"), expect(dead_code))] @@ -43,6 +47,11 @@ impl TryFrom for FormatFileStrategy { return Err(()); } + // Then TOML files + if is_toml_file(file_name) { + return Ok(Self::OxfmtToml { path }); + } + // Then external formatter files // `package.json` is special: sorted then formatted if file_name == "package.json" { @@ -61,12 +70,13 @@ impl TryFrom for FormatFileStrategy { impl FormatFileStrategy { #[cfg(not(feature = "napi"))] pub fn can_format_without_external(&self) -> bool { - matches!(self, Self::OxcFormatter { .. }) + matches!(self, Self::OxcFormatter { .. } | Self::OxfmtToml { .. }) } pub fn path(&self) -> &Path { match self { Self::OxcFormatter { path, .. } + | Self::OxfmtToml { path } | Self::ExternalFormatter { path, .. } | Self::ExternalFormatterPackageJson { path, .. } => path, } @@ -86,16 +96,43 @@ static EXCLUDE_FILENAMES: phf::Set<&'static str> = phf_set! { "Pipfile.lock", "flake.lock", "mcmod.info", + // TOML lock files + "Cargo.lock", + "Gopkg.lock", + "pdm.lock", + "poetry.lock", + "uv.lock", +}; + +// --- + +/// Returns `true` if this is a TOML file. +fn is_toml_file(file_name: &str) -> bool { + if TOML_FILENAMES.contains(file_name) { + return true; + } + + #[expect(clippy::case_sensitive_file_extension_comparisons)] + if file_name.ends_with(".toml.example") || file_name.ends_with(".toml") { + return true; + } + + false +} + +static TOML_FILENAMES: phf::Set<&'static str> = phf_set! { + "Pipfile", + "Cargo.toml.orig", }; // --- /// Returns parser name for external formatter, if supported. -/// NOTE: `package.json` is handled separately in `TryFrom`. /// See also `prettier --support-info | jq '.languages[]'` fn get_external_parser_name(file_name: &str, extension: Option<&str>) -> Option<&'static str> { // JSON and variants - if JSON_STRINGIFY_FILENAMES.contains(file_name) || extension == Some("importmap") { + // NOTE: `package.json` is handled separately in `FormatFileStrategy::try_from()` + if file_name == "composer.json" || extension == Some("importmap") { return Some("json-stringify"); } if JSON_FILENAMES.contains(file_name) { @@ -185,11 +222,6 @@ fn get_external_parser_name(file_name: &str, extension: Option<&str>) -> Option< None } -static JSON_STRINGIFY_FILENAMES: phf::Set<&'static str> = phf_set! { - // NOTE: `package.json` is handled separately as `ExternalFormatterPackageJson` - "composer.json", -}; - static JSON_EXTENSIONS: phf::Set<&'static str> = phf_set! { "json", "4DForm", @@ -399,4 +431,33 @@ mod tests { let source = FormatFileStrategy::try_from(PathBuf::from("composer.json")).unwrap(); assert!(matches!(source, FormatFileStrategy::ExternalFormatter { .. })); } + + #[test] + fn test_toml_files() { + // Files that should be detected as TOML + let toml_files = vec![ + "Cargo.toml", + "pyproject.toml", + "config.toml", + "config.toml.example", + "Pipfile", + "Cargo.toml.orig", + ]; + + for file_name in toml_files { + let result = FormatFileStrategy::try_from(PathBuf::from(file_name)); + assert!( + matches!(result, Ok(FormatFileStrategy::OxfmtToml { .. })), + "`{file_name}` should be detected as TOML" + ); + } + + // Lock files that should be excluded + let excluded_files = vec!["Cargo.lock", "poetry.lock", "pdm.lock", "uv.lock", "Gopkg.lock"]; + + for file_name in excluded_files { + let result = FormatFileStrategy::try_from(PathBuf::from(file_name)); + assert!(result.is_err(), "`{file_name}` should be excluded (lock file)"); + } + } } diff --git a/crates/oxc_formatter/Cargo.toml b/crates/oxc_formatter/Cargo.toml index 84482b0450cc4..5499f87216f4c 100644 --- a/crates/oxc_formatter/Cargo.toml +++ b/crates/oxc_formatter/Cargo.toml @@ -29,13 +29,13 @@ oxc_span = { workspace = true } oxc_syntax = { workspace = true } cow-utils = { workspace = true } -natord = "1.0.9" +natord = { workspace = true } phf = { workspace = true, features = ["macros"] } rustc-hash = { workspace = true } schemars = { workspace = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } -unicode-width = "0.2" +unicode-width = { workspace = true } [dev-dependencies] insta = { workspace = true } diff --git a/tasks/website_formatter/Cargo.toml b/tasks/website_formatter/Cargo.toml index d15775e8c7d96..d53dc735b3d3e 100644 --- a/tasks/website_formatter/Cargo.toml +++ b/tasks/website_formatter/Cargo.toml @@ -14,12 +14,12 @@ doctest = false [dependencies] bpaf = { workspace = true, features = ["docgen"] } -oxc_formatter = { path = "../../crates/oxc_formatter" } -oxfmt = { path = "../../apps/oxfmt" } +oxc_formatter = { workspace = true } +oxfmt = { workspace = true } pico-args = { workspace = true } schemars = { workspace = true } serde_json = { workspace = true } -website_common = { path = "../website_common" } +website_common = { workspace = true } [dev-dependencies] insta = { workspace = true } diff --git a/tasks/website_linter/Cargo.toml b/tasks/website_linter/Cargo.toml index 37f3a68975509..28a98991f6687 100644 --- a/tasks/website_linter/Cargo.toml +++ b/tasks/website_linter/Cargo.toml @@ -16,11 +16,11 @@ doctest = false bpaf = { workspace = true, features = ["docgen"] } itertools = { workspace = true } oxc_linter = { workspace = true, features = ["ruledocs"] } -oxlint = { path = "../../apps/oxlint", default-features = false } +oxlint = { workspace = true } pico-args = { workspace = true } project-root = { workspace = true } schemars = { workspace = true } -website_common = { path = "../website_common" } +website_common = { workspace = true } [dev-dependencies] insta = { workspace = true }