This commit is contained in:
Jonas Vacek 2025-11-16 18:32:07 +05:30 committed by GitHub
commit cf90fcd39c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
29 changed files with 1185 additions and 3 deletions

View file

@ -179,6 +179,7 @@ linter.flake8_self.ignore_names = [
_name_,
_value_,
]
linter.flake8_django.additional_path_functions = []
linter.flake8_tidy_imports.ban_relative_imports = "parents"
linter.flake8_tidy_imports.banned_api = {}
linter.flake8_tidy_imports.banned_module_level_imports = []

View file

@ -181,6 +181,7 @@ linter.flake8_self.ignore_names = [
_name_,
_value_,
]
linter.flake8_django.additional_path_functions = []
linter.flake8_tidy_imports.ban_relative_imports = "parents"
linter.flake8_tidy_imports.banned_api = {}
linter.flake8_tidy_imports.banned_module_level_imports = []

View file

@ -183,6 +183,7 @@ linter.flake8_self.ignore_names = [
_name_,
_value_,
]
linter.flake8_django.additional_path_functions = []
linter.flake8_tidy_imports.ban_relative_imports = "parents"
linter.flake8_tidy_imports.banned_api = {}
linter.flake8_tidy_imports.banned_module_level_imports = []

View file

@ -183,6 +183,7 @@ linter.flake8_self.ignore_names = [
_name_,
_value_,
]
linter.flake8_django.additional_path_functions = []
linter.flake8_tidy_imports.ban_relative_imports = "parents"
linter.flake8_tidy_imports.banned_api = {}
linter.flake8_tidy_imports.banned_module_level_imports = []

View file

@ -180,6 +180,7 @@ linter.flake8_self.ignore_names = [
_name_,
_value_,
]
linter.flake8_django.additional_path_functions = []
linter.flake8_tidy_imports.ban_relative_imports = "parents"
linter.flake8_tidy_imports.banned_api = {}
linter.flake8_tidy_imports.banned_module_level_imports = []

View file

@ -180,6 +180,7 @@ linter.flake8_self.ignore_names = [
_name_,
_value_,
]
linter.flake8_django.additional_path_functions = []
linter.flake8_tidy_imports.ban_relative_imports = "parents"
linter.flake8_tidy_imports.banned_api = {}
linter.flake8_tidy_imports.banned_module_level_imports = []

View file

@ -179,6 +179,7 @@ linter.flake8_self.ignore_names = [
_name_,
_value_,
]
linter.flake8_django.additional_path_functions = []
linter.flake8_tidy_imports.ban_relative_imports = "parents"
linter.flake8_tidy_imports.banned_api = {}
linter.flake8_tidy_imports.banned_module_level_imports = []

View file

@ -179,6 +179,7 @@ linter.flake8_self.ignore_names = [
_name_,
_value_,
]
linter.flake8_django.additional_path_functions = []
linter.flake8_tidy_imports.ban_relative_imports = "parents"
linter.flake8_tidy_imports.banned_api = {}
linter.flake8_tidy_imports.banned_module_level_imports = []

View file

@ -179,6 +179,7 @@ linter.flake8_self.ignore_names = [
_name_,
_value_,
]
linter.flake8_django.additional_path_functions = []
linter.flake8_tidy_imports.ban_relative_imports = "parents"
linter.flake8_tidy_imports.banned_api = {}
linter.flake8_tidy_imports.banned_module_level_imports = []

View file

@ -292,6 +292,7 @@ linter.flake8_self.ignore_names = [
_name_,
_value_,
]
linter.flake8_django.additional_path_functions = []
linter.flake8_tidy_imports.ban_relative_imports = "parents"
linter.flake8_tidy_imports.banned_api = {}
linter.flake8_tidy_imports.banned_module_level_imports = []

View file

@ -0,0 +1,46 @@
from django.urls import path
from . import views
# Errors - missing trailing slash
urlpatterns = [
path("help", views.help_view), # DJ100
path("about", views.about_view), # DJ100
path("contact", views.contact_view), # DJ100
path("api/users", views.users_view), # DJ100
path("blog/posts", views.posts_view), # DJ100
]
# OK - has trailing slash
urlpatterns_ok = [
path("help/", views.help_view),
path("about/", views.about_view),
path("contact/", views.contact_view),
path("api/users/", views.users_view),
path("blog/posts/", views.posts_view),
]
# OK - just root path
urlpatterns_root = [
path("/", views.index_view),
path("", views.home_view),
]
# OK - with path parameters
urlpatterns_params = [
path("users/<int:id>/", views.user_detail),
path("posts/<slug:slug>/", views.post_detail),
]
# Mixed cases
urlpatterns_mixed = [
path("good/", views.good_view),
path("bad", views.bad_view), # DJ100
path("also-good/", views.also_good_view),
path("also-bad", views.also_bad_view), # DJ100
]
# Error - missing trail slash and argument should stay in message
urlpatterns_params_bad = [
path("bad/<slug:slug>", views.bad_view), # DJ100
path("<slug:slug>", views.bad_view), # DJ100
]

View file

@ -0,0 +1,27 @@
from mytools import path as mypath
from . import views
# Test that custom path functions are also checked
urlpatterns_custom = [
mypath("help", views.help_view), # DJ100
mypath("about", views.about_view), # DJ100
]
# OK - custom path with trailing slash
urlpatterns_custom_ok = [
mypath("help/", views.help_view),
mypath("about/", views.about_view),
]
# Test multiple violations in same list
urlpatterns_multiple = [
mypath("api/users", views.users_view), # DJ100
mypath("api/posts", views.posts_view), # DJ100
mypath("api/comments/", views.comments_view), # OK
]
# OK - root path and empty string
urlpatterns_edge_cases = [
mypath("/", views.root_view), # OK - root path
mypath("", views.empty_view), # OK - empty string
]

View file

@ -0,0 +1,54 @@
from django.urls import path
from . import views
# Errors - leading slash
urlpatterns = [
path("/help/", views.help_view), # DJ101
path("/about/", views.about_view), # DJ101
path("/contact/", views.contact_view), # DJ101
path("/api/users/", views.users_view), # DJ101
path("/blog/posts/", views.posts_view), # DJ101
]
# OK - no leading slash
urlpatterns_ok = [
path("help/", views.help_view),
path("about/", views.about_view),
path("contact/", views.contact_view),
path("api/users/", views.users_view),
path("blog/posts/", views.posts_view),
]
# OK - just root path
urlpatterns_root = [
path("/", views.index_view),
path("", views.home_view),
]
# OK - with path parameters
urlpatterns_params = [
path("users/<int:id>/", views.user_detail),
path("posts/<slug:slug>/", views.post_detail),
]
# Mixed cases
urlpatterns_mixed = [
path("good/", views.good_view),
path("/bad/", views.bad_view), # DJ101
path("also-good/", views.also_good_view),
path("/also-bad/", views.also_bad_view), # DJ101
]
# Edge cases with different quote styles
urlpatterns_quotes = [
path('/single-quote/', views.single_quote_view), # DJ101
path("/double-quote/", views.double_quote_view), # DJ101
path('''/triple-single/''', views.triple_single_view), # DJ101
path("""/triple-double/""", views.triple_double_view), # DJ101
]
# Error - leading trail slash and argument should stay in message
urlpatterns_params_bad = [
path("/bad/<slug:slug>/", views.bad_view), # DJ101
path("/<slug:slug>", views.bad_view), # DJ101
]

View file

@ -0,0 +1,29 @@
from mytools import path as mypath
from . import views
# Test that custom path functions are also checked for leading slashes
urlpatterns_custom = [
mypath("/help/", views.help_view), # DJ101
mypath("/about/", views.about_view), # DJ101
]
# OK - custom path without leading slash
urlpatterns_custom_ok = [
mypath("help/", views.help_view),
mypath("about/", views.about_view),
]
# Test multiple violations in same list
urlpatterns_multiple = [
mypath("/api/users/", views.users_view), # DJ101
mypath("/api/posts/", views.posts_view), # DJ101
mypath("api/comments/", views.comments_view), # OK
]
# OK - root path and empty string
urlpatterns_edge_cases = [
mypath("/", views.root_view), # OK - root path
mypath("", views.empty_view), # OK - empty string
]

View file

@ -1181,6 +1181,12 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
if checker.is_rule_enabled(Rule::DjangoLocalsInRenderFunction) {
flake8_django::rules::locals_in_render_function(checker, call);
}
if checker.is_rule_enabled(Rule::DjangoURLPathWithoutTrailingSlash) {
flake8_django::rules::url_path_without_trailing_slash(checker, call);
}
if checker.is_rule_enabled(Rule::DjangoURLPathWithLeadingSlash) {
flake8_django::rules::url_path_with_leading_slash(checker, call);
}
if checker.is_rule_enabled(Rule::UnsupportedMethodCallOnAll) {
flake8_pyi::rules::unsupported_method_call_on_all(checker, func);
}

View file

@ -1100,6 +1100,8 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Flake8Django, "008") => rules::flake8_django::rules::DjangoModelWithoutDunderStr,
(Flake8Django, "012") => rules::flake8_django::rules::DjangoUnorderedBodyContentInModel,
(Flake8Django, "013") => rules::flake8_django::rules::DjangoNonLeadingReceiverDecorator,
(Flake8Django, "100") => rules::flake8_django::rules::DjangoURLPathWithoutTrailingSlash,
(Flake8Django, "101") => rules::flake8_django::rules::DjangoURLPathWithLeadingSlash,
// flynt
// Reserved: (Flynt, "001") => Rule: :StringConcatenationToFString,

View file

@ -1,6 +1,7 @@
//! Rules from [django-flake8](https://pypi.org/project/flake8-django/)
mod helpers;
pub(crate) mod rules;
pub mod settings;
#[cfg(test)]
mod tests {
@ -18,6 +19,8 @@ mod tests {
#[test_case(Rule::DjangoExcludeWithModelForm, Path::new("DJ006.py"))]
#[test_case(Rule::DjangoAllWithModelForm, Path::new("DJ007.py"))]
#[test_case(Rule::DjangoModelWithoutDunderStr, Path::new("DJ008.py"))]
#[test_case(Rule::DjangoURLPathWithoutTrailingSlash, Path::new("DJ100.py"))]
#[test_case(Rule::DjangoURLPathWithLeadingSlash, Path::new("DJ101.py"))]
#[test_case(Rule::DjangoUnorderedBodyContentInModel, Path::new("DJ012.py"))]
#[test_case(Rule::DjangoNonLeadingReceiverDecorator, Path::new("DJ013.py"))]
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
@ -29,4 +32,25 @@ mod tests {
assert_diagnostics!(snapshot, diagnostics);
Ok(())
}
#[test]
fn test_additional_path_functions_dj100() -> Result<()> {
let mut settings =
settings::LinterSettings::for_rule(Rule::DjangoURLPathWithoutTrailingSlash);
settings.flake8_django.additional_path_functions = vec!["mytools.path".to_string()];
let diagnostics = test_path(Path::new("flake8_django/DJ100_custom_paths.py"), &settings)?;
assert_diagnostics!("DJ100_custom_paths.py", diagnostics);
Ok(())
}
#[test]
fn test_additional_path_functions_dj101() -> Result<()> {
let mut settings = settings::LinterSettings::for_rule(Rule::DjangoURLPathWithLeadingSlash);
settings.flake8_django.additional_path_functions = vec!["mytools.path".to_string()];
let diagnostics = test_path(Path::new("flake8_django/DJ101_custom_paths.py"), &settings)?;
assert_diagnostics!("DJ101_custom_paths.py", diagnostics);
Ok(())
}
}

View file

@ -5,6 +5,8 @@ pub(crate) use model_without_dunder_str::*;
pub(crate) use non_leading_receiver_decorator::*;
pub(crate) use nullable_model_string_field::*;
pub(crate) use unordered_body_content_in_model::*;
pub(crate) use url_path_with_leading_slash::*;
pub(crate) use url_path_without_trailing_slash::*;
mod all_with_model_form;
mod exclude_with_model_form;
@ -13,3 +15,5 @@ mod model_without_dunder_str;
mod non_leading_receiver_decorator;
mod nullable_model_string_field;
mod unordered_body_content_in_model;
mod url_path_with_leading_slash;
mod url_path_without_trailing_slash;

View file

@ -0,0 +1,131 @@
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::{self as ast, Expr};
use ruff_text_size::{Ranged, TextSize};
use crate::checkers::ast::Checker;
use crate::{AlwaysFixableViolation, Edit, Fix};
/// ## What it does
/// Checks that all Django URL route definitions using `django.urls.path()`
/// do not start with a leading slash.
///
/// ## Why is this bad?
/// Django's URL patterns should not start with a leading slash. When using
/// `include()` or when patterns are combined, leading slashes can cause
/// issues with URL resolution. The Django documentation recommends that
/// URL patterns should not have leading slashes, as they are not necessary
/// and can lead to unexpected behavior.
///
/// ## Example
/// ```python
/// from django.urls import path
/// from . import views
///
/// urlpatterns = [
/// path("/help/", views.help_view), # Leading slash
/// path("/about/", views.about_view), # Leading slash
/// ]
/// ```
///
/// Use instead:
/// ```python
/// from django.urls import path
/// from . import views
///
/// urlpatterns = [
/// path("help/", views.help_view),
/// path("about/", views.about_view),
/// ]
/// ```
///
/// ## References
/// - [Django documentation: URL dispatcher](https://docs.djangoproject.com/en/stable/topics/http/urls/)
#[derive(ViolationMetadata)]
#[violation_metadata(preview_since = "v0.14.4")]
pub(crate) struct DjangoURLPathWithLeadingSlash {
url_pattern: String,
}
impl AlwaysFixableViolation for DjangoURLPathWithLeadingSlash {
#[derive_message_formats]
fn message(&self) -> String {
let DjangoURLPathWithLeadingSlash { url_pattern } = self;
format!("URL route `{url_pattern}` has an unnecessary leading slash")
}
fn fix_title(&self) -> String {
"Remove leading slash".to_string()
}
}
/// DJ101
pub(crate) fn url_path_with_leading_slash(checker: &Checker, call: &ast::ExprCall) {
// Check if this is a call to django.urls.path or any additional configured path functions
let is_path_function = checker
.semantic()
.resolve_qualified_name(&call.func)
.is_some_and(|qualified_name| {
let segments = qualified_name.segments();
// Check if it's the default django.urls.path
if matches!(segments, ["django", "urls", "path"]) {
return true;
}
// Check if it matches any additional configured path functions
let qualified_name_str = segments.join(".");
checker
.settings()
.flake8_django
.additional_path_functions
.iter()
.any(|path| path == &qualified_name_str)
});
if !is_path_function {
return;
}
// Get the first argument (the route pattern)
let Some(route_arg) = call.arguments.args.first() else {
return;
};
// Check if it's a string literal
if let Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) = route_arg {
let route = value.to_str();
// Skip empty strings and root path "/"
if route.is_empty() || route == "/" {
return;
}
// Check if route starts with a leading slash
if route.starts_with('/') {
// Report diagnostic for routes with leading slash
let mut diagnostic = checker.report_diagnostic(
DjangoURLPathWithLeadingSlash {
url_pattern: route.to_string(),
},
route_arg.range(),
);
// Determine the quote style to find the insertion point for removal
let string_content = checker.locator().slice(route_arg.range());
let quote_len =
if string_content.starts_with("'''") || string_content.starts_with("\"\"\"") {
3
} else if string_content.starts_with('\'') || string_content.starts_with('"') {
1
} else {
return; // Invalid string format
};
// Remove the leading slash (after the opening quote(s))
let removal_start = route_arg.range().start() + TextSize::new(quote_len);
let removal_end = removal_start + TextSize::new(1); // Remove one character (the slash)
diagnostic.set_fix(Fix::safe_edit(Edit::deletion(removal_start, removal_end)));
}
}
}

View file

@ -0,0 +1,134 @@
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::{self as ast, Expr};
use ruff_text_size::{Ranged, TextSize};
use crate::checkers::ast::Checker;
use crate::{AlwaysFixableViolation, Edit, Fix};
/// ## What it does
/// Checks that all Django URL route definitions using `django.urls.path()`
/// end with a trailing slash.
///
/// ## Why is this bad?
/// Django's convention is to use trailing slashes in URL patterns. This is
/// enforced by the `APPEND_SLASH` setting (enabled by default), which
/// redirects requests without trailing slashes to URLs with them. Omitting
/// the trailing slash can lead to unnecessary redirects and inconsistent URL
/// patterns throughout your application.
///
/// ## Example
/// ```python
/// from django.urls import path
/// from . import views
///
/// urlpatterns = [
/// path("help", views.help_view), # Missing trailing slash
/// path("about", views.about_view), # Missing trailing slash
/// ]
/// ```
///
/// Use instead:
/// ```python
/// from django.urls import path
/// from . import views
///
/// urlpatterns = [
/// path("help/", views.help_view),
/// path("about/", views.about_view),
/// ]
/// ```
///
/// ## References
/// - [Django documentation: URL dispatcher](https://docs.djangoproject.com/en/stable/topics/http/urls/)
#[derive(ViolationMetadata)]
#[violation_metadata(preview_since = "v0.14.4")]
pub(crate) struct DjangoURLPathWithoutTrailingSlash {
url_pattern: String,
}
impl AlwaysFixableViolation for DjangoURLPathWithoutTrailingSlash {
#[derive_message_formats]
fn message(&self) -> String {
let DjangoURLPathWithoutTrailingSlash { url_pattern } = self;
format!("URL route `{url_pattern}` is missing a trailing slash")
}
fn fix_title(&self) -> String {
"Add trailing slash".to_string()
}
}
/// DJ100
pub(crate) fn url_path_without_trailing_slash(checker: &Checker, call: &ast::ExprCall) {
// Check if this is a call to django.urls.path or any additional configured path functions
let is_path_function = checker
.semantic()
.resolve_qualified_name(&call.func)
.is_some_and(|qualified_name| {
let segments = qualified_name.segments();
// Check if it's the default django.urls.path
if matches!(segments, ["django", "urls", "path"]) {
return true;
}
// Check if it matches any additional configured path functions
let qualified_name_str = segments.join(".");
checker
.settings()
.flake8_django
.additional_path_functions
.iter()
.any(|path| path == &qualified_name_str)
});
if !is_path_function {
return;
}
// Get the first argument (the route pattern)
let Some(route_arg) = call.arguments.args.first() else {
return;
};
// Check if it's a string literal
if let Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) = route_arg {
let route = value.to_str();
// Skip empty strings
if route.is_empty() {
return;
}
// Skip routes that are just "/" or already end with "/"
if route == "/" || route.ends_with('/') {
return;
}
// Report diagnostic for routes without trailing slash
let mut diagnostic = checker.report_diagnostic(
DjangoURLPathWithoutTrailingSlash {
url_pattern: route.to_string(),
},
route_arg.range(),
);
// Determine the quote style to find the insertion point for the slash
// (just before the closing quotes)
let string_content = checker.locator().slice(route_arg.range());
let quote_len = if string_content.ends_with("'''") || string_content.ends_with("\"\"\"") {
3
} else if string_content.ends_with('\'') || string_content.ends_with('"') {
1
} else {
return; // Invalid string format
};
// Insert "/" just before the closing quote(s)
let insertion_point = route_arg.range().end() - TextSize::new(quote_len);
diagnostic.set_fix(Fix::safe_edit(Edit::insertion(
"/".to_string(),
insertion_point,
)));
}
}

View file

@ -0,0 +1,23 @@
//! Settings for the `flake8-django` plugin.
use crate::display_settings;
use ruff_macros::CacheKey;
use std::fmt::{Display, Formatter};
#[derive(Debug, Clone, CacheKey, Default)]
pub struct Settings {
pub additional_path_functions: Vec<String>,
}
impl Display for Settings {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
display_settings! {
formatter = f,
namespace = "linter.flake8_django",
fields = [
self.additional_path_functions | array
]
}
Ok(())
}
}

View file

@ -0,0 +1,176 @@
---
source: crates/ruff_linter/src/rules/flake8_django/mod.rs
---
DJ100 [*] URL route `help` is missing a trailing slash
--> DJ100.py:6:10
|
4 | # Errors - missing trailing slash
5 | urlpatterns = [
6 | path("help", views.help_view), # DJ100
| ^^^^^^
7 | path("about", views.about_view), # DJ100
8 | path("contact", views.contact_view), # DJ100
|
help: Add trailing slash
3 |
4 | # Errors - missing trailing slash
5 | urlpatterns = [
- path("help", views.help_view), # DJ100
6 + path("help/", views.help_view), # DJ100
7 | path("about", views.about_view), # DJ100
8 | path("contact", views.contact_view), # DJ100
9 | path("api/users", views.users_view), # DJ100
DJ100 [*] URL route `about` is missing a trailing slash
--> DJ100.py:7:10
|
5 | urlpatterns = [
6 | path("help", views.help_view), # DJ100
7 | path("about", views.about_view), # DJ100
| ^^^^^^^
8 | path("contact", views.contact_view), # DJ100
9 | path("api/users", views.users_view), # DJ100
|
help: Add trailing slash
4 | # Errors - missing trailing slash
5 | urlpatterns = [
6 | path("help", views.help_view), # DJ100
- path("about", views.about_view), # DJ100
7 + path("about/", views.about_view), # DJ100
8 | path("contact", views.contact_view), # DJ100
9 | path("api/users", views.users_view), # DJ100
10 | path("blog/posts", views.posts_view), # DJ100
DJ100 [*] URL route `contact` is missing a trailing slash
--> DJ100.py:8:10
|
6 | path("help", views.help_view), # DJ100
7 | path("about", views.about_view), # DJ100
8 | path("contact", views.contact_view), # DJ100
| ^^^^^^^^^
9 | path("api/users", views.users_view), # DJ100
10 | path("blog/posts", views.posts_view), # DJ100
|
help: Add trailing slash
5 | urlpatterns = [
6 | path("help", views.help_view), # DJ100
7 | path("about", views.about_view), # DJ100
- path("contact", views.contact_view), # DJ100
8 + path("contact/", views.contact_view), # DJ100
9 | path("api/users", views.users_view), # DJ100
10 | path("blog/posts", views.posts_view), # DJ100
11 | ]
DJ100 [*] URL route `api/users` is missing a trailing slash
--> DJ100.py:9:10
|
7 | path("about", views.about_view), # DJ100
8 | path("contact", views.contact_view), # DJ100
9 | path("api/users", views.users_view), # DJ100
| ^^^^^^^^^^^
10 | path("blog/posts", views.posts_view), # DJ100
11 | ]
|
help: Add trailing slash
6 | path("help", views.help_view), # DJ100
7 | path("about", views.about_view), # DJ100
8 | path("contact", views.contact_view), # DJ100
- path("api/users", views.users_view), # DJ100
9 + path("api/users/", views.users_view), # DJ100
10 | path("blog/posts", views.posts_view), # DJ100
11 | ]
12 |
DJ100 [*] URL route `blog/posts` is missing a trailing slash
--> DJ100.py:10:10
|
8 | path("contact", views.contact_view), # DJ100
9 | path("api/users", views.users_view), # DJ100
10 | path("blog/posts", views.posts_view), # DJ100
| ^^^^^^^^^^^^
11 | ]
|
help: Add trailing slash
7 | path("about", views.about_view), # DJ100
8 | path("contact", views.contact_view), # DJ100
9 | path("api/users", views.users_view), # DJ100
- path("blog/posts", views.posts_view), # DJ100
10 + path("blog/posts/", views.posts_view), # DJ100
11 | ]
12 |
13 | # OK - has trailing slash
DJ100 [*] URL route `bad` is missing a trailing slash
--> DJ100.py:37:10
|
35 | urlpatterns_mixed = [
36 | path("good/", views.good_view),
37 | path("bad", views.bad_view), # DJ100
| ^^^^^
38 | path("also-good/", views.also_good_view),
39 | path("also-bad", views.also_bad_view), # DJ100
|
help: Add trailing slash
34 | # Mixed cases
35 | urlpatterns_mixed = [
36 | path("good/", views.good_view),
- path("bad", views.bad_view), # DJ100
37 + path("bad/", views.bad_view), # DJ100
38 | path("also-good/", views.also_good_view),
39 | path("also-bad", views.also_bad_view), # DJ100
40 | ]
DJ100 [*] URL route `also-bad` is missing a trailing slash
--> DJ100.py:39:10
|
37 | path("bad", views.bad_view), # DJ100
38 | path("also-good/", views.also_good_view),
39 | path("also-bad", views.also_bad_view), # DJ100
| ^^^^^^^^^^
40 | ]
|
help: Add trailing slash
36 | path("good/", views.good_view),
37 | path("bad", views.bad_view), # DJ100
38 | path("also-good/", views.also_good_view),
- path("also-bad", views.also_bad_view), # DJ100
39 + path("also-bad/", views.also_bad_view), # DJ100
40 | ]
41 |
42 | # Error - missing trail slash and argument should stay in message
DJ100 [*] URL route `bad/<slug:slug>` is missing a trailing slash
--> DJ100.py:44:10
|
42 | # Error - missing trail slash and argument should stay in message
43 | urlpatterns_params_bad = [
44 | path("bad/<slug:slug>", views.bad_view), # DJ100
| ^^^^^^^^^^^^^^^^^
45 | path("<slug:slug>", views.bad_view), # DJ100
46 | ]
|
help: Add trailing slash
41 |
42 | # Error - missing trail slash and argument should stay in message
43 | urlpatterns_params_bad = [
- path("bad/<slug:slug>", views.bad_view), # DJ100
44 + path("bad/<slug:slug>/", views.bad_view), # DJ100
45 | path("<slug:slug>", views.bad_view), # DJ100
46 | ]
DJ100 [*] URL route `<slug:slug>` is missing a trailing slash
--> DJ100.py:45:10
|
43 | urlpatterns_params_bad = [
44 | path("bad/<slug:slug>", views.bad_view), # DJ100
45 | path("<slug:slug>", views.bad_view), # DJ100
| ^^^^^^^^^^^^^
46 | ]
|
help: Add trailing slash
42 | # Error - missing trail slash and argument should stay in message
43 | urlpatterns_params_bad = [
44 | path("bad/<slug:slug>", views.bad_view), # DJ100
- path("<slug:slug>", views.bad_view), # DJ100
45 + path("<slug:slug>/", views.bad_view), # DJ100
46 | ]

View file

@ -0,0 +1,81 @@
---
source: crates/ruff_linter/src/rules/flake8_django/mod.rs
---
DJ100 [*] URL route `help` is missing a trailing slash
--> DJ100_custom_paths.py:6:12
|
4 | # Test that custom path functions are also checked
5 | urlpatterns_custom = [
6 | mypath("help", views.help_view), # DJ100
| ^^^^^^
7 | mypath("about", views.about_view), # DJ100
8 | ]
|
help: Add trailing slash
3 |
4 | # Test that custom path functions are also checked
5 | urlpatterns_custom = [
- mypath("help", views.help_view), # DJ100
6 + mypath("help/", views.help_view), # DJ100
7 | mypath("about", views.about_view), # DJ100
8 | ]
9 |
DJ100 [*] URL route `about` is missing a trailing slash
--> DJ100_custom_paths.py:7:12
|
5 | urlpatterns_custom = [
6 | mypath("help", views.help_view), # DJ100
7 | mypath("about", views.about_view), # DJ100
| ^^^^^^^
8 | ]
|
help: Add trailing slash
4 | # Test that custom path functions are also checked
5 | urlpatterns_custom = [
6 | mypath("help", views.help_view), # DJ100
- mypath("about", views.about_view), # DJ100
7 + mypath("about/", views.about_view), # DJ100
8 | ]
9 |
10 | # OK - custom path with trailing slash
DJ100 [*] URL route `api/users` is missing a trailing slash
--> DJ100_custom_paths.py:18:12
|
16 | # Test multiple violations in same list
17 | urlpatterns_multiple = [
18 | mypath("api/users", views.users_view), # DJ100
| ^^^^^^^^^^^
19 | mypath("api/posts", views.posts_view), # DJ100
20 | mypath("api/comments/", views.comments_view), # OK
|
help: Add trailing slash
15 |
16 | # Test multiple violations in same list
17 | urlpatterns_multiple = [
- mypath("api/users", views.users_view), # DJ100
18 + mypath("api/users/", views.users_view), # DJ100
19 | mypath("api/posts", views.posts_view), # DJ100
20 | mypath("api/comments/", views.comments_view), # OK
21 | ]
DJ100 [*] URL route `api/posts` is missing a trailing slash
--> DJ100_custom_paths.py:19:12
|
17 | urlpatterns_multiple = [
18 | mypath("api/users", views.users_view), # DJ100
19 | mypath("api/posts", views.posts_view), # DJ100
| ^^^^^^^^^^^
20 | mypath("api/comments/", views.comments_view), # OK
21 | ]
|
help: Add trailing slash
16 | # Test multiple violations in same list
17 | urlpatterns_multiple = [
18 | mypath("api/users", views.users_view), # DJ100
- mypath("api/posts", views.posts_view), # DJ100
19 + mypath("api/posts/", views.posts_view), # DJ100
20 | mypath("api/comments/", views.comments_view), # OK
21 | ]
22 |

View file

@ -0,0 +1,255 @@
---
source: crates/ruff_linter/src/rules/flake8_django/mod.rs
---
DJ101 [*] URL route `/help/` has an unnecessary leading slash
--> DJ101.py:6:10
|
4 | # Errors - leading slash
5 | urlpatterns = [
6 | path("/help/", views.help_view), # DJ101
| ^^^^^^^^
7 | path("/about/", views.about_view), # DJ101
8 | path("/contact/", views.contact_view), # DJ101
|
help: Remove leading slash
3 |
4 | # Errors - leading slash
5 | urlpatterns = [
- path("/help/", views.help_view), # DJ101
6 + path("help/", views.help_view), # DJ101
7 | path("/about/", views.about_view), # DJ101
8 | path("/contact/", views.contact_view), # DJ101
9 | path("/api/users/", views.users_view), # DJ101
DJ101 [*] URL route `/about/` has an unnecessary leading slash
--> DJ101.py:7:10
|
5 | urlpatterns = [
6 | path("/help/", views.help_view), # DJ101
7 | path("/about/", views.about_view), # DJ101
| ^^^^^^^^^
8 | path("/contact/", views.contact_view), # DJ101
9 | path("/api/users/", views.users_view), # DJ101
|
help: Remove leading slash
4 | # Errors - leading slash
5 | urlpatterns = [
6 | path("/help/", views.help_view), # DJ101
- path("/about/", views.about_view), # DJ101
7 + path("about/", views.about_view), # DJ101
8 | path("/contact/", views.contact_view), # DJ101
9 | path("/api/users/", views.users_view), # DJ101
10 | path("/blog/posts/", views.posts_view), # DJ101
DJ101 [*] URL route `/contact/` has an unnecessary leading slash
--> DJ101.py:8:10
|
6 | path("/help/", views.help_view), # DJ101
7 | path("/about/", views.about_view), # DJ101
8 | path("/contact/", views.contact_view), # DJ101
| ^^^^^^^^^^^
9 | path("/api/users/", views.users_view), # DJ101
10 | path("/blog/posts/", views.posts_view), # DJ101
|
help: Remove leading slash
5 | urlpatterns = [
6 | path("/help/", views.help_view), # DJ101
7 | path("/about/", views.about_view), # DJ101
- path("/contact/", views.contact_view), # DJ101
8 + path("contact/", views.contact_view), # DJ101
9 | path("/api/users/", views.users_view), # DJ101
10 | path("/blog/posts/", views.posts_view), # DJ101
11 | ]
DJ101 [*] URL route `/api/users/` has an unnecessary leading slash
--> DJ101.py:9:10
|
7 | path("/about/", views.about_view), # DJ101
8 | path("/contact/", views.contact_view), # DJ101
9 | path("/api/users/", views.users_view), # DJ101
| ^^^^^^^^^^^^^
10 | path("/blog/posts/", views.posts_view), # DJ101
11 | ]
|
help: Remove leading slash
6 | path("/help/", views.help_view), # DJ101
7 | path("/about/", views.about_view), # DJ101
8 | path("/contact/", views.contact_view), # DJ101
- path("/api/users/", views.users_view), # DJ101
9 + path("api/users/", views.users_view), # DJ101
10 | path("/blog/posts/", views.posts_view), # DJ101
11 | ]
12 |
DJ101 [*] URL route `/blog/posts/` has an unnecessary leading slash
--> DJ101.py:10:10
|
8 | path("/contact/", views.contact_view), # DJ101
9 | path("/api/users/", views.users_view), # DJ101
10 | path("/blog/posts/", views.posts_view), # DJ101
| ^^^^^^^^^^^^^^
11 | ]
|
help: Remove leading slash
7 | path("/about/", views.about_view), # DJ101
8 | path("/contact/", views.contact_view), # DJ101
9 | path("/api/users/", views.users_view), # DJ101
- path("/blog/posts/", views.posts_view), # DJ101
10 + path("blog/posts/", views.posts_view), # DJ101
11 | ]
12 |
13 | # OK - no leading slash
DJ101 [*] URL route `/bad/` has an unnecessary leading slash
--> DJ101.py:37:10
|
35 | urlpatterns_mixed = [
36 | path("good/", views.good_view),
37 | path("/bad/", views.bad_view), # DJ101
| ^^^^^^^
38 | path("also-good/", views.also_good_view),
39 | path("/also-bad/", views.also_bad_view), # DJ101
|
help: Remove leading slash
34 | # Mixed cases
35 | urlpatterns_mixed = [
36 | path("good/", views.good_view),
- path("/bad/", views.bad_view), # DJ101
37 + path("bad/", views.bad_view), # DJ101
38 | path("also-good/", views.also_good_view),
39 | path("/also-bad/", views.also_bad_view), # DJ101
40 | ]
DJ101 [*] URL route `/also-bad/` has an unnecessary leading slash
--> DJ101.py:39:10
|
37 | path("/bad/", views.bad_view), # DJ101
38 | path("also-good/", views.also_good_view),
39 | path("/also-bad/", views.also_bad_view), # DJ101
| ^^^^^^^^^^^^
40 | ]
|
help: Remove leading slash
36 | path("good/", views.good_view),
37 | path("/bad/", views.bad_view), # DJ101
38 | path("also-good/", views.also_good_view),
- path("/also-bad/", views.also_bad_view), # DJ101
39 + path("also-bad/", views.also_bad_view), # DJ101
40 | ]
41 |
42 | # Edge cases with different quote styles
DJ101 [*] URL route `/single-quote/` has an unnecessary leading slash
--> DJ101.py:44:10
|
42 | # Edge cases with different quote styles
43 | urlpatterns_quotes = [
44 | path('/single-quote/', views.single_quote_view), # DJ101
| ^^^^^^^^^^^^^^^^
45 | path("/double-quote/", views.double_quote_view), # DJ101
46 | path('''/triple-single/''', views.triple_single_view), # DJ101
|
help: Remove leading slash
41 |
42 | # Edge cases with different quote styles
43 | urlpatterns_quotes = [
- path('/single-quote/', views.single_quote_view), # DJ101
44 + path('single-quote/', views.single_quote_view), # DJ101
45 | path("/double-quote/", views.double_quote_view), # DJ101
46 | path('''/triple-single/''', views.triple_single_view), # DJ101
47 | path("""/triple-double/""", views.triple_double_view), # DJ101
DJ101 [*] URL route `/double-quote/` has an unnecessary leading slash
--> DJ101.py:45:10
|
43 | urlpatterns_quotes = [
44 | path('/single-quote/', views.single_quote_view), # DJ101
45 | path("/double-quote/", views.double_quote_view), # DJ101
| ^^^^^^^^^^^^^^^^
46 | path('''/triple-single/''', views.triple_single_view), # DJ101
47 | path("""/triple-double/""", views.triple_double_view), # DJ101
|
help: Remove leading slash
42 | # Edge cases with different quote styles
43 | urlpatterns_quotes = [
44 | path('/single-quote/', views.single_quote_view), # DJ101
- path("/double-quote/", views.double_quote_view), # DJ101
45 + path("double-quote/", views.double_quote_view), # DJ101
46 | path('''/triple-single/''', views.triple_single_view), # DJ101
47 | path("""/triple-double/""", views.triple_double_view), # DJ101
48 | ]
DJ101 [*] URL route `/triple-single/` has an unnecessary leading slash
--> DJ101.py:46:10
|
44 | path('/single-quote/', views.single_quote_view), # DJ101
45 | path("/double-quote/", views.double_quote_view), # DJ101
46 | path('''/triple-single/''', views.triple_single_view), # DJ101
| ^^^^^^^^^^^^^^^^^^^^^
47 | path("""/triple-double/""", views.triple_double_view), # DJ101
48 | ]
|
help: Remove leading slash
43 | urlpatterns_quotes = [
44 | path('/single-quote/', views.single_quote_view), # DJ101
45 | path("/double-quote/", views.double_quote_view), # DJ101
- path('''/triple-single/''', views.triple_single_view), # DJ101
46 + path('''triple-single/''', views.triple_single_view), # DJ101
47 | path("""/triple-double/""", views.triple_double_view), # DJ101
48 | ]
49 |
DJ101 [*] URL route `/triple-double/` has an unnecessary leading slash
--> DJ101.py:47:10
|
45 | path("/double-quote/", views.double_quote_view), # DJ101
46 | path('''/triple-single/''', views.triple_single_view), # DJ101
47 | path("""/triple-double/""", views.triple_double_view), # DJ101
| ^^^^^^^^^^^^^^^^^^^^^
48 | ]
|
help: Remove leading slash
44 | path('/single-quote/', views.single_quote_view), # DJ101
45 | path("/double-quote/", views.double_quote_view), # DJ101
46 | path('''/triple-single/''', views.triple_single_view), # DJ101
- path("""/triple-double/""", views.triple_double_view), # DJ101
47 + path("""triple-double/""", views.triple_double_view), # DJ101
48 | ]
49 |
50 | # Error - leading trail slash and argument should stay in message
DJ101 [*] URL route `/bad/<slug:slug>/` has an unnecessary leading slash
--> DJ101.py:52:10
|
50 | # Error - leading trail slash and argument should stay in message
51 | urlpatterns_params_bad = [
52 | path("/bad/<slug:slug>/", views.bad_view), # DJ101
| ^^^^^^^^^^^^^^^^^^^
53 | path("/<slug:slug>", views.bad_view), # DJ101
54 | ]
|
help: Remove leading slash
49 |
50 | # Error - leading trail slash and argument should stay in message
51 | urlpatterns_params_bad = [
- path("/bad/<slug:slug>/", views.bad_view), # DJ101
52 + path("bad/<slug:slug>/", views.bad_view), # DJ101
53 | path("/<slug:slug>", views.bad_view), # DJ101
54 | ]
DJ101 [*] URL route `/<slug:slug>` has an unnecessary leading slash
--> DJ101.py:53:10
|
51 | urlpatterns_params_bad = [
52 | path("/bad/<slug:slug>/", views.bad_view), # DJ101
53 | path("/<slug:slug>", views.bad_view), # DJ101
| ^^^^^^^^^^^^^^
54 | ]
|
help: Remove leading slash
50 | # Error - leading trail slash and argument should stay in message
51 | urlpatterns_params_bad = [
52 | path("/bad/<slug:slug>/", views.bad_view), # DJ101
- path("/<slug:slug>", views.bad_view), # DJ101
53 + path("<slug:slug>", views.bad_view), # DJ101
54 | ]

View file

@ -0,0 +1,81 @@
---
source: crates/ruff_linter/src/rules/flake8_django/mod.rs
---
DJ101 [*] URL route `/help/` has an unnecessary leading slash
--> DJ101_custom_paths.py:6:12
|
4 | # Test that custom path functions are also checked for leading slashes
5 | urlpatterns_custom = [
6 | mypath("/help/", views.help_view), # DJ101
| ^^^^^^^^
7 | mypath("/about/", views.about_view), # DJ101
8 | ]
|
help: Remove leading slash
3 |
4 | # Test that custom path functions are also checked for leading slashes
5 | urlpatterns_custom = [
- mypath("/help/", views.help_view), # DJ101
6 + mypath("help/", views.help_view), # DJ101
7 | mypath("/about/", views.about_view), # DJ101
8 | ]
9 |
DJ101 [*] URL route `/about/` has an unnecessary leading slash
--> DJ101_custom_paths.py:7:12
|
5 | urlpatterns_custom = [
6 | mypath("/help/", views.help_view), # DJ101
7 | mypath("/about/", views.about_view), # DJ101
| ^^^^^^^^^
8 | ]
|
help: Remove leading slash
4 | # Test that custom path functions are also checked for leading slashes
5 | urlpatterns_custom = [
6 | mypath("/help/", views.help_view), # DJ101
- mypath("/about/", views.about_view), # DJ101
7 + mypath("about/", views.about_view), # DJ101
8 | ]
9 |
10 | # OK - custom path without leading slash
DJ101 [*] URL route `/api/users/` has an unnecessary leading slash
--> DJ101_custom_paths.py:18:12
|
16 | # Test multiple violations in same list
17 | urlpatterns_multiple = [
18 | mypath("/api/users/", views.users_view), # DJ101
| ^^^^^^^^^^^^^
19 | mypath("/api/posts/", views.posts_view), # DJ101
20 | mypath("api/comments/", views.comments_view), # OK
|
help: Remove leading slash
15 |
16 | # Test multiple violations in same list
17 | urlpatterns_multiple = [
- mypath("/api/users/", views.users_view), # DJ101
18 + mypath("api/users/", views.users_view), # DJ101
19 | mypath("/api/posts/", views.posts_view), # DJ101
20 | mypath("api/comments/", views.comments_view), # OK
21 | ]
DJ101 [*] URL route `/api/posts/` has an unnecessary leading slash
--> DJ101_custom_paths.py:19:12
|
17 | urlpatterns_multiple = [
18 | mypath("/api/users/", views.users_view), # DJ101
19 | mypath("/api/posts/", views.posts_view), # DJ101
| ^^^^^^^^^^^^^
20 | mypath("api/comments/", views.comments_view), # OK
21 | ]
|
help: Remove leading slash
16 | # Test multiple violations in same list
17 | urlpatterns_multiple = [
18 | mypath("/api/users/", views.users_view), # DJ101
- mypath("/api/posts/", views.posts_view), # DJ101
19 + mypath("api/posts/", views.posts_view), # DJ101
20 | mypath("api/comments/", views.comments_view), # OK
21 | ]
22 |

View file

@ -17,7 +17,7 @@ use crate::line_width::LineLength;
use crate::registry::{Linter, Rule};
use crate::rules::{
flake8_annotations, flake8_bandit, flake8_boolean_trap, flake8_bugbear, flake8_builtins,
flake8_comprehensions, flake8_copyright, flake8_errmsg, flake8_gettext,
flake8_comprehensions, flake8_copyright, flake8_django, 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, pydoclint, pydocstyle, pyflakes, pylint, pyupgrade, ruff,
@ -262,6 +262,7 @@ pub struct LinterSettings {
pub flake8_builtins: flake8_builtins::settings::Settings,
pub flake8_comprehensions: flake8_comprehensions::settings::Settings,
pub flake8_copyright: flake8_copyright::settings::Settings,
pub flake8_django: flake8_django::settings::Settings,
pub flake8_errmsg: flake8_errmsg::settings::Settings,
pub flake8_gettext: flake8_gettext::settings::Settings,
pub flake8_implicit_str_concat: flake8_implicit_str_concat::settings::Settings,
@ -337,6 +338,7 @@ impl Display for LinterSettings {
self.flake8_pytest_style | nested,
self.flake8_quotes | nested,
self.flake8_self | nested,
self.flake8_django | nested,
self.flake8_tidy_imports | nested,
self.flake8_type_checking | nested,
self.flake8_unused_arguments | nested,
@ -438,6 +440,7 @@ impl LinterSettings {
flake8_pytest_style: flake8_pytest_style::settings::Settings::default(),
flake8_quotes: flake8_quotes::settings::Settings::default(),
flake8_self: flake8_self::settings::Settings::default(),
flake8_django: flake8_django::settings::Settings::default(),
flake8_tidy_imports: flake8_tidy_imports::settings::Settings::default(),
flake8_type_checking: flake8_type_checking::settings::Settings::default(),
flake8_unused_arguments: flake8_unused_arguments::settings::Settings::default(),

View file

@ -47,7 +47,7 @@ use ruff_python_formatter::{
use crate::options::{
AnalyzeOptions, Flake8AnnotationsOptions, Flake8BanditOptions, Flake8BooleanTrapOptions,
Flake8BugbearOptions, Flake8BuiltinsOptions, Flake8ComprehensionsOptions,
Flake8CopyrightOptions, Flake8ErrMsgOptions, Flake8GetTextOptions,
Flake8CopyrightOptions, Flake8DjangoOptions, Flake8ErrMsgOptions, Flake8GetTextOptions,
Flake8ImplicitStrConcatOptions, Flake8ImportConventionsOptions, Flake8PytestStyleOptions,
Flake8QuotesOptions, Flake8SelfOptions, Flake8TidyImportsOptions, Flake8TypeCheckingOptions,
Flake8UnusedArgumentsOptions, FormatOptions, IsortOptions, LintCommonOptions, LintOptions,
@ -366,6 +366,10 @@ impl Configuration {
.map(Flake8CopyrightOptions::try_into_settings)
.transpose()?
.unwrap_or_default(),
flake8_django: lint
.flake8_django
.map(Flake8DjangoOptions::into_settings)
.unwrap_or_default(),
flake8_errmsg: lint
.flake8_errmsg
.map(Flake8ErrMsgOptions::into_settings)
@ -662,6 +666,7 @@ pub struct LintConfiguration {
pub flake8_builtins: Option<Flake8BuiltinsOptions>,
pub flake8_comprehensions: Option<Flake8ComprehensionsOptions>,
pub flake8_copyright: Option<Flake8CopyrightOptions>,
pub flake8_django: Option<Flake8DjangoOptions>,
pub flake8_errmsg: Option<Flake8ErrMsgOptions>,
pub flake8_gettext: Option<Flake8GetTextOptions>,
pub flake8_implicit_str_concat: Option<Flake8ImplicitStrConcatOptions>,
@ -779,6 +784,7 @@ impl LintConfiguration {
flake8_builtins: options.common.flake8_builtins,
flake8_comprehensions: options.common.flake8_comprehensions,
flake8_copyright: options.common.flake8_copyright,
flake8_django: options.common.flake8_django,
flake8_errmsg: options.common.flake8_errmsg,
flake8_gettext: options.common.flake8_gettext,
flake8_implicit_str_concat: options.common.flake8_implicit_str_concat,
@ -1168,6 +1174,7 @@ impl LintConfiguration {
.flake8_comprehensions
.combine(config.flake8_comprehensions),
flake8_copyright: self.flake8_copyright.combine(config.flake8_copyright),
flake8_django: self.flake8_django.combine(config.flake8_django),
flake8_errmsg: self.flake8_errmsg.combine(config.flake8_errmsg),
flake8_gettext: self.flake8_gettext.combine(config.flake8_gettext),
flake8_implicit_str_concat: self
@ -1394,6 +1401,7 @@ fn warn_about_deprecated_top_level_lint_options(
flake8_builtins,
flake8_comprehensions,
flake8_copyright,
flake8_django,
flake8_errmsg,
flake8_quotes,
flake8_self,
@ -1517,6 +1525,10 @@ fn warn_about_deprecated_top_level_lint_options(
used_options.push("flake8-copyright");
}
if flake8_django.is_some() {
used_options.push("flake8-django");
}
if flake8_errmsg.is_some() {
used_options.push("flake8-errmsg");
}

View file

@ -22,7 +22,7 @@ use ruff_linter::rules::pep8_naming::settings::IgnoreNames;
use ruff_linter::rules::pydocstyle::settings::Convention;
use ruff_linter::rules::pylint::settings::ConstantType;
use ruff_linter::rules::{
flake8_copyright, flake8_errmsg, flake8_gettext, flake8_implicit_str_concat,
flake8_copyright, flake8_django, 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, pydoclint, pydocstyle, pyflakes, pylint, pyupgrade, ruff,
@ -900,6 +900,10 @@ pub struct LintCommonOptions {
#[option_group]
pub flake8_copyright: Option<Flake8CopyrightOptions>,
/// Options for the `flake8-django` plugin.
#[option_group]
pub flake8_django: Option<Flake8DjangoOptions>,
/// Options for the `flake8-errmsg` plugin.
#[option_group]
pub flake8_errmsg: Option<Flake8ErrMsgOptions>,
@ -1431,6 +1435,39 @@ impl Flake8CopyrightOptions {
}
}
/// Options for the `flake8-django` plugin.
#[derive(
Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize, OptionsMetadata, CombineOptions,
)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct Flake8DjangoOptions {
/// Additional qualified paths to Django URL path functions beyond
/// the default `django.urls.path`. This allows the rule to check
/// URL patterns defined using custom path functions or re-exported
/// path functions from other modules.
///
/// For example, if you have a custom module `mytools` that re-exports
/// Django's path function, you can add `"mytools.path"` to this list.
#[option(
default = "[]",
value_type = "list[str]",
example = r#"
# Allow checking URL patterns from custom path functions
additional-path-functions = ["mytools.path", "myapp.urls.custom_path"]
"#
)]
pub additional_path_functions: Option<Vec<String>>,
}
impl Flake8DjangoOptions {
pub fn into_settings(self) -> flake8_django::settings::Settings {
flake8_django::settings::Settings {
additional_path_functions: self.additional_path_functions.unwrap_or_default(),
}
}
}
/// Options for the `flake8-errmsg` plugin.
#[derive(
Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize, OptionsMetadata, CombineOptions,
@ -3937,6 +3974,7 @@ pub struct LintOptionsWire {
flake8_builtins: Option<Flake8BuiltinsOptions>,
flake8_comprehensions: Option<Flake8ComprehensionsOptions>,
flake8_copyright: Option<Flake8CopyrightOptions>,
flake8_django: Option<Flake8DjangoOptions>,
flake8_errmsg: Option<Flake8ErrMsgOptions>,
flake8_quotes: Option<Flake8QuotesOptions>,
flake8_self: Option<Flake8SelfOptions>,
@ -3994,6 +4032,7 @@ impl From<LintOptionsWire> for LintOptions {
flake8_builtins,
flake8_comprehensions,
flake8_copyright,
flake8_django,
flake8_errmsg,
flake8_quotes,
flake8_self,
@ -4050,6 +4089,7 @@ impl From<LintOptionsWire> for LintOptions {
flake8_builtins,
flake8_comprehensions,
flake8_copyright,
flake8_django,
flake8_errmsg,
flake8_quotes,
flake8_self,

44
ruff.schema.json generated
View file

@ -297,6 +297,18 @@
],
"deprecated": true
},
"flake8-django": {
"description": "Options for the `flake8-django` plugin.",
"anyOf": [
{
"$ref": "#/definitions/Flake8DjangoOptions"
},
{
"type": "null"
}
],
"deprecated": true
},
"flake8-errmsg": {
"description": "Options for the `flake8-errmsg` plugin.",
"anyOf": [
@ -1145,6 +1157,23 @@
},
"additionalProperties": false
},
"Flake8DjangoOptions": {
"description": "Options for the `flake8-django` plugin.",
"type": "object",
"properties": {
"additional-path-functions": {
"description": "Additional qualified paths to Django URL path functions beyond\nthe default `django.urls.path`. This allows the rule to check\nURL patterns defined using custom path functions or re-exported\npath functions from other modules.\n\nFor example, if you have a custom module `mytools` that re-exports\nDjango's path function, you can add `\"mytools.path\"` to this list.",
"type": [
"array",
"null"
],
"items": {
"type": "string"
}
}
},
"additionalProperties": false
},
"Flake8ErrMsgOptions": {
"description": "Options for the `flake8-errmsg` plugin.",
"type": "object",
@ -2167,6 +2196,17 @@
}
]
},
"flake8-django": {
"description": "Options for the `flake8-django` plugin.",
"anyOf": [
{
"$ref": "#/definitions/Flake8DjangoOptions"
},
{
"type": "null"
}
]
},
"flake8-errmsg": {
"description": "Options for the `flake8-errmsg` plugin.",
"anyOf": [
@ -3164,6 +3204,10 @@
"DJ01",
"DJ012",
"DJ013",
"DJ1",
"DJ10",
"DJ100",
"DJ101",
"DOC",
"DOC1",
"DOC10",