mirror of
https://github.com/astral-sh/ruff.git
synced 2025-10-02 06:42:02 +00:00
Implement B010 (#683)
This commit is contained in:
parent
b5549382a7
commit
bd3b40688f
12 changed files with 100 additions and 8 deletions
|
@ -502,6 +502,7 @@ For more, see [flake8-bugbear](https://pypi.org/project/flake8-bugbear/22.10.27/
|
||||||
| B007 | UnusedLoopControlVariable | Loop control variable `i` not used within the loop body. | 🛠 |
|
| B007 | UnusedLoopControlVariable | Loop control variable `i` not used within the loop body. | 🛠 |
|
||||||
| B008 | FunctionCallArgumentDefault | Do not perform function calls in argument defaults. | |
|
| B008 | FunctionCallArgumentDefault | Do not perform function calls in argument defaults. | |
|
||||||
| B009 | GetAttrWithConstant | Do not call `getattr` with a constant attribute value, it is not any safer than normal property access. | |
|
| B009 | GetAttrWithConstant | Do not call `getattr` with a constant attribute value, it is not any safer than normal property access. | |
|
||||||
|
| B010 | SetAttrWithConstant | Do not call setattr with a constant attribute value, it is not any safer than normal property access. | |
|
||||||
| B011 | DoNotAssertFalse | Do not `assert False` (`python -O` removes these calls), raise `AssertionError()` | 🛠 |
|
| B011 | DoNotAssertFalse | Do not `assert False` (`python -O` removes these calls), raise `AssertionError()` | 🛠 |
|
||||||
| B013 | RedundantTupleInExceptionHandler | A length-one tuple literal is redundant. Write `except ValueError:` instead of `except (ValueError,):`. | |
|
| B013 | RedundantTupleInExceptionHandler | A length-one tuple literal is redundant. Write `except ValueError:` instead of `except (ValueError,):`. | |
|
||||||
| B014 | DuplicateHandlerException | Exception handler with duplicate exception: `ValueError` | 🛠 |
|
| B014 | DuplicateHandlerException | Exception handler with duplicate exception: `ValueError` | 🛠 |
|
||||||
|
@ -665,7 +666,7 @@ including:
|
||||||
- [`flake8-quotes`](https://pypi.org/project/flake8-quotes/)
|
- [`flake8-quotes`](https://pypi.org/project/flake8-quotes/)
|
||||||
- [`flake8-annotations`](https://pypi.org/project/flake8-annotations/)
|
- [`flake8-annotations`](https://pypi.org/project/flake8-annotations/)
|
||||||
- [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/)
|
- [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/)
|
||||||
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (19/32)
|
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (20/32)
|
||||||
- [`pyupgrade`](https://pypi.org/project/pyupgrade/) (14/34)
|
- [`pyupgrade`](https://pypi.org/project/pyupgrade/) (14/34)
|
||||||
- [`autoflake`](https://pypi.org/project/autoflake/) (1/7)
|
- [`autoflake`](https://pypi.org/project/autoflake/) (1/7)
|
||||||
|
|
||||||
|
@ -688,7 +689,7 @@ Today, Ruff can be used to replace Flake8 when used with any of the following pl
|
||||||
- [`flake8-quotes`](https://pypi.org/project/flake8-quotes/)
|
- [`flake8-quotes`](https://pypi.org/project/flake8-quotes/)
|
||||||
- [`flake8-annotations`](https://pypi.org/project/flake8-annotations/)
|
- [`flake8-annotations`](https://pypi.org/project/flake8-annotations/)
|
||||||
- [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/)
|
- [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/)
|
||||||
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (19/32)
|
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (20/32)
|
||||||
|
|
||||||
Ruff can also replace [`isort`](https://pypi.org/project/isort/), [`yesqa`](https://github.com/asottile/yesqa),
|
Ruff can also replace [`isort`](https://pypi.org/project/isort/), [`yesqa`](https://github.com/asottile/yesqa),
|
||||||
and a subset of the rules implemented in [`pyupgrade`](https://pypi.org/project/pyupgrade/) (14/34).
|
and a subset of the rules implemented in [`pyupgrade`](https://pypi.org/project/pyupgrade/) (14/34).
|
||||||
|
|
|
@ -46,6 +46,7 @@ pub enum ScopeKind<'a> {
|
||||||
Generator,
|
Generator,
|
||||||
Module,
|
Module,
|
||||||
Arg,
|
Arg,
|
||||||
|
Lambda,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
|
|
@ -1074,6 +1074,16 @@ where
|
||||||
if self.settings.enabled.contains(&CheckCode::B009) {
|
if self.settings.enabled.contains(&CheckCode::B009) {
|
||||||
flake8_bugbear::plugins::getattr_with_constant(self, expr, func, args);
|
flake8_bugbear::plugins::getattr_with_constant(self, expr, func, args);
|
||||||
}
|
}
|
||||||
|
if self.settings.enabled.contains(&CheckCode::B010) {
|
||||||
|
if !self
|
||||||
|
.scopes
|
||||||
|
.iter()
|
||||||
|
.rev()
|
||||||
|
.any(|scope| matches!(scope.kind, ScopeKind::Lambda))
|
||||||
|
{
|
||||||
|
flake8_bugbear::plugins::setattr_with_constant(self, expr, func, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
if self.settings.enabled.contains(&CheckCode::B026) {
|
if self.settings.enabled.contains(&CheckCode::B026) {
|
||||||
flake8_bugbear::plugins::star_arg_unpacking_after_keyword_arg(
|
flake8_bugbear::plugins::star_arg_unpacking_after_keyword_arg(
|
||||||
self, args, keywords,
|
self, args, keywords,
|
||||||
|
@ -1461,6 +1471,7 @@ where
|
||||||
for expr in &args.defaults {
|
for expr in &args.defaults {
|
||||||
self.visit_expr(expr);
|
self.visit_expr(expr);
|
||||||
}
|
}
|
||||||
|
self.push_scope(Scope::new(ScopeKind::Lambda))
|
||||||
}
|
}
|
||||||
|
|
||||||
ExprKind::ListComp { elt, generators } | ExprKind::SetComp { elt, generators } => {
|
ExprKind::ListComp { elt, generators } | ExprKind::SetComp { elt, generators } => {
|
||||||
|
@ -1649,7 +1660,8 @@ where
|
||||||
|
|
||||||
// Post-visit.
|
// Post-visit.
|
||||||
match &expr.node {
|
match &expr.node {
|
||||||
ExprKind::GeneratorExp { .. }
|
ExprKind::Lambda { .. }
|
||||||
|
| ExprKind::GeneratorExp { .. }
|
||||||
| ExprKind::ListComp { .. }
|
| ExprKind::ListComp { .. }
|
||||||
| ExprKind::DictComp { .. }
|
| ExprKind::DictComp { .. }
|
||||||
| ExprKind::SetComp { .. } => {
|
| ExprKind::SetComp { .. } => {
|
||||||
|
|
|
@ -85,6 +85,7 @@ pub enum CheckCode {
|
||||||
B007,
|
B007,
|
||||||
B008,
|
B008,
|
||||||
B009,
|
B009,
|
||||||
|
B010,
|
||||||
B011,
|
B011,
|
||||||
B013,
|
B013,
|
||||||
B014,
|
B014,
|
||||||
|
@ -357,6 +358,7 @@ pub enum CheckKind {
|
||||||
UnusedLoopControlVariable(String),
|
UnusedLoopControlVariable(String),
|
||||||
FunctionCallArgumentDefault,
|
FunctionCallArgumentDefault,
|
||||||
GetAttrWithConstant,
|
GetAttrWithConstant,
|
||||||
|
SetAttrWithConstant,
|
||||||
DoNotAssertFalse,
|
DoNotAssertFalse,
|
||||||
RedundantTupleInExceptionHandler(String),
|
RedundantTupleInExceptionHandler(String),
|
||||||
DuplicateHandlerException(Vec<String>),
|
DuplicateHandlerException(Vec<String>),
|
||||||
|
@ -573,6 +575,7 @@ impl CheckCode {
|
||||||
CheckCode::B007 => CheckKind::UnusedLoopControlVariable("i".to_string()),
|
CheckCode::B007 => CheckKind::UnusedLoopControlVariable("i".to_string()),
|
||||||
CheckCode::B008 => CheckKind::FunctionCallArgumentDefault,
|
CheckCode::B008 => CheckKind::FunctionCallArgumentDefault,
|
||||||
CheckCode::B009 => CheckKind::GetAttrWithConstant,
|
CheckCode::B009 => CheckKind::GetAttrWithConstant,
|
||||||
|
CheckCode::B010 => CheckKind::SetAttrWithConstant,
|
||||||
CheckCode::B011 => CheckKind::DoNotAssertFalse,
|
CheckCode::B011 => CheckKind::DoNotAssertFalse,
|
||||||
CheckCode::B013 => {
|
CheckCode::B013 => {
|
||||||
CheckKind::RedundantTupleInExceptionHandler("ValueError".to_string())
|
CheckKind::RedundantTupleInExceptionHandler("ValueError".to_string())
|
||||||
|
@ -794,6 +797,7 @@ impl CheckCode {
|
||||||
CheckCode::B007 => CheckCategory::Flake8Bugbear,
|
CheckCode::B007 => CheckCategory::Flake8Bugbear,
|
||||||
CheckCode::B008 => CheckCategory::Flake8Bugbear,
|
CheckCode::B008 => CheckCategory::Flake8Bugbear,
|
||||||
CheckCode::B009 => CheckCategory::Flake8Bugbear,
|
CheckCode::B009 => CheckCategory::Flake8Bugbear,
|
||||||
|
CheckCode::B010 => CheckCategory::Flake8Bugbear,
|
||||||
CheckCode::B011 => CheckCategory::Flake8Bugbear,
|
CheckCode::B011 => CheckCategory::Flake8Bugbear,
|
||||||
CheckCode::B013 => CheckCategory::Flake8Bugbear,
|
CheckCode::B013 => CheckCategory::Flake8Bugbear,
|
||||||
CheckCode::B014 => CheckCategory::Flake8Bugbear,
|
CheckCode::B014 => CheckCategory::Flake8Bugbear,
|
||||||
|
@ -978,6 +982,7 @@ impl CheckKind {
|
||||||
CheckKind::UnusedLoopControlVariable(_) => &CheckCode::B007,
|
CheckKind::UnusedLoopControlVariable(_) => &CheckCode::B007,
|
||||||
CheckKind::FunctionCallArgumentDefault => &CheckCode::B008,
|
CheckKind::FunctionCallArgumentDefault => &CheckCode::B008,
|
||||||
CheckKind::GetAttrWithConstant => &CheckCode::B009,
|
CheckKind::GetAttrWithConstant => &CheckCode::B009,
|
||||||
|
CheckKind::SetAttrWithConstant => &CheckCode::B010,
|
||||||
CheckKind::DoNotAssertFalse => &CheckCode::B011,
|
CheckKind::DoNotAssertFalse => &CheckCode::B011,
|
||||||
CheckKind::RedundantTupleInExceptionHandler(_) => &CheckCode::B013,
|
CheckKind::RedundantTupleInExceptionHandler(_) => &CheckCode::B013,
|
||||||
CheckKind::DuplicateHandlerException(_) => &CheckCode::B014,
|
CheckKind::DuplicateHandlerException(_) => &CheckCode::B014,
|
||||||
|
@ -1287,6 +1292,10 @@ impl CheckKind {
|
||||||
value, it is not any safer than normal property \
|
value, it is not any safer than normal property \
|
||||||
access."
|
access."
|
||||||
.to_string(),
|
.to_string(),
|
||||||
|
CheckKind::SetAttrWithConstant => "Do not call setattr with a constant attribute \
|
||||||
|
value, it is not any safer than normal property \
|
||||||
|
access."
|
||||||
|
.to_string(),
|
||||||
CheckKind::DoNotAssertFalse => "Do not `assert False` (`python -O` removes these \
|
CheckKind::DoNotAssertFalse => "Do not `assert False` (`python -O` removes these \
|
||||||
calls), raise `AssertionError()`"
|
calls), raise `AssertionError()`"
|
||||||
.to_string(),
|
.to_string(),
|
||||||
|
|
|
@ -45,6 +45,7 @@ pub enum CheckCodePrefix {
|
||||||
B008,
|
B008,
|
||||||
B009,
|
B009,
|
||||||
B01,
|
B01,
|
||||||
|
B010,
|
||||||
B011,
|
B011,
|
||||||
B013,
|
B013,
|
||||||
B014,
|
B014,
|
||||||
|
@ -342,6 +343,7 @@ impl CheckCodePrefix {
|
||||||
CheckCode::B007,
|
CheckCode::B007,
|
||||||
CheckCode::B008,
|
CheckCode::B008,
|
||||||
CheckCode::B009,
|
CheckCode::B009,
|
||||||
|
CheckCode::B010,
|
||||||
CheckCode::B011,
|
CheckCode::B011,
|
||||||
CheckCode::B013,
|
CheckCode::B013,
|
||||||
CheckCode::B014,
|
CheckCode::B014,
|
||||||
|
@ -361,6 +363,7 @@ impl CheckCodePrefix {
|
||||||
CheckCode::B007,
|
CheckCode::B007,
|
||||||
CheckCode::B008,
|
CheckCode::B008,
|
||||||
CheckCode::B009,
|
CheckCode::B009,
|
||||||
|
CheckCode::B010,
|
||||||
CheckCode::B011,
|
CheckCode::B011,
|
||||||
CheckCode::B013,
|
CheckCode::B013,
|
||||||
CheckCode::B014,
|
CheckCode::B014,
|
||||||
|
@ -390,6 +393,7 @@ impl CheckCodePrefix {
|
||||||
CheckCodePrefix::B008 => vec![CheckCode::B008],
|
CheckCodePrefix::B008 => vec![CheckCode::B008],
|
||||||
CheckCodePrefix::B009 => vec![CheckCode::B009],
|
CheckCodePrefix::B009 => vec![CheckCode::B009],
|
||||||
CheckCodePrefix::B01 => vec![
|
CheckCodePrefix::B01 => vec![
|
||||||
|
CheckCode::B010,
|
||||||
CheckCode::B011,
|
CheckCode::B011,
|
||||||
CheckCode::B013,
|
CheckCode::B013,
|
||||||
CheckCode::B014,
|
CheckCode::B014,
|
||||||
|
@ -398,6 +402,7 @@ impl CheckCodePrefix {
|
||||||
CheckCode::B017,
|
CheckCode::B017,
|
||||||
CheckCode::B018,
|
CheckCode::B018,
|
||||||
],
|
],
|
||||||
|
CheckCodePrefix::B010 => vec![CheckCode::B010],
|
||||||
CheckCodePrefix::B011 => vec![CheckCode::B011],
|
CheckCodePrefix::B011 => vec![CheckCode::B011],
|
||||||
CheckCodePrefix::B013 => vec![CheckCode::B013],
|
CheckCodePrefix::B013 => vec![CheckCode::B013],
|
||||||
CheckCodePrefix::B014 => vec![CheckCode::B014],
|
CheckCodePrefix::B014 => vec![CheckCode::B014],
|
||||||
|
@ -1066,6 +1071,7 @@ impl CheckCodePrefix {
|
||||||
CheckCodePrefix::B008 => PrefixSpecificity::Explicit,
|
CheckCodePrefix::B008 => PrefixSpecificity::Explicit,
|
||||||
CheckCodePrefix::B009 => PrefixSpecificity::Explicit,
|
CheckCodePrefix::B009 => PrefixSpecificity::Explicit,
|
||||||
CheckCodePrefix::B01 => PrefixSpecificity::Tens,
|
CheckCodePrefix::B01 => PrefixSpecificity::Tens,
|
||||||
|
CheckCodePrefix::B010 => PrefixSpecificity::Explicit,
|
||||||
CheckCodePrefix::B011 => PrefixSpecificity::Explicit,
|
CheckCodePrefix::B011 => PrefixSpecificity::Explicit,
|
||||||
CheckCodePrefix::B013 => PrefixSpecificity::Explicit,
|
CheckCodePrefix::B013 => PrefixSpecificity::Explicit,
|
||||||
CheckCodePrefix::B014 => PrefixSpecificity::Explicit,
|
CheckCodePrefix::B014 => PrefixSpecificity::Explicit,
|
||||||
|
|
5
src/flake8_bugbear/constants.rs
Normal file
5
src/flake8_bugbear/constants.rs
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
use regex::Regex;
|
||||||
|
|
||||||
|
pub static IDENTIFIER_REGEX: Lazy<Regex> =
|
||||||
|
Lazy::new(|| Regex::new(r"^[A-Za-z_][A-Za-z0-9_]*$").unwrap());
|
|
@ -1 +1,2 @@
|
||||||
|
mod constants;
|
||||||
pub mod plugins;
|
pub mod plugins;
|
||||||
|
|
|
@ -1,15 +1,11 @@
|
||||||
use once_cell::sync::Lazy;
|
|
||||||
use regex::Regex;
|
|
||||||
use rustpython_ast::{Constant, Expr, ExprKind};
|
use rustpython_ast::{Constant, Expr, ExprKind};
|
||||||
|
|
||||||
use crate::ast::types::Range;
|
use crate::ast::types::Range;
|
||||||
use crate::check_ast::Checker;
|
use crate::check_ast::Checker;
|
||||||
use crate::checks::{Check, CheckKind};
|
use crate::checks::{Check, CheckKind};
|
||||||
|
use crate::flake8_bugbear::constants::IDENTIFIER_REGEX;
|
||||||
use crate::python::keyword::KWLIST;
|
use crate::python::keyword::KWLIST;
|
||||||
|
|
||||||
static IDENTIFIER_REGEX: Lazy<Regex> =
|
|
||||||
Lazy::new(|| Regex::new(r"^[A-Za-z_][A-Za-z0-9_]*$").unwrap());
|
|
||||||
|
|
||||||
/// B009
|
/// B009
|
||||||
pub fn getattr_with_constant(checker: &mut Checker, expr: &Expr, func: &Expr, args: &[Expr]) {
|
pub fn getattr_with_constant(checker: &mut Checker, expr: &Expr, func: &Expr, args: &[Expr]) {
|
||||||
if let ExprKind::Name { id, .. } = &func.node {
|
if let ExprKind::Name { id, .. } = &func.node {
|
||||||
|
|
|
@ -7,6 +7,7 @@ pub use function_call_argument_default::function_call_argument_default;
|
||||||
pub use getattr_with_constant::getattr_with_constant;
|
pub use getattr_with_constant::getattr_with_constant;
|
||||||
pub use mutable_argument_default::mutable_argument_default;
|
pub use mutable_argument_default::mutable_argument_default;
|
||||||
pub use redundant_tuple_in_exception_handler::redundant_tuple_in_exception_handler;
|
pub use redundant_tuple_in_exception_handler::redundant_tuple_in_exception_handler;
|
||||||
|
pub use setattr_with_constant::setattr_with_constant;
|
||||||
pub use star_arg_unpacking_after_keyword_arg::star_arg_unpacking_after_keyword_arg;
|
pub use star_arg_unpacking_after_keyword_arg::star_arg_unpacking_after_keyword_arg;
|
||||||
pub use strip_with_multi_characters::strip_with_multi_characters;
|
pub use strip_with_multi_characters::strip_with_multi_characters;
|
||||||
pub use unary_prefix_increment::unary_prefix_increment;
|
pub use unary_prefix_increment::unary_prefix_increment;
|
||||||
|
@ -24,6 +25,7 @@ mod function_call_argument_default;
|
||||||
mod getattr_with_constant;
|
mod getattr_with_constant;
|
||||||
mod mutable_argument_default;
|
mod mutable_argument_default;
|
||||||
mod redundant_tuple_in_exception_handler;
|
mod redundant_tuple_in_exception_handler;
|
||||||
|
mod setattr_with_constant;
|
||||||
mod star_arg_unpacking_after_keyword_arg;
|
mod star_arg_unpacking_after_keyword_arg;
|
||||||
mod strip_with_multi_characters;
|
mod strip_with_multi_characters;
|
||||||
mod unary_prefix_increment;
|
mod unary_prefix_increment;
|
||||||
|
|
29
src/flake8_bugbear/plugins/setattr_with_constant.rs
Normal file
29
src/flake8_bugbear/plugins/setattr_with_constant.rs
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
use rustpython_ast::{Constant, Expr, ExprKind};
|
||||||
|
|
||||||
|
use crate::ast::types::Range;
|
||||||
|
use crate::check_ast::Checker;
|
||||||
|
use crate::checks::{Check, CheckKind};
|
||||||
|
use crate::flake8_bugbear::constants::IDENTIFIER_REGEX;
|
||||||
|
use crate::python::keyword::KWLIST;
|
||||||
|
|
||||||
|
/// B010
|
||||||
|
pub fn setattr_with_constant(checker: &mut Checker, expr: &Expr, func: &Expr, args: &[Expr]) {
|
||||||
|
if let ExprKind::Name { id, .. } = &func.node {
|
||||||
|
if id == "setattr" {
|
||||||
|
if let [_, arg, _] = args {
|
||||||
|
if let ExprKind::Constant {
|
||||||
|
value: Constant::Str(value),
|
||||||
|
..
|
||||||
|
} = &arg.node
|
||||||
|
{
|
||||||
|
if IDENTIFIER_REGEX.is_match(value) && !KWLIST.contains(&value.as_str()) {
|
||||||
|
checker.add_check(Check::new(
|
||||||
|
CheckKind::GetAttrWithConstant,
|
||||||
|
Range::from_located(expr),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -305,6 +305,7 @@ mod tests {
|
||||||
#[test_case(CheckCode::B007, Path::new("B007.py"); "B007")]
|
#[test_case(CheckCode::B007, Path::new("B007.py"); "B007")]
|
||||||
#[test_case(CheckCode::B008, Path::new("B006_B008.py"); "B008")]
|
#[test_case(CheckCode::B008, Path::new("B006_B008.py"); "B008")]
|
||||||
#[test_case(CheckCode::B009, Path::new("B009_B010.py"); "B009")]
|
#[test_case(CheckCode::B009, Path::new("B009_B010.py"); "B009")]
|
||||||
|
#[test_case(CheckCode::B010, Path::new("B009_B010.py"); "B010")]
|
||||||
#[test_case(CheckCode::B011, Path::new("B011.py"); "B011")]
|
#[test_case(CheckCode::B011, Path::new("B011.py"); "B011")]
|
||||||
#[test_case(CheckCode::B013, Path::new("B013.py"); "B013")]
|
#[test_case(CheckCode::B013, Path::new("B013.py"); "B013")]
|
||||||
#[test_case(CheckCode::B014, Path::new("B014.py"); "B014")]
|
#[test_case(CheckCode::B014, Path::new("B014.py"); "B014")]
|
||||||
|
|
29
src/snapshots/ruff__linter__tests__B010_B009_B010.py.snap
Normal file
29
src/snapshots/ruff__linter__tests__B010_B009_B010.py.snap
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
---
|
||||||
|
source: src/linter.rs
|
||||||
|
expression: checks
|
||||||
|
---
|
||||||
|
- kind: GetAttrWithConstant
|
||||||
|
location:
|
||||||
|
row: 28
|
||||||
|
column: 0
|
||||||
|
end_location:
|
||||||
|
row: 28
|
||||||
|
column: 25
|
||||||
|
fix: ~
|
||||||
|
- kind: GetAttrWithConstant
|
||||||
|
location:
|
||||||
|
row: 29
|
||||||
|
column: 0
|
||||||
|
end_location:
|
||||||
|
row: 29
|
||||||
|
column: 29
|
||||||
|
fix: ~
|
||||||
|
- kind: GetAttrWithConstant
|
||||||
|
location:
|
||||||
|
row: 30
|
||||||
|
column: 0
|
||||||
|
end_location:
|
||||||
|
row: 30
|
||||||
|
column: 28
|
||||||
|
fix: ~
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue