mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-18 17:40:37 +00:00
[flake8-markupsafe] Adds Implementation for MS001 via RUF035 (#14224)
Co-authored-by: Micha Reiser <micha@reiser.io>
This commit is contained in:
parent
b8a65182dd
commit
f82ee8ea59
16 changed files with 321 additions and 1 deletions
|
@ -375,6 +375,7 @@ linter.pylint.max_public_methods = 20
|
||||||
linter.pylint.max_locals = 15
|
linter.pylint.max_locals = 15
|
||||||
linter.pyupgrade.keep_runtime_typing = false
|
linter.pyupgrade.keep_runtime_typing = false
|
||||||
linter.ruff.parenthesize_tuple_in_subscript = false
|
linter.ruff.parenthesize_tuple_in_subscript = false
|
||||||
|
linter.ruff.extend_markup_names = []
|
||||||
|
|
||||||
# Formatter Settings
|
# Formatter Settings
|
||||||
formatter.exclude = []
|
formatter.exclude = []
|
||||||
|
|
18
crates/ruff_linter/resources/test/fixtures/ruff/RUF035.py
vendored
Normal file
18
crates/ruff_linter/resources/test/fixtures/ruff/RUF035.py
vendored
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import flask
|
||||||
|
from markupsafe import Markup, escape
|
||||||
|
|
||||||
|
content = "<script>alert('Hello, world!')</script>"
|
||||||
|
Markup(f"unsafe {content}") # RUF035
|
||||||
|
flask.Markup("unsafe {}".format(content)) # RUF035
|
||||||
|
Markup("safe {}").format(content)
|
||||||
|
flask.Markup(b"safe {}", encoding='utf-8').format(content)
|
||||||
|
escape(content)
|
||||||
|
Markup(content) # RUF035
|
||||||
|
flask.Markup("unsafe %s" % content) # RUF035
|
||||||
|
Markup(object="safe")
|
||||||
|
Markup(object="unsafe {}".format(content)) # Not currently detected
|
||||||
|
|
||||||
|
# NOTE: We may be able to get rid of these false positives with red-knot
|
||||||
|
# if it includes comprehensive constant expression detection/evaluation.
|
||||||
|
Markup("*" * 8) # RUF035 (false positive)
|
||||||
|
flask.Markup("hello {}".format("world")) # RUF035 (false positive)
|
6
crates/ruff_linter/resources/test/fixtures/ruff/RUF035_extend_markup_names.py
vendored
Normal file
6
crates/ruff_linter/resources/test/fixtures/ruff/RUF035_extend_markup_names.py
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
from markupsafe import Markup
|
||||||
|
from webhelpers.html import literal
|
||||||
|
|
||||||
|
content = "<script>alert('Hello, world!')</script>"
|
||||||
|
Markup(f"unsafe {content}") # RUF035
|
||||||
|
literal(f"unsafe {content}") # RUF035
|
7
crates/ruff_linter/resources/test/fixtures/ruff/RUF035_skip_early_out.py
vendored
Normal file
7
crates/ruff_linter/resources/test/fixtures/ruff/RUF035_skip_early_out.py
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
from webhelpers.html import literal
|
||||||
|
|
||||||
|
# NOTE: This test case exists to make sure our optimization doesn't cause
|
||||||
|
# additional markup names to be skipped if we don't import either
|
||||||
|
# markupsafe or flask first.
|
||||||
|
content = "<script>alert('Hello, world!')</script>"
|
||||||
|
literal(f"unsafe {content}") # RUF035
|
|
@ -1030,6 +1030,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||||
if checker.enabled(Rule::IntOnSlicedStr) {
|
if checker.enabled(Rule::IntOnSlicedStr) {
|
||||||
refurb::rules::int_on_sliced_str(checker, call);
|
refurb::rules::int_on_sliced_str(checker, call);
|
||||||
}
|
}
|
||||||
|
if checker.enabled(Rule::UnsafeMarkupUse) {
|
||||||
|
ruff::rules::unsafe_markup_call(checker, call);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Expr::Dict(dict) => {
|
Expr::Dict(dict) => {
|
||||||
if checker.any_enabled(&[
|
if checker.any_enabled(&[
|
||||||
|
|
|
@ -966,6 +966,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||||
(Ruff, "032") => (RuleGroup::Preview, rules::ruff::rules::DecimalFromFloatLiteral),
|
(Ruff, "032") => (RuleGroup::Preview, rules::ruff::rules::DecimalFromFloatLiteral),
|
||||||
(Ruff, "033") => (RuleGroup::Preview, rules::ruff::rules::PostInitDefault),
|
(Ruff, "033") => (RuleGroup::Preview, rules::ruff::rules::PostInitDefault),
|
||||||
(Ruff, "034") => (RuleGroup::Preview, rules::ruff::rules::UselessIfElse),
|
(Ruff, "034") => (RuleGroup::Preview, rules::ruff::rules::UselessIfElse),
|
||||||
|
(Ruff, "035") => (RuleGroup::Preview, rules::ruff::rules::UnsafeMarkupUse),
|
||||||
(Ruff, "100") => (RuleGroup::Stable, rules::ruff::rules::UnusedNOQA),
|
(Ruff, "100") => (RuleGroup::Stable, rules::ruff::rules::UnusedNOQA),
|
||||||
(Ruff, "101") => (RuleGroup::Stable, rules::ruff::rules::RedirectedNOQA),
|
(Ruff, "101") => (RuleGroup::Stable, rules::ruff::rules::RedirectedNOQA),
|
||||||
|
|
||||||
|
|
|
@ -79,6 +79,7 @@ mod tests {
|
||||||
&LinterSettings {
|
&LinterSettings {
|
||||||
ruff: super::settings::Settings {
|
ruff: super::settings::Settings {
|
||||||
parenthesize_tuple_in_subscript: true,
|
parenthesize_tuple_in_subscript: true,
|
||||||
|
extend_markup_names: vec![],
|
||||||
},
|
},
|
||||||
..LinterSettings::for_rule(Rule::IncorrectlyParenthesizedTupleInSubscript)
|
..LinterSettings::for_rule(Rule::IncorrectlyParenthesizedTupleInSubscript)
|
||||||
},
|
},
|
||||||
|
@ -94,6 +95,7 @@ mod tests {
|
||||||
&LinterSettings {
|
&LinterSettings {
|
||||||
ruff: super::settings::Settings {
|
ruff: super::settings::Settings {
|
||||||
parenthesize_tuple_in_subscript: false,
|
parenthesize_tuple_in_subscript: false,
|
||||||
|
extend_markup_names: vec![],
|
||||||
},
|
},
|
||||||
target_version: PythonVersion::Py310,
|
target_version: PythonVersion::Py310,
|
||||||
..LinterSettings::for_rule(Rule::IncorrectlyParenthesizedTupleInSubscript)
|
..LinterSettings::for_rule(Rule::IncorrectlyParenthesizedTupleInSubscript)
|
||||||
|
@ -385,6 +387,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test_case(Rule::ZipInsteadOfPairwise, Path::new("RUF007.py"))]
|
#[test_case(Rule::ZipInsteadOfPairwise, Path::new("RUF007.py"))]
|
||||||
|
#[test_case(Rule::UnsafeMarkupUse, Path::new("RUF035.py"))]
|
||||||
fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> {
|
fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||||
let snapshot = format!(
|
let snapshot = format!(
|
||||||
"preview__{}_{}",
|
"preview__{}_{}",
|
||||||
|
@ -401,4 +404,27 @@ mod tests {
|
||||||
assert_messages!(snapshot, diagnostics);
|
assert_messages!(snapshot, diagnostics);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test_case(Rule::UnsafeMarkupUse, Path::new("RUF035_extend_markup_names.py"))]
|
||||||
|
#[test_case(Rule::UnsafeMarkupUse, Path::new("RUF035_skip_early_out.py"))]
|
||||||
|
fn extend_allowed_callable(rule_code: Rule, path: &Path) -> Result<()> {
|
||||||
|
let snapshot = format!(
|
||||||
|
"extend_allow_callables__{}_{}",
|
||||||
|
rule_code.noqa_code(),
|
||||||
|
path.to_string_lossy()
|
||||||
|
);
|
||||||
|
let diagnostics = test_path(
|
||||||
|
Path::new("ruff").join(path).as_path(),
|
||||||
|
&LinterSettings {
|
||||||
|
ruff: super::settings::Settings {
|
||||||
|
parenthesize_tuple_in_subscript: true,
|
||||||
|
extend_markup_names: vec!["webhelpers.html.literal".to_string()],
|
||||||
|
},
|
||||||
|
preview: PreviewMode::Enabled,
|
||||||
|
..LinterSettings::for_rule(rule_code)
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
assert_messages!(snapshot, diagnostics);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,7 @@ pub(crate) use static_key_dict_comprehension::*;
|
||||||
pub(crate) use test_rules::*;
|
pub(crate) use test_rules::*;
|
||||||
pub(crate) use unnecessary_iterable_allocation_for_first_element::*;
|
pub(crate) use unnecessary_iterable_allocation_for_first_element::*;
|
||||||
pub(crate) use unnecessary_key_check::*;
|
pub(crate) use unnecessary_key_check::*;
|
||||||
|
pub(crate) use unsafe_markup_use::*;
|
||||||
pub(crate) use unused_async::*;
|
pub(crate) use unused_async::*;
|
||||||
pub(crate) use unused_noqa::*;
|
pub(crate) use unused_noqa::*;
|
||||||
pub(crate) use useless_if_else::*;
|
pub(crate) use useless_if_else::*;
|
||||||
|
@ -67,6 +68,7 @@ mod suppression_comment_visitor;
|
||||||
pub(crate) mod test_rules;
|
pub(crate) mod test_rules;
|
||||||
mod unnecessary_iterable_allocation_for_first_element;
|
mod unnecessary_iterable_allocation_for_first_element;
|
||||||
mod unnecessary_key_check;
|
mod unnecessary_key_check;
|
||||||
|
mod unsafe_markup_use;
|
||||||
mod unused_async;
|
mod unused_async;
|
||||||
mod unused_noqa;
|
mod unused_noqa;
|
||||||
mod useless_if_else;
|
mod useless_if_else;
|
||||||
|
|
138
crates/ruff_linter/src/rules/ruff/rules/unsafe_markup_use.rs
Normal file
138
crates/ruff_linter/src/rules/ruff/rules/unsafe_markup_use.rs
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
use ruff_python_ast::ExprCall;
|
||||||
|
|
||||||
|
use ruff_diagnostics::{Diagnostic, Violation};
|
||||||
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
|
use ruff_python_ast::name::QualifiedName;
|
||||||
|
use ruff_python_semantic::Modules;
|
||||||
|
use ruff_text_size::Ranged;
|
||||||
|
|
||||||
|
use crate::{checkers::ast::Checker, settings::LinterSettings};
|
||||||
|
|
||||||
|
/// ## What it does
|
||||||
|
/// Checks for non-literal strings being passed to [`markupsafe.Markup`].
|
||||||
|
///
|
||||||
|
/// ## Why is this bad?
|
||||||
|
/// [`markupsafe.Markup`] does not perform any escaping, so passing dynamic
|
||||||
|
/// content, like f-strings, variables or interpolated strings will potentially
|
||||||
|
/// lead to XSS vulnerabilities.
|
||||||
|
///
|
||||||
|
/// Instead you should interpolate the [`markupsafe.Markup`] object.
|
||||||
|
///
|
||||||
|
/// Using [`lint.ruff.extend-markup-names`] additional objects can be
|
||||||
|
/// treated like [`markupsafe.Markup`].
|
||||||
|
///
|
||||||
|
/// This rule was originally inspired by [flake8-markupsafe] but doesn't carve
|
||||||
|
/// out any exceptions for i18n related calls.
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
/// Given:
|
||||||
|
/// ```python
|
||||||
|
/// from markupsafe import Markup
|
||||||
|
///
|
||||||
|
/// content = "<script>alert('Hello, world!')</script>"
|
||||||
|
/// html = Markup(f"<b>{content}</b>") # XSS
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Use instead:
|
||||||
|
/// ```python
|
||||||
|
/// from markupsafe import Markup
|
||||||
|
///
|
||||||
|
/// content = "<script>alert('Hello, world!')</script>"
|
||||||
|
/// html = Markup("<b>{}</b>").format(content) # Safe
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Given:
|
||||||
|
/// ```python
|
||||||
|
/// from markupsafe import Markup
|
||||||
|
///
|
||||||
|
/// lines = [
|
||||||
|
/// Markup("<b>heading</b>"),
|
||||||
|
/// "<script>alert('XSS attempt')</script>",
|
||||||
|
/// ]
|
||||||
|
/// html = Markup("<br>".join(lines)) # XSS
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Use instead:
|
||||||
|
/// ```python
|
||||||
|
/// from markupsafe import Markup
|
||||||
|
///
|
||||||
|
/// lines = [
|
||||||
|
/// Markup("<b>heading</b>"),
|
||||||
|
/// "<script>alert('XSS attempt')</script>",
|
||||||
|
/// ]
|
||||||
|
/// html = Markup("<br>").join(lines) # Safe
|
||||||
|
/// ```
|
||||||
|
/// ## Options
|
||||||
|
/// - `lint.ruff.extend-markup-names`
|
||||||
|
///
|
||||||
|
/// ## References
|
||||||
|
/// - [MarkupSafe](https://pypi.org/project/MarkupSafe/)
|
||||||
|
/// - [`markupsafe.Markup`](https://markupsafe.palletsprojects.com/en/stable/escaping/#markupsafe.Markup)
|
||||||
|
///
|
||||||
|
/// [markupsafe.Markup]: https://markupsafe.palletsprojects.com/en/stable/escaping/#markupsafe.Markup
|
||||||
|
/// [flake8-markupsafe]: https://github.com/vmagamedov/flake8-markupsafe
|
||||||
|
#[violation]
|
||||||
|
pub struct UnsafeMarkupUse {
|
||||||
|
name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Violation for UnsafeMarkupUse {
|
||||||
|
#[derive_message_formats]
|
||||||
|
fn message(&self) -> String {
|
||||||
|
let UnsafeMarkupUse { name } = self;
|
||||||
|
format!("Unsafe use of `{name}` detected")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks for unsafe calls to `[markupsafe.Markup]`.
|
||||||
|
///
|
||||||
|
/// [markupsafe.Markup]: https://markupsafe.palletsprojects.com/en/stable/escaping/#markupsafe.Markup
|
||||||
|
pub(crate) fn unsafe_markup_call(checker: &mut Checker, call: &ExprCall) {
|
||||||
|
if checker.settings.ruff.extend_markup_names.is_empty()
|
||||||
|
&& !(checker.semantic().seen_module(Modules::MARKUPSAFE)
|
||||||
|
|| checker.semantic().seen_module(Modules::FLASK))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !is_unsafe_call(call) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(qualified_name) = checker.semantic().resolve_qualified_name(&call.func) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
if !is_markup_call(&qualified_name, checker.settings) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
checker.diagnostics.push(Diagnostic::new(
|
||||||
|
UnsafeMarkupUse {
|
||||||
|
name: qualified_name.to_string(),
|
||||||
|
},
|
||||||
|
call.range(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_markup_call(qualified_name: &QualifiedName, settings: &LinterSettings) -> bool {
|
||||||
|
matches!(
|
||||||
|
qualified_name.segments(),
|
||||||
|
["markupsafe" | "flask", "Markup"]
|
||||||
|
) || settings
|
||||||
|
.ruff
|
||||||
|
.extend_markup_names
|
||||||
|
.iter()
|
||||||
|
.map(|target| QualifiedName::from_dotted_name(target))
|
||||||
|
.any(|target| *qualified_name == target)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_unsafe_call(call: &ExprCall) -> bool {
|
||||||
|
// technically this could be circumvented by using a keyword argument
|
||||||
|
// but without type-inference we can't really know which keyword argument
|
||||||
|
// corresponds to the first positional argument and either way it is
|
||||||
|
// unlikely that someone will actually use a keyword argument here
|
||||||
|
// TODO: Eventually we may want to allow dynamic values, as long as they
|
||||||
|
// have a __html__ attribute, since that is part of the API
|
||||||
|
matches!(&*call.arguments.args, [first] if !first.is_string_literal_expr() && !first.is_bytes_literal_expr())
|
||||||
|
}
|
|
@ -7,6 +7,7 @@ use std::fmt;
|
||||||
#[derive(Debug, Clone, CacheKey, Default)]
|
#[derive(Debug, Clone, CacheKey, Default)]
|
||||||
pub struct Settings {
|
pub struct Settings {
|
||||||
pub parenthesize_tuple_in_subscript: bool,
|
pub parenthesize_tuple_in_subscript: bool,
|
||||||
|
pub extend_markup_names: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Settings {
|
impl fmt::Display for Settings {
|
||||||
|
@ -15,7 +16,8 @@ impl fmt::Display for Settings {
|
||||||
formatter = f,
|
formatter = f,
|
||||||
namespace = "linter.ruff",
|
namespace = "linter.ruff",
|
||||||
fields = [
|
fields = [
|
||||||
self.parenthesize_tuple_in_subscript
|
self.parenthesize_tuple_in_subscript,
|
||||||
|
self.extend_markup_names | array,
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
---
|
||||||
|
source: crates/ruff_linter/src/rules/ruff/mod.rs
|
||||||
|
---
|
||||||
|
RUF035_extend_markup_names.py:5:1: RUF035 Unsafe use of `markupsafe.Markup` detected
|
||||||
|
|
|
||||||
|
4 | content = "<script>alert('Hello, world!')</script>"
|
||||||
|
5 | Markup(f"unsafe {content}") # RUF035
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF035
|
||||||
|
6 | literal(f"unsafe {content}") # RUF035
|
||||||
|
|
|
||||||
|
|
||||||
|
RUF035_extend_markup_names.py:6:1: RUF035 Unsafe use of `webhelpers.html.literal` detected
|
||||||
|
|
|
||||||
|
4 | content = "<script>alert('Hello, world!')</script>"
|
||||||
|
5 | Markup(f"unsafe {content}") # RUF035
|
||||||
|
6 | literal(f"unsafe {content}") # RUF035
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF035
|
||||||
|
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
---
|
||||||
|
source: crates/ruff_linter/src/rules/ruff/mod.rs
|
||||||
|
---
|
||||||
|
RUF035_skip_early_out.py:7:1: RUF035 Unsafe use of `webhelpers.html.literal` detected
|
||||||
|
|
|
||||||
|
5 | # markupsafe or flask first.
|
||||||
|
6 | content = "<script>alert('Hello, world!')</script>"
|
||||||
|
7 | literal(f"unsafe {content}") # RUF035
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF035
|
||||||
|
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
---
|
||||||
|
source: crates/ruff_linter/src/rules/ruff/mod.rs
|
||||||
|
---
|
||||||
|
RUF035.py:5:1: RUF035 Unsafe use of `markupsafe.Markup` detected
|
||||||
|
|
|
||||||
|
4 | content = "<script>alert('Hello, world!')</script>"
|
||||||
|
5 | Markup(f"unsafe {content}") # RUF035
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF035
|
||||||
|
6 | flask.Markup("unsafe {}".format(content)) # RUF035
|
||||||
|
7 | Markup("safe {}").format(content)
|
||||||
|
|
|
||||||
|
|
||||||
|
RUF035.py:6:1: RUF035 Unsafe use of `flask.Markup` detected
|
||||||
|
|
|
||||||
|
4 | content = "<script>alert('Hello, world!')</script>"
|
||||||
|
5 | Markup(f"unsafe {content}") # RUF035
|
||||||
|
6 | flask.Markup("unsafe {}".format(content)) # RUF035
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF035
|
||||||
|
7 | Markup("safe {}").format(content)
|
||||||
|
8 | flask.Markup(b"safe {}", encoding='utf-8').format(content)
|
||||||
|
|
|
||||||
|
|
||||||
|
RUF035.py:10:1: RUF035 Unsafe use of `markupsafe.Markup` detected
|
||||||
|
|
|
||||||
|
8 | flask.Markup(b"safe {}", encoding='utf-8').format(content)
|
||||||
|
9 | escape(content)
|
||||||
|
10 | Markup(content) # RUF035
|
||||||
|
| ^^^^^^^^^^^^^^^ RUF035
|
||||||
|
11 | flask.Markup("unsafe %s" % content) # RUF035
|
||||||
|
12 | Markup(object="safe")
|
||||||
|
|
|
||||||
|
|
||||||
|
RUF035.py:11:1: RUF035 Unsafe use of `flask.Markup` detected
|
||||||
|
|
|
||||||
|
9 | escape(content)
|
||||||
|
10 | Markup(content) # RUF035
|
||||||
|
11 | flask.Markup("unsafe %s" % content) # RUF035
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF035
|
||||||
|
12 | Markup(object="safe")
|
||||||
|
13 | Markup(object="unsafe {}".format(content)) # Not currently detected
|
||||||
|
|
|
||||||
|
|
||||||
|
RUF035.py:17:1: RUF035 Unsafe use of `markupsafe.Markup` detected
|
||||||
|
|
|
||||||
|
15 | # NOTE: We may be able to get rid of these false positives with red-knot
|
||||||
|
16 | # if it includes comprehensive constant expression detection/evaluation.
|
||||||
|
17 | Markup("*" * 8) # RUF035 (false positive)
|
||||||
|
| ^^^^^^^^^^^^^^^ RUF035
|
||||||
|
18 | flask.Markup("hello {}".format("world")) # RUF035 (false positive)
|
||||||
|
|
|
||||||
|
|
||||||
|
RUF035.py:18:1: RUF035 Unsafe use of `flask.Markup` detected
|
||||||
|
|
|
||||||
|
16 | # if it includes comprehensive constant expression detection/evaluation.
|
||||||
|
17 | Markup("*" * 8) # RUF035 (false positive)
|
||||||
|
18 | flask.Markup("hello {}".format("world")) # RUF035 (false positive)
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF035
|
||||||
|
|
|
|
@ -1272,7 +1272,9 @@ impl<'a> SemanticModel<'a> {
|
||||||
"datetime" => self.seen.insert(Modules::DATETIME),
|
"datetime" => self.seen.insert(Modules::DATETIME),
|
||||||
"django" => self.seen.insert(Modules::DJANGO),
|
"django" => self.seen.insert(Modules::DJANGO),
|
||||||
"fastapi" => self.seen.insert(Modules::FASTAPI),
|
"fastapi" => self.seen.insert(Modules::FASTAPI),
|
||||||
|
"flask" => self.seen.insert(Modules::FLASK),
|
||||||
"logging" => self.seen.insert(Modules::LOGGING),
|
"logging" => self.seen.insert(Modules::LOGGING),
|
||||||
|
"markupsafe" => self.seen.insert(Modules::MARKUPSAFE),
|
||||||
"mock" => self.seen.insert(Modules::MOCK),
|
"mock" => self.seen.insert(Modules::MOCK),
|
||||||
"numpy" => self.seen.insert(Modules::NUMPY),
|
"numpy" => self.seen.insert(Modules::NUMPY),
|
||||||
"os" => self.seen.insert(Modules::OS),
|
"os" => self.seen.insert(Modules::OS),
|
||||||
|
@ -1858,6 +1860,8 @@ bitflags! {
|
||||||
const ANYIO = 1 << 20;
|
const ANYIO = 1 << 20;
|
||||||
const FASTAPI = 1 << 21;
|
const FASTAPI = 1 << 21;
|
||||||
const COPY = 1 << 22;
|
const COPY = 1 << 22;
|
||||||
|
const MARKUPSAFE = 1 << 23;
|
||||||
|
const FLASK = 1 << 24;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1387,6 +1387,7 @@ impl Flake8ImportConventionsOptions {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(
|
#[derive(
|
||||||
Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize, OptionsMetadata, CombineOptions,
|
Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize, OptionsMetadata, CombineOptions,
|
||||||
)]
|
)]
|
||||||
|
@ -3005,6 +3006,19 @@ pub struct RuffOptions {
|
||||||
"#
|
"#
|
||||||
)]
|
)]
|
||||||
pub parenthesize_tuple_in_subscript: Option<bool>,
|
pub parenthesize_tuple_in_subscript: Option<bool>,
|
||||||
|
|
||||||
|
/// A list of additional callable names that behave like [`markupsafe.Markup`].
|
||||||
|
///
|
||||||
|
/// Expects to receive a list of fully-qualified names (e.g., `webhelpers.html.literal`, rather than
|
||||||
|
/// `literal`).
|
||||||
|
///
|
||||||
|
/// [markupsafe.Markup]: https://markupsafe.palletsprojects.com/en/stable/escaping/#markupsafe.Markup
|
||||||
|
#[option(
|
||||||
|
default = "[]",
|
||||||
|
value_type = "list[str]",
|
||||||
|
example = "extend-markup-names = [\"webhelpers.html.literal\", \"my_package.Markup\"]"
|
||||||
|
)]
|
||||||
|
pub extend_markup_names: Option<Vec<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RuffOptions {
|
impl RuffOptions {
|
||||||
|
@ -3013,6 +3027,7 @@ impl RuffOptions {
|
||||||
parenthesize_tuple_in_subscript: self
|
parenthesize_tuple_in_subscript: self
|
||||||
.parenthesize_tuple_in_subscript
|
.parenthesize_tuple_in_subscript
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
|
extend_markup_names: self.extend_markup_names.unwrap_or_default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
11
ruff.schema.json
generated
11
ruff.schema.json
generated
|
@ -2755,6 +2755,16 @@
|
||||||
"RuffOptions": {
|
"RuffOptions": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"extend-markup-names": {
|
||||||
|
"description": "A list of additional callable names that behave like [`markupsafe.Markup`].\n\nExpects to receive a list of fully-qualified names (e.g., `webhelpers.html.literal`, rather than `literal`).\n\n[markupsafe.Markup]: https://markupsafe.palletsprojects.com/en/stable/escaping/#markupsafe.Markup",
|
||||||
|
"type": [
|
||||||
|
"array",
|
||||||
|
"null"
|
||||||
|
],
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
"parenthesize-tuple-in-subscript": {
|
"parenthesize-tuple-in-subscript": {
|
||||||
"description": "Whether to prefer accessing items keyed by tuples with parentheses around the tuple (see `RUF031`).",
|
"description": "Whether to prefer accessing items keyed by tuples with parentheses around the tuple (see `RUF031`).",
|
||||||
"type": [
|
"type": [
|
||||||
|
@ -3817,6 +3827,7 @@
|
||||||
"RUF032",
|
"RUF032",
|
||||||
"RUF033",
|
"RUF033",
|
||||||
"RUF034",
|
"RUF034",
|
||||||
|
"RUF035",
|
||||||
"RUF1",
|
"RUF1",
|
||||||
"RUF10",
|
"RUF10",
|
||||||
"RUF100",
|
"RUF100",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue