Skip to content

Commit

Permalink
Respect deprecated attribute in configuration options
Browse files Browse the repository at this point in the history
  • Loading branch information
MichaReiser committed Oct 18, 2023
1 parent a583805 commit 0e630f3
Show file tree
Hide file tree
Showing 9 changed files with 3,220 additions and 16 deletions.
23 changes: 19 additions & 4 deletions crates/ruff_dev/src/generate_docs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use strum::IntoEnumIterator;
use ruff_diagnostics::FixAvailability;
use ruff_linter::registry::{Linter, Rule, RuleNamespace};
use ruff_workspace::options::Options;
use ruff_workspace::options_base::OptionsMetadata;
use ruff_workspace::options_base::{OptionEntry, OptionsMetadata};

use crate::ROOT_DIR;

Expand Down Expand Up @@ -55,7 +55,11 @@ pub(crate) fn main(args: &Args) -> Result<()> {
output.push('\n');
}

process_documentation(explanation.trim(), &mut output);
process_documentation(
explanation.trim(),
&mut output,
rule.noqa_code().to_string(),
);

let filename = PathBuf::from(ROOT_DIR)
.join("docs")
Expand All @@ -74,7 +78,7 @@ pub(crate) fn main(args: &Args) -> Result<()> {
Ok(())
}

fn process_documentation(documentation: &str, out: &mut String) {
fn process_documentation(documentation: &str, out: &mut String, rule_name: String) {
let mut in_options = false;
let mut after = String::new();

Expand All @@ -100,7 +104,17 @@ fn process_documentation(documentation: &str, out: &mut String) {
if let Some(rest) = line.strip_prefix("- `") {
let option = rest.trim_end().trim_end_matches('`');

assert!(Options::metadata().has(option), "unknown option {option}");
match Options::metadata().find(option) {
Some(OptionEntry::Field(field)) => {
if field.deprecated.is_some() {
eprintln!("Rule {rule_name} references deprecated option {option}.");
}
}
Some(_) => {}
None => {
panic!("Unknown option {option} referenced by rule {rule_name}");
}
}

let anchor = option.replace('.', "-");
out.push_str(&format!("- [`{option}`][{option}]\n"));
Expand Down Expand Up @@ -138,6 +152,7 @@ Something [`else`][other].
[other]: http://example.com.",
&mut output,
"example".to_string(),
);
assert_eq!(
output,
Expand Down
15 changes: 15 additions & 0 deletions crates/ruff_dev/src/generate_options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,21 @@ fn emit_field(output: &mut String, name: &str, field: &OptionField, parent_set:
field.example
));
output.push('\n');

if let Some(deprecated) = &field.deprecated {
output.push_str("> **Warning**\n");
output.push_str("> This option is deprecated");

if let Some(since) = deprecated.since {
write!(output, " (since {since})").unwrap();
}

if let Some(message) = deprecated.message {
writeln!(output, ": {message}").unwrap();
} else {
output.push('.');
}
}
}

#[derive(Default)]
Expand Down
1 change: 1 addition & 0 deletions crates/ruff_macros/src/combine_options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ pub(crate) fn derive_impl(input: DeriveInput) -> syn::Result<proc_macro2::TokenS
Ok(quote! {
impl crate::configuration::CombinePluginOptions for #ident {
fn combine(self, other: Self) -> Self {
#[allow(deprecated)]
Self {
#(
#output
Expand Down
90 changes: 89 additions & 1 deletion crates/ruff_macros/src/config.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use proc_macro2::TokenTree;
use proc_macro2::{TokenStream, TokenTree};
use quote::{quote, quote_spanned};
use syn::meta::ParseNestedMeta;
use syn::parse::{Parse, ParseStream};
use syn::spanned::Spanned;
use syn::token::Comma;
Expand Down Expand Up @@ -193,13 +194,35 @@ fn handle_option(field: &Field, attr: &Attribute) -> syn::Result<proc_macro2::To
} = attr.parse_args::<FieldAttributes>()?;
let kebab_name = LitStr::new(&ident.to_string().replace('_', "-"), ident.span());

let deprecated = if let Some(deprecated) = field
.attrs
.iter()
.find(|attr| attr.path().is_ident("deprecated"))
{
fn quote_option(option: Option<String>) -> TokenStream {
match option {
None => quote!(None),
Some(value) => quote!(Some(#value)),
}
}

let deprecated = parse_deprecated_attribute(deprecated)?;
let note = quote_option(deprecated.note);
let since = quote_option(deprecated.since);

quote!(Some(crate::options_base::Deprecated { since: #since, message: #note }))
} else {
quote!(None)
};

Ok(quote_spanned!(
ident.span() => {
visit.record_field(#kebab_name, crate::options_base::OptionField{
doc: &#doc,
default: &#default,
value_type: &#value_type,
example: &#example,
deprecated: #deprecated
})
}
))
Expand Down Expand Up @@ -248,3 +271,68 @@ fn _parse_key_value(input: ParseStream, name: &str) -> syn::Result<String> {
_ => Err(syn::Error::new(value.span(), "Expected literal string")),
}
}

fn parse_deprecated_attribute(attribute: &Attribute) -> syn::Result<DeprecatedAttribute> {
let mut deprecated = DeprecatedAttribute::default();
attribute.parse_nested_meta(|meta| {
if meta.path.is_ident("note") {
deprecated.note = Some(get_string_literal(&meta, "note", "deprecated")?.value())
} else if meta.path.is_ident("since") {
deprecated.since = Some(get_string_literal(&meta, "since", "deprecated")?.value())
} else {
return Err(syn::Error::new(
meta.path.span(),
format!(
"Deprecated meta {:?} is not supported by ruff's option macro.",
meta.path.get_ident()
),
));
}

Ok(())
})?;

Ok(deprecated)
}

fn get_string_literal(
meta: &ParseNestedMeta,
meta_name: &str,
attribute_name: &str,
) -> syn::Result<syn::LitStr> {
let expr: syn::Expr = meta.value()?.parse()?;

let mut value = &expr;
while let syn::Expr::Group(e) = value {
value = &e.expr;
}

if let syn::Expr::Lit(ExprLit {
lit: Lit::Str(lit), ..
}) = value
{
let suffix = lit.suffix();
if !suffix.is_empty() {
return Err(syn::Error::new(
lit.span(),
format!("unexpected suffix `{}` on string literal", suffix),
));
}

Ok(lit.clone())
} else {
Err(syn::Error::new(
expr.span(),
format!(
"expected {} attribute to be a string: `{} = \"...\"`",
attribute_name, meta_name
),
))
}
}

#[derive(Default, Debug)]
struct DeprecatedAttribute {
since: Option<String>,
note: Option<String>,
}
6 changes: 5 additions & 1 deletion crates/ruff_workspace/src/configuration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,9 @@ impl Configuration {
indent_style: format.indent_style.unwrap_or(format_defaults.indent_style),
indent_width: self
.tab_size
.map_or(format_defaults.indent_width, |tab_size| IndentWidth::from(NonZeroU8::from(tab_size))),
.map_or(format_defaults.indent_width, |tab_size| {
IndentWidth::from(NonZeroU8::from(tab_size))
}),
quote_style: format.quote_style.unwrap_or(format_defaults.quote_style),
magic_trailing_comma: format
.magic_trailing_comma
Expand Down Expand Up @@ -577,6 +579,7 @@ impl LintConfiguration {
}),
preview: options.preview.map(PreviewMode::from),

#[allow(deprecated)]
rule_selections: vec![RuleSelection {
select: options.common.select,
ignore: options
Expand All @@ -588,6 +591,7 @@ impl LintConfiguration {
.collect(),
extend_select: options.common.extend_select.unwrap_or_default(),
fixable: options.common.fixable,
#[allow(deprecated)]
unfixable: options
.common
.unfixable
Expand Down
14 changes: 6 additions & 8 deletions crates/ruff_workspace/src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -471,9 +471,6 @@ pub struct LintCommonOptions {

/// A list of rule codes or prefixes to ignore, in addition to those
/// specified by `ignore`.
///
/// This option has been **deprecated** in favor of `ignore`
/// since its usage is now interchangeable with `ignore`.
#[option(
default = "[]",
value_type = "list[RuleSelector]",
Expand All @@ -482,7 +479,9 @@ pub struct LintCommonOptions {
extend-ignore = ["F841"]
"#
)]
#[cfg_attr(feature = "schemars", schemars(skip))]
#[deprecated(
note = "This option has been **deprecated** in favor of `ignore` since its usage is now interchangeable with `ignore`."
)]
pub extend_ignore: Option<Vec<RuleSelector>>,

/// A list of rule codes or prefixes to enable, in addition to those
Expand Down Expand Up @@ -511,10 +510,9 @@ pub struct LintCommonOptions {

/// A list of rule codes or prefixes to consider non-auto-fixable, in addition to those
/// specified by `unfixable`.
///
/// This option has been **deprecated** in favor of `unfixable` since its usage is now
/// interchangeable with `unfixable`.
#[cfg_attr(feature = "schemars", schemars(skip))]
#[deprecated(
note = "This option has been **deprecated** in favor of `unfixable` since its usage is now interchangeable with `unfixable`."
)]
pub extend_unfixable: Option<Vec<RuleSelector>>,

/// A list of rule codes that are unsupported by Ruff, but should be
Expand Down
39 changes: 37 additions & 2 deletions crates/ruff_workspace/src/options_base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ impl OptionSet {
/// default: "false",
/// value_type: "bool",
/// example: "",
/// deprecated: None,
/// });
/// }
/// }
Expand All @@ -121,6 +122,7 @@ impl OptionSet {
/// default: "false",
/// value_type: "bool",
/// example: "",
/// deprecated: None
/// });
///
/// visit.record_set("format", Nested::metadata());
Expand Down Expand Up @@ -166,6 +168,7 @@ impl OptionSet {
/// default: "false",
/// value_type: "bool",
/// example: "",
/// deprecated: None
/// };
///
/// impl OptionsMetadata for WithOptions {
Expand All @@ -187,6 +190,7 @@ impl OptionSet {
/// default: "false",
/// value_type: "bool",
/// example: "",
/// deprecated: None
/// };
///
/// struct Root;
Expand All @@ -198,6 +202,7 @@ impl OptionSet {
/// default: "false",
/// value_type: "bool",
/// example: "",
/// deprecated: None
/// });
///
/// visit.record_set("format", Nested::metadata());
Expand Down Expand Up @@ -283,8 +288,16 @@ impl Visit for DisplayVisitor<'_, '_> {
self.result = self.result.and_then(|_| writeln!(self.f, "{name}"));
}

fn record_field(&mut self, name: &str, _: OptionField) {
self.result = self.result.and_then(|_| writeln!(self.f, "{name}"));
fn record_field(&mut self, name: &str, field: OptionField) {
self.result = self.result.and_then(|_| {
write!(self.f, "{name}")?;

if field.deprecated.is_some() {
write!(self.f, " (deprecated)")?;
}

writeln!(self.f)
});
}
}

Expand All @@ -308,6 +321,13 @@ pub struct OptionField {
pub default: &'static str,
pub value_type: &'static str,
pub example: &'static str,
pub deprecated: Option<Deprecated>,
}

#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Deprecated {
pub since: Option<&'static str>,
pub message: Option<&'static str>,
}

impl Display for OptionField {
Expand All @@ -316,6 +336,21 @@ impl Display for OptionField {
writeln!(f)?;
writeln!(f, "Default value: {}", self.default)?;
writeln!(f, "Type: {}", self.value_type)?;

if let Some(deprecated) = &self.deprecated {
write!(f, "Deprecated")?;

if let Some(since) = deprecated.since {
write!(f, " (since {since})")?;
}

if let Some(message) = deprecated.message {
write!(f, ": {message}")?;
}

writeln!(f)?;
}

writeln!(f, "Example usage:\n```toml\n{}\n```", self.example)
}
}
Loading

0 comments on commit 0e630f3

Please sign in to comment.