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 db0a174f8..38c221f27 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 2d240170f..3fb081fb1 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 9209b11b2..af1264bae 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 858e5d7c2..581863799 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 a16e28a21..4c894be11 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 faf44d9c2..88bfa3de5 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 ec7662aa6..5b0d22ed2 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 ef05f50fd..2c6cb5fbc 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 3f7eb0b78..9d8f61895 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 be98cad39..f4289633c 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 e59c488da..ab45d824a 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 000000000..9441ad5df --- /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 000000000..151a658d8 --- /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 b0a9d45ee..963f89932 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 48b872f02..d0b1c654c 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."), } }