Implement copyright notice detection (#4701)

## Summary

Add copyright notice detection to enforce the presence of copyright
headers in Python files.

Configurable settings include: the relevant regular expression, the
author name, and the minimum file size, similar to
[flake8-copyright](https://github.com/savoirfairelinux/flake8-copyright).

Closes https://github.com/charliermarsh/ruff/issues/3579

---------

Signed-off-by: ryan <ryang@waabi.ai>
Co-authored-by: Charlie Marsh <charlie.r.marsh@gmail.com>
This commit is contained in:
Ryan Yang 2023-06-10 19:17:58 -07:00 committed by GitHub
parent 9f7cc86a22
commit ab3c02342b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 465 additions and 24 deletions

View file

@ -261,6 +261,7 @@ quality tools, including:
- [flake8-builtins](https://pypi.org/project/flake8-builtins/)
- [flake8-commas](https://pypi.org/project/flake8-commas/)
- [flake8-comprehensions](https://pypi.org/project/flake8-comprehensions/)
- [flake8-copyright](https://pypi.org/project/flake8-copyright/)
- [flake8-datetimez](https://pypi.org/project/flake8-datetimez/)
- [flake8-debugger](https://pypi.org/project/flake8-debugger/)
- [flake8-django](https://pypi.org/project/flake8-django/)

View file

@ -8,6 +8,7 @@ use ruff_python_ast::source_code::{Indexer, Locator, Stylist};
use ruff_python_whitespace::UniversalNewlines;
use crate::registry::Rule;
use crate::rules::copyright::rules::missing_copyright_notice;
use crate::rules::flake8_executable::helpers::{extract_shebang, ShebangDirective};
use crate::rules::flake8_executable::rules::{
shebang_missing, shebang_newline, shebang_not_executable, shebang_python, shebang_whitespace,
@ -49,6 +50,7 @@ pub(crate) fn check_physical_lines(
let enforce_blank_line_contains_whitespace =
settings.rules.enabled(Rule::BlankLineWithWhitespace);
let enforce_tab_indentation = settings.rules.enabled(Rule::TabIndentation);
let enforce_copyright_notice = settings.rules.enabled(Rule::MissingCopyrightNotice);
let fix_unnecessary_coding_comment = settings.rules.should_fix(Rule::UTF8EncodingDeclaration);
let fix_shebang_whitespace = settings.rules.should_fix(Rule::ShebangLeadingWhitespace);
@ -172,6 +174,12 @@ pub(crate) fn check_physical_lines(
}
}
if enforce_copyright_notice {
if let Some(diagnostic) = missing_copyright_notice(locator, settings) {
diagnostics.push(diagnostic);
}
}
diagnostics
}

View file

@ -374,6 +374,9 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Flake8Simplify, "401") => (RuleGroup::Unspecified, rules::flake8_simplify::rules::IfElseBlockInsteadOfDictGet),
(Flake8Simplify, "910") => (RuleGroup::Unspecified, rules::flake8_simplify::rules::DictGetWithNoneDefault),
// copyright
(Copyright, "001") => (RuleGroup::Unspecified, rules::copyright::rules::MissingCopyrightNotice),
// pyupgrade
(Pyupgrade, "001") => (RuleGroup::Unspecified, rules::pyupgrade::rules::UselessMetaclassType),
(Pyupgrade, "003") => (RuleGroup::Unspecified, rules::pyupgrade::rules::TypeOfPrimitive),

View file

@ -80,6 +80,9 @@ pub enum Linter {
/// [flake8-commas](https://pypi.org/project/flake8-commas/)
#[prefix = "COM"]
Flake8Commas,
/// Copyright-related rules
#[prefix = "CPY"]
Copyright,
/// [flake8-comprehensions](https://pypi.org/project/flake8-comprehensions/)
#[prefix = "C4"]
Flake8Comprehensions,
@ -267,6 +270,7 @@ impl Rule {
| Rule::ShebangLeadingWhitespace
| Rule::TrailingWhitespace
| Rule::TabIndentation
| Rule::MissingCopyrightNotice
| Rule::BlankLineWithWhitespace => LintSource::PhysicalLines,
Rule::AmbiguousUnicodeCharacterComment
| Rule::AmbiguousUnicodeCharacterDocstring

View file

@ -0,0 +1,158 @@
//! Rules related to copyright notices.
pub(crate) mod rules;
pub mod settings;
#[cfg(test)]
mod tests {
use crate::registry::Rule;
use crate::test::test_snippet;
use crate::{assert_messages, settings};
#[test]
fn notice() {
let diagnostics = test_snippet(
r#"
# Copyright 2023
import os
"#
.trim(),
&settings::Settings::for_rules(vec![Rule::MissingCopyrightNotice]),
);
assert_messages!(diagnostics);
}
#[test]
fn notice_with_c() {
let diagnostics = test_snippet(
r#"
# Copyright (C) 2023
import os
"#
.trim(),
&settings::Settings::for_rules(vec![Rule::MissingCopyrightNotice]),
);
assert_messages!(diagnostics);
}
#[test]
fn notice_with_caps() {
let diagnostics = test_snippet(
r#"
# COPYRIGHT (C) 2023
import os
"#
.trim(),
&settings::Settings::for_rules(vec![Rule::MissingCopyrightNotice]),
);
assert_messages!(diagnostics);
}
#[test]
fn notice_with_range() {
let diagnostics = test_snippet(
r#"
# Copyright (C) 2021-2023
import os
"#
.trim(),
&settings::Settings::for_rules(vec![Rule::MissingCopyrightNotice]),
);
assert_messages!(diagnostics);
}
#[test]
fn valid_author() {
let diagnostics = test_snippet(
r#"
# Copyright (C) 2023 Ruff
import os
"#
.trim(),
&settings::Settings {
copyright: super::settings::Settings {
author: Some("Ruff".to_string()),
..super::settings::Settings::default()
},
..settings::Settings::for_rules(vec![Rule::MissingCopyrightNotice])
},
);
assert_messages!(diagnostics);
}
#[test]
fn invalid_author() {
let diagnostics = test_snippet(
r#"
# Copyright (C) 2023 Some Author
import os
"#
.trim(),
&settings::Settings {
copyright: super::settings::Settings {
author: Some("Ruff".to_string()),
..super::settings::Settings::default()
},
..settings::Settings::for_rules(vec![Rule::MissingCopyrightNotice])
},
);
assert_messages!(diagnostics);
}
#[test]
fn small_file() {
let diagnostics = test_snippet(
r#"
import os
"#
.trim(),
&settings::Settings {
copyright: super::settings::Settings {
min_file_size: 256,
..super::settings::Settings::default()
},
..settings::Settings::for_rules(vec![Rule::MissingCopyrightNotice])
},
);
assert_messages!(diagnostics);
}
#[test]
fn late_notice() {
let diagnostics = test_snippet(
r#"
# Content Content Content Content Content Content Content Content Content Content
# Content Content Content Content Content Content Content Content Content Content
# Content Content Content Content Content Content Content Content Content Content
# Content Content Content Content Content Content Content Content Content Content
# Content Content Content Content Content Content Content Content Content Content
# Content Content Content Content Content Content Content Content Content Content
# Content Content Content Content Content Content Content Content Content Content
# Content Content Content Content Content Content Content Content Content Content
# Content Content Content Content Content Content Content Content Content Content
# Content Content Content Content Content Content Content Content Content Content
# Content Content Content Content Content Content Content Content Content Content
# Content Content Content Content Content Content Content Content Content Content
# Content Content Content Content Content Content Content Content Content Content
# Content Content Content Content Content Content Content Content Content Content
# Content Content Content Content Content Content Content Content Content Content
# Content Content Content Content Content Content Content Content Content Content
# Content Content Content Content Content Content Content Content Content Content
# Content Content Content Content Content Content Content Content Content Content
# Content Content Content Content Content Content Content Content Content Content
# Content Content Content Content Content Content Content Content Content Content
# Copyright 2023
"#
.trim(),
&settings::Settings::for_rules(vec![Rule::MissingCopyrightNotice]),
);
assert_messages!(diagnostics);
}
}

View file

@ -0,0 +1,59 @@
use ruff_text_size::{TextRange, TextSize};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::source_code::Locator;
use crate::settings::Settings;
/// ## What it does
/// Checks for the absence of copyright notices within Python files.
///
/// ## Why is this bad?
/// In some codebases, it's common to have a license header at the top of every
/// file. This rule ensures that the license header is present.
#[violation]
pub struct MissingCopyrightNotice;
impl Violation for MissingCopyrightNotice {
#[derive_message_formats]
fn message(&self) -> String {
format!("Missing copyright notice at top of file")
}
}
/// CPY001
pub(crate) fn missing_copyright_notice(
locator: &Locator,
settings: &Settings,
) -> Option<Diagnostic> {
// Ignore files that are too small to contain a copyright notice.
if locator.len() < settings.copyright.min_file_size {
return None;
}
// Only search the first 1024 bytes in the file.
let contents = if locator.len() < 1024 {
locator.contents()
} else {
locator.up_to(TextSize::from(1024))
};
// Locate the copyright notice.
if let Some(match_) = settings.copyright.notice_rgx.find(contents) {
match settings.copyright.author {
Some(ref author) => {
// Ensure that it's immediately followed by the author.
if contents[match_.end()..].trim_start().starts_with(author) {
return None;
}
}
None => return None,
}
}
Some(Diagnostic::new(
MissingCopyrightNotice,
TextRange::default(),
))
}

View file

@ -0,0 +1,3 @@
pub(crate) use missing_copyright_notice::{missing_copyright_notice, MissingCopyrightNotice};
mod missing_copyright_notice;

View file

@ -0,0 +1,94 @@
//! Settings for the `copyright` plugin.
use once_cell::sync::Lazy;
use regex::Regex;
use serde::{Deserialize, Serialize};
use ruff_macros::{CacheKey, CombineOptions, ConfigurationOptions};
#[derive(
Debug, PartialEq, Eq, Serialize, Deserialize, Default, ConfigurationOptions, CombineOptions,
)]
#[serde(
deny_unknown_fields,
rename_all = "kebab-case",
rename = "CopyrightOptions"
)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct Options {
#[option(
default = r#"(?i)Copyright\s+(\(C\)\s+)?\d{4}([-,]\d{4})*"#,
value_type = "str",
example = r#"notice-rgx = "(?i)Copyright \\(C\\) \\d{4}""#
)]
/// The regular expression used to match the copyright notice, compiled
/// with the [`regex`](https://docs.rs/regex/latest/regex/) crate.
///
/// Defaults to `(?i)Copyright\s+(\(C\)\s+)?\d{4}(-\d{4})*`, which matches
/// the following:
/// - `Copyright 2023`
/// - `Copyright (C) 2023`
/// - `Copyright 2021-2023`
/// - `Copyright (C) 2021-2023`
pub notice_rgx: Option<String>,
#[option(default = "None", value_type = "str", example = r#"author = "Ruff""#)]
/// Author to enforce within the copyright notice. If provided, the
/// author must be present immediately following the copyright notice.
pub author: Option<String>,
#[option(
default = r#"0"#,
value_type = "int",
example = r#"
# Avoid enforcing a header on files smaller than 1024 bytes.
min-file-size = 1024
"#
)]
/// A minimum file size (in bytes) required for a copyright notice to
/// be enforced. By default, all files are validated.
pub min_file_size: Option<usize>,
}
#[derive(Debug, CacheKey)]
pub struct Settings {
pub notice_rgx: Regex,
pub author: Option<String>,
pub min_file_size: usize,
}
static COPYRIGHT: Lazy<Regex> =
Lazy::new(|| Regex::new(r"(?i)Copyright\s+(\(C\)\s+)?\d{4}(-\d{4})*").unwrap());
impl Default for Settings {
fn default() -> Self {
Self {
notice_rgx: COPYRIGHT.clone(),
author: None,
min_file_size: 0,
}
}
}
impl From<Options> for Settings {
fn from(options: Options) -> Self {
Self {
notice_rgx: options
.notice_rgx
.map(|pattern| Regex::new(&pattern))
.transpose()
.expect("Invalid `notice-rgx`")
.unwrap_or_else(|| COPYRIGHT.clone()),
author: options.author,
min_file_size: options.min_file_size.unwrap_or_default(),
}
}
}
impl From<Settings> for Options {
fn from(settings: Settings) -> Self {
Self {
notice_rgx: Some(settings.notice_rgx.to_string()),
author: settings.author,
min_file_size: Some(settings.min_file_size),
}
}
}

View file

@ -0,0 +1,12 @@
---
source: crates/ruff/src/rules/copyright/mod.rs
---
<filename>:1:1: CPY001 Missing copyright notice at top of file
|
1 | # Copyright (C) 2023 Some Author
| CPY001
2 |
3 | import os
|

View file

@ -0,0 +1,12 @@
---
source: crates/ruff/src/rules/copyright/mod.rs
---
<filename>:1:1: CPY001 Missing copyright notice at top of file
|
1 | # Content Content Content Content Content Content Content Content Content Content
| CPY001
2 | # Content Content Content Content Content Content Content Content Content Content
3 | # Content Content Content Content Content Content Content Content Content Content
|

View file

@ -0,0 +1,4 @@
---
source: crates/ruff/src/rules/copyright/mod.rs
---

View file

@ -0,0 +1,4 @@
---
source: crates/ruff/src/rules/copyright/mod.rs
---

View file

@ -0,0 +1,4 @@
---
source: crates/ruff/src/rules/copyright/mod.rs
---

View file

@ -0,0 +1,4 @@
---
source: crates/ruff/src/rules/copyright/mod.rs
---

View file

@ -0,0 +1,4 @@
---
source: crates/ruff/src/rules/copyright/mod.rs
---

View file

@ -0,0 +1,4 @@
---
source: crates/ruff/src/rules/copyright/mod.rs
---

View file

@ -1,5 +1,6 @@
#![allow(clippy::useless_format)]
pub mod airflow;
pub mod copyright;
pub mod eradicate;
pub mod flake8_2020;
pub mod flake8_annotations;

View file

@ -16,10 +16,11 @@ use crate::fs;
use crate::line_width::{LineLength, TabSize};
use crate::rule_selector::RuleSelector;
use crate::rules::{
flake8_annotations, flake8_bandit, flake8_bugbear, flake8_builtins, flake8_comprehensions,
flake8_errmsg, flake8_gettext, flake8_implicit_str_concat, flake8_import_conventions,
flake8_pytest_style, flake8_quotes, flake8_self, flake8_tidy_imports, flake8_type_checking,
flake8_unused_arguments, isort, mccabe, pep8_naming, pycodestyle, pydocstyle, pyflakes, pylint,
copyright, flake8_annotations, flake8_bandit, flake8_bugbear, flake8_builtins,
flake8_comprehensions, flake8_errmsg, flake8_gettext, flake8_implicit_str_concat,
flake8_import_conventions, flake8_pytest_style, flake8_quotes, flake8_self,
flake8_tidy_imports, flake8_type_checking, flake8_unused_arguments, isort, mccabe, pep8_naming,
pycodestyle, pydocstyle, pyflakes, pylint,
};
use crate::settings::options::Options;
use crate::settings::types::{
@ -74,6 +75,7 @@ pub struct Configuration {
pub flake8_bugbear: Option<flake8_bugbear::settings::Options>,
pub flake8_builtins: Option<flake8_builtins::settings::Options>,
pub flake8_comprehensions: Option<flake8_comprehensions::settings::Options>,
pub copyright: Option<copyright::settings::Options>,
pub flake8_errmsg: Option<flake8_errmsg::settings::Options>,
pub flake8_implicit_str_concat: Option<flake8_implicit_str_concat::settings::Options>,
pub flake8_import_conventions: Option<flake8_import_conventions::settings::Options>,
@ -227,6 +229,7 @@ impl Configuration {
flake8_bugbear: options.flake8_bugbear,
flake8_builtins: options.flake8_builtins,
flake8_comprehensions: options.flake8_comprehensions,
copyright: options.copyright,
flake8_errmsg: options.flake8_errmsg,
flake8_gettext: options.flake8_gettext,
flake8_implicit_str_concat: options.flake8_implicit_str_concat,
@ -305,6 +308,7 @@ impl Configuration {
flake8_comprehensions: self
.flake8_comprehensions
.combine(config.flake8_comprehensions),
copyright: self.copyright.combine(config.copyright),
flake8_errmsg: self.flake8_errmsg.combine(config.flake8_errmsg),
flake8_gettext: self.flake8_gettext.combine(config.flake8_gettext),
flake8_implicit_str_concat: self

View file

@ -10,10 +10,11 @@ use crate::line_width::{LineLength, TabSize};
use crate::registry::Linter;
use crate::rule_selector::{prefix_to_selector, RuleSelector};
use crate::rules::{
flake8_annotations, flake8_bandit, flake8_bugbear, flake8_builtins, flake8_comprehensions,
flake8_errmsg, flake8_gettext, flake8_implicit_str_concat, flake8_import_conventions,
flake8_pytest_style, flake8_quotes, flake8_self, flake8_tidy_imports, flake8_type_checking,
flake8_unused_arguments, isort, mccabe, pep8_naming, pycodestyle, pydocstyle, pyflakes, pylint,
copyright, flake8_annotations, flake8_bandit, flake8_bugbear, flake8_builtins,
flake8_comprehensions, flake8_errmsg, flake8_gettext, flake8_implicit_str_concat,
flake8_import_conventions, flake8_pytest_style, flake8_quotes, flake8_self,
flake8_tidy_imports, flake8_type_checking, flake8_unused_arguments, isort, mccabe, pep8_naming,
pycodestyle, pydocstyle, pyflakes, pylint,
};
use crate::settings::types::FilePatternSet;
@ -95,6 +96,7 @@ impl Default for Settings {
flake8_bugbear: flake8_bugbear::settings::Settings::default(),
flake8_builtins: flake8_builtins::settings::Settings::default(),
flake8_comprehensions: flake8_comprehensions::settings::Settings::default(),
copyright: copyright::settings::Settings::default(),
flake8_errmsg: flake8_errmsg::settings::Settings::default(),
flake8_implicit_str_concat: flake8_implicit_str_concat::settings::Settings::default(),
flake8_import_conventions: flake8_import_conventions::settings::Settings::default(),

View file

@ -16,10 +16,11 @@ use ruff_macros::CacheKey;
use crate::registry::{Rule, RuleNamespace, RuleSet, INCOMPATIBLE_CODES};
use crate::rule_selector::{RuleSelector, Specificity};
use crate::rules::{
flake8_annotations, flake8_bandit, flake8_bugbear, flake8_builtins, flake8_comprehensions,
flake8_errmsg, flake8_gettext, flake8_implicit_str_concat, flake8_import_conventions,
flake8_pytest_style, flake8_quotes, flake8_self, flake8_tidy_imports, flake8_type_checking,
flake8_unused_arguments, isort, mccabe, pep8_naming, pycodestyle, pydocstyle, pyflakes, pylint,
copyright, flake8_annotations, flake8_bandit, flake8_bugbear, flake8_builtins,
flake8_comprehensions, flake8_errmsg, flake8_gettext, flake8_implicit_str_concat,
flake8_import_conventions, flake8_pytest_style, flake8_quotes, flake8_self,
flake8_tidy_imports, flake8_type_checking, flake8_unused_arguments, isort, mccabe, pep8_naming,
pycodestyle, pydocstyle, pyflakes, pylint,
};
use crate::settings::configuration::Configuration;
use crate::settings::types::{FilePatternSet, PerFileIgnore, PythonVersion, SerializationFormat};
@ -111,6 +112,7 @@ pub struct Settings {
pub flake8_bugbear: flake8_bugbear::settings::Settings,
pub flake8_builtins: flake8_builtins::settings::Settings,
pub flake8_comprehensions: flake8_comprehensions::settings::Settings,
pub copyright: copyright::settings::Settings,
pub flake8_errmsg: flake8_errmsg::settings::Settings,
pub flake8_implicit_str_concat: flake8_implicit_str_concat::settings::Settings,
pub flake8_import_conventions: flake8_import_conventions::settings::Settings,
@ -199,6 +201,7 @@ impl Settings {
.flake8_comprehensions
.map(Into::into)
.unwrap_or_default(),
copyright: config.copyright.map(Into::into).unwrap_or_default(),
flake8_errmsg: config.flake8_errmsg.map(Into::into).unwrap_or_default(),
flake8_implicit_str_concat: config
.flake8_implicit_str_concat

View file

@ -8,10 +8,11 @@ use ruff_macros::ConfigurationOptions;
use crate::line_width::{LineLength, TabSize};
use crate::rule_selector::RuleSelector;
use crate::rules::{
flake8_annotations, flake8_bandit, flake8_bugbear, flake8_builtins, flake8_comprehensions,
flake8_errmsg, flake8_gettext, flake8_implicit_str_concat, flake8_import_conventions,
flake8_pytest_style, flake8_quotes, flake8_self, flake8_tidy_imports, flake8_type_checking,
flake8_unused_arguments, isort, mccabe, pep8_naming, pycodestyle, pydocstyle, pyflakes, pylint,
copyright, flake8_annotations, flake8_bandit, flake8_bugbear, flake8_builtins,
flake8_comprehensions, flake8_errmsg, flake8_gettext, flake8_implicit_str_concat,
flake8_import_conventions, flake8_pytest_style, flake8_quotes, flake8_self,
flake8_tidy_imports, flake8_type_checking, flake8_unused_arguments, isort, mccabe, pep8_naming,
pycodestyle, pydocstyle, pyflakes, pylint,
};
use crate::settings::types::{PythonVersion, SerializationFormat, Version};
@ -495,6 +496,9 @@ pub struct Options {
/// Options for the `flake8-comprehensions` plugin.
pub flake8_comprehensions: Option<flake8_comprehensions::settings::Options>,
#[option_group]
/// Options for the `copyright` plugin.
pub copyright: Option<copyright::settings::Options>,
#[option_group]
/// Options for the `flake8-errmsg` plugin.
pub flake8_errmsg: Option<flake8_errmsg::settings::Options>,
#[option_group]

View file

@ -15,9 +15,9 @@ pub(crate) fn derive_impl(input: DeriveInput) -> syn::Result<proc_macro2::TokenS
let mut parsed = Vec::new();
let mut common_prefix_match_arms = quote!();
let mut name_match_arms =
quote!(Self::Ruff => "Ruff-specific rules", Self::Numpy => "NumPy-specific rules", );
let mut url_match_arms = quote!(Self::Ruff => None, Self::Numpy => None, );
let mut name_match_arms = quote!(Self::Ruff => "Ruff-specific rules", Self::Numpy => "NumPy-specific rules", Self::Copyright => "Copyright-related rules", );
let mut url_match_arms =
quote!(Self::Ruff => None, Self::Numpy => None, Self::Copyright => None, );
let mut all_prefixes = HashSet::new();
@ -59,7 +59,7 @@ pub(crate) fn derive_impl(input: DeriveInput) -> syn::Result<proc_macro2::TokenS
let variant_ident = variant.ident;
if variant_ident != "Ruff" && variant_ident != "Numpy" {
if variant_ident != "Ruff" && variant_ident != "Numpy" && variant_ident != "Copyright" {
let (name, url) = parse_doc_attr(doc_attr)?;
name_match_arms.extend(quote! {Self::#variant_ident => #name,});
url_match_arms.extend(quote! {Self::#variant_ident => Some(#url),});

View file

@ -9,10 +9,11 @@ use ruff::line_width::{LineLength, TabSize};
use ruff::linter::{check_path, LinterResult};
use ruff::registry::AsRule;
use ruff::rules::{
flake8_annotations, flake8_bandit, flake8_bugbear, flake8_builtins, flake8_comprehensions,
flake8_errmsg, flake8_gettext, flake8_implicit_str_concat, flake8_import_conventions,
flake8_pytest_style, flake8_quotes, flake8_self, flake8_tidy_imports, flake8_type_checking,
flake8_unused_arguments, isort, mccabe, pep8_naming, pycodestyle, pydocstyle, pyflakes, pylint,
copyright, flake8_annotations, flake8_bandit, flake8_bugbear, flake8_builtins,
flake8_comprehensions, flake8_errmsg, flake8_gettext, flake8_implicit_str_concat,
flake8_import_conventions, flake8_pytest_style, flake8_quotes, flake8_self,
flake8_tidy_imports, flake8_type_checking, flake8_unused_arguments, isort, mccabe, pep8_naming,
pycodestyle, pydocstyle, pyflakes, pylint,
};
use ruff::settings::configuration::Configuration;
use ruff::settings::options::Options;
@ -137,6 +138,7 @@ pub fn defaultSettings() -> Result<JsValue, JsValue> {
flake8_bugbear: Some(flake8_bugbear::settings::Settings::default().into()),
flake8_builtins: Some(flake8_builtins::settings::Settings::default().into()),
flake8_comprehensions: Some(flake8_comprehensions::settings::Settings::default().into()),
copyright: Some(copyright::settings::Settings::default().into()),
flake8_errmsg: Some(flake8_errmsg::settings::Settings::default().into()),
flake8_pytest_style: Some(flake8_pytest_style::settings::Settings::default().into()),
flake8_quotes: Some(flake8_quotes::settings::Settings::default().into()),

View file

@ -41,6 +41,7 @@ natively, including:
- [flake8-builtins](https://pypi.org/project/flake8-builtins/)
- [flake8-commas](https://pypi.org/project/flake8-commas/)
- [flake8-comprehensions](https://pypi.org/project/flake8-comprehensions/)
- [flake8-copyright](https://pypi.org/project/flake8-copyright/)
- [flake8-datetimez](https://pypi.org/project/flake8-datetimez/)
- [flake8-debugger](https://pypi.org/project/flake8-debugger/)
- [flake8-django](https://pypi.org/project/flake8-django/)
@ -143,6 +144,7 @@ Today, Ruff can be used to replace Flake8 when used with any of the following pl
- [flake8-builtins](https://pypi.org/project/flake8-builtins/)
- [flake8-commas](https://pypi.org/project/flake8-commas/)
- [flake8-comprehensions](https://pypi.org/project/flake8-comprehensions/)
- [flake8-copyright](https://pypi.org/project/flake8-comprehensions/)
- [flake8-datetimez](https://pypi.org/project/flake8-datetimez/)
- [flake8-debugger](https://pypi.org/project/flake8-debugger/)
- [flake8-django](https://pypi.org/project/flake8-django/)

44
ruff.schema.json generated
View file

@ -32,6 +32,17 @@
"null"
]
},
"copyright": {
"description": "Options for the `copyright` plugin.",
"anyOf": [
{
"$ref": "#/definitions/CopyrightOptions"
},
{
"type": "null"
}
]
},
"dummy-variable-rgx": {
"description": "A regular expression used to identify \"dummy\" variables, or those which should be ignored when enforcing (e.g.) unused-variable rules. The default expression matches `_`, `__`, and `_var`, but not `_var_`.",
"type": [
@ -620,6 +631,35 @@
}
]
},
"CopyrightOptions": {
"type": "object",
"properties": {
"author": {
"description": "Author to enforce within the copyright notice. If provided, the author must be present immediately following the copyright notice.",
"type": [
"string",
"null"
]
},
"min-file-size": {
"description": "A minimum file size (in bytes) required for a copyright notice to be enforced. By default, all files are validated.",
"type": [
"integer",
"null"
],
"format": "uint",
"minimum": 0.0
},
"notice-rgx": {
"description": "The regular expression used to match the copyright notice, compiled with the [`regex`](https://docs.rs/regex/latest/regex/) crate.\n\nDefaults to `(?i)Copyright\\s+(\\(C\\)\\s+)?\\d{4}(-\\d{4})*`, which matches the following: - `Copyright 2023` - `Copyright (C) 2023` - `Copyright 2021-2023` - `Copyright (C) 2021-2023`",
"type": [
"string",
"null"
]
}
},
"additionalProperties": false
},
"Flake8AnnotationsOptions": {
"type": "object",
"properties": {
@ -1679,6 +1719,10 @@
"COM812",
"COM818",
"COM819",
"CPY",
"CPY0",
"CPY00",
"CPY001",
"D",
"D1",
"D10",

View file

@ -15,6 +15,7 @@ ignore = [
"D", # pydocstyle
"PL", # pylint
"S", # bandit
"CPY", # copyright
"G", # flake8-logging
"T", # flake8-print
"FBT", # flake8-boolean-trap