Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs(website): tables from source rules to Biome rules #1583

Merged
merged 7 commits into from
Jan 18, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 88 additions & 3 deletions crates/biome_analyze/src/rule.rs
Original file line number Diff line number Diff line change
@@ -14,6 +14,7 @@ use biome_diagnostics::{
Visit,
};
use biome_rowan::{AstNode, BatchMutation, BatchMutationExt, Language, TextRange};
use std::cmp::Ordering;
use std::fmt::Debug;

#[derive(Debug, Clone)]
@@ -56,7 +57,7 @@ impl Display for FixKind {
}
}

#[derive(Debug, Clone, Eq, PartialEq)]
#[derive(Debug, Clone, Eq)]
pub enum RuleSource {
/// Rules from [Rust Clippy](https://rust-lang.github.io/rust-clippy/master/index.html)
Clippy(&'static str),
@@ -86,6 +87,54 @@ pub enum RuleSource {
EslintMysticatea(&'static str),
}

impl PartialEq for RuleSource {
fn eq(&self, other: &Self) -> bool {
std::mem::discriminant(self) == std::mem::discriminant(other)
}
}

impl std::fmt::Display for RuleSource {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
RuleSource::Clippy(_) => write!(f, "Clippy"),
RuleSource::Eslint(_) => write!(f, "ESLint"),
RuleSource::EslintImport(_) => write!(f, "eslint-plugin-import"),
RuleSource::EslintImportAccess(_) => write!(f, "eslint-plugin-import-access"),
RuleSource::EslintJest(_) => write!(f, "eslint-plugin-jest"),
RuleSource::EslintJsxA11y(_) => write!(f, "eslint-plugin-jsx-a11y"),
RuleSource::EslintReact(_) => write!(f, "eslint-plugin-react"),
RuleSource::EslintReactHooks(_) => write!(f, "eslint-plugin-react-hooks"),
RuleSource::EslintSonarJs(_) => write!(f, "eslint-plugin-sonarjs"),
RuleSource::EslintStylistic(_) => write!(f, "eslint-plugin-stylistic"),
RuleSource::EslintTypeScript(_) => write!(f, "eslint-plugin-typescript"),
RuleSource::EslintUnicorn(_) => write!(f, "eslint-plugin-unicorn"),
RuleSource::EslintMysticatea(_) => write!(f, "eslint-plugin-mysticates"),
}
}
}

impl PartialOrd for RuleSource {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}

impl Ord for RuleSource {
fn cmp(&self, other: &Self) -> Ordering {
if let (RuleSource::Eslint(self_rule), RuleSource::Eslint(other_rule)) = (self, other) {
self_rule.cmp(other_rule)
} else if self.is_eslint() {
Ordering::Greater
} else if other.is_eslint() {
Ordering::Less
} else {
let self_rule = self.as_rule_name();
let other_rule = other.as_rule_name();
self_rule.cmp(other_rule)
}
}
}

impl RuleSource {
pub fn as_rule_name(&self) -> &'static str {
match self {
@@ -105,7 +154,7 @@ impl RuleSource {
}
}

pub fn as_rule_url(&self) -> String {
pub fn to_rule_url(&self) -> String {
match self {
Self::Clippy(rule_name) => format!("https://rust-lang.github.io/rust-clippy/master/#/{rule_name}"),
Self::Eslint(rule_name) => format!("https://eslint.org/docs/latest/rules/{rule_name}"),
@@ -124,7 +173,37 @@ impl RuleSource {
}

pub fn as_url_and_rule_name(&self) -> (String, &'static str) {
(self.as_rule_url(), self.as_rule_name())
(self.to_rule_url(), self.as_rule_name())
}

/// Original ESLint rule
pub const fn is_eslint(&self) -> bool {
matches!(self, Self::Eslint(_))
}

/// TypeScript plugin
pub const fn is_eslint_typescript(&self) -> bool {
matches!(self, Self::EslintTypeScript(_))
}

/// All ESLint plugins, exception for the TypeScript one
pub const fn is_eslint_plugin(&self) -> bool {
matches!(
self,
Self::EslintImport(_)
| Self::EslintImportAccess(_)
| Self::EslintJest(_)
| Self::EslintStylistic(_)
| Self::EslintJsxA11y(_)
| Self::EslintReact(_)
| Self::EslintReactHooks(_)
| Self::EslintSonarJs(_)
| Self::EslintUnicorn(_)
)
}

pub const fn is_clippy(&self) -> bool {
matches!(self, Self::Clippy(_))
}
}

@@ -137,6 +216,12 @@ pub enum RuleSourceKind {
Inspired,
}

impl RuleSourceKind {
pub const fn is_inspired(&self) -> bool {
matches!(self, Self::Inspired)
}
}

impl RuleMetadata {
pub const fn new(version: &'static str, name: &'static str, docs: &'static str) -> Self {
Self {
12 changes: 8 additions & 4 deletions website/astro.config.ts
Original file line number Diff line number Diff line change
@@ -210,14 +210,18 @@ export default defineConfig({
},
},
{
label: "Lint rules",
label: "Rules",
link: "/linter/rules",
translations: {
ja: "Lintルール",
"zh-CN": "Lint 规则",
"pt-BR": "Regras do Linter",
ja: "ルール",
"zh-CN": "规则",
"pt-BR": "Regras",
},
},
{
label: "Rules sources",
link: "/linter/rules-sources",
},
],
},
],
186 changes: 186 additions & 0 deletions website/src/components/generated/Sources.md

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion website/src/content/docs/linter/index.mdx
Original file line number Diff line number Diff line change
@@ -89,7 +89,6 @@ debugger;
debugger;
```


## Configuration

### Enable a lint rule
@@ -178,3 +177,7 @@ When they do _accept_ some, you can pass them by shaping the value of the rule d

- `level` will indicate the severity of the diagnostic, valid values are: `"off"`, `"warn"` and `"error"`;
- `options` will change based on the rule.

## Migrating from other linters

Many of Biome lint rules are inspired from other linters. If you want to migrate from other linters such as ESLint or `typescript-eslint`, check the [rule sources page](/linter/rule)
194 changes: 194 additions & 0 deletions website/src/content/docs/linter/rules-sources.mdx

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion website/src/content/docs/linter/rules/index.mdx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
title: Lint Rules
title: Rules
description: List of available lint rules.
---

8 changes: 7 additions & 1 deletion xtask/lintdoc/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
mod rules_sources;

use crate::rules_sources::generate_rule_sources;
use biome_analyze::{
AnalysisFilter, AnalyzerOptions, ControlFlow, FixKind, GroupCategory, Queryable,
RegistryVisitor, Rule, RuleCategory, RuleFilter, RuleGroup, RuleMetadata, RuleSource,
@@ -30,6 +33,7 @@ use xtask::{glue::fs2, *};
fn main() -> Result<()> {
let root = project_root().join("website/src/content/docs/linter/rules");
let reference_groups = project_root().join("website/src/components/generated/Groups.astro");
let rules_sources = project_root().join("website/src/content/docs/linter/rules-sources.mdx");
let reference_number_of_rules =
project_root().join("website/src/components/generated/NumberOfRules.astro");
let reference_recommended_rules =
@@ -52,7 +56,7 @@ fn main() -> Result<()> {
let mut index = Vec::new();
let mut reference_buffer = Vec::new();
writeln!(index, "---")?;
writeln!(index, "title: Lint Rules")?;
writeln!(index, "title: Rules")?;
writeln!(index, "description: List of available lint rules.")?;
writeln!(index, "---")?;
writeln!(index)?;
@@ -145,6 +149,7 @@ fn main() -> Result<()> {
reference_buffer,
"<!-- this file is auto generated, use `cargo lintdoc` to update it -->"
)?;
let rule_sources_buffer = generate_rule_sources(groups.clone())?;
for (group, rules) in groups {
generate_group(
group,
@@ -192,6 +197,7 @@ fn main() -> Result<()> {
fs2::write(reference_groups, reference_buffer)?;
fs2::write(reference_number_of_rules, number_of_rules_buffer)?;
fs2::write(reference_recommended_rules, recommended_rules_buffer)?;
fs2::write(rules_sources, rule_sources_buffer)?;

Ok(())
}
121 changes: 121 additions & 0 deletions xtask/lintdoc/src/rules_sources.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
use biome_analyze::RuleMetadata;
use convert_case::{Case, Casing};
use std::cmp::Ordering;
use std::collections::{BTreeMap, BTreeSet};
use std::io::Write;
use xtask::*;

#[derive(Debug, Eq, PartialEq)]
struct SourceSet {
source_rule_name: String,
source_link: String,
biome_rule_name: String,
biome_link: String,
inspired: bool,
}

impl Ord for SourceSet {
fn cmp(&self, other: &Self) -> Ordering {
self.source_rule_name.cmp(&other.source_rule_name)
}
}

impl PartialOrd for SourceSet {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}

impl SourceSet {}

pub(crate) fn generate_rule_sources(
rules: BTreeMap<&str, BTreeMap<&'static str, RuleMetadata>>,
) -> Result<Vec<u8>> {
let mut buffer = vec![];

writeln!(
buffer,
r#"---
title: Rules sources
description: A page that maps lint rules from other sources to Biome
---
"#
)?;

writeln!(
buffer,
r#":::note
Some **Biome** rules might **not** have options, compared to the original rule.
:::"#
)?;

let rules = rules
.into_iter()
.flat_map(|(_, rule)| rule)
.collect::<BTreeMap<&str, RuleMetadata>>();

let mut rules_by_source = BTreeMap::<String, BTreeSet<SourceSet>>::new();

for (rule_name, metadata) in rules {
if let Some(source) = &metadata.source {
let set = rules_by_source.get_mut(&format!("{source}"));
if let Some(set) = set {
set.insert(SourceSet {
biome_rule_name: rule_name.to_string(),
biome_link: format!("/lint/rules/{}", rule_name.to_case(Case::Kebab)),
source_link: source.to_rule_url(),
source_rule_name: source.as_rule_name().to_string(),
inspired: metadata
.source_kind
.map(|kind| kind.is_inspired())
.unwrap_or(false),
});
} else {
let mut set = BTreeSet::new();
set.insert(SourceSet {
biome_rule_name: rule_name.to_string(),
biome_link: format!("/lint/rules/{}", rule_name.to_case(Case::Kebab)),
source_link: source.to_rule_url(),
source_rule_name: source.as_rule_name().to_string(),
inspired: metadata
.source_kind
.map(|kind| kind.is_inspired())
.unwrap_or(true),
});
rules_by_source.insert(format!("{source}"), set);
}
}
}

for (source, rules) in rules_by_source {
writeln!(buffer, "## {source}")?;
writeln!(buffer, r#"| {source} rule name | Biome rule name |"#)?;
writeln!(buffer, r#"| ---- | ---- |"#)?;

push_to_table(rules, &mut buffer)?;
}

Ok(buffer)
}

fn push_to_table(source_set: BTreeSet<SourceSet>, buffer: &mut Vec<u8>) -> Result<u8> {
let mut footnotes = 0;
for source_set in source_set {
write!(
buffer,
"| [{}]({}) |[{}](/linter/rules/{})",
source_set.source_rule_name,
source_set.source_link,
source_set.biome_rule_name,
source_set.biome_link
)?;

if source_set.inspired {
footnotes += 1;
write!(buffer, " (inspired)")?;
}
writeln!(buffer, " |")?;
}

Ok(footnotes)
}