Skip to content

Commit

Permalink
Add flake8-copyright to ruff
Browse files Browse the repository at this point in the history
Signed-off-by: ryan <ryang@waabi.ai>
  • Loading branch information
Ryang20718 committed May 29, 2023
1 parent 8ca3977 commit 5d4dc6f
Show file tree
Hide file tree
Showing 24 changed files with 373 additions and 15 deletions.
24 changes: 24 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -1176,6 +1176,8 @@ are:

- flake8-django, licensed under the GPL license.

- flake8-copyright, licensed under the GPL license.

- rust-analyzer/text-size, licensed under the MIT license:
"""
Permission is hereby granted, free of charge, to any
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,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/)
Expand Down
1 change: 1 addition & 0 deletions crates/ruff/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ ignore = { workspace = true }
imperative = { version = "1.0.4" }
is-macro = { workspace = true }
itertools = { workspace = true }
lazy-regex = { version = "2.5.0" }
libcst = { workspace = true }
log = { workspace = true }
natord = { version = "1.0.9" }
Expand Down
3 changes: 3 additions & 0 deletions crates/ruff/resources/test/fixtures/flake8_copyright/C801.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Copyright (C) 2023 author

import os
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Copyright 2023 (c) rufffff

import os
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# filling
# filling
# filling

# Copyright 2023 (c) ruff
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import os

# Copyright (c) 2023 ruff
24 changes: 24 additions & 0 deletions crates/ruff/src/checkers/physical_lines.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ use std::path::Path;
use ruff_diagnostics::Diagnostic;
use ruff_python_ast::newlines::StrExt;
use ruff_python_ast::source_code::{Indexer, Locator, Stylist};
use ruff_text_size::TextRange;

use crate::registry::Rule;
use crate::rules::flake8_copyright::rules::{copyright_header_absent, HeaderLacksCopyright};
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,
Expand Down Expand Up @@ -49,6 +51,10 @@ 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_header = settings.rules.enabled(Rule::HeaderLacksCopyright);

let mut checked_copyright_header_absent = false;
let mut chars_before_copyright_header: i64 = 0;

let fix_unnecessary_coding_comment = settings.rules.should_fix(Rule::UTF8EncodingDeclaration);
let fix_shebang_whitespace = settings.rules.should_fix(Rule::ShebangLeadingWhitespace);
Expand Down Expand Up @@ -154,6 +160,24 @@ pub(crate) fn check_physical_lines(
diagnostics.push(diagnostic);
}
}
if enforce_copyright_header && !checked_copyright_header_absent {
let missing_copyright_header =
copyright_header_absent(&line, settings, chars_before_copyright_header);

match missing_copyright_header {
Some(false) => checked_copyright_header_absent = true,
Some(true) => {
let diagnostic = Diagnostic::new(
HeaderLacksCopyright,
TextRange::new(line.start(), line.end()),
);
diagnostics.push(diagnostic);
checked_copyright_header_absent = true;
}
None => (),
}
chars_before_copyright_header += line.len() as i64;
}
}

if enforce_no_newline_at_end_of_file {
Expand Down
3 changes: 3 additions & 0 deletions crates/ruff/src/codes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,9 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Flake8Simplify, "401") => (RuleGroup::Unspecified, Rule::IfElseBlockInsteadOfDictGet),
(Flake8Simplify, "910") => (RuleGroup::Unspecified, Rule::DictGetWithNoneDefault),

// flake8-copyright
(Flake8Copyright, "1") => (RuleGroup::Unspecified, Rule::HeaderLacksCopyright),

// pyupgrade
(Pyupgrade, "001") => (RuleGroup::Unspecified, Rule::UselessMetaclassType),
(Pyupgrade, "003") => (RuleGroup::Unspecified, Rule::TypeOfPrimitive),
Expand Down
6 changes: 6 additions & 0 deletions crates/ruff/src/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,8 @@ ruff_macros::register_rules!(
rules::flake8_simplify::rules::YodaConditions,
rules::flake8_simplify::rules::IfElseBlockInsteadOfDictGet,
rules::flake8_simplify::rules::DictGetWithNoneDefault,
// flake8-copyright
rules::flake8_copyright::rules::HeaderLacksCopyright,
// pyupgrade
rules::pyupgrade::rules::UselessMetaclassType,
rules::pyupgrade::rules::TypeOfPrimitive,
Expand Down Expand Up @@ -736,6 +738,9 @@ pub enum Linter {
#[prefix = "COM"]
Flake8Commas,
/// [flake8-comprehensions](https://pypi.org/project/flake8-comprehensions/)
#[prefix = "C80"]
Flake8Copyright,
/// [flake8-copyright](https://pypi.org/project/flake8-copyright/)
#[prefix = "C4"]
Flake8Comprehensions,
/// [flake8-datetimez](https://pypi.org/project/flake8-datetimez/)
Expand Down Expand Up @@ -913,6 +918,7 @@ impl Rule {
| Rule::ShebangLeadingWhitespace
| Rule::TrailingWhitespace
| Rule::TabIndentation
| Rule::HeaderLacksCopyright
| Rule::BlankLineWithWhitespace => LintSource::PhysicalLines,
Rule::AmbiguousUnicodeCharacterComment
| Rule::AmbiguousUnicodeCharacterDocstring
Expand Down
69 changes: 69 additions & 0 deletions crates/ruff/src/rules/flake8_copyright/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
//! Rules from [flake8-copyright](https://github.com/savoirfairelinux/flake8-copyright).
pub(crate) mod rules;

pub mod settings;

#[cfg(test)]
mod tests {
use std::path::Path;

use anyhow::Result;

use crate::registry::Rule;
use crate::test::test_path;
use crate::{assert_messages, settings};

#[test]
fn test_default_fail() -> Result<()> {
let diagnostics = test_path(
Path::new("flake8_copyright/C801_default_fail.py"),
&settings::Settings::for_rules(vec![Rule::HeaderLacksCopyright]),
)?;
assert_messages!("test_default_fail", diagnostics);
Ok(())
}

#[test]
fn test_default_pass() -> Result<()> {
let diagnostics = test_path(
Path::new("flake8_copyright/C801.py"),
&settings::Settings::for_rules(vec![Rule::HeaderLacksCopyright]),
)?;
assert!(diagnostics.is_empty());
Ok(())
}

#[test]
fn test_custom_regex_fail() -> Result<()> {
let diagnostics = test_path(
Path::new("flake8_copyright/C801_custom_author_fail.py"),
&settings::Settings {
flake8_copyright: super::settings::Settings {
copyright_author: "ruff".to_string(),
copyright_regexp: "(?i)Copyright \\d{4} \\(C\\".to_string(),
copyright_min_file_size: 0,
},
..settings::Settings::for_rules(vec![Rule::HeaderLacksCopyright])
},
)?;
assert_messages!("test_custom_regex_fail", diagnostics);
Ok(())
}

#[test]
fn test_custom_regex_pass() -> Result<()> {
let diagnostics = test_path(
Path::new("flake8_copyright/C801_custom_regexp_pass.py"),
&settings::Settings {
flake8_copyright: super::settings::Settings {
copyright_author: "ruff".to_string(),
copyright_regexp: "(?i)Copyright \\d{4} \\(C\\)".to_string(),
copyright_min_file_size: 300,
},
..settings::Settings::for_rules(vec![Rule::HeaderLacksCopyright])
},
)?;
assert!(diagnostics.is_empty());
Ok(())
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
use ruff_diagnostics::Violation;
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::newlines::Line;

use crate::settings::Settings;

use lazy_regex::Regex;
#[violation]
pub struct HeaderLacksCopyright;

impl Violation for HeaderLacksCopyright {
#[derive_message_formats]
fn message(&self) -> String {
format!("Copyright notice not present")
}
}
/// ## What it does
/// Checks for Copyright Header to exist within at the top of a file within `copyright_min_file_size chars`
/// format Copyright (C) <year> <author>
///
/// Error code C801
pub(crate) fn copyright_header_absent(
line: &Line,
settings: &Settings,
current_char_index: i64,
) -> Option<bool> {
let copyright_regexp = format!(
"{} {}",
settings.flake8_copyright.copyright_regexp, settings.flake8_copyright.copyright_author
);
let regex = Regex::new(copyright_regexp.trim()).unwrap();

let out_of_range =
current_char_index > (settings.flake8_copyright.copyright_min_file_size as i64);
let copyright_missing = regex.find(line.as_str()).is_none();

if copyright_missing && out_of_range {
// Missing copyright header
return Some(true);
}
if !copyright_missing {
// Found copyright header, should stop checking
return Some(false);
}
// Missing copyright header, but need to keep checking
None
}
3 changes: 3 additions & 0 deletions crates/ruff/src/rules/flake8_copyright/rules/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub(crate) use copyright_header_absent::{copyright_header_absent, HeaderLacksCopyright};

mod copyright_header_absent;
83 changes: 83 additions & 0 deletions crates/ruff/src/rules/flake8_copyright/settings.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// //! Settings for the `flake8-copyright` plugin.

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 = "Flake8CopyrightOptions"
)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct Options {
#[option(
default = "(?i)Copyright \\(C\\) \\d{4}",
value_type = "str",
example = r#"copyright-regexp = "(?i)Copyright \\(C\\) \\d{4}""#
)]
/// Regexp to check for copyright headers.
///
/// Default is "Copyright\s+(\(C\)\s+)?\d{4}([-,]\d{4})*\s+%(author)s"
/// E.g # Copyright (c) 2023 ruff
pub copyright_regexp: Option<String>,
#[option(
default = "",
value_type = "str",
example = r#"copyright-author = "ruff""#
)]
/// Author to check for specifically within the header
///
/// Default is ""
/// E.g # Copyright (c) 2023 ruff
pub copyright_author: Option<String>,
#[option(
default = r#"0"#,
value_type = "int",
example = r#"
# Ensure copyright exists within 1024 characters of header
copyright-min-file-size = 1024
"#
)]
/// Minimum number of characters in a file before requiring a copyright notice
pub copyright_min_file_size: Option<isize>,
}

#[derive(Debug, CacheKey)]
pub struct Settings {
pub copyright_regexp: String,
pub copyright_author: String,
pub copyright_min_file_size: isize,
}

impl Default for Settings {
fn default() -> Self {
Self {
copyright_regexp: String::from("(?i)Copyright \\(C\\) \\d{4}"),
copyright_author: String::new(),
copyright_min_file_size: 0,
}
}
}

impl From<Options> for Settings {
fn from(options: Options) -> Self {
Self {
copyright_regexp: options.copyright_regexp.unwrap_or_default(),
copyright_author: options.copyright_author.unwrap_or_default(),
copyright_min_file_size: options.copyright_min_file_size.unwrap_or_default(),
}
}
}

impl From<Settings> for Options {
fn from(settings: Settings) -> Self {
Self {
copyright_regexp: Some(settings.copyright_regexp),
copyright_author: Some(settings.copyright_author),
copyright_min_file_size: Some(settings.copyright_min_file_size),
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
source: crates/ruff/src/rules/flake8_copyright/mod.rs
---
C801_custom_author_fail.py:2:1: C801 Copyright notice not present
|
2 | # Copyright 2023 (c) rufffff
3 |
| C801
4 | import os
|


Loading

0 comments on commit 5d4dc6f

Please sign in to comment.