From f62951411d6d99ce4ffdd4b2f834392fb6c72de0 Mon Sep 17 00:00:00 2001 From: DonIsaac <22823424+DonIsaac@users.noreply.github.com> Date: Sat, 10 Aug 2024 00:13:06 +0000 Subject: [PATCH] feat(website): auto-generate rule docs pages (#4640) > AI-generated description because I'm lazy ### TL;DR This PR introduces the ability to generate documentation for linter rules and adds new methods and metadata for rule fix capabilities. To see what this looks like, please check out https://github.com/oxc-project/oxc-project.github.io/pull/165. ## Screenshots Hyperlinks to rule doc pages in auto-generated rules table image Example of a docs page image ### What changed? - Added `RuleFixMeta` to indicate rule fix capabilities - Introduced methods `is_none` and `is_pending` in `RuleFixMeta` - Modified `render_markdown_table` in `RuleTableSection` to accept an optional link prefix - Created new modules for rule documentation and HTML rendering - Updated `print_rules` function to generate markdown for rules and detailed documentation pages ### How to test? Run the `linter-rules` task with appropriate arguments to generate the markdown table and documentation pages. Verify the generated files for correctness and that all metadata is correctly displayed. ### Why make this change? To enhance the project documentation and provide clear rule fix capabilities, thereby improving the developer experience and easing the integration process. --- --- crates/oxc_linter/src/lib.rs | 4 +- crates/oxc_linter/src/rule.rs | 21 ++- .../oxc_linter/src/rules/import/namespace.rs | 10 +- .../src/rules/jsx_a11y/anchor_is_valid.rs | 22 +-- crates/oxc_linter/src/rules/jsx_a11y/lang.rs | 6 +- .../rules/jsx_a11y/no_distracting_elements.rs | 8 +- crates/oxc_linter/src/rules/jsx_a11y/scope.rs | 4 +- .../src/rules/nextjs/no_duplicate_head.rs | 4 +- .../src/rules/typescript/no_this_alias.rs | 10 +- .../no_unnecessary_type_constraint.rs | 4 +- crates/oxc_linter/src/table.rs | 19 ++- tasks/website/src/linter/rules/doc_page.rs | 57 ++++++++ tasks/website/src/linter/rules/html.rs | 121 +++++++++++++++++ tasks/website/src/linter/rules/mod.rs | 128 ++++++++++++++++++ .../src/linter/{rules.rs => rules/table.rs} | 14 +- tasks/website/src/main.rs | 2 +- 16 files changed, 377 insertions(+), 57 deletions(-) create mode 100644 tasks/website/src/linter/rules/doc_page.rs create mode 100644 tasks/website/src/linter/rules/html.rs create mode 100644 tasks/website/src/linter/rules/mod.rs rename tasks/website/src/linter/{rules.rs => rules/table.rs} (71%) diff --git a/crates/oxc_linter/src/lib.rs b/crates/oxc_linter/src/lib.rs index db0a174f886d0..38c221f27bb98 100644 --- a/crates/oxc_linter/src/lib.rs +++ b/crates/oxc_linter/src/lib.rs @@ -31,7 +31,7 @@ pub use crate::{ fixer::FixKind, frameworks::FrameworkFlags, options::{AllowWarnDeny, LintOptions}, - rule::{RuleCategory, RuleMeta, RuleWithSeverity}, + rule::{RuleCategory, RuleFixMeta, RuleMeta, RuleWithSeverity}, service::{LintService, LintServiceOptions}, }; use crate::{ @@ -146,7 +146,7 @@ impl Linter { pub fn print_rules(writer: &mut W) { let table = RuleTable::new(); for section in table.sections { - writeln!(writer, "{}", section.render_markdown_table()).unwrap(); + writeln!(writer, "{}", section.render_markdown_table(None)).unwrap(); } writeln!(writer, "Default: {}", table.turned_on_by_default_count).unwrap(); writeln!(writer, "Total: {}", table.total).unwrap(); diff --git a/crates/oxc_linter/src/rule.rs b/crates/oxc_linter/src/rule.rs index 2d240170fa6f4..3fb081fb1315d 100644 --- a/crates/oxc_linter/src/rule.rs +++ b/crates/oxc_linter/src/rule.rs @@ -117,7 +117,7 @@ impl fmt::Display for RuleCategory { // NOTE: this could be packed into a single byte if we wanted. I don't think // this is needed, but we could do it if it would have a performance impact. -/// Describes the auto-fixing capabilities of a [`Rule`]. +/// Describes the auto-fixing capabilities of a `Rule`. #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] pub enum RuleFixMeta { /// An auto-fix is not available. @@ -132,7 +132,12 @@ pub enum RuleFixMeta { } impl RuleFixMeta { - /// Does this [`Rule`] have some kind of auto-fix available? + #[inline] + pub fn is_none(self) -> bool { + matches!(self, Self::None) + } + + /// Does this `Rule` have some kind of auto-fix available? /// /// Also returns `true` for suggestions. #[inline] @@ -140,6 +145,11 @@ impl RuleFixMeta { matches!(self, Self::Fixable(_) | Self::Conditional(_)) } + #[inline] + pub fn is_pending(self) -> bool { + matches!(self, Self::FixPending) + } + pub fn supports_fix(self, kind: FixKind) -> bool { matches!(self, Self::Fixable(fix_kind) | Self::Conditional(fix_kind) if fix_kind.can_apply(kind)) } @@ -163,9 +173,10 @@ impl RuleFixMeta { let mut message = if kind.is_dangerous() { format!("dangerous {noun}") } else { noun.into() }; - let article = match message.chars().next().unwrap() { - 'a' | 'e' | 'i' | 'o' | 'u' => "An", - _ => "A", + let article = match message.chars().next() { + Some('a' | 'e' | 'i' | 'o' | 'u') => "An", + Some(_) => "A", + None => unreachable!(), }; if matches!(self, Self::Conditional(_)) { diff --git a/crates/oxc_linter/src/rules/import/namespace.rs b/crates/oxc_linter/src/rules/import/namespace.rs index 9209b11b20d44..af1264baec0db 100644 --- a/crates/oxc_linter/src/rules/import/namespace.rs +++ b/crates/oxc_linter/src/rules/import/namespace.rs @@ -40,10 +40,12 @@ pub struct Namespace { declare_oxc_lint!( /// ### What it does - /// Enforces names exist at the time they are dereferenced, when imported as a full namespace (i.e. import * as foo from './foo'; foo.bar(); will report if bar is not exported by ./foo.). - /// Will report at the import declaration if there are no exported names found. - /// Also, will report for computed references (i.e. foo["bar"]()). - /// Reports on assignment to a member of an imported namespace. + /// Enforces names exist at the time they are dereferenced, when imported as + /// a full namespace (i.e. `import * as foo from './foo'; foo.bar();` will + /// report if bar is not exported by `./foo.`). Will report at the import + /// declaration if there are no exported names found. Also, will report for + /// computed references (i.e. `foo["bar"]()`). Reports on assignment to a + /// member of an imported namespace. Namespace, correctness ); diff --git a/crates/oxc_linter/src/rules/jsx_a11y/anchor_is_valid.rs b/crates/oxc_linter/src/rules/jsx_a11y/anchor_is_valid.rs index 858e5d7c2ae63..5818637998967 100644 --- a/crates/oxc_linter/src/rules/jsx_a11y/anchor_is_valid.rs +++ b/crates/oxc_linter/src/rules/jsx_a11y/anchor_is_valid.rs @@ -56,7 +56,7 @@ declare_oxc_lint!( /// /// Consider the following: /// - /// ```javascript + /// ```jsx /// Perform action /// Perform action /// Perform action @@ -64,7 +64,7 @@ declare_oxc_lint!( /// /// All these anchor implementations indicate that the element is only used to execute JavaScript code. All the above should be replaced with: /// - /// ```javascript + /// ```jsx /// /// ``` /// ` @@ -78,33 +78,19 @@ declare_oxc_lint!( /// /// #### Valid /// - /// ```javascript + /// ```jsx /// navigate here - /// ``` - /// - /// ```javascript /// navigate here - /// ``` - /// - /// ```javascript /// navigate here /// ``` /// /// #### Invalid /// - /// ```javascript + /// ```jsx /// navigate here - /// ``` - /// ```javascript /// navigate here - /// ``` - /// ```javascript /// navigate here - /// ``` - /// ```javascript /// navigate here - /// ``` - /// ```javascript /// navigate here /// ``` /// diff --git a/crates/oxc_linter/src/rules/jsx_a11y/lang.rs b/crates/oxc_linter/src/rules/jsx_a11y/lang.rs index a16e28a21dd41..4c894be11e8bd 100644 --- a/crates/oxc_linter/src/rules/jsx_a11y/lang.rs +++ b/crates/oxc_linter/src/rules/jsx_a11y/lang.rs @@ -26,7 +26,7 @@ pub struct Lang; declare_oxc_lint!( /// ### What it does /// - /// The lang prop on the element must be a valid IETF's BCP 47 language tag. + /// The lang prop on the `` element must be a valid IETF's BCP 47 language tag. /// /// ### Why is this bad? /// @@ -39,13 +39,13 @@ declare_oxc_lint!( /// ### Example /// /// // good - /// ```javascript + /// ```jsx /// /// /// ``` /// /// // bad - /// ```javascript + /// ```jsx /// /// /// ```` diff --git a/crates/oxc_linter/src/rules/jsx_a11y/no_distracting_elements.rs b/crates/oxc_linter/src/rules/jsx_a11y/no_distracting_elements.rs index faf44d9c20ffe..88bfa3de54efc 100644 --- a/crates/oxc_linter/src/rules/jsx_a11y/no_distracting_elements.rs +++ b/crates/oxc_linter/src/rules/jsx_a11y/no_distracting_elements.rs @@ -22,15 +22,17 @@ declare_oxc_lint!( /// /// ### Why is this necessary? /// - /// Elements that can be visually distracting can cause accessibility issues with visually impaired users. - /// Such elements are most likely deprecated, and should be avoided. By default, and elements are visually distracting. + /// Elements that can be visually distracting can cause accessibility issues + /// with visually impaired users. Such elements are most likely deprecated, + /// and should be avoided. By default, `` and `` elements + /// are visually distracting. /// /// ### What it checks /// /// This rule checks for marquee and blink element. /// /// ### Example - /// ```javascript + /// ```jsx /// // Bad /// /// diff --git a/crates/oxc_linter/src/rules/jsx_a11y/scope.rs b/crates/oxc_linter/src/rules/jsx_a11y/scope.rs index ec7662aa6422a..5b0d22ed23427 100644 --- a/crates/oxc_linter/src/rules/jsx_a11y/scope.rs +++ b/crates/oxc_linter/src/rules/jsx_a11y/scope.rs @@ -23,7 +23,7 @@ pub struct Scope; declare_oxc_lint!( /// ### What it does /// - /// The scope prop should be used only on elements. + /// The scope prop should be used only on `` elements. /// /// ### Why is this bad? /// The scope attribute makes table navigation much easier for screen reader users, provided that it is used correctly. @@ -31,7 +31,7 @@ declare_oxc_lint!( /// A screen reader operates under the assumption that a table has a header and that this header specifies a scope. Because of the way screen readers function, having an accurate header makes viewing a table far more accessible and more efficient for people who use the device. /// /// ### Example - /// ```javascript + /// ```jsx /// // Bad ///
/// diff --git a/crates/oxc_linter/src/rules/nextjs/no_duplicate_head.rs b/crates/oxc_linter/src/rules/nextjs/no_duplicate_head.rs index ef05f50fdad25..2c6cb5fbc51d3 100644 --- a/crates/oxc_linter/src/rules/nextjs/no_duplicate_head.rs +++ b/crates/oxc_linter/src/rules/nextjs/no_duplicate_head.rs @@ -10,13 +10,13 @@ pub struct NoDuplicateHead; declare_oxc_lint!( /// ### What it does - /// Prevent duplicate usage of in pages/_document.js. + /// Prevent duplicate usage of `` in `pages/_document.js``. /// /// ### Why is this bad? /// This can cause unexpected behavior in your application. /// /// ### Example - /// ```javascript + /// ```jsx /// import Document, { Html, Head, Main, NextScript } from 'next/document' /// class MyDocument extends Document { /// static async getInitialProps(ctx) { diff --git a/crates/oxc_linter/src/rules/typescript/no_this_alias.rs b/crates/oxc_linter/src/rules/typescript/no_this_alias.rs index 3f7eb0b78e5f0..9d8f618950d88 100644 --- a/crates/oxc_linter/src/rules/typescript/no_this_alias.rs +++ b/crates/oxc_linter/src/rules/typescript/no_this_alias.rs @@ -59,12 +59,14 @@ declare_oxc_lint!( /// /// ### Why is this bad? /// - /// Generic type parameters () in TypeScript may be "constrained" with an extends keyword. - /// When no extends is provided, type parameters default a constraint to unknown. It is therefore redundant to extend from any or unknown. + /// Generic type parameters (``) in TypeScript may be "constrained" with + /// an extends keyword. When no extends is provided, type parameters + /// default a constraint to unknown. It is therefore redundant to extend + /// from any or unknown. /// - /// the rule doesn't allow const {allowedName} = this + /// the rule doesn't allow `const {allowedName} = this` /// this is to keep 1:1 with eslint implementation - /// sampe with obj. = this + /// sampe with `obj. = this` /// ``` NoThisAlias, correctness diff --git a/crates/oxc_linter/src/rules/typescript/no_unnecessary_type_constraint.rs b/crates/oxc_linter/src/rules/typescript/no_unnecessary_type_constraint.rs index be98cad392d1a..f4289633c4fcb 100644 --- a/crates/oxc_linter/src/rules/typescript/no_unnecessary_type_constraint.rs +++ b/crates/oxc_linter/src/rules/typescript/no_unnecessary_type_constraint.rs @@ -28,11 +28,11 @@ declare_oxc_lint!( /// /// ### Why is this bad? /// - /// Generic type parameters () in TypeScript may be "constrained" with an extends keyword. + /// Generic type parameters (``) in TypeScript may be "constrained" with an extends keyword. /// When no extends is provided, type parameters default a constraint to unknown. It is therefore redundant to extend from any or unknown. /// /// ### Example - /// ```javascript + /// ```typescript /// interface FooAny {} /// interface FooUnknown {} /// type BarAny = {}; diff --git a/crates/oxc_linter/src/table.rs b/crates/oxc_linter/src/table.rs index e59c488da0737..ab45d824a6bd7 100644 --- a/crates/oxc_linter/src/table.rs +++ b/crates/oxc_linter/src/table.rs @@ -1,8 +1,8 @@ -use std::fmt::Write; +use std::{borrow::Cow, fmt::Write}; use rustc_hash::{FxHashMap, FxHashSet}; -use crate::{rules::RULES, Linter, RuleCategory}; +use crate::{rules::RULES, Linter, RuleCategory, RuleFixMeta}; pub struct RuleTable { pub sections: Vec, @@ -23,6 +23,7 @@ pub struct RuleTableRow { pub category: RuleCategory, pub documentation: Option<&'static str>, pub turned_on_by_default: bool, + pub autofix: RuleFixMeta, } impl Default for RuleTable { @@ -49,6 +50,7 @@ impl RuleTable { plugin: rule.plugin_name().to_string(), category: rule.category(), turned_on_by_default: default_rules.contains(name), + autofix: rule.fix(), } }) .collect::>(); @@ -88,7 +90,11 @@ impl RuleTable { } impl RuleTableSection { - pub fn render_markdown_table(&self) -> String { + /// 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 { let mut s = String::new(); let category = &self.category; let rows = &self.rows; @@ -108,7 +114,12 @@ impl RuleTableSection { let plugin_name = &row.plugin; let (default, default_width) = if row.turned_on_by_default { ("✅", 6) } else { ("", 7) }; - writeln!(s, "| {rule_name: Result { + const APPROX_FIX_CATEGORY_AND_PLUGIN_LEN: usize = 512; + let RuleTableRow { name, documentation, plugin, turned_on_by_default, autofix, .. } = rule; + + let mut page = HtmlWriter::with_capacity( + documentation.map_or(0, str::len) + name.len() + APPROX_FIX_CATEGORY_AND_PLUGIN_LEN, + ); + + writeln!( + page, + "\n", + file!() + )?; + writeln!(page, "# {plugin}/{name}\n")?; + + // rule metadata + page.div(r#"class="rule-meta""#, |p| { + if *turned_on_by_default { + p.span(r#"class="default-on""#, |p| { + p.writeln("✅ This rule is turned on by default.") + })?; + } + + if let Some(emoji) = fix_emoji(*autofix) { + p.span(r#"class="fix""#, |p| { + p.writeln(format!("{} {}", emoji, autofix.description())) + })?; + } + + Ok(()) + })?; + + // rule documentation + if let Some(docs) = documentation { + writeln!(page, "\n{}", *docs)?; + } + + // TODO: link to rule source + + Ok(page.into()) +} + +fn fix_emoji(fix: RuleFixMeta) -> Option<&'static str> { + match fix { + RuleFixMeta::None => None, + RuleFixMeta::FixPending => Some("🚧"), + RuleFixMeta::Conditional(_) | RuleFixMeta::Fixable(_) => Some("🛠️"), + } +} diff --git a/tasks/website/src/linter/rules/html.rs b/tasks/website/src/linter/rules/html.rs new file mode 100644 index 0000000000000..9441ad5dff622 --- /dev/null +++ b/tasks/website/src/linter/rules/html.rs @@ -0,0 +1,121 @@ +use std::{ + cell::RefCell, + fmt::{self, Write}, +}; + +#[derive(Debug)] +pub(crate) struct HtmlWriter { + inner: RefCell, +} + +impl fmt::Write for HtmlWriter { + #[inline] + fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> fmt::Result { + self.inner.get_mut().write_fmt(args) + } + + #[inline] + fn write_char(&mut self, c: char) -> fmt::Result { + self.inner.get_mut().write_char(c) + } + + #[inline] + fn write_str(&mut self, s: &str) -> fmt::Result { + self.inner.get_mut().write_str(s) + } +} + +impl From for String { + #[inline] + fn from(html: HtmlWriter) -> Self { + html.into_inner() + } +} + +impl HtmlWriter { + pub fn new() -> Self { + Self { inner: RefCell::new(String::new()) } + } + + pub fn with_capacity(capacity: usize) -> Self { + Self { inner: RefCell::new(String::with_capacity(capacity)) } + } + + pub fn writeln>(&self, line: S) -> fmt::Result { + writeln!(self.inner.borrow_mut(), "{}", line.as_ref()) + } + + pub fn into_inner(self) -> String { + self.inner.into_inner() + } + + pub fn html(&self, tag: &'static str, attrs: &str, inner: F) -> fmt::Result + where + F: FnOnce(&Self) -> fmt::Result, + { + // Allocate space for the HTML being printed + let write_amt_guess = { + // opening tag. 2 extra for '<' and '>' + 2 + tag.len() + attrs.len() + + // approximate inner content length + 256 + + // closing tag. 3 extra for '' + 3 + tag.len() + }; + let mut s = self.inner.borrow_mut(); + s.reserve(write_amt_guess); + + // Write the opening tag + write!(s, "<{tag}")?; + if attrs.is_empty() { + writeln!(s, ">")?; + } else { + writeln!(s, " {attrs}>")?; + } + + // Callback produces the inner content + drop(s); + inner(self)?; + + // Write the closing tag + writeln!(self.inner.borrow_mut(), "")?; + + Ok(()) + } +} + +macro_rules! make_tag { + ($name:ident) => { + impl HtmlWriter { + #[inline] + pub fn $name(&self, attrs: &str, inner: F) -> fmt::Result + where + F: FnOnce(&Self) -> fmt::Result, + { + self.html(stringify!($name), attrs, inner) + } + } + }; +} + +make_tag!(div); +make_tag!(span); + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_div() { + let html = HtmlWriter::new(); + html.div("", |html| html.writeln("Hello, world!")).unwrap(); + + assert_eq!( + html.into_inner().as_str(), + "
+Hello, world! +
+" + ); + } +} diff --git a/tasks/website/src/linter/rules/mod.rs b/tasks/website/src/linter/rules/mod.rs new file mode 100644 index 0000000000000..151a658d8c0a8 --- /dev/null +++ b/tasks/website/src/linter/rules/mod.rs @@ -0,0 +1,128 @@ +mod doc_page; +mod html; +mod table; + +use std::{ + borrow::Cow, + env, fs, + path::{Path, PathBuf}, + process, +}; + +use doc_page::render_rule_docs_page; +use oxc_linter::table::RuleTable; +use pico_args::Arguments; +use table::render_rules_table; + +const HELP: &str = " +usage: linter-rules [args] + +Arguments: + -t,--table Path to file where rule markdown table will be saved. + -r,--rule-docs Path to directory where rule doc pages will be saved. + A directory will be created if one doesn't exist. + -h,--help Show this help message. + +"; + +/// `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 +/// ` +/// +pub fn print_rules(mut args: Arguments) { + let pwd = PathBuf::from(env::var("PWD").unwrap()); + if args.contains(["-h", "--help"]) { + println!("{HELP}"); + return; + } + + let table = RuleTable::new(); + let table_path = args.opt_value_from_str::<_, PathBuf>(["-t", "--table"]).unwrap(); + let rules_dir = args.opt_value_from_str::<_, PathBuf>(["-r", "--rule-docs"]).unwrap(); + + let (prefix, root) = rules_dir.as_ref().and_then(|p| p.as_os_str().to_str()).map_or( + (Cow::Borrowed(""), None), + |p| { + if p.contains("src/docs") { + let split = p.split("src/docs").collect::>(); + assert!(split.len() > 1); + let root = split[0]; + let root = pwd.join(root).canonicalize().unwrap(); + let prefix = Cow::Owned("/docs".to_string() + split.last().unwrap()); + (prefix, Some(root)) + } else { + (Cow::Borrowed(p), None) + } + }, + ); + + if let Some(table_path) = table_path { + let table_path = pwd.join(table_path).canonicalize().unwrap(); + + println!("Rendering rules table..."); + let rules_table = render_rules_table(&table, prefix.as_ref()); + fs::write(table_path, rules_table).unwrap(); + } + + if let Some(rules_dir) = rules_dir { + println!("Rendering rule doc pages..."); + let rules_dir = pwd.join(rules_dir); + if !rules_dir.exists() { + fs::create_dir_all(&rules_dir).unwrap(); + } + let rules_dir = rules_dir.canonicalize().unwrap(); + assert!( + !rules_dir.is_file(), + "Cannot write rule docs to a file. Please specify a directory." + ); + write_rule_doc_pages(&table, &rules_dir); + + // auto-fix code and natural language issues + if let Some(root) = root { + println!("Formatting rule doc pages..."); + prettier(&root, &rules_dir); + println!("Fixing textlint issues..."); + textlint(&root); + } + } + + println!("Done."); +} + +fn write_rule_doc_pages(table: &RuleTable, outdir: &Path) { + for rule in table.sections.iter().flat_map(|section| §ion.rows) { + let plugin_path = outdir.join(&rule.plugin); + fs::create_dir_all(&plugin_path).unwrap(); + let page_path = plugin_path.join(format!("{}.md", rule.name)); + println!("{}", page_path.display()); + let docs = render_rule_docs_page(rule).unwrap(); + fs::write(&page_path, docs).unwrap(); + } +} + +/// Run prettier and fix style issues in generated rule doc pages. +fn prettier(website_root: &Path, rule_docs_path: &Path) { + assert!(rule_docs_path.is_dir(), "Rule docs path must be a directory."); + assert!(rule_docs_path.is_absolute(), "Rule docs path must be an absolute path."); + let relative_path = rule_docs_path.strip_prefix(website_root).unwrap(); + let path_str = + relative_path.to_str().expect("Invalid rule docs path: could not convert to str"); + let generated_md_glob = format!("{path_str}/**/*.md"); + + process::Command::new("pnpm") + .current_dir(website_root) + .args(["run", "fmt", "--write", &generated_md_glob]) + .status() + .unwrap(); +} + +/// Run textlint and fix any issues it finds. +fn textlint(website_root: &Path) { + assert!(website_root.is_dir(), "Rule docs path must be a directory."); + process::Command::new("pnpm") + .current_dir(website_root) + .args(["run", "textlint:fix"]) + .status() + .unwrap(); +} diff --git a/tasks/website/src/linter/rules.rs b/tasks/website/src/linter/rules/table.rs similarity index 71% rename from tasks/website/src/linter/rules.rs rename to tasks/website/src/linter/rules/table.rs index b0a9d45ee2ec0..963f899328d1e 100644 --- a/tasks/website/src/linter/rules.rs +++ b/tasks/website/src/linter/rules/table.rs @@ -2,20 +2,20 @@ use oxc_linter::table::RuleTable; // `cargo run -p website linter-rules > /path/to/oxc/oxc-project.github.io/src/docs/guide/usage/linter/generated-rules.md` // -pub fn print_rules() { - let table = RuleTable::new(); - +/// `docs_prefix` is a path prefix to the base URL all rule documentation pages +/// share in common. +pub fn render_rules_table(table: &RuleTable, docs_prefix: &str) -> String { let total = table.total; let turned_on_by_default_count = table.turned_on_by_default_count; let body = table .sections - .into_iter() - .map(|section| section.render_markdown_table()) + .iter() + .map(|s| s.render_markdown_table(Some(docs_prefix))) .collect::>() .join("\n"); - println!(" + format!(" # Rules The progress of all rule implementations is tracked [here](https://github.com/oxc-project/oxc/issues/481). @@ -29,5 +29,5 @@ The progress of all rule implementations is tracked [here](https://github.com/ox -"); +") } diff --git a/tasks/website/src/main.rs b/tasks/website/src/main.rs index 48b872f02f742..d0b1c654c1e29 100644 --- a/tasks/website/src/main.rs +++ b/tasks/website/src/main.rs @@ -12,7 +12,7 @@ fn main() { "linter-schema-json" => linter::print_schema_json(), "linter-schema-markdown" => linter::print_schema_markdown(), "linter-cli" => linter::print_cli(), - "linter-rules" => linter::print_rules(), + "linter-rules" => linter::print_rules(args), _ => println!("Missing task command."), } }