From 8f2a566f9f3af57aff0a1fbe40204d05d15a1434 Mon Sep 17 00:00:00 2001
From: DonIsaac <22823424+DonIsaac@users.noreply.github.com>
Date: Sat, 10 Aug 2024 05:03:09 +0000
Subject: [PATCH] test(linter): ensure rule docs have valid syntax (#4644)
Adds tests for rule documentation by
1. Compiling doc markdown into HTML, which ensures docs use valid markdown syntax
2. Converts the generated HTML into JSX and parses the results with the parser, ensuring the generated HTML is valid
Has the added benefit of adding a lot of JSX test cases to the parser. I've also fixed all violations for these tests in this PR.
---
Cargo.lock | 342 +++++++++++++++++-
crates/oxc_linter/Cargo.toml | 1 +
crates/oxc_linter/src/rule.rs | 12 +-
.../src/rules/eslint/for_direction.rs | 2 +-
.../src/rules/eslint/guard_for_in.rs | 4 +-
.../oxc_linter/src/rules/eslint/max_lines.rs | 13 +-
.../src/rules/eslint/no_await_in_loop.rs | 12 +-
.../src/rules/eslint/no_case_declarations.rs | 26 +-
.../src/rules/eslint/no_cond_assign.rs | 14 +-
.../src/rules/eslint/no_continue.rs | 18 +-
.../src/rules/eslint/no_debugger.rs | 9 +-
.../src/rules/eslint/no_ex_assign.rs | 10 +-
.../src/rules/eslint/no_undefined.rs | 7 +-
.../src/rules/eslint/no_useless_escape.rs | 38 ++
crates/oxc_linter/src/rules/eslint/no_void.rs | 6 +-
.../src/rules/eslint/unicode_bom.rs | 2 +-
crates/oxc_linter/src/rules/import/named.rs | 47 ++-
.../src/rules/jest/no_conditional_expect.rs | 4 +-
.../src/rules/jest/no_restricted_matchers.rs | 3 +-
.../rules/jest/no_test_return_statement.rs | 3 +
.../rules/jest/require_top_level_describe.rs | 12 +-
.../oxc_linter/src/rules/jsx_a11y/alt_text.rs | 13 +-
.../src/rules/jsx_a11y/anchor_is_valid.rs | 28 +-
.../aria_activedescendant_has_tabindex.rs | 36 +-
.../src/rules/jsx_a11y/aria_props.rs | 8 +-
.../src/rules/jsx_a11y/aria_role.rs | 66 ++--
.../src/rules/jsx_a11y/autocomplete_valid.rs | 2 +-
.../src/rules/jsx_a11y/heading_has_content.rs | 2 +-
.../src/rules/jsx_a11y/html_has_lang.rs | 2 +-
.../src/rules/jsx_a11y/iframe_has_title.rs | 2 +-
.../src/rules/jsx_a11y/img_redundant_alt.rs | 2 +-
.../src/rules/jsx_a11y/no_access_key.rs | 2 +-
.../jsx_a11y/no_aria_hidden_on_focusable.rs | 2 +-
.../src/rules/jsx_a11y/no_autofocus.rs | 25 +-
.../src/rules/jsx_a11y/no_redundant_roles.rs | 6 +-
.../rules/jsx_a11y/prefer_tag_over_role.rs | 2 +-
.../jsx_a11y/role_has_required_aria_props.rs | 10 +-
.../rules/jsx_a11y/tabindex_no_positive.rs | 12 +-
.../src/rules/nextjs/google_font_display.rs | 59 ++-
.../src/rules/oxc/approx_constant.rs | 9 +-
.../oxc_linter/src/rules/oxc/no_const_enum.rs | 8 +-
.../src/rules/oxc/only_used_in_recursion.rs | 6 +-
.../oxc_linter/src/rules/promise/avoid_new.rs | 2 +-
.../src/rules/promise/param_names.rs | 4 +-
.../src/rules/react/button_has_type.rs | 6 +-
.../checked_requires_onchange_or_readonly.rs | 2 +-
.../src/rules/react/jsx_boolean_value.rs | 2 +-
crates/oxc_linter/src/rules/react/jsx_key.rs | 2 +-
.../rules/react/jsx_no_comment_textnodes.rs | 2 +-
.../src/rules/react/jsx_no_duplicate_props.rs | 2 +-
.../src/rules/react/jsx_no_target_blank.rs | 2 +-
.../rules/react/jsx_no_useless_fragment.rs | 2 +-
.../src/rules/react/no_children_prop.rs | 2 +-
.../oxc_linter/src/rules/react/no_danger.rs | 20 +-
.../rules/react/no_direct_mutation_state.rs | 2 +-
.../src/rules/react/no_find_dom_node.rs | 2 +-
.../src/rules/react/no_is_mounted.rs | 2 +-
.../src/rules/react/no_render_return_value.rs | 2 +-
.../src/rules/react/no_set_state.rs | 2 +-
.../src/rules/react/no_string_refs.rs | 2 +-
.../src/rules/react/prefer_es6_class.rs | 2 +-
.../src/rules/react/react_in_jsx_scope.rs | 5 +-
.../src/rules/react/require_render_return.rs | 2 +-
.../react/void_dom_elements_no_children.rs | 2 +-
.../rules/react_perf/jsx_no_jsx_as_prop.rs | 2 +-
.../react_perf/jsx_no_new_array_as_prop.rs | 4 +-
.../react_perf/jsx_no_new_function_as_prop.rs | 2 +-
.../react_perf/jsx_no_new_object_as_prop.rs | 2 +-
.../src/rules/typescript/ban_ts_comment.rs | 2 +-
.../rules/typescript/ban_tslint_comment.rs | 2 +-
.../consistent_indexed_object_style.rs | 4 +-
.../typescript/consistent_type_imports.rs | 2 +-
.../explicit_function_return_type.rs | 55 ++-
.../no_confusing_non_null_assertion.rs | 2 +-
.../typescript/no_duplicate_enum_values.rs | 12 +-
.../src/rules/typescript/no_dynamic_delete.rs | 2 +-
.../rules/typescript/no_empty_interface.rs | 2 +-
.../typescript/no_extra_non_null_assertion.rs | 2 +-
.../rules/typescript/no_extraneous_class.rs | 2 +-
.../typescript/no_import_type_side_effects.rs | 30 +-
.../src/rules/typescript/no_misused_new.rs | 21 +-
...no_non_null_asserted_nullish_coalescing.rs | 2 +-
.../no_non_null_asserted_optional_chain.rs | 2 +-
.../rules/typescript/no_non_null_assertion.rs | 2 +-
.../no_unsafe_declaration_merging.rs | 2 +-
.../typescript/no_useless_empty_export.rs | 4 +-
.../src/rules/typescript/prefer_as_const.rs | 2 +-
.../src/rules/typescript/prefer_for_of.rs | 2 +-
.../rules/typescript/prefer_function_type.rs | 2 +-
.../typescript/prefer_literal_enum_member.rs | 2 +-
.../typescript/prefer_ts_expect_error.rs | 2 +-
.../typescript/triple_slash_reference.rs | 2 +-
.../src/rules/unicorn/no_array_reduce.rs | 2 +
.../unicorn/no_await_expression_member.rs | 10 +-
.../unicorn/no_await_in_promise_methods.rs | 24 +-
.../src/rules/unicorn/no_document_cookie.rs | 24 +-
.../src/rules/unicorn/no_process_exit.rs | 4 +-
.../no_single_promise_in_promise_methods.rs | 25 +-
.../src/rules/unicorn/no_thenable.rs | 14 +-
.../src/rules/unicorn/no_unnecessary_await.rs | 5 +-
.../rules/unicorn/no_useless_length_check.rs | 17 +-
.../rules/unicorn/no_useless_spread/mod.rs | 8 +-
.../unicorn/prefer_blob_reading_methods.rs | 28 +-
.../rules/unicorn/prefer_modern_dom_apis.rs | 14 +-
.../unicorn/prefer_string_replace_all.rs | 14 +-
.../src/rules/unicorn/prefer_string_slice.rs | 8 +
.../unicorn/text_encoding_identifier_case.rs | 20 +-
.../src/snapshots/google_font_display.snap | 10 +-
crates/oxc_linter/src/table.rs | 46 +++
tasks/website/Cargo.toml | 9 +-
tasks/website/src/linter/rules/mod.rs | 5 +
tasks/website/src/linter/rules/test.rs | 117 ++++++
112 files changed, 1192 insertions(+), 342 deletions(-)
create mode 100644 tasks/website/src/linter/rules/test.rs
diff --git a/Cargo.lock b/Cargo.lock
index fdbf6286dd174..4e5d5417394a7 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -24,6 +24,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
dependencies = [
"cfg-if",
+ "getrandom",
"once_cell",
"version_check",
"zerocopy",
@@ -192,6 +193,12 @@ dependencies = [
"allocator-api2",
]
+[[package]]
+name = "byteorder"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
+
[[package]]
name = "bytes"
version = "1.6.0"
@@ -388,6 +395,29 @@ dependencies = [
"typenum",
]
+[[package]]
+name = "cssparser"
+version = "0.31.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b3df4f93e5fbbe73ec01ec8d3f68bba73107993a5b1e7519273c32db9b0d5be"
+dependencies = [
+ "cssparser-macros",
+ "dtoa-short",
+ "itoa",
+ "phf 0.11.2",
+ "smallvec",
+]
+
+[[package]]
+name = "cssparser-macros"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331"
+dependencies = [
+ "quote",
+ "syn",
+]
+
[[package]]
name = "ctor"
version = "0.2.8"
@@ -440,6 +470,17 @@ dependencies = [
"powerfmt",
]
+[[package]]
+name = "derive_more"
+version = "0.99.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
[[package]]
name = "digest"
version = "0.10.7"
@@ -450,6 +491,21 @@ dependencies = [
"crypto-common",
]
+[[package]]
+name = "dtoa"
+version = "1.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dcbb2bf8e87535c23f7a8a321e364ce21462d0ff10cb6407820e8e96dfff6653"
+
+[[package]]
+name = "dtoa-short"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87"
+dependencies = [
+ "dtoa",
+]
+
[[package]]
name = "dunce"
version = "1.0.4"
@@ -462,6 +518,12 @@ version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125"
+[[package]]
+name = "ego-tree"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3a68a4904193147e0a8dec3314640e6db742afd5f6e634f428a6af230d9b3591"
+
[[package]]
name = "either"
version = "1.13.0"
@@ -559,6 +621,16 @@ dependencies = [
"percent-encoding",
]
+[[package]]
+name = "futf"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843"
+dependencies = [
+ "mac",
+ "new_debug_unreachable",
+]
+
[[package]]
name = "futures"
version = "0.3.30"
@@ -648,6 +720,15 @@ dependencies = [
"slab",
]
+[[package]]
+name = "fxhash"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
+dependencies = [
+ "byteorder",
+]
+
[[package]]
name = "generic-array"
version = "0.14.7"
@@ -658,6 +739,15 @@ dependencies = [
"version_check",
]
+[[package]]
+name = "getopts"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5"
+dependencies = [
+ "unicode-width",
+]
+
[[package]]
name = "getrandom"
version = "0.2.15"
@@ -762,6 +852,20 @@ version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
+[[package]]
+name = "html5ever"
+version = "0.27.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c13771afe0e6e846f1e67d038d4cb29998a6779f93c809212e4e9c32efd244d4"
+dependencies = [
+ "log",
+ "mac",
+ "markup5ever",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
[[package]]
name = "httparse"
version = "1.9.4"
@@ -937,7 +1041,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e310b3a6b5907f99202fcdb4960ff45b93735d7c7d96b760fcff8db2dc0e103d"
dependencies = [
"cfg-if",
- "windows-targets 0.48.5",
+ "windows-targets 0.52.6",
]
[[package]]
@@ -997,6 +1101,35 @@ dependencies = [
"url",
]
+[[package]]
+name = "mac"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
+
+[[package]]
+name = "markdown"
+version = "1.0.0-alpha.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4e61c5c85b392273c4d4ea546e6399ace3e3db172ab01b6de8f3d398d1dbd2ec"
+dependencies = [
+ "unicode-id",
+]
+
+[[package]]
+name = "markup5ever"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16ce3abbeba692c8b8441d036ef91aea6df8da2c6b6e21c7e14d3c18e526be45"
+dependencies = [
+ "log",
+ "phf 0.11.2",
+ "phf_codegen 0.11.2",
+ "string_cache",
+ "string_cache_codegen",
+ "tendril",
+]
+
[[package]]
name = "memchr"
version = "2.7.4"
@@ -1147,6 +1280,12 @@ dependencies = [
"libloading",
]
+[[package]]
+name = "new_debug_unreachable"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
+
[[package]]
name = "nom"
version = "7.1.3"
@@ -1437,7 +1576,7 @@ dependencies = [
"oxc_span",
"oxc_tasks_common",
"oxc_transformer",
- "phf",
+ "phf 0.11.2",
"pico-args",
"project-root",
"rayon",
@@ -1525,6 +1664,7 @@ dependencies = [
"json-strip-comments",
"language-tags",
"lazy_static",
+ "markdown",
"memchr",
"mime_guess",
"once_cell",
@@ -1540,7 +1680,7 @@ dependencies = [
"oxc_semantic",
"oxc_span",
"oxc_syntax",
- "phf",
+ "phf 0.11.2",
"project-root",
"rayon",
"regex",
@@ -1728,7 +1868,7 @@ dependencies = [
"oxc_parser",
"oxc_span",
"oxc_syntax",
- "phf",
+ "phf 0.11.2",
"rustc-hash",
"serde",
"serde_json",
@@ -1773,7 +1913,7 @@ dependencies = [
"oxc_ast_macros",
"oxc_index",
"oxc_span",
- "phf",
+ "phf 0.11.2",
"rustc-hash",
"ryu-js",
"serde",
@@ -1980,6 +2120,15 @@ dependencies = [
"indexmap",
]
+[[package]]
+name = "phf"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259"
+dependencies = [
+ "phf_shared 0.10.0",
+]
+
[[package]]
name = "phf"
version = "0.11.2"
@@ -1987,7 +2136,37 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc"
dependencies = [
"phf_macros",
- "phf_shared",
+ "phf_shared 0.11.2",
+]
+
+[[package]]
+name = "phf_codegen"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd"
+dependencies = [
+ "phf_generator 0.10.0",
+ "phf_shared 0.10.0",
+]
+
+[[package]]
+name = "phf_codegen"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a"
+dependencies = [
+ "phf_generator 0.11.2",
+ "phf_shared 0.11.2",
+]
+
+[[package]]
+name = "phf_generator"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6"
+dependencies = [
+ "phf_shared 0.10.0",
+ "rand",
]
[[package]]
@@ -1996,7 +2175,7 @@ version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0"
dependencies = [
- "phf_shared",
+ "phf_shared 0.11.2",
"rand",
]
@@ -2006,13 +2185,22 @@ version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b"
dependencies = [
- "phf_generator",
- "phf_shared",
+ "phf_generator 0.11.2",
+ "phf_shared 0.11.2",
"proc-macro2",
"quote",
"syn",
]
+[[package]]
+name = "phf_shared"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096"
+dependencies = [
+ "siphasher",
+]
+
[[package]]
name = "phf_shared"
version = "0.11.2"
@@ -2066,6 +2254,21 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
+[[package]]
+name = "ppv-lite86"
+version = "0.2.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
+dependencies = [
+ "zerocopy",
+]
+
+[[package]]
+name = "precomputed-hash"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
+
[[package]]
name = "prettyplease"
version = "0.2.20"
@@ -2119,6 +2322,18 @@ version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
+ "libc",
+ "rand_chacha",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
"rand_core",
]
@@ -2127,6 +2342,9 @@ name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+dependencies = [
+ "getrandom",
+]
[[package]]
name = "rayon"
@@ -2374,6 +2592,41 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+[[package]]
+name = "scraper"
+version = "0.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b90460b31bfe1fc07be8262e42c665ad97118d4585869de9345a84d501a9eaf0"
+dependencies = [
+ "ahash",
+ "cssparser",
+ "ego-tree",
+ "getopts",
+ "html5ever",
+ "once_cell",
+ "selectors",
+ "tendril",
+]
+
+[[package]]
+name = "selectors"
+version = "0.25.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4eb30575f3638fc8f6815f448d50cb1a2e255b0897985c8c59f4d37b72a07b06"
+dependencies = [
+ "bitflags 2.6.0",
+ "cssparser",
+ "derive_more",
+ "fxhash",
+ "log",
+ "new_debug_unreachable",
+ "phf 0.10.1",
+ "phf_codegen 0.10.0",
+ "precomputed-hash",
+ "servo_arc",
+ "smallvec",
+]
+
[[package]]
name = "semver"
version = "1.0.23"
@@ -2463,6 +2716,15 @@ dependencies = [
"syn",
]
+[[package]]
+name = "servo_arc"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d036d71a959e00c77a63538b90a6c2390969f9772b096ea837205c6bd0491a44"
+dependencies = [
+ "stable_deref_trait",
+]
+
[[package]]
name = "sha2"
version = "0.10.8"
@@ -2541,6 +2803,12 @@ version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
+[[package]]
+name = "stable_deref_trait"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
+
[[package]]
name = "static_assertions"
version = "1.1.0"
@@ -2553,6 +2821,32 @@ version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9557cb6521e8d009c51a8666f09356f4b817ba9ba0981a305bd86aee47bd35c"
+[[package]]
+name = "string_cache"
+version = "0.8.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b"
+dependencies = [
+ "new_debug_unreachable",
+ "once_cell",
+ "parking_lot",
+ "phf_shared 0.10.0",
+ "precomputed-hash",
+ "serde",
+]
+
+[[package]]
+name = "string_cache_codegen"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6bb30289b722be4ff74a408c3cc27edeaad656e06cb1fe8fa9231fa59c728988"
+dependencies = [
+ "phf_generator 0.10.0",
+ "phf_shared 0.10.0",
+ "proc-macro2",
+ "quote",
+]
+
[[package]]
name = "subtle"
version = "2.6.1"
@@ -2592,6 +2886,17 @@ dependencies = [
"windows-sys 0.52.0",
]
+[[package]]
+name = "tendril"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0"
+dependencies = [
+ "futf",
+ "mac",
+ "utf-8",
+]
+
[[package]]
name = "textwrap"
version = "0.16.1"
@@ -2879,6 +3184,12 @@ version = "0.3.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75"
+[[package]]
+name = "unicode-id"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1b6def86329695390197b82c1e244a54a131ceb66c996f2088a3876e2ae083f"
+
[[package]]
name = "unicode-id-start"
version = "1.2.0"
@@ -2953,6 +3264,12 @@ dependencies = [
"serde",
]
+[[package]]
+name = "utf-8"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
+
[[package]]
name = "valuable"
version = "0.1.0"
@@ -3067,10 +3384,16 @@ dependencies = [
"bpaf",
"handlebars",
"insta",
+ "markdown",
+ "oxc_allocator",
+ "oxc_diagnostics",
"oxc_linter",
+ "oxc_parser",
+ "oxc_span",
"oxlint",
"pico-args",
"schemars",
+ "scraper",
"serde",
"serde_json",
]
@@ -3257,6 +3580,7 @@ version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
dependencies = [
+ "byteorder",
"zerocopy-derive",
]
diff --git a/crates/oxc_linter/Cargo.toml b/crates/oxc_linter/Cargo.toml
index fd82b48b5e359..e25ac5120ec00 100644
--- a/crates/oxc_linter/Cargo.toml
+++ b/crates/oxc_linter/Cargo.toml
@@ -57,3 +57,4 @@ schemars = { workspace = true, features = ["indexmap2"] }
static_assertions = { workspace = true }
insta = { workspace = true }
project-root = { workspace = true }
+markdown = { version = "1.0.0-alpha.18" }
diff --git a/crates/oxc_linter/src/rule.rs b/crates/oxc_linter/src/rule.rs
index 3fb081fb1315d..287bd40a9bfe3 100644
--- a/crates/oxc_linter/src/rule.rs
+++ b/crates/oxc_linter/src/rule.rs
@@ -235,12 +235,22 @@ impl RuleWithSeverity {
#[cfg(test)]
mod test {
use crate::rules::RULES;
+ use markdown::{to_html_with_options, Options};
#[test]
fn ensure_documentation() {
assert!(!RULES.is_empty());
+ let options = Options::gfm();
+
for rule in RULES.iter() {
- assert!(rule.documentation().is_some_and(|s| !s.is_empty()), "{}", rule.name());
+ let name = rule.name();
+ assert!(
+ rule.documentation().is_some_and(|s| !s.is_empty()),
+ "Rule '{name}' is missing documentation."
+ );
+ // will panic if provided invalid markdown
+ let html = to_html_with_options(rule.documentation().unwrap(), &options).unwrap();
+ assert!(!html.is_empty());
}
}
}
diff --git a/crates/oxc_linter/src/rules/eslint/for_direction.rs b/crates/oxc_linter/src/rules/eslint/for_direction.rs
index 13dd1219f629a..b227ed8bc716c 100644
--- a/crates/oxc_linter/src/rules/eslint/for_direction.rs
+++ b/crates/oxc_linter/src/rules/eslint/for_direction.rs
@@ -35,7 +35,7 @@ declare_oxc_lint!(
/// ```javascript
/// for (var i = 0; i < 10; i--) {}
///
- /// for (var = 10; i >= 0; i++) {}
+ /// for (var i = 10; i >= 0; i++) {}
/// ```
ForDirection,
correctness,
diff --git a/crates/oxc_linter/src/rules/eslint/guard_for_in.rs b/crates/oxc_linter/src/rules/eslint/guard_for_in.rs
index 71d33322ee07e..0a487c4c0d233 100644
--- a/crates/oxc_linter/src/rules/eslint/guard_for_in.rs
+++ b/crates/oxc_linter/src/rules/eslint/guard_for_in.rs
@@ -24,8 +24,8 @@ declare_oxc_lint!(
/// ### Example
/// ```javascript
/// for (key in foo) {
- // doSomething(key);
- // }
+ /// doSomething(key);
+ /// }
/// ```
GuardForIn,
style
diff --git a/crates/oxc_linter/src/rules/eslint/max_lines.rs b/crates/oxc_linter/src/rules/eslint/max_lines.rs
index 3159d077ed00c..9aae4a5925380 100644
--- a/crates/oxc_linter/src/rules/eslint/max_lines.rs
+++ b/crates/oxc_linter/src/rules/eslint/max_lines.rs
@@ -37,16 +37,15 @@ impl Default for MaxLinesConfig {
declare_oxc_lint!(
/// ### What it does
- /// Enforce a maximum number of lines per file
+ /// Enforce a maximum number of lines per file.
///
/// ### Why is this bad?
///
- /// Some people consider large files a code smell. Large files tend to do a lot of things and can make it hard following what’s going.
- /// While there is not an objective maximum number of lines considered acceptable in a file, most people would agree it should not be in the thousands. Recommendations usually range from 100 to 500 lines.
- ///
- /// ### Example
- /// ```javascript
- /// ```
+ /// Some people consider large files a code smell. Large files tend to do a
+ /// lot of things and can make it hard following what’s going. While there
+ /// is not an objective maximum number of lines considered acceptable in a
+ /// file, most people would agree it should not be in the thousands.
+ /// Recommendations usually range from 100 to 500 lines.
MaxLines,
pedantic
);
diff --git a/crates/oxc_linter/src/rules/eslint/no_await_in_loop.rs b/crates/oxc_linter/src/rules/eslint/no_await_in_loop.rs
index e2622c5c0195b..b142dd6ab966b 100644
--- a/crates/oxc_linter/src/rules/eslint/no_await_in_loop.rs
+++ b/crates/oxc_linter/src/rules/eslint/no_await_in_loop.rs
@@ -18,7 +18,7 @@ pub struct NoAwaitInLoop;
declare_oxc_lint!(
/// ### What it does
///
- /// This rule disallows the use of await within loop bodies. (for, for-in, for-of, while, do-while).
+ /// This rule disallows the use of `await` within loop bodies. (for, for-in, for-of, while, do-while).
///
/// ### Why is this bad?
///
@@ -28,14 +28,18 @@ declare_oxc_lint!(
/// ### Example
/// Bad:
/// ```javascript
- /// for (const user of users) {
- /// const userRecord = await getUserRecord(user);
+ /// async function bad() {
+ /// for (const user of users) {
+ /// const userRecord = await getUserRecord(user);
+ /// }
/// }
/// ```
///
/// Good:
/// ```javascript
- /// await Promise.all(users.map(user => getUserRecord(user)));
+ /// async function good() {
+ /// await Promise.all(users.map(user => getUserRecord(user)));
+ /// }
/// ```
NoAwaitInLoop,
perf
diff --git a/crates/oxc_linter/src/rules/eslint/no_case_declarations.rs b/crates/oxc_linter/src/rules/eslint/no_case_declarations.rs
index d654c7f24f8e7..b2ad9e9792746 100644
--- a/crates/oxc_linter/src/rules/eslint/no_case_declarations.rs
+++ b/crates/oxc_linter/src/rules/eslint/no_case_declarations.rs
@@ -26,19 +26,19 @@ declare_oxc_lint!(
///
/// ### Example
/// ```javascript
- // switch (foo) {
- // case 1:
- // let x = 1;
- // break;
- // case 2:
- // const y = 2;
- // break;
- // case 3:
- // function f() {}
- // break;
- // default:
- // class C {}
- // }
+ /// switch (foo) {
+ /// case 1:
+ /// let x = 1;
+ /// break;
+ /// case 2:
+ /// const y = 2;
+ /// break;
+ /// case 3:
+ /// function f() {}
+ /// break;
+ /// default:
+ /// class C {}
+ /// }
/// ```
NoCaseDeclarations,
pedantic
diff --git a/crates/oxc_linter/src/rules/eslint/no_cond_assign.rs b/crates/oxc_linter/src/rules/eslint/no_cond_assign.rs
index 8a649b3d638cc..ea35fa436ad9d 100644
--- a/crates/oxc_linter/src/rules/eslint/no_cond_assign.rs
+++ b/crates/oxc_linter/src/rules/eslint/no_cond_assign.rs
@@ -29,12 +29,24 @@ enum NoCondAssignConfig {
declare_oxc_lint!(
/// ### What it does
///
+ /// Disallow assignment operators in conditional expressions
///
/// ### Why is this bad?
///
+ /// In conditional statements, it is very easy to mistype a comparison
+ /// operator (such as `==`) as an assignment operator (such as `=`).
+ ///
+ /// There are valid reasons to use assignment operators in conditional
+ /// statements. However, it can be difficult to tell whether a specific
+ /// assignment was intentional.
///
/// ### Example
- /// ```javascript
+ ///
+ /// ```js
+ /// // Check the user's job title
+ /// if (user.jobTitle = "manager") {
+ /// // user.jobTitle is now incorrect
+ /// }
/// ```
NoCondAssign,
correctness
diff --git a/crates/oxc_linter/src/rules/eslint/no_continue.rs b/crates/oxc_linter/src/rules/eslint/no_continue.rs
index dcca60785e98d..4c41048972703 100644
--- a/crates/oxc_linter/src/rules/eslint/no_continue.rs
+++ b/crates/oxc_linter/src/rules/eslint/no_continue.rs
@@ -24,15 +24,15 @@ declare_oxc_lint!(
/// ### Example
/// ```javascript
/// var sum = 0,
- // i;
- //
- // for(i = 0; i < 10; i++) {
- // if(i >= 5) {
- // continue;
- // }
- //
- // sum += i;
- // }
+ /// i;
+ ///
+ /// for(i = 0; i < 10; i++) {
+ /// if(i >= 5) {
+ /// continue;
+ /// }
+ ///
+ /// sum += i;
+ /// }
/// ```
NoContinue,
style
diff --git a/crates/oxc_linter/src/rules/eslint/no_debugger.rs b/crates/oxc_linter/src/rules/eslint/no_debugger.rs
index af14da3d0fe3c..d61b7a5f24be2 100644
--- a/crates/oxc_linter/src/rules/eslint/no_debugger.rs
+++ b/crates/oxc_linter/src/rules/eslint/no_debugger.rs
@@ -21,10 +21,13 @@ declare_oxc_lint!(
/// They're most commonly an accidental debugging leftover.
///
/// ### Example
+ ///
/// ```javascript
- /// const data = await getData();
- /// const result = complexCalculation(data);
- /// debugger;
+ /// async function main() {
+ /// const data = await getData();
+ /// const result = complexCalculation(data);
+ /// debugger;
+ /// }
/// ```
NoDebugger,
correctness,
diff --git a/crates/oxc_linter/src/rules/eslint/no_ex_assign.rs b/crates/oxc_linter/src/rules/eslint/no_ex_assign.rs
index 8ed18e65692c6..5629ceaeb2389 100644
--- a/crates/oxc_linter/src/rules/eslint/no_ex_assign.rs
+++ b/crates/oxc_linter/src/rules/eslint/no_ex_assign.rs
@@ -25,11 +25,11 @@ declare_oxc_lint!(
///
/// ### Example
/// ```javascript
- // try {
- // // code
- // } catch (e) {
- // e = 10;
- // }
+ /// try {
+ /// // code
+ /// } catch (e) {
+ /// e = 10;
+ /// }
/// ```
NoExAssign,
correctness
diff --git a/crates/oxc_linter/src/rules/eslint/no_undefined.rs b/crates/oxc_linter/src/rules/eslint/no_undefined.rs
index 213fcd6021ddd..ea860a512d45d 100644
--- a/crates/oxc_linter/src/rules/eslint/no_undefined.rs
+++ b/crates/oxc_linter/src/rules/eslint/no_undefined.rs
@@ -29,15 +29,14 @@ declare_oxc_lint!(
/// var undefined = "foo";
///
/// if (foo === undefined) {
- /// ...
+ /// // ...
/// }
///
/// function baz(undefined) {
- /// ...
+ /// // ...
/// }
///
/// bar(undefined, "lorem");
- ///
/// ```
///
/// ### Example of good code
@@ -47,7 +46,7 @@ declare_oxc_lint!(
/// var Undefined = "foo";
///
/// if (typeof foo === "undefined") {
- /// ...
+ /// // ...
/// }
///
/// global.undefined = "foo";
diff --git a/crates/oxc_linter/src/rules/eslint/no_useless_escape.rs b/crates/oxc_linter/src/rules/eslint/no_useless_escape.rs
index ffda849250af2..1d9bc5a978b3d 100644
--- a/crates/oxc_linter/src/rules/eslint/no_useless_escape.rs
+++ b/crates/oxc_linter/src/rules/eslint/no_useless_escape.rs
@@ -23,7 +23,45 @@ declare_oxc_lint!(
///
///
/// ### Example
+ ///
+ /// Examples of **incorrect** code for this rule:
+ ///
+ /// ```javascript
+ /// /*eslint no-useless-escape: "error"*/
+ ///
+ /// "\'";
+ /// '\"';
+ /// "\#";
+ /// "\e";
+ /// `\"`;
+ /// `\"${foo}\"`;
+ /// `\#{foo}`;
+ /// /\!/;
+ /// /\@/;
+ /// /[\[]/;
+ /// /[a-z\-]/;
+ /// ```
+ ///
+ /// Examples of **correct** code for this rule:
+ ///
/// ```javascript
+ /// /*eslint no-useless-escape: "error"*/
+ ///
+ /// "\"";
+ /// '\'';
+ /// "\x12";
+ /// "\u00a9";
+ /// "\371";
+ /// "xs\u2111";
+ /// `\``;
+ /// `\${${foo}}`;
+ /// `$\{${foo}}`;
+ /// /\\/g;
+ /// /\t/g;
+ /// /\w\$\*\^\./;
+ /// /[[]/;
+ /// /[\]]/;
+ /// /[a-z-]/;
/// ```
NoUselessEscape,
correctness,
diff --git a/crates/oxc_linter/src/rules/eslint/no_void.rs b/crates/oxc_linter/src/rules/eslint/no_void.rs
index 0785114107417..931c20891dab7 100644
--- a/crates/oxc_linter/src/rules/eslint/no_void.rs
+++ b/crates/oxc_linter/src/rules/eslint/no_void.rs
@@ -30,9 +30,9 @@ declare_oxc_lint!(
/// var foo = void 0;
///
/// // success
- /// "var foo = bar()",
- /// "foo.void()",
- /// "foo.void = bar",
+ /// "var foo = bar()";
+ /// "foo.void()";
+ /// "foo.void = bar";
/// ```
NoVoid,
restriction,
diff --git a/crates/oxc_linter/src/rules/eslint/unicode_bom.rs b/crates/oxc_linter/src/rules/eslint/unicode_bom.rs
index d0d7596f361bf..a8591fef24750 100644
--- a/crates/oxc_linter/src/rules/eslint/unicode_bom.rs
+++ b/crates/oxc_linter/src/rules/eslint/unicode_bom.rs
@@ -36,7 +36,7 @@ declare_oxc_lint!(
///
/// ### Example
/// ```javascript
- /// var a = 123;"
+ /// var a = 123;
/// ```
UnicodeBom,
restriction,
diff --git a/crates/oxc_linter/src/rules/import/named.rs b/crates/oxc_linter/src/rules/import/named.rs
index 6142745b4497c..9ae1dd0c19134 100644
--- a/crates/oxc_linter/src/rules/import/named.rs
+++ b/crates/oxc_linter/src/rules/import/named.rs
@@ -18,10 +18,55 @@ pub struct Named;
declare_oxc_lint!(
/// ### What it does
///
+ /// Verifies that all named imports are part of the set of named exports in
+ /// the referenced module.
+ ///
+ /// For `export`, verifies that all named exports exist in the referenced
+ /// module.
+ ///
+ /// Note: for packages, the plugin will find exported names from
+ /// `jsnext:main` (deprecated) or `module`, if present in `package.json`.
+ /// Redux's npm module includes this key, and thereby is lintable, for
+ /// example.
+ ///
+ /// A module path that is ignored or not unambiguously an ES module will not
+ /// be reported when imported. Note that type imports and exports, as used
+ /// by Flow, are always ignored.
+ ///
/// ### Why is this bad?
///
/// ### Example
- /// ```javascript
+ /// Given
+ /// ```js
+ /// // ./foo.js
+ /// export const foo = "I'm so foo"
+ /// ```
+ ///
+ /// The following is considered valid:
+ ///
+ /// ```js
+ /// // ./bar.js
+ /// import { foo } from './foo'
+ ///
+ /// // ES7 proposal
+ /// export { foo as bar } from './foo'
+ ///
+ /// // node_modules without jsnext:main are not analyzed by default
+ /// // (import/ignore setting)
+ /// import { SomeNonsenseThatDoesntExist } from 'react'
+ /// ```
+ ///
+ /// ...and the following are reported:
+ ///
+ /// ```js
+ /// // ./baz.js
+ /// import { notFoo } from './foo'
+ ///
+ /// // ES7 proposal
+ /// export { notFoo as defNotBar } from './foo'
+ ///
+ /// // will follow 'jsnext:main', if available
+ /// import { dontCreateStore } from 'redux'
/// ```
Named,
correctness
diff --git a/crates/oxc_linter/src/rules/jest/no_conditional_expect.rs b/crates/oxc_linter/src/rules/jest/no_conditional_expect.rs
index 7d4427f4900b4..e6e028b883569 100644
--- a/crates/oxc_linter/src/rules/jest/no_conditional_expect.rs
+++ b/crates/oxc_linter/src/rules/jest/no_conditional_expect.rs
@@ -47,8 +47,8 @@ declare_oxc_lint!(
/// });
///
/// it('throws an error', async () => {
- // await foo().catch(error => expect(error).toBeInstanceOf(error));
- // });
+ /// await foo().catch(error => expect(error).toBeInstanceOf(error));
+ /// });
/// ```
///
/// This rule is compatible with [eslint-plugin-vitest](https://github.com/veritem/eslint-plugin-vitest/blob/main/docs/rules/no-conditional-expect.md),
diff --git a/crates/oxc_linter/src/rules/jest/no_restricted_matchers.rs b/crates/oxc_linter/src/rules/jest/no_restricted_matchers.rs
index 893478c7c40d7..a66aac45882fb 100644
--- a/crates/oxc_linter/src/rules/jest/no_restricted_matchers.rs
+++ b/crates/oxc_linter/src/rules/jest/no_restricted_matchers.rs
@@ -53,7 +53,7 @@ declare_oxc_lint!(
/// ```javascript
///
/// it('is false', () => {
- /// if this has a modifier (i.e. `not.toBeFalsy`), it would be considered fine
+ /// // if this has a modifier (i.e. `not.toBeFalsy`), it would be considered fine
/// expect(a).toBeFalsy();
/// });
///
@@ -68,6 +68,7 @@ declare_oxc_lint!(
/// expect(uploadFileMock).not.toHaveBeenCalledWith('file.name');
/// });
/// });
+ /// ```
///
NoRestrictedMatchers,
style,
diff --git a/crates/oxc_linter/src/rules/jest/no_test_return_statement.rs b/crates/oxc_linter/src/rules/jest/no_test_return_statement.rs
index e76130faf9235..b9ea8f912b683 100644
--- a/crates/oxc_linter/src/rules/jest/no_test_return_statement.rs
+++ b/crates/oxc_linter/src/rules/jest/no_test_return_statement.rs
@@ -34,6 +34,9 @@ declare_oxc_lint!(
///
/// ### Example
/// ```javascript
+ /// test('one', () => {
+ /// return expect(1).toBe(1);
+ /// });
/// ```
NoTestReturnStatement,
style,
diff --git a/crates/oxc_linter/src/rules/jest/require_top_level_describe.rs b/crates/oxc_linter/src/rules/jest/require_top_level_describe.rs
index a80c2b6cc1d91..432baf458d3b2 100644
--- a/crates/oxc_linter/src/rules/jest/require_top_level_describe.rs
+++ b/crates/oxc_linter/src/rules/jest/require_top_level_describe.rs
@@ -57,20 +57,24 @@ declare_oxc_lint!(
///
/// ```javascript
/// // invalid
- /// Above a describe block
+ ///
+ /// // Above a describe block
/// test('my test', () => {});
/// describe('test suite', () => {
/// it('test', () => {});
/// });
+ ///
/// // Below a describe block
/// describe('test suite', () => {});
/// test('my test', () => {});
+ ///
/// // Same for hooks
/// beforeAll('my beforeAll', () => {});
/// describe('test suite', () => {});
/// afterEach('my afterEach', () => {});
///
/// //valid
+ ///
/// // Above a describe block
/// // In a describe block
/// describe('test suite', () => {
@@ -80,9 +84,9 @@ declare_oxc_lint!(
/// // In a nested describe block
/// describe('test suite', () => {
/// test('my test', () => {});
- /// describe('another test suite', () => {
- /// test('my other test', () => {});
- /// });
+ /// describe('another test suite', () => {
+ /// test('my other test', () => {});
+ /// });
/// });
/// ```
///
diff --git a/crates/oxc_linter/src/rules/jsx_a11y/alt_text.rs b/crates/oxc_linter/src/rules/jsx_a11y/alt_text.rs
index 27b6544d85d12..6d65d68b917a9 100644
--- a/crates/oxc_linter/src/rules/jsx_a11y/alt_text.rs
+++ b/crates/oxc_linter/src/rules/jsx_a11y/alt_text.rs
@@ -119,12 +119,15 @@ declare_oxc_lint!(
/// text that describes the element's content or purpose.
///
/// ### Example
- /// ```javascript
- /// // Bad
- ///
///
- /// // Good
- ///
+ /// Examples of **incorrect** code for this rule:
+ /// ```jsx
+ ///
+ /// ```
+ ///
+ /// Examples of **correct** code for this rule:
+ /// ```jsx
+ ///
/// ```
AltText,
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 5818637998967..bad859069fb99 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
@@ -57,9 +57,11 @@ declare_oxc_lint!(
/// Consider the following:
///
/// ```jsx
- /// Perform action
- /// Perform action
- /// Perform action
+ /// <>
+ /// Perform action
+ /// Perform action
+ /// Perform action
+ /// >
/// ````
///
/// All these anchor implementations indicate that the element is only used to execute JavaScript code. All the above should be replaced with:
@@ -79,19 +81,23 @@ declare_oxc_lint!(
/// #### Valid
///
/// ```jsx
- /// navigate here
- /// navigate here
- /// navigate here
+ /// <>
+ /// navigate here
+ /// navigate here
+ /// navigate here
+ /// >
/// ```
///
/// #### Invalid
///
/// ```jsx
- /// navigate here
- /// navigate here
- /// navigate here
- /// navigate here
- /// navigate here
+ /// <>
+ /// navigate here
+ /// navigate here
+ /// navigate here
+ /// navigate here
+ /// navigate here
+ /// >
/// ```
///
/// ### Reference
diff --git a/crates/oxc_linter/src/rules/jsx_a11y/aria_activedescendant_has_tabindex.rs b/crates/oxc_linter/src/rules/jsx_a11y/aria_activedescendant_has_tabindex.rs
index 5d7962c3d8f62..0ccaef174e63a 100644
--- a/crates/oxc_linter/src/rules/jsx_a11y/aria_activedescendant_has_tabindex.rs
+++ b/crates/oxc_linter/src/rules/jsx_a11y/aria_activedescendant_has_tabindex.rs
@@ -32,25 +32,25 @@ declare_oxc_lint!(
///
/// ### Example
/// ```jsx
- /// // Good
- ///
///
diff --git a/crates/oxc_linter/src/rules/jsx_a11y/iframe_has_title.rs b/crates/oxc_linter/src/rules/jsx_a11y/iframe_has_title.rs
index 23833b414cde9..3564d49898bb1 100644
--- a/crates/oxc_linter/src/rules/jsx_a11y/iframe_has_title.rs
+++ b/crates/oxc_linter/src/rules/jsx_a11y/iframe_has_title.rs
@@ -37,7 +37,7 @@ declare_oxc_lint!(
/// This rule checks for title property on iframe element.
///
/// ### Example
- /// ```javascript
+ /// ```jsx
/// // Bad
///
///
diff --git a/crates/oxc_linter/src/rules/jsx_a11y/img_redundant_alt.rs b/crates/oxc_linter/src/rules/jsx_a11y/img_redundant_alt.rs
index e8ed401761666..9be533c2b9dbd 100644
--- a/crates/oxc_linter/src/rules/jsx_a11y/img_redundant_alt.rs
+++ b/crates/oxc_linter/src/rules/jsx_a11y/img_redundant_alt.rs
@@ -79,7 +79,7 @@ declare_oxc_lint!(
/// `` and the components which you define in options.components with the exception of components which is hidden from screen reader.
///
/// ### Example
- /// ```javascript
+ /// ```jsx
/// // Bad
///
///
diff --git a/crates/oxc_linter/src/rules/jsx_a11y/no_access_key.rs b/crates/oxc_linter/src/rules/jsx_a11y/no_access_key.rs
index eeab4ae0caa4e..ead7ecfa5f882 100644
--- a/crates/oxc_linter/src/rules/jsx_a11y/no_access_key.rs
+++ b/crates/oxc_linter/src/rules/jsx_a11y/no_access_key.rs
@@ -26,7 +26,7 @@ declare_oxc_lint!(
/// Inconsistencies between keyboard shortcuts and keyboard commands used by screenreaders and keyboard-only users create accessibility complications so to avoid complications, access keys should not be used.
///
/// ### Example
- /// ```javascript
+ /// ```jsx
/// // Bad
///
/// diff --git a/crates/oxc_linter/src/rules/jsx_a11y/no_aria_hidden_on_focusable.rs b/crates/oxc_linter/src/rules/jsx_a11y/no_aria_hidden_on_focusable.rs index bc21518695080..df0bd33e0796c 100644 --- a/crates/oxc_linter/src/rules/jsx_a11y/no_aria_hidden_on_focusable.rs +++ b/crates/oxc_linter/src/rules/jsx_a11y/no_aria_hidden_on_focusable.rs @@ -30,7 +30,7 @@ declare_oxc_lint!( /// `aria-hidden="true"` on focusable elements can lead to confusion or unexpected behavior for screen reader users. /// /// ### Example - /// ```javascript + /// ```jsx /// // Bad ///
/// diff --git a/crates/oxc_linter/src/rules/jsx_a11y/no_autofocus.rs b/crates/oxc_linter/src/rules/jsx_a11y/no_autofocus.rs index 4a8303f840213..25da21bb7afbd 100644 --- a/crates/oxc_linter/src/rules/jsx_a11y/no_autofocus.rs +++ b/crates/oxc_linter/src/rules/jsx_a11y/no_autofocus.rs @@ -24,12 +24,16 @@ pub struct NoAutofocus { declare_oxc_lint!( /// ### What it does - /// Enforce that autoFocus prop is not used on elements. Autofocusing elements can cause usability issues for sighted and non-sighted users, alike. + /// + /// Enforce that `autoFocus` prop is not used on elements. Autofocusing + /// elements can cause usability issues for sighted and non-sighted users, + /// alike. /// /// ### Rule Option + /// /// This rule takes one optional object argument of type object: /// - /// ``` + /// ```json /// { /// "rules": { /// "jsx-a11y/no-autofocus": [ 2, { @@ -39,24 +43,25 @@ declare_oxc_lint!( /// } /// ``` /// - /// For the `ignoreNonDOM` option, this determines if developer created components are checked. + /// For the `ignoreNonDOM` option, this determines if developer created + /// components are checked. /// /// ### Example - /// // good - /// - /// ```javascript - ///
- /// ``` /// - /// // bad + /// Examples of **incorrect** code for this rule: /// - /// ``` + /// ```jsx ///
///
///
///
/// ``` /// + /// Examples of **correct** code for this rule: + /// + /// ```jsx + ///
+ /// ``` NoAutofocus, correctness, fix diff --git a/crates/oxc_linter/src/rules/jsx_a11y/no_redundant_roles.rs b/crates/oxc_linter/src/rules/jsx_a11y/no_redundant_roles.rs index a4542a8391ef8..77670164dbc21 100644 --- a/crates/oxc_linter/src/rules/jsx_a11y/no_redundant_roles.rs +++ b/crates/oxc_linter/src/rules/jsx_a11y/no_redundant_roles.rs @@ -27,13 +27,15 @@ pub struct NoRedundantRoles; declare_oxc_lint!( /// ### What it does - /// Enforces that the explicit role property is not the same as implicit/default role property on element. + /// + /// Enforces that the explicit `role` property is not the same as + /// implicit/default role property on element. /// /// ### Why is this bad? /// Redundant roles can lead to confusion and verbosity in the codebase. /// /// ### Example - /// ```javascript + /// ```jsx /// // Bad /// /// diff --git a/crates/oxc_linter/src/rules/jsx_a11y/prefer_tag_over_role.rs b/crates/oxc_linter/src/rules/jsx_a11y/prefer_tag_over_role.rs index 0e43aae3130b1..e6bee0de19088 100644 --- a/crates/oxc_linter/src/rules/jsx_a11y/prefer_tag_over_role.rs +++ b/crates/oxc_linter/src/rules/jsx_a11y/prefer_tag_over_role.rs @@ -32,7 +32,7 @@ declare_oxc_lint!( /// Using semantic HTML tags can improve accessibility and readability of the code. /// /// ### Example - /// ```javascript + /// ```jsx /// // Bad ///
/// diff --git a/crates/oxc_linter/src/rules/jsx_a11y/role_has_required_aria_props.rs b/crates/oxc_linter/src/rules/jsx_a11y/role_has_required_aria_props.rs index d96c715ce8aaf..75730319bfa69 100644 --- a/crates/oxc_linter/src/rules/jsx_a11y/role_has_required_aria_props.rs +++ b/crates/oxc_linter/src/rules/jsx_a11y/role_has_required_aria_props.rs @@ -19,13 +19,17 @@ fn role_has_required_aria_props_diagnostic(span: Span, role: &str, props: &str) pub struct RoleHasRequiredAriaProps; declare_oxc_lint!( /// ### What it does - /// Enforces that elements with ARIA roles must have all required attributes for that role. + /// + /// Enforces that elements with ARIA roles must have all required attributes + /// for that role. /// /// ### Why is this bad? - /// Certain ARIA roles require specific attributes to express necessary semantics for assistive technology. + /// + /// Certain ARIA roles require specific attributes to express necessary + /// semantics for assistive technology. /// /// ### Example - /// ```javascript + /// ```jsx /// // Bad ///
/// diff --git a/crates/oxc_linter/src/rules/jsx_a11y/tabindex_no_positive.rs b/crates/oxc_linter/src/rules/jsx_a11y/tabindex_no_positive.rs index 16dc7f9ab2018..6db1afc40c8bb 100644 --- a/crates/oxc_linter/src/rules/jsx_a11y/tabindex_no_positive.rs +++ b/crates/oxc_linter/src/rules/jsx_a11y/tabindex_no_positive.rs @@ -21,13 +21,19 @@ pub struct TabindexNoPositive; declare_oxc_lint!( /// ### What it does - /// Enforces that positive values for the tabIndex attribute are not used in JSX. + /// + /// Enforces that positive values for the `tabIndex` attribute are not used + /// in JSX. /// /// ### Why is this bad? - /// Using tabIndex values greater than 0 can make navigation and interaction difficult for keyboard and assistive technology users, disrupting the logical order of content. + /// + /// Using `tabIndex` values greater than `0` can make navigation and + /// interaction difficult for keyboard and assistive technology users, + /// disrupting the logical order of content. /// /// ### Example - /// ```javascript + /// + /// ```jsx /// // Bad /// foo /// diff --git a/crates/oxc_linter/src/rules/nextjs/google_font_display.rs b/crates/oxc_linter/src/rules/nextjs/google_font_display.rs index 967c0befb1aa5..a7ae75787d872 100644 --- a/crates/oxc_linter/src/rules/nextjs/google_font_display.rs +++ b/crates/oxc_linter/src/rules/nextjs/google_font_display.rs @@ -30,13 +30,50 @@ pub struct GoogleFontDisplay; declare_oxc_lint!( /// ### What it does /// + /// Enforce font-display behavior with Google Fonts. /// /// ### Why is this bad? /// + /// Specifying display=optional minimizes the risk of invisible text or + /// layout shift. If swapping to the custom font after it has loaded is + /// important to you, then use `display=swap`` instead. /// /// ### Example - /// ```javascript + /// + /// Examples of **incorrect** code for this rule: + /// + /// ```jsx + /// import Head from "next/head"; + /// + /// export default Test = () => { + /// return ( + ///
+ /// + /// + /// ); + /// }; /// ``` + /// + /// Examples of **correct** code for this rule: + /// + /// ```jsx + /// import Head from "next/head"; + /// + /// export default Test = () => { + /// return ( + ///
+ /// + /// + /// ); + /// }; + /// ``` + /// GoogleFontDisplay, correctness ); @@ -155,16 +192,16 @@ fn test() { let fail = vec
///
/// ### Example
- /// ```javascript
+ /// ```jsx
/// class MyComponent extends Component {
/// componentDidMount() {
/// findDOMNode(this).scrollIntoView();
diff --git a/crates/oxc_linter/src/rules/react/no_is_mounted.rs b/crates/oxc_linter/src/rules/react/no_is_mounted.rs
index f69eeea35544c..7e2fdd2b7c957 100644
--- a/crates/oxc_linter/src/rules/react/no_is_mounted.rs
+++ b/crates/oxc_linter/src/rules/react/no_is_mounted.rs
@@ -25,7 +25,7 @@ declare_oxc_lint!(
/// and it is on its way to being officially deprecated.///
///
/// ### Example
- /// ```javascript
+ /// ```jsx
/// class Hello extends React.Component {
/// someMethod() {
/// if (!this.isMounted()) {
diff --git a/crates/oxc_linter/src/rules/react/no_render_return_value.rs b/crates/oxc_linter/src/rules/react/no_render_return_value.rs
index c27f860c4245a..3cb324983400c 100644
--- a/crates/oxc_linter/src/rules/react/no_render_return_value.rs
+++ b/crates/oxc_linter/src/rules/react/no_render_return_value.rs
@@ -20,7 +20,7 @@ declare_oxc_lint!(
/// This rule will warn you if you try to use the ReactDOM.render() return value.
///
/// ### Example
- /// ```javascript
+ /// ```jsx
/// // Bad
/// vaa inst =ReactDOM.render(
; diff --git a/crates/oxc_linter/src/rules/react/react_in_jsx_scope.rs b/crates/oxc_linter/src/rules/react/react_in_jsx_scope.rs index 1ea93d1362d8e..70967d8f77f0a 100644 --- a/crates/oxc_linter/src/rules/react/react_in_jsx_scope.rs +++ b/crates/oxc_linter/src/rules/react/react_in_jsx_scope.rs @@ -21,10 +21,11 @@ declare_oxc_lint!( /// /// ### Why is this bad? /// - /// When using JSX, `` expands to `React.createElement("a")`. Therefore the `React` variable must be in scope. + /// When using JSX, `` expands to `React.createElement("a")`. Therefore + /// the `React` variable must be in scope. /// /// ### Example - /// ```javascript + /// ```jsx /// // Bad /// var a = ; /// diff --git a/crates/oxc_linter/src/rules/react/require_render_return.rs b/crates/oxc_linter/src/rules/react/require_render_return.rs index 32ba615b19307..1cf13ba358654 100644 --- a/crates/oxc_linter/src/rules/react/require_render_return.rs +++ b/crates/oxc_linter/src/rules/react/require_render_return.rs @@ -31,7 +31,7 @@ declare_oxc_lint!( /// When writing the `render` method in a component it is easy to forget to return the JSX content. This rule will warn if the return statement is missing. /// /// ### Example - /// ```javascript + /// ```jsx /// var Hello = createReactClass({ /// render() { ///
;
diff --git a/crates/oxc_linter/src/rules/react/void_dom_elements_no_children.rs b/crates/oxc_linter/src/rules/react/void_dom_elements_no_children.rs
index 6860b588850ae..5c2de109f93bd 100644
--- a/crates/oxc_linter/src/rules/react/void_dom_elements_no_children.rs
+++ b/crates/oxc_linter/src/rules/react/void_dom_elements_no_children.rs
@@ -29,7 +29,7 @@ declare_oxc_lint!(
/// This rule checks that children are not passed to void DOM elements.
///
/// ### Example
- /// ```javascript
+ /// ```jsx
/// // Bad
///
Children
///
diff --git a/crates/oxc_linter/src/rules/react_perf/jsx_no_jsx_as_prop.rs b/crates/oxc_linter/src/rules/react_perf/jsx_no_jsx_as_prop.rs
index 968a4efc243b9..fe70fe4810f09 100644
--- a/crates/oxc_linter/src/rules/react_perf/jsx_no_jsx_as_prop.rs
+++ b/crates/oxc_linter/src/rules/react_perf/jsx_no_jsx_as_prop.rs
@@ -13,7 +13,7 @@ declare_oxc_lint!(
/// Prevent JSX that are local to the current method from being used as values of JSX props
///
/// ### Example
- /// ```javascript
+ /// ```jsx
/// // Bad
/// "));
+ }
+ }
+
+ #[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));
+ 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("
"));
+ assert!(html.contains(PREFIX_WITH_SLASH));
+ }
+ }
+}
diff --git a/tasks/website/Cargo.toml b/tasks/website/Cargo.toml
index 934333cbf7222..c35428112dd35 100644
--- a/tasks/website/Cargo.toml
+++ b/tasks/website/Cargo.toml
@@ -27,7 +27,14 @@ serde = { workspace = true }
bpaf = { workspace = true, features = ["docgen"] }
[dev-dependencies]
-insta = { workspace = true }
+oxc_allocator = { workspace = true }
+oxc_diagnostics = { workspace = true }
+oxc_parser = { workspace = true }
+oxc_span = { workspace = true }
+
+insta = { workspace = true }
+markdown = { version = "1.0.0-alpha.18" }
+scraper = { version = "0.20.0" }
[package.metadata.cargo-shear]
ignored = ["bpaf"]
diff --git a/tasks/website/src/linter/rules/mod.rs b/tasks/website/src/linter/rules/mod.rs
index 151a658d8c0a8..88cafd8dfd6eb 100644
--- a/tasks/website/src/linter/rules/mod.rs
+++ b/tasks/website/src/linter/rules/mod.rs
@@ -1,6 +1,8 @@
mod doc_page;
mod html;
mod table;
+#[cfg(test)]
+mod test;
use std::{
borrow::Cow,
@@ -95,6 +97,9 @@ fn write_rule_doc_pages(table: &RuleTable, outdir: &Path) {
let plugin_path = outdir.join(&rule.plugin);
fs::create_dir_all(&plugin_path).unwrap();
let page_path = plugin_path.join(format!("{}.md", rule.name));
+ if page_path.exists() {
+ fs::remove_file(&page_path).unwrap();
+ }
println!("{}", page_path.display());
let docs = render_rule_docs_page(rule).unwrap();
fs::write(&page_path, docs).unwrap();
diff --git a/tasks/website/src/linter/rules/test.rs b/tasks/website/src/linter/rules/test.rs
new file mode 100644
index 0000000000000..5238ee060afde
--- /dev/null
+++ b/tasks/website/src/linter/rules/test.rs
@@ -0,0 +1,117 @@
+use markdown::{to_html, to_html_with_options, Options};
+use oxc_diagnostics::NamedSource;
+use scraper::{ElementRef, Html, Selector};
+use std::sync::{Arc, OnceLock};
+
+use oxc_allocator::Allocator;
+use oxc_linter::table::RuleTable;
+use oxc_parser::Parser;
+use oxc_span::SourceType;
+
+use super::{render_rule_docs_page, render_rules_table};
+
+static TABLE: OnceLock