mirror of
https://github.com/astral-sh/ruff.git
synced 2025-07-09 22:25:09 +00:00
derive-msg-formats 4/5: Implement #[derive_message_formats]
The idea is nice and simple we replace: fn placeholder() -> Self; with fn message_formats() -> &'static [&'static str]; So e.g. if a Violation implementation defines: fn message(&self) -> String { format!("Local variable `{name}` is assigned to but never used") } it would also have to define: fn message_formats() -> &'static [&'static str] { &["Local variable `{name}` is assigned to but never used"] } Since we however obviously do not want to duplicate all of our format strings we simply introduce a new procedural macro attribute #[derive_message_formats] that can be added to the message method declaration in order to automatically derive the message_formats implementation. This commit implements the macro. The following and final commit updates violations.rs to use the macro. (The changes have been separated because the next commit is autogenerated via a Python script.)
This commit is contained in:
parent
8f6d8e215c
commit
16e79c8db6
10 changed files with 87 additions and 20 deletions
|
@ -297,7 +297,7 @@ pub fn explain(rule: &Rule, format: SerializationFormat) -> Result<()> {
|
|||
"{} ({}): {}",
|
||||
rule.code(),
|
||||
rule.origin().name(),
|
||||
rule.kind().body()
|
||||
rule.message_formats()[0]
|
||||
);
|
||||
}
|
||||
SerializationFormat::Json => {
|
||||
|
@ -306,7 +306,7 @@ pub fn explain(rule: &Rule, format: SerializationFormat) -> Result<()> {
|
|||
serde_json::to_string_pretty(&Explanation {
|
||||
code: rule.code(),
|
||||
origin: rule.origin().name(),
|
||||
summary: &rule.kind().body(),
|
||||
summary: rule.message_formats()[0],
|
||||
})?
|
||||
);
|
||||
}
|
||||
|
|
|
@ -26,7 +26,6 @@ fn generate_table(table_out: &mut String, prefix: &RuleCodePrefix) {
|
|||
table_out.push_str("| ---- | ---- | ------- | --- |");
|
||||
table_out.push('\n');
|
||||
for rule in prefix.codes() {
|
||||
let kind = rule.kind();
|
||||
let fix_token = match rule.autofixable() {
|
||||
None => "",
|
||||
Some(_) => "🛠",
|
||||
|
@ -36,7 +35,7 @@ fn generate_table(table_out: &mut String, prefix: &RuleCodePrefix) {
|
|||
"| {} | {} | {} | {} |",
|
||||
rule.code(),
|
||||
rule.as_ref(),
|
||||
kind.body().replace('|', r"\|"),
|
||||
rule.message_formats()[0].replace('|', r"\|"),
|
||||
fix_token
|
||||
));
|
||||
table_out.push('\n');
|
||||
|
|
|
@ -8,7 +8,7 @@ use syn::{Ident, LitStr, Path, Token};
|
|||
pub fn define_rule_mapping(mapping: &Mapping) -> proc_macro2::TokenStream {
|
||||
let mut rule_variants = quote!();
|
||||
let mut diagkind_variants = quote!();
|
||||
let mut rule_kind_match_arms = quote!();
|
||||
let mut rule_message_formats_match_arms = quote!();
|
||||
let mut rule_autofixable_match_arms = quote!();
|
||||
let mut rule_origin_match_arms = quote!();
|
||||
let mut rule_code_match_arms = quote!();
|
||||
|
@ -26,9 +26,8 @@ pub fn define_rule_mapping(mapping: &Mapping) -> proc_macro2::TokenStream {
|
|||
#name,
|
||||
});
|
||||
diagkind_variants.extend(quote! {#name(#path),});
|
||||
rule_kind_match_arms.extend(
|
||||
quote! {Self::#name => DiagnosticKind::#name(<#path as Violation>::placeholder()),},
|
||||
);
|
||||
rule_message_formats_match_arms
|
||||
.extend(quote! {Self::#name => <#path as Violation>::message_formats(),});
|
||||
rule_autofixable_match_arms.extend(quote! {Self::#name => <#path as Violation>::AUTOFIX,});
|
||||
let origin = get_origin(code);
|
||||
rule_origin_match_arms.extend(quote! {Self::#name => RuleOrigin::#origin,});
|
||||
|
@ -86,9 +85,9 @@ pub fn define_rule_mapping(mapping: &Mapping) -> proc_macro2::TokenStream {
|
|||
}
|
||||
|
||||
impl Rule {
|
||||
/// A placeholder representation of the `DiagnosticKind` for the diagnostic.
|
||||
pub fn kind(&self) -> DiagnosticKind {
|
||||
match self { #rule_kind_match_arms }
|
||||
/// Returns the format strings used to report violations of this rule.
|
||||
pub fn message_formats(&self) -> &'static [&'static str] {
|
||||
match self { #rule_message_formats_match_arms }
|
||||
}
|
||||
|
||||
pub fn autofixable(&self) -> Option<crate::violation::AutofixKind> {
|
||||
|
|
55
ruff_macros/src/derive_message_formats.rs
Normal file
55
ruff_macros/src/derive_message_formats.rs
Normal file
|
@ -0,0 +1,55 @@
|
|||
use proc_macro2::TokenStream;
|
||||
use quote::{quote, quote_spanned, ToTokens};
|
||||
use syn::spanned::Spanned;
|
||||
use syn::{Block, Expr, ItemFn, Stmt};
|
||||
|
||||
pub fn derive_message_formats(func: &ItemFn) -> proc_macro2::TokenStream {
|
||||
let mut strings = quote!();
|
||||
|
||||
if let Err(err) = parse_block(&func.block, &mut strings) {
|
||||
return err;
|
||||
}
|
||||
|
||||
quote! {
|
||||
#func
|
||||
fn message_formats() -> &'static [&'static str] {
|
||||
&[#strings]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_block(block: &Block, strings: &mut TokenStream) -> Result<(), TokenStream> {
|
||||
let Some(Stmt::Expr(last)) = block.stmts.last() else {panic!("expected last statement in block to be an expression")};
|
||||
parse_expr(last, strings)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_expr(expr: &Expr, strings: &mut TokenStream) -> Result<(), TokenStream> {
|
||||
match expr {
|
||||
Expr::Macro(mac) if mac.mac.path.is_ident("format") => {
|
||||
let Some(first_token) = mac.mac.tokens.to_token_stream().into_iter().next() else {
|
||||
return Err(quote_spanned!(expr.span() => compile_error!("expected format! to have an argument")))
|
||||
};
|
||||
strings.extend(quote! {#first_token,});
|
||||
Ok(())
|
||||
}
|
||||
Expr::Block(block) => parse_block(&block.block, strings),
|
||||
Expr::If(expr) => {
|
||||
parse_block(&expr.then_branch, strings)?;
|
||||
if let Some((_, then)) = &expr.else_branch {
|
||||
parse_expr(then, strings)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Expr::Match(block) => {
|
||||
for arm in &block.arms {
|
||||
parse_expr(&arm.body, strings)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
_ => Err(quote_spanned!(
|
||||
expr.span() =>
|
||||
compile_error!("expected last expression to be a format! macro or a match block")
|
||||
)),
|
||||
}
|
||||
}
|
|
@ -13,10 +13,12 @@
|
|||
)]
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
use syn::{parse_macro_input, DeriveInput};
|
||||
use proc_macro::TokenStream;
|
||||
use syn::{parse_macro_input, DeriveInput, ItemFn};
|
||||
|
||||
mod config;
|
||||
mod define_rule_mapping;
|
||||
mod derive_message_formats;
|
||||
mod prefixes;
|
||||
mod rule_code_prefix;
|
||||
|
||||
|
@ -34,3 +36,9 @@ pub fn define_rule_mapping(item: proc_macro::TokenStream) -> proc_macro::TokenSt
|
|||
let mapping = parse_macro_input!(item as define_rule_mapping::Mapping);
|
||||
define_rule_mapping::define_rule_mapping(&mapping).into()
|
||||
}
|
||||
|
||||
#[proc_macro_attribute]
|
||||
pub fn derive_message_formats(_attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let func = parse_macro_input!(item as ItemFn);
|
||||
derive_message_formats::derive_message_formats(&func).into()
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use ruff_macros::derive_message_formats;
|
||||
use rustc_hash::FxHashMap;
|
||||
use rustpython_ast::{Alias, Expr, Located};
|
||||
use schemars::JsonSchema;
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use ruff_macros::derive_message_formats;
|
||||
use rustpython_ast::Stmt;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#![allow(clippy::useless_format)]
|
||||
pub mod eradicate;
|
||||
pub mod flake8_2020;
|
||||
pub mod flake8_annotations;
|
||||
|
|
|
@ -18,8 +18,8 @@ pub trait Violation: Debug + PartialEq + Eq + Serialize + DeserializeOwned {
|
|||
None
|
||||
}
|
||||
|
||||
/// A placeholder instance of the violation.
|
||||
fn placeholder() -> Self;
|
||||
/// Returns the format strings used by [`message`](Violation::message).
|
||||
fn message_formats() -> &'static [&'static str];
|
||||
}
|
||||
|
||||
pub struct AutofixKind {
|
||||
|
@ -48,8 +48,9 @@ pub trait AlwaysAutofixableViolation:
|
|||
/// The title displayed for the available autofix.
|
||||
fn autofix_title(&self) -> String;
|
||||
|
||||
/// A placeholder instance of the violation.
|
||||
fn placeholder() -> Self;
|
||||
/// Returns the format strings used by
|
||||
/// [`message`](AlwaysAutofixableViolation::message).
|
||||
fn message_formats() -> &'static [&'static str];
|
||||
}
|
||||
|
||||
/// A blanket implementation.
|
||||
|
@ -64,8 +65,8 @@ impl<VA: AlwaysAutofixableViolation> Violation for VA {
|
|||
Some(Self::autofix_title)
|
||||
}
|
||||
|
||||
fn placeholder() -> Self {
|
||||
<Self as AlwaysAutofixableViolation>::placeholder()
|
||||
fn message_formats() -> &'static [&'static str] {
|
||||
<Self as AlwaysAutofixableViolation>::message_formats()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
#![allow(clippy::useless_format)]
|
||||
use std::fmt;
|
||||
|
||||
use itertools::Itertools;
|
||||
use ruff_macros::derive_message_formats;
|
||||
use rustpython_ast::Cmpop;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
@ -255,7 +257,7 @@ define_violation!(
|
|||
impl Violation for IOError {
|
||||
fn message(&self) -> String {
|
||||
let IOError(message) = self;
|
||||
message.clone()
|
||||
format!("{message}")
|
||||
}
|
||||
|
||||
fn placeholder() -> Self {
|
||||
|
@ -4280,7 +4282,7 @@ define_violation!(
|
|||
);
|
||||
impl Violation for NoThisPrefix {
|
||||
fn message(&self) -> String {
|
||||
"First word of the docstring should not be \"This\"".to_string()
|
||||
r#"First word of the docstring should not be "This""#.to_string()
|
||||
}
|
||||
|
||||
fn placeholder() -> Self {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue