diff --git a/Cargo.lock b/Cargo.lock index a402177bc3e6c..2b166a4e84d06 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3849,7 +3849,22 @@ dependencies = [ ] [[package]] -name = "website" +name = "website_common" +version = "0.0.0" + +[[package]] +name = "website_formatter" +version = "0.0.0" +dependencies = [ + "bpaf", + "insta", + "oxfmt", + "pico-args", + "website_common", +] + +[[package]] +name = "website_linter" version = "0.0.0" dependencies = [ "bpaf", @@ -3868,6 +3883,7 @@ dependencies = [ "project-root", "serde", "serde_json", + "website_common", ] [[package]] diff --git a/apps/oxfmt/src/command.rs b/apps/oxfmt/src/command.rs index f872b3ce310fc..ebe6ca7253338 100644 --- a/apps/oxfmt/src/command.rs +++ b/apps/oxfmt/src/command.rs @@ -21,7 +21,7 @@ const PATHS_ERROR_MESSAGE: &str = "PATH must not contain \"..\""; #[derive(Debug, Clone, Bpaf)] #[bpaf(options, version(VERSION))] pub struct FormatCommand { - #[bpaf(external, fallback(OutputOptions::DefaultWrite))] + #[bpaf(external, fallback(OutputOptions::DefaultWrite), hide_usage)] pub output_options: OutputOptions, #[bpaf(external)] pub basic_options: BasicOptions, @@ -31,7 +31,7 @@ pub struct FormatCommand { pub misc_options: MiscOptions, /// Single file, single path or list of paths. /// If not provided, current working directory is used. - /// Glob is supported only for exclude patterns like `'!**/fixtures/*.js'. + /// Glob is supported only for exclude patterns like `'!**/fixtures/*.js'`. // `bpaf(fallback)` seems to have issues with `many` or `positional`, // so we implement the fallback behavior in code instead. #[bpaf(positional("PATH"), many, guard(validate_paths, PATHS_ERROR_MESSAGE))] @@ -65,14 +65,14 @@ pub struct BasicOptions { pub struct IgnoreOptions { /// Path to ignore file(s). Can be specified multiple times. /// If not specified, .gitignore and .prettierignore in the current directory are used. - #[bpaf(argument("PATH"), many)] + #[bpaf(argument("PATH"), many, hide_usage)] pub ignore_path: Vec, /// Format code in node_modules directory (skipped by default) #[bpaf(switch, hide_usage)] pub with_node_modules: bool, } -/// Miscellaneous +/// Misc Options #[derive(Debug, Clone, Bpaf)] pub struct MiscOptions { /// Start language server protocol (LSP) server diff --git a/justfile b/justfile index a4989acf56add..ca66744862ac9 100755 --- a/justfile +++ b/justfile @@ -247,13 +247,14 @@ watch-playground: # When testing changes to the website documentation, you may also want to run `dprint fmt --staged` # in the website directory. website path: - cargo run -p website -- linter-rules --table {{path}}/src/docs/guide/usage/linter/generated-rules.md --rule-docs {{path}}/src/docs/guide/usage/linter/rules --git-ref $(git rev-parse HEAD) - cargo run -p website -- linter-cli > {{path}}/src/docs/guide/usage/linter/generated-cli.md - cargo run -p website -- linter-schema-markdown > {{path}}/src/docs/guide/usage/linter/generated-config.md + cargo run -p website_linter rules --table {{path}}/src/docs/guide/usage/linter/generated-rules.md --rule-docs {{path}}/src/docs/guide/usage/linter/rules --git-ref $(git rev-parse HEAD) + cargo run -p website_linter cli > {{path}}/src/docs/guide/usage/linter/generated-cli.md + cargo run -p website_linter schema-markdown > {{path}}/src/docs/guide/usage/linter/generated-config.md + cargo run -p website_formatter cli > {{path}}/src/docs/guide/usage/formatter/generated-cli.md # Generate linter schema json for `npm/oxlint/configuration_schema.json` linter-schema-json: - cargo run -p website -- linter-schema-json > npm/oxlint/configuration_schema.json + cargo run -p website_linter schema-json > npm/oxlint/configuration_schema.json # Automatically DRY up Cargo.toml manifests in a workspace autoinherit: diff --git a/tasks/website/src/lib.rs b/tasks/website/src/lib.rs deleted file mode 100644 index 90ff84b284bab..0000000000000 --- a/tasks/website/src/lib.rs +++ /dev/null @@ -1,3 +0,0 @@ -#![expect(clippy::print_stdout, clippy::missing_panics_doc, clippy::disallowed_methods)] - -pub mod linter; diff --git a/tasks/website/src/linter/cli.rs b/tasks/website/src/linter/cli.rs deleted file mode 100644 index 8602fef70edc6..0000000000000 --- a/tasks/website/src/linter/cli.rs +++ /dev/null @@ -1,64 +0,0 @@ -use oxlint::cli::lint_command; - -#[test] -fn test_cli() { - let snapshot = generate_cli(); - insta::with_settings!({ prepend_module_to_snapshot => false }, { - insta::assert_snapshot!(snapshot); - }); -} - -#[test] -fn test_cli_terminal() { - let snapshot = oxlint::cli::lint_command().run_inner(&["--help"]).unwrap_err().unwrap_stdout(); - insta::with_settings!({ prepend_module_to_snapshot => false }, { - insta::assert_snapshot!(snapshot); - }); -} - -// -pub fn print_cli() { - println!("{}", generate_cli()); -} - -fn generate_cli() -> String { - let markdown = lint_command().render_markdown("oxlint"); - // Remove the extra header - let markdown = markdown.trim_start_matches("# oxlint\n"); - - // Add ---\nsearch: false\n---\n at the top to prevent Vitepress from indexing this file. - let markdown = format!("---\nsearch: false\n---\n\n{markdown}"); - - // Hack usage line - let markdown = markdown.replacen("**Usage**:", "## Usage\n", 1); - - let markdown = markdown - .split('\n') - .flat_map(|line| { - // Hack the bug on the line containing `###` - if line.contains("###") { - line.split("###").map(str::trim).chain(["\n"]).collect::>() - } else { - vec![line] - } - }) - .map(|line| { - // Make `** title **` to `## title` - if let Some(line) = line.strip_prefix("**") - && let Some(line) = line.strip_suffix("**") - { - format!("## {line}") - } else { - line.to_string() - } - }) - .collect::>() - .join("\n"); - - // Add note about .gitignore only being respected inside Git repositories - // This note should appear after the ignore options and before "Handle Warnings" - markdown.replace( - "\n\n## Handle Warnings\n", - "\n\n> [!NOTE]\n> `.gitignore` is only respected inside a Git repository.\n\n## Handle Warnings\n" - ) -} diff --git a/tasks/website/src/linter/mod.rs b/tasks/website/src/linter/mod.rs deleted file mode 100644 index 7b9c04fbfe651..0000000000000 --- a/tasks/website/src/linter/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -mod cli; -mod json_schema; -mod rules; - -pub use self::{ - cli::print_cli, - json_schema::{print_schema_json, print_schema_markdown}, - rules::print_rules, -}; diff --git a/tasks/website_common/Cargo.toml b/tasks/website_common/Cargo.toml new file mode 100644 index 0000000000000..374ef9332871a --- /dev/null +++ b/tasks/website_common/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "website_common" +version = "0.0.0" +edition.workspace = true +license.workspace = true +publish = false + +[lints] +workspace = true + +[dependencies] diff --git a/tasks/website_common/src/lib.rs b/tasks/website_common/src/lib.rs new file mode 100644 index 0000000000000..445e9e254e304 --- /dev/null +++ b/tasks/website_common/src/lib.rs @@ -0,0 +1,62 @@ +/// Generate CLI documentation from bpaf-generated markdown. +/// +/// Takes raw markdown from bpaf's `render_markdown()` and processes it into +/// website-ready format with proper frontmatter and section headers. +/// +/// # Arguments +/// * `raw_markdown` - The markdown string from bpaf's render_markdown() +/// * `tool_name` - The name of the tool (e.g., "oxlint", "oxfmt") used to strip the header +/// * `gitignore_note_anchor` - Optional section header after which to insert the gitignore note +/// +/// # Returns +/// Processed markdown ready for the website +#[expect(clippy::disallowed_methods)] +pub fn generate_cli_docs( + raw_markdown: &str, + tool_name: &str, + gitignore_note_anchor: Option<&str>, +) -> String { + // Remove the extra header + let header = format!("# {tool_name}\n"); + let markdown = raw_markdown.trim_start_matches(header.as_str()); + + // Add ---\nsearch: false\n---\n at the top to prevent Vitepress from indexing this file. + let markdown = format!("---\nsearch: false\n---\n\n{markdown}"); + + // Hack usage line + let markdown = markdown.replacen("**Usage**:", "## Usage\n", 1); + + let markdown = markdown + .split('\n') + .flat_map(|line| { + // Hack the bug on the line containing `###` + if line.contains("###") { + line.split("###").map(str::trim).chain(["\n"]).collect::>() + } else { + vec![line] + } + }) + .map(|line| { + // Make `** title **` to `## title` + if let Some(line) = line.strip_prefix("**") + && let Some(line) = line.strip_suffix("**") + { + format!("## {line}") + } else { + line.to_string() + } + }) + .collect::>() + .join("\n"); + + // Add note about .gitignore only being respected inside Git repositories + if let Some(anchor) = gitignore_note_anchor { + let search_pattern = format!("\n\n## {anchor}\n"); + let replacement = format!( + "\n\n> [!NOTE]\n> `.gitignore` is only respected inside a Git repository.\n\n## {anchor}\n" + ); + markdown.replace(&search_pattern, &replacement) + } else { + markdown + } +} diff --git a/tasks/website_formatter/Cargo.toml b/tasks/website_formatter/Cargo.toml new file mode 100644 index 0000000000000..1173b2160bfe0 --- /dev/null +++ b/tasks/website_formatter/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "website_formatter" +version = "0.0.0" +edition.workspace = true +license.workspace = true +publish = false + +[lints] +workspace = true + +[[bin]] +name = "website_formatter" +test = false +doctest = false + +[dependencies] +bpaf = { workspace = true, features = ["docgen"] } +oxfmt = { path = "../../apps/oxfmt", default-features = false } +pico-args = { workspace = true } +website_common = { path = "../website_common" } + +[dev-dependencies] +insta = { workspace = true } + +[package.metadata.cargo-shear] +ignored = ["bpaf"] diff --git a/tasks/website_formatter/src/cli.rs b/tasks/website_formatter/src/cli.rs new file mode 100644 index 0000000000000..0bce4c8ee6f67 --- /dev/null +++ b/tasks/website_formatter/src/cli.rs @@ -0,0 +1,29 @@ +use oxfmt::format_command; +use website_common::generate_cli_docs; + +#[test] +fn test_cli() { + let snapshot = generate_cli(); + insta::with_settings!({ prepend_module_to_snapshot => false }, { + insta::assert_snapshot!(snapshot); + }); +} + +#[test] +fn test_cli_terminal() { + let snapshot = oxfmt::format_command().run_inner(&["--help"]).unwrap_err().unwrap_stdout(); + insta::with_settings!({ prepend_module_to_snapshot => false }, { + insta::assert_snapshot!(snapshot); + }); +} + +// +#[expect(clippy::print_stdout)] +pub fn print_cli() { + println!("{}", generate_cli()); +} + +fn generate_cli() -> String { + let markdown = format_command().render_markdown("oxfmt"); + generate_cli_docs(&markdown, "oxfmt", None) +} diff --git a/tasks/website/src/main.rs b/tasks/website_formatter/src/main.rs similarity index 55% rename from tasks/website/src/main.rs rename to tasks/website_formatter/src/main.rs index 0e835f11c5360..1460c165ee739 100644 --- a/tasks/website/src/main.rs +++ b/tasks/website_formatter/src/main.rs @@ -1,19 +1,16 @@ #![expect(clippy::print_stderr)] use pico_args::Arguments; -use website::linter; + +mod cli; fn main() { let mut args = Arguments::from_env(); let command = args.subcommand().expect("subcommands"); - let task = command.as_deref().unwrap_or("default"); match task { - "linter-schema-json" => linter::print_schema_json(), - "linter-schema-markdown" => linter::print_schema_markdown(), - "linter-cli" => linter::print_cli(), - "linter-rules" => linter::print_rules(args), + "cli" => cli::print_cli(), _ => eprintln!("Missing task command."), } } diff --git a/tasks/website/Cargo.toml b/tasks/website_linter/Cargo.toml similarity index 85% rename from tasks/website/Cargo.toml rename to tasks/website_linter/Cargo.toml index 53cf23097b075..6f7ab0c579903 100644 --- a/tasks/website/Cargo.toml +++ b/tasks/website_linter/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "website" +name = "website_linter" version = "0.0.0" edition.workspace = true license.workspace = true @@ -9,34 +9,30 @@ publish = false workspace = true [[bin]] -name = "website" +name = "website_linter" test = false doctest = false -[lib] -doctest = false - [dependencies] bpaf = { workspace = true, features = ["docgen"] } handlebars = { workspace = true } itertools = { workspace = true } oxc_linter = { workspace = true, features = ["ruledocs"] } -# Disable default features to avoid `napi-rs` dependency, which causes linker errors oxlint = { path = "../../apps/oxlint", default-features = false } pico-args = { workspace = true } project-root = { workspace = true } schemars = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } +website_common = { path = "../website_common" } [dev-dependencies] +insta = { workspace = true } +markdown = { workspace = true } oxc_allocator = { workspace = true } oxc_diagnostics = { workspace = true } oxc_parser = { workspace = true } oxc_span = { workspace = true } -insta = { workspace = true } -markdown = { workspace = true } - [package.metadata.cargo-shear] ignored = ["bpaf"] diff --git a/tasks/website_linter/src/cli.rs b/tasks/website_linter/src/cli.rs new file mode 100644 index 0000000000000..adc67569c7b02 --- /dev/null +++ b/tasks/website_linter/src/cli.rs @@ -0,0 +1,29 @@ +use oxlint::cli::lint_command; +use website_common::generate_cli_docs; + +#[test] +fn test_cli() { + let snapshot = generate_cli(); + insta::with_settings!({ prepend_module_to_snapshot => false }, { + insta::assert_snapshot!(snapshot); + }); +} + +#[test] +fn test_cli_terminal() { + let snapshot = oxlint::cli::lint_command().run_inner(&["--help"]).unwrap_err().unwrap_stdout(); + insta::with_settings!({ prepend_module_to_snapshot => false }, { + insta::assert_snapshot!(snapshot); + }); +} + +// +#[expect(clippy::print_stdout)] +pub fn print_cli() { + println!("{}", generate_cli()); +} + +fn generate_cli() -> String { + let markdown = lint_command().render_markdown("oxlint"); + generate_cli_docs(&markdown, "oxlint", Some("Handle Warnings")) +} diff --git a/tasks/website/src/linter/json_schema.rs b/tasks/website_linter/src/json_schema.rs similarity index 99% rename from tasks/website/src/linter/json_schema.rs rename to tasks/website_linter/src/json_schema.rs index 37480f9511308..f990a4dfee702 100644 --- a/tasks/website/src/linter/json_schema.rs +++ b/tasks/website_linter/src/json_schema.rs @@ -1,3 +1,5 @@ +#![expect(clippy::disallowed_methods)] + use handlebars::Handlebars; use itertools::Itertools; use oxc_linter::Oxlintrc; @@ -7,6 +9,7 @@ use schemars::{ }; use serde::Serialize; +#[expect(clippy::print_stdout)] pub fn print_schema_json() { println!("{}", Oxlintrc::generate_schema_json()); } @@ -19,6 +22,7 @@ fn test_schema_markdown() { }); } +#[expect(clippy::print_stdout)] pub fn print_schema_markdown() { println!("{}", generate_schema_markdown()); } @@ -65,7 +69,7 @@ struct Root { /// Data passed to [`SECTION`] hbs template #[derive(Serialize)] -pub(super) struct Section { +pub struct Section { level: String, title: String, pub(super) instance_type: Option, diff --git a/tasks/website_linter/src/main.rs b/tasks/website_linter/src/main.rs new file mode 100644 index 0000000000000..74f3c929f23b4 --- /dev/null +++ b/tasks/website_linter/src/main.rs @@ -0,0 +1,21 @@ +#![expect(clippy::print_stderr)] + +use pico_args::Arguments; + +mod cli; +mod json_schema; +mod rules; + +fn main() { + let mut args = Arguments::from_env(); + let command = args.subcommand().expect("subcommands"); + let task = command.as_deref().unwrap_or("default"); + + match task { + "schema-json" => json_schema::print_schema_json(), + "schema-markdown" => json_schema::print_schema_markdown(), + "cli" => cli::print_cli(), + "rules" => rules::print_rules(args), + _ => eprintln!("Missing task command."), + } +} diff --git a/tasks/website/src/linter/rules/doc_page.rs b/tasks/website_linter/src/rules/doc_page.rs similarity index 99% rename from tasks/website/src/linter/rules/doc_page.rs rename to tasks/website_linter/src/rules/doc_page.rs index b4f831e576035..5221f4fd045c5 100644 --- a/tasks/website/src/linter/rules/doc_page.rs +++ b/tasks/website_linter/src/rules/doc_page.rs @@ -13,7 +13,7 @@ use schemars::{ schema::{InstanceType, Schema, SchemaObject, SingleOrVec}, }; -use crate::linter::json_schema::{self, Renderer}; +use crate::json_schema::{self, Renderer}; use super::HtmlWriter; @@ -205,6 +205,7 @@ const source = `{}`; } } +#[expect(clippy::disallowed_methods)] fn rule_source(rule: &RuleTableRow) -> String { use project_root::get_project_root; use std::sync::OnceLock; diff --git a/tasks/website/src/linter/rules/html.rs b/tasks/website_linter/src/rules/html.rs similarity index 100% rename from tasks/website/src/linter/rules/html.rs rename to tasks/website_linter/src/rules/html.rs diff --git a/tasks/website/src/linter/rules/mod.rs b/tasks/website_linter/src/rules/mod.rs similarity index 99% rename from tasks/website/src/linter/rules/mod.rs rename to tasks/website_linter/src/rules/mod.rs index 445e2d2ed7097..57cd472369b0c 100644 --- a/tasks/website/src/linter/rules/mod.rs +++ b/tasks/website_linter/src/rules/mod.rs @@ -37,6 +37,7 @@ Arguments: /// --git-ref dc9dc03872101c15b0d02f05ce45705565665829 /// ` /// +#[expect(clippy::print_stdout)] pub fn print_rules(mut args: Arguments) { let pwd = PathBuf::from(env::var("PWD").unwrap()); if args.contains(["-h", "--help"]) { diff --git a/tasks/website/src/linter/rules/table.rs b/tasks/website_linter/src/rules/table.rs similarity index 100% rename from tasks/website/src/linter/rules/table.rs rename to tasks/website_linter/src/rules/table.rs diff --git a/tasks/website/src/linter/rules/test.rs b/tasks/website_linter/src/rules/test.rs similarity index 100% rename from tasks/website/src/linter/rules/test.rs rename to tasks/website_linter/src/rules/test.rs diff --git a/tasks/website/src/linter/snapshots/cli.snap b/tasks/website_linter/src/snapshots/cli.snap similarity index 100% rename from tasks/website/src/linter/snapshots/cli.snap rename to tasks/website_linter/src/snapshots/cli.snap diff --git a/tasks/website/src/linter/snapshots/cli_terminal.snap b/tasks/website_linter/src/snapshots/cli_terminal.snap similarity index 100% rename from tasks/website/src/linter/snapshots/cli_terminal.snap rename to tasks/website_linter/src/snapshots/cli_terminal.snap diff --git a/tasks/website/src/linter/snapshots/schema_markdown.snap b/tasks/website_linter/src/snapshots/schema_markdown.snap similarity index 100% rename from tasks/website/src/linter/snapshots/schema_markdown.snap rename to tasks/website_linter/src/snapshots/schema_markdown.snap