refactor(codegen): generate lint/assist/syntax groups by proc macro#8901
refactor(codegen): generate lint/assist/syntax groups by proc macro#8901
Conversation
|
d65df35 to
544b125
Compare
Parser conformance results onjs/262
jsx/babel
symbols/microsoft
ts/babel
ts/microsoft
|
Merging this PR will not alter performance
Comparing Footnotes
|
WalkthroughAdds a new procedural‑macro crate Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 2✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (6)
crates/biome_graphql_analyze/src/lint/correctness.rs (1)
1-3: Duplicate "Generated file" comment.Lines 1 and 3 both contain the same comment. This appears to be unintentional duplication from codegen.
crates/biome_css_analyze/src/lint/suspicious.rs (1)
1-3: Duplicate "Generated file" comment.Same issue as in
correctness.rs- lines 1 and 3 both contain identical comments.crates/biome_css_analyze/src/assist/source.rs (1)
1-3: Duplicate "Generated file" comment.Consistent with other generated files - the duplicate should be addressed in the codegen template.
xtask/codegen/src/generate_analyzer.rs (1)
141-156: Double preamble causing duplicate "Generated file" comments.
xtask_glue::reformat(line 141) andreformat(line 156) both add the "Generated file" preamble, resulting in the duplicate comments visible in all generated group files. Consider usingreformat_without_preamblefor one of these calls.🔧 Proposed fix
- let tokens = xtask_glue::reformat(quote! { + let tokens = xtask_glue::reformat_without_preamble(quote! { //! Group description generated by proc macro at compile time.Alternatively, remove the explicit
//!doc comment from the quote and let the singlereformatcall add the standard preamble.crates/biome_js_analyze/src/lint/correctness.rs (1)
1-3: Duplicate header comment.Lines 1 and 3 both contain the same "Generated file, do not edit by hand" comment. One of these should be removed.
🧹 Proposed fix
//! Generated file, do not edit by hand, see `xtask/codegen` - -//! Generated file, do not edit by hand, see `xtask/codegen`crates/biome_graphql_analyze/src/lint/suspicious.rs (1)
1-3: Duplicate header comment.Same issue as in other files — lines 1 and 3 both contain the "Generated file" comment.
🧹 Proposed fix
//! Generated file, do not edit by hand, see `xtask/codegen` - -//! Generated file, do not edit by hand, see `xtask/codegen`
🤖 Fix all issues with AI agents
In `@crates/biome_json_analyze/Cargo.toml`:
- Line 22: Add the internal crate to the workspace dependencies and switch the
local path usage to the workspace alias: add biome_analyze_macros = { version =
"<appropriate-version-or-omit-if-managed>", workspace = true } to the root
Cargo.toml [workspace.dependencies] and then change the dependency in the
crate's Cargo.toml (the line with biome_analyze_macros) to biome_analyze_macros
= { workspace = true } so the crate uses the workspace-managed dependency
instead of a path.
🧹 Nitpick comments (7)
crates/biome_html_analyze/build.rs (1)
50-50: Consider using&Pathinstead of&PathBuffor the parameter type.Using
&Pathis more idiomatic as it accepts both&Pathand&PathBufvia deref coercion. This is a minor nit and consistent with what's done in other build.rs files in this PR, so feel free to address across all of them together or leave as-is.♻️ Suggested change
-fn touch_file(path: &PathBuf) -> io::Result<()> { +fn touch_file(path: &Path) -> io::Result<()> {You'd also need to add
use std::path::Path;to the imports.crates/biome_js_analyze/Cargo.toml (1)
24-24: Path dependency forbiome_analyze_macros.Same pattern as other crates - uses path instead of workspace. If workspace dependencies are updated, this should follow suit.
crates/biome_graphql_analyze/Cargo.toml (1)
16-16: Consider addingbiome_analyze_macrosto workspace dependencies for consistency.
biome_analyze_macroscurrently uses a path dependency whilst other internal crates useworkspace = true. All other analyse crates (biome_js_analyze, biome_css_analyze, etc.) follow the same pattern, but ifbiome_analyze_macrosis added to root workspace dependencies, this reference should be updated to{ workspace = true }to align with the coding guideline: "Use workspace dependencies withworkspace = truefor internal crates in Cargo.toml".crates/biome_json_analyze/build.rs (1)
52-61: Consider using&Pathinstead of&PathBuf.The function only reads the path; accepting
&Pathis more flexible and idiomatic, as it works with bothPathBufandPathreferences.♻️ Proposed fix
-fn touch_file(path: &PathBuf) -> io::Result<()> { +fn touch_file(path: &Path) -> io::Result<()> {You'll also need to add
Pathto the import:-use std::path::PathBuf; +use std::path::{Path, PathBuf};crates/biome_js_analyze/build.rs (2)
62-71: Consider using&Pathinstead of&PathBuf.Same suggestion as for the JSON analyzer —
&Pathis more idiomatic when only reading the path.♻️ Proposed fix
-fn touch_file(path: &PathBuf) -> io::Result<()> { +fn touch_file(path: &Path) -> io::Result<()> {Add
Pathto the import:-use std::path::PathBuf; +use std::path::{Path, PathBuf};
1-71: Consider extracting shared build script logic.The
watch_groupandtouch_filefunctions are duplicated across multiple crates' build scripts (css, graphql, html, js, json). While build scripts are isolated per crate, you might consider extracting this logic to a shared utility crate (e.g.,biome_build_utils) to reduce maintenance burden if the pattern needs to change in future.This is purely optional and can be deferred.
crates/biome_analyze_macros/src/group_macro.rs (1)
78-108: Defensively ignore non‑.rsfiles and guard duplicate rule names.
A stray README/fixture would be treated as a rule today, and duplicate generated names would silently overwrite earlier entries. Filtering and explicit duplicate checks make failures clearer.Proposed tweak
let entry = entry?.path(); if !entry.is_file() { continue; } + if entry.extension().and_then(|e| e.to_str()) != Some("rs") { + continue; + } let file_name = entry .file_stem() .context("path has no file name")? .to_str() .context("could not convert file name to string")?; let rule_type = Case::Pascal.convert(file_name); let key = rule_type.clone(); let module_name = format_ident!("{}", file_name); let rule_type = format_ident!("{}", rule_type); - rules.insert( - key, + if rules.insert( + key.clone(), ( quote! { pub mod `#module_name`; }, quote! { self::`#module_name`::`#rule_type` }, ), - ); + ).is_some() { + bail!("Duplicate rule name generated: {}", key); + }
544b125 to
f53e0d3
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
xtask/codegen/src/generate_analyzer.rs (1)
139-157: Avoid double preamble by formatting only once.
xtask_glue::reformatalready prepends the generated header, so the secondreformatduplicates it (see the double “Generated file” lines in the output). One pass is enough.🧹 Suggested tweak
- let tokens = xtask_glue::reformat(quote! { + let tokens = reformat(quote! { //! Group description generated by proc macro at compile time. //! //! To add a new rule, create a `.rs` file in the group subdirectory //! and run `cargo check`. The build system will automatically discover //! and register your rule. use biome_analyze_macros::declare_group_from_fs; declare_group_from_fs! { category: `#category`, group: `#group` } })?; - let tokens = reformat(tokens)?; fs2::write(base_path.join(category).join(format!("{group}.rs")), tokens)?;crates/biome_graphql_analyze/src/lint/suspicious.rs (1)
1-4: Duplicate module-level doc comment.Lines 1 and 3 contain identical
//! Generated file...comments. This appears to be a codegen artifact — consider deduplicating in the generation logic.
🤖 Fix all issues with AI agents
In `@crates/biome_analyze_macros/src/group_macro.rs`:
- Around line 78-109: Filter directory entries to only Rust source files and
skip mod.rs to avoid invalid modules: when iterating results from
fs2::read_dir(&group_dir) in the loop that builds module_name and rule_type,
first check that entry.path().is_file(), that
entry.path().extension().and_then(|e| e.to_str()) == Some("rs"), and that the
file_stem (the value later used as module_name/file_name) is not "mod"; if any
of these checks fail, continue the loop. Keep using the same symbols
(fs2::read_dir, entry.path(), file_stem(), module_name, rule_type, rules.insert)
so the guard sits before creating format_ident! and inserting into rules.
🧹 Nitpick comments (1)
crates/biome_json_analyze/build.rs (1)
8-12: Prefer&Pathintouch_fileto avoid unnecessary type coupling.Keeps the helper flexible without changing behaviour.
♻️ Suggested tweak
-use std::path::PathBuf; +use std::path::{Path, PathBuf}; ... -fn touch_file(path: &PathBuf) -> io::Result<()> { +fn touch_file(path: &Path) -> io::Result<()> {Also applies to: 53-54
f53e0d3 to
6ab3bf4
Compare
There was a problem hiding this comment.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
crates/biome_js_analyze/src/assist/source.rs (1)
1-4: Duplicate header comment.Lines 1 and 3 both contain the same "Generated file, do not edit by hand" comment. This appears to be a generation artefact that should be deduplicated.
🧹 Suggested fix
//! Generated file, do not edit by hand, see `xtask/codegen` - -//! Generated file, do not edit by hand, see `xtask/codegen`crates/biome_css_analyze/src/assist/source.rs (1)
1-4: Duplicate header comment.Same issue as in
biome_js_analyze/src/assist/source.rs— lines 1 and 3 contain identical "Generated file" comments.🧹 Suggested fix
//! Generated file, do not edit by hand, see `xtask/codegen` - -//! Generated file, do not edit by hand, see `xtask/codegen`crates/biome_js_analyze/src/lint/security.rs (1)
1-3: Duplicate generated file comment.Lines 1 and 3 contain the same
//! Generated file...comment. This appears to be a codegen artefact that occurs across all modified files. Consider updatingxtask/codegento emit this comment only once.crates/biome_css_analyze/src/lint/a11y.rs (1)
1-3: Duplicate header comment.Lines 1 and 3 both contain the same generated file notice. Looks like the codegen is stuttering a bit.
Proposed fix
//! Generated file, do not edit by hand, see `xtask/codegen` - -//! Generated file, do not edit by hand, see `xtask/codegen`
🧹 Nitpick comments (2)
crates/biome_css_analyze/build.rs (1)
57-57: Nit: prefer&Pathover&PathBufin function signatures.Clippy's
ptr_arglint recommends using&Pathfor better API flexibility—callers can pass&PathBuf,&Path, or&strwithout conversion.♻️ Suggested change
-fn touch_file(path: &PathBuf) -> io::Result<()> { +fn touch_file(path: &Path) -> io::Result<()> {And add
use std::path::Path;to the imports.crates/biome_js_analyze/src/lint/style/use_block_statements.rs (1)
77-116: Macros are safely local and the enum is only used within this file. The macros have zero external callers, so moving them to function scope poses no risk. TheUseBlockStatementsOperationTypeenum could be made private if you'd prefer to keep the public API surface tighter—all eight usages are contained within this module.
crates/biome_json_analyze/build.rs
Outdated
There was a problem hiding this comment.
I believe we can codegen these (checking the code), so that when we add a new group, we don't need to manually update the build file. It would be unfortunate if a contributor adds a new lint rule to a new group, and we miss that part. If we can't codegen, maybe we can read the folders under assist and lint.
What do you think? I hope you see the issue. Maybe you'll have better solution
There was a problem hiding this comment.
I think that makes sense.
6ab3bf4 to
5450c18
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
crates/biome_json_analyze/src/assist/source.rs (1)
1-3: Duplicate "generated file" comment.Lines 1 and 3 both contain the same comment. Likely a codegen hiccup worth tidying up.
🔧 Proposed fix
//! Generated file, do not edit by hand, see `xtask/codegen` - -//! Generated file, do not edit by hand, see `xtask/codegen` #![doc = r" Group description generated by proc macro at compile time."]crates/biome_js_analyze/src/lint/performance.rs (1)
1-3: Duplicate header comment.Lines 1 and 3 contain identical
//! Generated file...comments. This appears to be a codegen oversight.
🤖 Fix all issues with AI agents
In `@crates/biome_html_analyze/build.rs`:
- Around line 21-36: In watch_group, do not ignore fs::read_dir errors: when
read_dir(&group_dir) returns Err, check the error kind and propagate or return
the error if it's not NotFound so filesystem problems surface during build;
update the block that currently does `if let Ok(entries) =
fs::read_dir(&group_dir) { ... }` to match on the Result for
fs::read_dir(&group_dir) and return Err(e) (or propagate with the ? operator)
when e.kind() != io::ErrorKind::NotFound, while keeping the existing handling of
Ok(entries) and still calling touch_file(&group_file) at the end.
In `@xtask/codegen/src/generate_analyzer.rs`:
- Around line 20-36: The code creates _category_comment as a borrowed temporary
and then references an undefined identifier inside the quote; change to create
an owned String (e.g., let category_comment: String = match *category { "lint"
=> "Lint groups".into(), "assist" => "Assist groups".into(), "syntax" => "Syntax
groups".into(), other => { let mut title = other.to_string(); if let Some(ch) =
title.get_mut(0..1) { ch.make_ascii_uppercase(); } format!("{} groups", title) }
}; then splice that owned variable into the quote! invocation used to push into
watch_calls (watch_calls.push(quote! { // `#category_comment` });) so the
identifier exists and no temporary borrow occurs.
| fn watch_group(category: &str, group: &str) -> io::Result<()> { | ||
| let manifest_dir = env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not set"); | ||
| let base_path = PathBuf::from(&manifest_dir).join("src"); | ||
| let group_dir = base_path.join(category).join(group); | ||
| let group_file = base_path.join(category).join(format!("{}.rs", group)); | ||
| println!("cargo:rerun-if-changed={}", group_dir.display()); | ||
| if let Ok(entries) = fs::read_dir(&group_dir) { | ||
| for entry in entries.flatten() { | ||
| let path = entry.path(); | ||
| if path.extension().is_some_and(|ext| ext == "rs") { | ||
| println!("cargo:rerun-if-changed={}", path.display()); | ||
| } | ||
| } | ||
| } | ||
| touch_file(&group_file)?; | ||
| Ok(()) |
There was a problem hiding this comment.
Don’t silently ignore read_dir failures.
If read_dir fails for reasons other than “not found”, the build continues without rerun hints, which can hide filesystem problems.
🔧 Suggested tweak
- if let Ok(entries) = fs::read_dir(&group_dir) {
- for entry in entries.flatten() {
- let path = entry.path();
- if path.extension().is_some_and(|ext| ext == "rs") {
- println!("cargo:rerun-if-changed={}", path.display());
- }
- }
- }
+ match fs::read_dir(&group_dir) {
+ Ok(entries) => {
+ for entry in entries.flatten() {
+ let path = entry.path();
+ if path.extension().is_some_and(|ext| ext == "rs") {
+ println!("cargo:rerun-if-changed={}", path.display());
+ }
+ }
+ }
+ Err(err) if err.kind() == io::ErrorKind::NotFound => {}
+ Err(err) => return Err(err),
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| fn watch_group(category: &str, group: &str) -> io::Result<()> { | |
| let manifest_dir = env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not set"); | |
| let base_path = PathBuf::from(&manifest_dir).join("src"); | |
| let group_dir = base_path.join(category).join(group); | |
| let group_file = base_path.join(category).join(format!("{}.rs", group)); | |
| println!("cargo:rerun-if-changed={}", group_dir.display()); | |
| if let Ok(entries) = fs::read_dir(&group_dir) { | |
| for entry in entries.flatten() { | |
| let path = entry.path(); | |
| if path.extension().is_some_and(|ext| ext == "rs") { | |
| println!("cargo:rerun-if-changed={}", path.display()); | |
| } | |
| } | |
| } | |
| touch_file(&group_file)?; | |
| Ok(()) | |
| fn watch_group(category: &str, group: &str) -> io::Result<()> { | |
| let manifest_dir = env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not set"); | |
| let base_path = PathBuf::from(&manifest_dir).join("src"); | |
| let group_dir = base_path.join(category).join(group); | |
| let group_file = base_path.join(category).join(format!("{}.rs", group)); | |
| println!("cargo:rerun-if-changed={}", group_dir.display()); | |
| match fs::read_dir(&group_dir) { | |
| Ok(entries) => { | |
| for entry in entries.flatten() { | |
| let path = entry.path(); | |
| if path.extension().is_some_and(|ext| ext == "rs") { | |
| println!("cargo:rerun-if-changed={}", path.display()); | |
| } | |
| } | |
| } | |
| Err(err) if err.kind() == io::ErrorKind::NotFound => {} | |
| Err(err) => return Err(err), | |
| } | |
| touch_file(&group_file)?; | |
| Ok(()) | |
| } |
🤖 Prompt for AI Agents
In `@crates/biome_html_analyze/build.rs` around lines 21 - 36, In watch_group, do
not ignore fs::read_dir errors: when read_dir(&group_dir) returns Err, check the
error kind and propagate or return the error if it's not NotFound so filesystem
problems surface during build; update the block that currently does `if let
Ok(entries) = fs::read_dir(&group_dir) { ... }` to match on the Result for
fs::read_dir(&group_dir) and return Err(e) (or propagate with the ? operator)
when e.kind() != io::ErrorKind::NotFound, while keeping the existing handling of
Ok(entries) and still calling touch_file(&group_file) at the end.
5450c18 to
d2f0adf
Compare
There was a problem hiding this comment.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (11)
crates/biome_js_analyze/src/syntax/correctness.rs (1)
1-9: Clarify generation source in the header docs.The new proc‑macro docs say the file is generated at compile time, but the header still (twice) points at
xtask/codegen, which is now misleading. Consider collapsing to a single, accurate header.💡 Suggested tidy-up
-//! Generated file, do not edit by hand, see `xtask/codegen` - -//! Generated file, do not edit by hand, see `xtask/codegen` +//! Generated file, do not edit by hand. Contents are produced at compile time by `declare_group_from_fs!`.crates/biome_js_analyze/src/assist/source.rs (1)
1-3: Duplicate header comment.The "Generated file, do not edit by hand" comment appears twice (lines 1 and 3). This looks like a codegen bug.
crates/biome_graphql_analyze/src/lint/suspicious.rs (1)
1-3: Duplicate header comment (codegen-wide issue).Same as other generated files — the header appears twice. The codegen template likely has this bug.
crates/biome_css_analyze/src/assist/source.rs (1)
1-4: Duplicate "Generated file" comment.Lines 1 and 3 both contain the same
//! Generated file, do not edit by hand, see xtask/codegencomment. Looks like the codegen duplicated this accidentally.🧹 Proposed fix
//! Generated file, do not edit by hand, see `xtask/codegen` - -//! Generated file, do not edit by hand, see `xtask/codegen` #![doc = r" Group description generated by proc macro at compile time."]crates/biome_css_analyze/src/lint/suspicious.rs (1)
1-4: Duplicate "Generated file" comment.Same issue as other files — lines 1 and 3 are duplicates.
crates/biome_js_analyze/src/lint/performance.rs (1)
1-4: Duplicate "Generated file" comment.Lines 1 and 3 — same duplication issue present across all these generated files.
crates/biome_js_analyze/src/lint/style.rs (1)
1-4: Duplicate "Generated file" comment.Consistent with the other files — duplicate on lines 1 and 3.
crates/biome_css_analyze/src/lint/correctness.rs (1)
1-4: Duplicate "Generated file" comment.Same duplication on lines 1 and 3.
crates/biome_js_analyze/src/lint/a11y.rs (1)
1-4: Duplicate "Generated file" comment.Lines 1 and 3 duplicated.
crates/biome_js_analyze/src/lint/complexity.rs (1)
1-4: Fix duplicate "Generated file" comment in the codegen template.The comment appears on both lines 1 and 3 of all generated group files across the codebase — this needs fixing at the source rather than in individual files.
crates/biome_js_analyze/src/lint/correctness.rs (1)
1-3: Duplicate header comment.Lines 1 and 3 are identical. Looks like one slipped through during generation—one of these should be removed.
🧹 Proposed fix
//! Generated file, do not edit by hand, see `xtask/codegen` - -//! Generated file, do not edit by hand, see `xtask/codegen`
🧹 Nitpick comments (3)
crates/biome_analyze_macros/src/lib.rs (1)
5-25: Prefer a runnable doctest for the macro example.
The currentignoreblock won’t be validated; consider adding a smallrust/no_rundoctest (with an assertion) and keep the full macro usage in a separate ignored block if needed. As per coding guidelines: Use doc tests (doctest) format with code blocks in rustdoc comments; ensure assertions pass in tests.xtask/codegen/src/generate_analyzer.rs (1)
114-149: Consider extracting the repeated category-checking pattern.The same pattern (check if directory exists → generate category → insert if non-empty) is repeated across all
generate_*_analyzerfunctions. A helper function could reduce duplication.♻️ Suggested helper approach
fn try_add_category( category: &'static str, base_path: &Path, analyzers: &mut BTreeMap<&'static str, TokenStream>, categories_and_groups: &mut BTreeMap<&'static str, Vec<String>>, ) -> Result<()> { let path = base_path.join(category); if path.exists() { let groups = generate_category(category, analyzers, base_path)?; if !groups.is_empty() { categories_and_groups.insert(category, groups); } } Ok(()) }crates/biome_json_analyze/src/assist/source.rs (1)
1-4: Minor: Duplicate generated-file comment.Lines 1 and 3 both contain
//! Generated file, do not edit by hand, see xtask/codegen. This appears to be a codegen artifact worth tidying up.
Summary
The goal of this refactor is to make it so that we can avoid merge conflicts when merging new rules.
Frequently, as new nursery rules get added, merging one rule causes all the other PRs for new rules to have merge conflicts.
There's 2 parts to this:
This was generated by sonnet 4.5.
Test Plan
CI should remain green. No changes to any snapshots.
Docs