Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions apps/oxlint/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,15 @@ mod js_plugins;
))]
#[global_allocator]
static GLOBAL: mimalloc_safe::MiMalloc = mimalloc_safe::MiMalloc;

/// Return a JSON blob containing metadata for all available oxlint rules.
///
/// This uses the internal JSON output formatter to generate the full list.
///
/// # Panics
/// Panics if the JSON generation fails, which should never happen under normal circumstances.
pub fn get_all_rules_json() -> String {
use crate::output_formatter::{OutputFormat, OutputFormatter};

OutputFormatter::new(OutputFormat::Json).all_rules().expect("Failed to generate rules JSON")
}
2 changes: 1 addition & 1 deletion apps/oxlint/src/lint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,7 @@ impl CliRunner {

let table = RuleTable::default();
for section in &table.sections {
let md = section.render_markdown_table_cli(None, &enabled);
let md = section.render_markdown_table_cli(&enabled);
print_and_flush_stdout(stdout, &md);
print_and_flush_stdout(stdout, "\n");
}
Expand Down
2 changes: 1 addition & 1 deletion apps/oxlint/src/output_formatter/default.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ impl InternalFormatter for DefaultOutputFormatter {
let mut output = String::new();
let table = RuleTable::default();
for section in table.sections {
output.push_str(section.render_markdown_table(None).as_str());
output.push_str(section.render_markdown_table().as_str());
output.push('\n');
}
output.push_str(format!("Default: {}\n", table.turned_on_by_default_count).as_str());
Expand Down
52 changes: 8 additions & 44 deletions crates/oxc_linter/src/table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,11 +119,7 @@ impl RuleTableSection {
/// When `enabled` is [`Some`], an "Enabled?" column is added and the set
/// is used to determine which rules are enabled. When `enabled` is
/// [`None`], the column is omitted (used by docs rendering).
fn render_markdown_table_inner(
&self,
link_prefix: Option<&str>,
enabled: Option<&FxHashSet<&str>>,
) -> String {
fn render_markdown_table_inner(&self, enabled: Option<&FxHashSet<&str>>) -> String {
const FIX_EMOJI_COL_WIDTH: usize = 10;
const DEFAULT_EMOJI_COL_WIDTH: usize = 9;
const ENABLED_EMOJI_COL_WIDTH: usize = 10;
Expand Down Expand Up @@ -182,11 +178,7 @@ impl RuleTableSection {

let (default, default_width) =
if row.turned_on_by_default { ("✅", DEFAULT - 1) } else { ("", DEFAULT) };
let rendered_name = if let Some(prefix) = link_prefix {
Cow::Owned(format!("[{rule_name}]({prefix}/{plugin_name}/{rule_name}.html)"))
} else {
Cow::Borrowed(rule_name)
};
let rendered_name = Cow::Borrowed(rule_name);
// Improved mapping for emoji column alignment, allowing FIX to grow for negative display widths
let (fix_emoji, fix_emoji_width) =
Self::calculate_fix_emoji_width(row.autofix.emoji(), FIX);
Expand Down Expand Up @@ -230,19 +222,12 @@ impl RuleTableSection {
}

/// Renders all the rules in this section as a markdown table.
///
/// Provide [`Some`] prefix to render the rule name as a link. Provide
/// [`None`] to just display the rule name as text.
pub fn render_markdown_table(&self, link_prefix: Option<&str>) -> String {
self.render_markdown_table_inner(link_prefix, None)
pub fn render_markdown_table(&self) -> String {
self.render_markdown_table_inner(None)
}

pub fn render_markdown_table_cli(
&self,
link_prefix: Option<&str>,
enabled: &FxHashSet<&str>,
) -> String {
self.render_markdown_table_inner(link_prefix, Some(enabled))
pub fn render_markdown_table_cli(&self, enabled: &FxHashSet<&str>) -> String {
self.render_markdown_table_inner(Some(enabled))
}
}

Expand All @@ -264,47 +249,26 @@ mod test {
fn test_table_no_links() {
let options = Options::gfm();
for section in &table().sections {
let rendered_table = section.render_markdown_table(None);
assert!(!rendered_table.is_empty());
assert_eq!(rendered_table.split('\n').count(), 5 + section.rows.len());

let html = to_html_with_options(&rendered_table, &options).unwrap();
assert!(!html.is_empty());
assert!(html.contains("<table>"));
}
}

#[test]
fn test_table_with_links() {
const PREFIX: &str = "/foo/bar";
const PREFIX_WITH_SLASH: &str = "/foo/bar/";

let options = Options::gfm();

for section in &table().sections {
let rendered_table = section.render_markdown_table(Some(PREFIX));
let rendered_table = section.render_markdown_table();
assert!(!rendered_table.is_empty());
assert_eq!(rendered_table.split('\n').count(), 5 + section.rows.len());

let html = to_html_with_options(&rendered_table, &options).unwrap();
assert!(!html.is_empty());
assert!(html.contains("<table>"));
assert!(html.contains(PREFIX_WITH_SLASH));
}
}

#[test]
fn test_table_cli_enabled_column() {
const PREFIX: &str = "/foo/bar";

for section in &table().sections {
// enable the first rule in the section for the CLI view
let mut enabled = FxHashSet::default();
if let Some(first) = section.rows.first() {
enabled.insert(first.name);
}

let rendered_table = section.render_markdown_table_cli(Some(PREFIX), &enabled);
let rendered_table = section.render_markdown_table_cli(&enabled);
assert!(!rendered_table.is_empty());
// same number of lines as other renderer (header + desc + separator + rows + trailing newline)
assert_eq!(rendered_table.split('\n').count(), 5 + section.rows.len());
Expand Down
2 changes: 1 addition & 1 deletion justfile
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ watch-playground:
# When testing changes to the website documentation, you may also want to run `pnpm run fmt`
# 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) --rule-count {{path}}/src/docs/guide/usage
cargo run -p website_linter rules --rules-json {{path}}/.vitepress/data/rules.json --rule-docs {{path}}/src/docs/guide/usage/linter/rules --git-ref $(git rev-parse HEAD) --rule-count {{path}}/src/docs/guide/usage
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
Expand Down
38 changes: 12 additions & 26 deletions tasks/website_linter/src/rules/mod.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
#![expect(clippy::print_stderr)]
mod doc_page;
mod html;
mod table;

use std::{
borrow::Cow,
env, fs,
path::{Path, PathBuf},
};
Expand All @@ -14,13 +12,12 @@ use html::HtmlWriter;
use oxc_linter::{Oxlintrc, table::RuleTable};
use pico_args::Arguments;
use schemars::{SchemaGenerator, r#gen::SchemaSettings};
use table::render_rules_table;

const HELP: &str = "
usage: linter-rules [args]

Arguments:
-t,--table <path> Path to file where rule markdown table will be saved.
-j,--rules-json <path> Path to file where rules json blob will be saved.
-r,--rule-docs <path> Path to directory where rule doc pages will be saved.
A directory will be created if one doesn't exist.
-c,--rule-count <path> Path to directory where rule count data file will be saved.
Expand All @@ -32,11 +29,11 @@ Arguments:

/// `print_rules`
///
/// `cargo run -p website linter-rules --table
/// /path/to/oxc/oxc-project.github.io/src/docs/guide/usage/linter/generated-rules.md
/// --rule-docs /path/to/oxc/oxc-project.github.io/src/docs/guide/usage/linter/rules
/// --rule-count /path/to/oxc/oxc-project.github.io/src/docs/guide/usage
/// --git-ref dc9dc03872101c15b0d02f05ce45705565665829
/// `cargo run -p website linter-rules
/// --rules-json /path/to/oxc/oxc-project.github.io/.vitepress/data/rules.json
/// --rule-docs /path/to/oxc/oxc-project.github.io/src/docs/guide/usage/linter/rules
/// --rule-count /path/to/oxc/oxc-project.github.io/src/docs/guide/usage
/// --git-ref dc9dc03872101c15b0d02f05ce45705565665829
/// `
/// <https://oxc.rs/docs/guide/usage/linter/rules.html>
#[expect(clippy::print_stdout)]
Expand All @@ -48,30 +45,19 @@ pub fn print_rules(mut args: Arguments) {
}

let git_ref: Option<String> = args.opt_value_from_str("--git-ref").unwrap();
let table_path = args.opt_value_from_str::<_, PathBuf>(["-t", "--table"]).unwrap();
let rules_json_path = args.opt_value_from_str::<_, PathBuf>(["-j", "--rules-json"]).unwrap();
let rules_dir = args.opt_value_from_str::<_, PathBuf>(["-r", "--rule-docs"]).unwrap();
let rule_count_dir = args.opt_value_from_str::<_, PathBuf>(["-c", "--rule-count"]).unwrap();

let prefix =
rules_dir.as_ref().and_then(|p| p.as_os_str().to_str()).map_or(Cow::Borrowed(""), |p| {
if p.contains("src/docs") {
let split = p.split("src/docs").collect::<Vec<_>>();
assert!(split.len() > 1);
Cow::Owned("/docs".to_string() + split.last().unwrap())
} else {
Cow::Borrowed(p)
}
});

let mut generator = SchemaGenerator::new(SchemaSettings::default());
let table = RuleTable::new(Some(&mut generator));

if let Some(table_path) = table_path {
let table_path = pwd.join(table_path).canonicalize().unwrap();
if let Some(rules_json_path) = rules_json_path {
let rules_json_path = pwd.join(rules_json_path).canonicalize().unwrap();

eprintln!("Rendering rules table...");
let rules_table = render_rules_table(&table, prefix.as_ref());
fs::write(table_path, rules_table).unwrap();
eprintln!("Rendering rules JSON blob...");
let rules_json = oxlint::get_all_rules_json();
fs::write(rules_json_path, rules_json).unwrap();
}

if let Some(rules_dir) = &rules_dir {
Expand Down
42 changes: 0 additions & 42 deletions tasks/website_linter/src/rules/table.rs

This file was deleted.

Loading