mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-30 22:01:18 +00:00
refactor: Replace Vec
in options metadata with static array (#3433)
This commit is contained in:
parent
1e081cf9a6
commit
cc8b13d3a7
7 changed files with 204 additions and 81 deletions
|
@ -12,8 +12,6 @@ license = "MIT"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "ruff"
|
name = "ruff"
|
||||||
crate-type = ["cdylib", "rlib"]
|
|
||||||
doctest = false
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
ruff_cache = { path = "../ruff_cache" }
|
ruff_cache = { path = "../ruff_cache" }
|
||||||
|
|
|
@ -803,7 +803,7 @@ impl Linter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(is_macro::Is)]
|
#[derive(is_macro::Is, Copy, Clone)]
|
||||||
pub enum LintSource {
|
pub enum LintSource {
|
||||||
Ast,
|
Ast,
|
||||||
Io,
|
Io,
|
||||||
|
@ -818,9 +818,9 @@ pub enum LintSource {
|
||||||
impl Rule {
|
impl Rule {
|
||||||
/// The source for the diagnostic (either the AST, the filesystem, or the
|
/// The source for the diagnostic (either the AST, the filesystem, or the
|
||||||
/// physical lines).
|
/// physical lines).
|
||||||
pub const fn lint_source(&self) -> &'static LintSource {
|
pub const fn lint_source(&self) -> LintSource {
|
||||||
match self {
|
match self {
|
||||||
Rule::UnusedNOQA => &LintSource::Noqa,
|
Rule::UnusedNOQA => LintSource::Noqa,
|
||||||
Rule::BlanketNOQA
|
Rule::BlanketNOQA
|
||||||
| Rule::BlanketTypeIgnore
|
| Rule::BlanketTypeIgnore
|
||||||
| Rule::DocLineTooLong
|
| Rule::DocLineTooLong
|
||||||
|
@ -836,7 +836,7 @@ impl Rule {
|
||||||
| Rule::ShebangWhitespace
|
| Rule::ShebangWhitespace
|
||||||
| Rule::TrailingWhitespace
|
| Rule::TrailingWhitespace
|
||||||
| Rule::IndentationContainsTabs
|
| Rule::IndentationContainsTabs
|
||||||
| Rule::BlankLineContainsWhitespace => &LintSource::PhysicalLines,
|
| Rule::BlankLineContainsWhitespace => LintSource::PhysicalLines,
|
||||||
Rule::AmbiguousUnicodeCharacterComment
|
Rule::AmbiguousUnicodeCharacterComment
|
||||||
| Rule::AmbiguousUnicodeCharacterDocstring
|
| Rule::AmbiguousUnicodeCharacterDocstring
|
||||||
| Rule::AmbiguousUnicodeCharacterString
|
| Rule::AmbiguousUnicodeCharacterString
|
||||||
|
@ -855,10 +855,10 @@ impl Rule {
|
||||||
| Rule::UselessSemicolon
|
| Rule::UselessSemicolon
|
||||||
| Rule::MultipleStatementsOnOneLineSemicolon
|
| Rule::MultipleStatementsOnOneLineSemicolon
|
||||||
| Rule::TrailingCommaProhibited
|
| Rule::TrailingCommaProhibited
|
||||||
| Rule::TypeCommentInStub => &LintSource::Tokens,
|
| Rule::TypeCommentInStub => LintSource::Tokens,
|
||||||
Rule::IOError => &LintSource::Io,
|
Rule::IOError => LintSource::Io,
|
||||||
Rule::UnsortedImports | Rule::MissingRequiredImport => &LintSource::Imports,
|
Rule::UnsortedImports | Rule::MissingRequiredImport => LintSource::Imports,
|
||||||
Rule::ImplicitNamespacePackage | Rule::InvalidModuleName => &LintSource::Filesystem,
|
Rule::ImplicitNamespacePackage | Rule::InvalidModuleName => LintSource::Filesystem,
|
||||||
#[cfg(feature = "logical_lines")]
|
#[cfg(feature = "logical_lines")]
|
||||||
Rule::IndentationWithInvalidMultiple
|
Rule::IndentationWithInvalidMultiple
|
||||||
| Rule::IndentationWithInvalidMultipleComment
|
| Rule::IndentationWithInvalidMultipleComment
|
||||||
|
@ -890,8 +890,8 @@ impl Rule {
|
||||||
| Rule::WhitespaceAfterOpenBracket
|
| Rule::WhitespaceAfterOpenBracket
|
||||||
| Rule::WhitespaceBeforeCloseBracket
|
| Rule::WhitespaceBeforeCloseBracket
|
||||||
| Rule::WhitespaceBeforeParameters
|
| Rule::WhitespaceBeforeParameters
|
||||||
| Rule::WhitespaceBeforePunctuation => &LintSource::LogicalLines,
|
| Rule::WhitespaceBeforePunctuation => LintSource::LogicalLines,
|
||||||
_ => &LintSource::Ast,
|
_ => LintSource::Ast,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,42 +1,180 @@
|
||||||
pub trait ConfigurationOptions {
|
use std::fmt::{Display, Formatter};
|
||||||
fn get_available_options() -> Vec<(&'static str, OptionEntry)>;
|
|
||||||
|
|
||||||
/// Get an option entry by its fully-qualified name
|
#[derive(Debug, Eq, PartialEq)]
|
||||||
/// (e.g. `foo.bar` refers to the `bar` option in the `foo` group).
|
pub enum OptionEntry {
|
||||||
fn get(name: Option<&str>) -> Option<OptionEntry> {
|
Field(OptionField),
|
||||||
let mut entries = Self::get_available_options();
|
Group(OptionGroup),
|
||||||
|
}
|
||||||
|
|
||||||
let mut parts_iter = name.into_iter().flat_map(|s| s.split('.'));
|
impl Display for OptionEntry {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
while let Some(part) = parts_iter.next() {
|
match self {
|
||||||
let (_, field) = entries.into_iter().find(|(name, _)| *name == part)?;
|
OptionEntry::Field(field) => field.fmt(f),
|
||||||
match field {
|
OptionEntry::Group(group) => group.fmt(f),
|
||||||
OptionEntry::Field(..) => {
|
|
||||||
if parts_iter.next().is_some() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Some(field);
|
|
||||||
}
|
|
||||||
OptionEntry::Group(fields) => {
|
|
||||||
entries = fields;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Some(OptionEntry::Group(entries))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Eq, PartialEq)]
|
||||||
pub enum OptionEntry {
|
pub struct OptionGroup(&'static [(&'static str, OptionEntry)]);
|
||||||
Field(OptionField),
|
|
||||||
Group(Vec<(&'static str, OptionEntry)>),
|
impl OptionGroup {
|
||||||
|
pub const fn new(options: &'static [(&'static str, OptionEntry)]) -> Self {
|
||||||
|
Self(options)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn iter(&self) -> std::slice::Iter<(&str, OptionEntry)> {
|
||||||
|
self.into_iter()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get an option entry by its fully-qualified name
|
||||||
|
/// (e.g. `foo.bar` refers to the `bar` option in the `foo` group).
|
||||||
|
///
|
||||||
|
/// ## Examples
|
||||||
|
///
|
||||||
|
/// ### Find a direct child
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use ruff::settings::options_base::{OptionGroup, OptionEntry, OptionField};
|
||||||
|
///
|
||||||
|
/// const options: [(&'static str, OptionEntry); 2] = [
|
||||||
|
/// ("ignore_names", OptionEntry::Field(OptionField {
|
||||||
|
/// doc: "ignore_doc",
|
||||||
|
/// default: "ignore_default",
|
||||||
|
/// value_type: "value_type",
|
||||||
|
/// example: "ignore code"
|
||||||
|
/// })),
|
||||||
|
///
|
||||||
|
/// ("global_names", OptionEntry::Field(OptionField {
|
||||||
|
/// doc: "global_doc",
|
||||||
|
/// default: "global_default",
|
||||||
|
/// value_type: "value_type",
|
||||||
|
/// example: "global code"
|
||||||
|
/// }))
|
||||||
|
/// ];
|
||||||
|
///
|
||||||
|
/// let group = OptionGroup::new(&options);
|
||||||
|
///
|
||||||
|
/// let ignore_names = group.get("ignore_names");
|
||||||
|
///
|
||||||
|
/// match ignore_names {
|
||||||
|
/// None => panic!("Expect option 'ignore_names' to be Some"),
|
||||||
|
/// Some(OptionEntry::Group(group)) => panic!("Expected 'ignore_names' to be a field but found group {}", group),
|
||||||
|
/// Some(OptionEntry::Field(field)) => {
|
||||||
|
/// assert_eq!("ignore_doc", field.doc);
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// assert_eq!(None, group.get("not_existing_option"));
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ### Find a nested options
|
||||||
|
///
|
||||||
|
///```rust
|
||||||
|
/// # use ruff::settings::options_base::{OptionGroup, OptionEntry, OptionField};
|
||||||
|
///
|
||||||
|
/// const ignore_options: [(&'static str, OptionEntry); 2] = [
|
||||||
|
/// ("names", OptionEntry::Field(OptionField {
|
||||||
|
/// doc: "ignore_name_doc",
|
||||||
|
/// default: "ignore_name_default",
|
||||||
|
/// value_type: "value_type",
|
||||||
|
/// example: "ignore name code"
|
||||||
|
/// })),
|
||||||
|
///
|
||||||
|
/// ("extensions", OptionEntry::Field(OptionField {
|
||||||
|
/// doc: "ignore_extensions_doc",
|
||||||
|
/// default: "ignore_extensions_default",
|
||||||
|
/// value_type: "value_type",
|
||||||
|
/// example: "ignore extensions code"
|
||||||
|
/// }))
|
||||||
|
/// ];
|
||||||
|
///
|
||||||
|
/// const options: [(&'static str, OptionEntry); 2] = [
|
||||||
|
/// ("ignore", OptionEntry::Group(OptionGroup::new(&ignore_options))),
|
||||||
|
///
|
||||||
|
/// ("global_names", OptionEntry::Field(OptionField {
|
||||||
|
/// doc: "global_doc",
|
||||||
|
/// default: "global_default",
|
||||||
|
/// value_type: "value_type",
|
||||||
|
/// example: "global code"
|
||||||
|
/// }))
|
||||||
|
/// ];
|
||||||
|
///
|
||||||
|
/// let group = OptionGroup::new(&options);
|
||||||
|
///
|
||||||
|
/// let ignore_names = group.get("ignore.names");
|
||||||
|
///
|
||||||
|
/// match ignore_names {
|
||||||
|
/// None => panic!("Expect option 'ignore.names' to be Some"),
|
||||||
|
/// Some(OptionEntry::Group(group)) => panic!("Expected 'ignore_names' to be a field but found group {}", group),
|
||||||
|
/// Some(OptionEntry::Field(field)) => {
|
||||||
|
/// assert_eq!("ignore_name_doc", field.doc);
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub fn get(&self, name: &str) -> Option<&OptionEntry> {
|
||||||
|
let mut parts = name.split('.').peekable();
|
||||||
|
|
||||||
|
let mut options = self.iter();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let part = parts.next()?;
|
||||||
|
|
||||||
|
let (_, field) = options.find(|(name, _)| *name == part)?;
|
||||||
|
|
||||||
|
match (parts.peek(), field) {
|
||||||
|
(None, field) => return Some(field),
|
||||||
|
(Some(..), OptionEntry::Field(..)) => return None,
|
||||||
|
(Some(..), OptionEntry::Group(group)) => {
|
||||||
|
options = group.iter();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
impl<'a> IntoIterator for &'a OptionGroup {
|
||||||
|
type Item = &'a (&'a str, OptionEntry);
|
||||||
|
type IntoIter = std::slice::Iter<'a, (&'a str, OptionEntry)>;
|
||||||
|
|
||||||
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
self.0.iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoIterator for OptionGroup {
|
||||||
|
type Item = &'static (&'static str, OptionEntry);
|
||||||
|
type IntoIter = std::slice::Iter<'static, (&'static str, OptionEntry)>;
|
||||||
|
|
||||||
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
self.0.iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for OptionGroup {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
for (name, _) in self.iter() {
|
||||||
|
writeln!(f, "{name}")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Eq, PartialEq)]
|
||||||
pub struct OptionField {
|
pub struct OptionField {
|
||||||
pub doc: &'static str,
|
pub doc: &'static str,
|
||||||
pub default: &'static str,
|
pub default: &'static str,
|
||||||
pub value_type: &'static str,
|
pub value_type: &'static str,
|
||||||
pub example: &'static str,
|
pub example: &'static str,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Display for OptionField {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
writeln!(f, "{}", self.doc)?;
|
||||||
|
writeln!(f)?;
|
||||||
|
writeln!(f, "Default value: {}", self.default)?;
|
||||||
|
writeln!(f, "Type: {}", self.value_type)?;
|
||||||
|
writeln!(f, "Example usage:\n```toml\n{}\n```", self.example)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,35 +1,19 @@
|
||||||
use crate::ExitStatus;
|
use crate::ExitStatus;
|
||||||
use ruff::settings::{
|
use ruff::settings::options::Options;
|
||||||
options::Options,
|
|
||||||
options_base::{ConfigurationOptions, OptionEntry, OptionField},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[allow(clippy::print_stdout)]
|
#[allow(clippy::print_stdout)]
|
||||||
pub(crate) fn config(key: Option<&str>) -> ExitStatus {
|
pub(crate) fn config(key: Option<&str>) -> ExitStatus {
|
||||||
let Some(entry) = Options::get(key) else {
|
match key {
|
||||||
println!("Unknown option");
|
None => print!("{}", Options::metadata()),
|
||||||
return ExitStatus::Error;
|
Some(key) => match Options::metadata().get(key) {
|
||||||
};
|
None => {
|
||||||
|
println!("Unknown option");
|
||||||
match entry {
|
return ExitStatus::Error;
|
||||||
OptionEntry::Field(OptionField {
|
|
||||||
doc,
|
|
||||||
default,
|
|
||||||
value_type,
|
|
||||||
example,
|
|
||||||
}) => {
|
|
||||||
println!("{doc}");
|
|
||||||
println!();
|
|
||||||
println!("Default value: {default}");
|
|
||||||
println!("Type: {value_type}");
|
|
||||||
println!("Example usage:\n```toml\n{example}\n```");
|
|
||||||
}
|
|
||||||
OptionEntry::Group(entries) => {
|
|
||||||
for (name, _) in entries {
|
|
||||||
println!("{name}");
|
|
||||||
}
|
}
|
||||||
}
|
Some(entry) => {
|
||||||
|
print!("{entry}");
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
ExitStatus::Success
|
ExitStatus::Success
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,6 @@ use strum::IntoEnumIterator;
|
||||||
|
|
||||||
use ruff::registry::{Linter, Rule, RuleNamespace};
|
use ruff::registry::{Linter, Rule, RuleNamespace};
|
||||||
use ruff::settings::options::Options;
|
use ruff::settings::options::Options;
|
||||||
use ruff::settings::options_base::ConfigurationOptions;
|
|
||||||
use ruff_diagnostics::Availability;
|
use ruff_diagnostics::Availability;
|
||||||
|
|
||||||
use crate::ROOT_DIR;
|
use crate::ROOT_DIR;
|
||||||
|
@ -92,7 +91,7 @@ fn process_documentation(documentation: &str, out: &mut String) {
|
||||||
let option = rest.trim_end().trim_end_matches('`');
|
let option = rest.trim_end().trim_end_matches('`');
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
Options::get(Some(option)).is_some(),
|
Options::metadata().get(option).is_some(),
|
||||||
"unknown option {option}"
|
"unknown option {option}"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
//! Generate a Markdown-compatible listing of configuration options.
|
//! Generate a Markdown-compatible listing of configuration options.
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use ruff::settings::options::Options;
|
use ruff::settings::options::Options;
|
||||||
use ruff::settings::options_base::{ConfigurationOptions, OptionEntry, OptionField};
|
use ruff::settings::options_base::{OptionEntry, OptionField};
|
||||||
|
|
||||||
fn emit_field(output: &mut String, name: &str, field: &OptionField, group_name: Option<&str>) {
|
fn emit_field(output: &mut String, name: &str, field: &OptionField, group_name: Option<&str>) {
|
||||||
output.push_str(&format!("#### [`{name}`](#{name})\n"));
|
output.push_str(&format!("#### [`{name}`](#{name})\n"));
|
||||||
|
@ -27,8 +27,10 @@ fn emit_field(output: &mut String, name: &str, field: &OptionField, group_name:
|
||||||
pub fn generate() -> String {
|
pub fn generate() -> String {
|
||||||
let mut output: String = "### Top-level\n\n".into();
|
let mut output: String = "### Top-level\n\n".into();
|
||||||
|
|
||||||
let mut sorted_options = Options::get_available_options();
|
let sorted_options: Vec<_> = Options::metadata()
|
||||||
sorted_options.sort_by_key(|(name, _)| *name);
|
.into_iter()
|
||||||
|
.sorted_by_key(|(name, _)| *name)
|
||||||
|
.collect();
|
||||||
|
|
||||||
// Generate all the top-level fields.
|
// Generate all the top-level fields.
|
||||||
for (name, entry) in &sorted_options {
|
for (name, entry) in &sorted_options {
|
||||||
|
|
|
@ -44,15 +44,17 @@ pub fn derive_impl(input: DeriveInput) -> syn::Result<proc_macro2::TokenStream>
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(quote! {
|
let options_len = output.len();
|
||||||
use crate::settings::options_base::{OptionEntry, OptionField, ConfigurationOptions};
|
|
||||||
|
|
||||||
#[automatically_derived]
|
Ok(quote! {
|
||||||
impl ConfigurationOptions for #ident {
|
use crate::settings::options_base::{OptionEntry, OptionField, OptionGroup};
|
||||||
fn get_available_options() -> Vec<(&'static str, OptionEntry)> {
|
|
||||||
vec![#(#output),*]
|
impl #ident {
|
||||||
}
|
pub const fn metadata() -> OptionGroup {
|
||||||
}
|
const OPTIONS: [(&'static str, OptionEntry); #options_len] = [#(#output),*];
|
||||||
|
OptionGroup::new(&OPTIONS)
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
_ => Err(syn::Error::new(
|
_ => Err(syn::Error::new(
|
||||||
|
@ -86,7 +88,7 @@ fn handle_option_group(field: &Field) -> syn::Result<proc_macro2::TokenStream> {
|
||||||
let kebab_name = LitStr::new(&ident.to_string().replace('_', "-"), ident.span());
|
let kebab_name = LitStr::new(&ident.to_string().replace('_', "-"), ident.span());
|
||||||
|
|
||||||
Ok(quote_spanned!(
|
Ok(quote_spanned!(
|
||||||
ident.span() => (#kebab_name, OptionEntry::Group(#path::get_available_options()))
|
ident.span() => (#kebab_name, OptionEntry::Group(#path::metadata()))
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
_ => Err(syn::Error::new(
|
_ => Err(syn::Error::new(
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue