mirror of https://github.com/astral-sh/ruff
Implement pylint too-many-lines (C0302)
This commit is contained in:
parent
e548ce1ca9
commit
ada46d883d
|
|
@ -259,6 +259,7 @@ linter.pylint.max_statements = 50
|
||||||
linter.pylint.max_public_methods = 20
|
linter.pylint.max_public_methods = 20
|
||||||
linter.pylint.max_locals = 15
|
linter.pylint.max_locals = 15
|
||||||
linter.pylint.max_nested_blocks = 5
|
linter.pylint.max_nested_blocks = 5
|
||||||
|
linter.pylint.max_module_lines = 1000
|
||||||
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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -261,6 +261,7 @@ linter.pylint.max_statements = 50
|
||||||
linter.pylint.max_public_methods = 20
|
linter.pylint.max_public_methods = 20
|
||||||
linter.pylint.max_locals = 15
|
linter.pylint.max_locals = 15
|
||||||
linter.pylint.max_nested_blocks = 5
|
linter.pylint.max_nested_blocks = 5
|
||||||
|
linter.pylint.max_module_lines = 1000
|
||||||
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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -263,6 +263,7 @@ linter.pylint.max_statements = 50
|
||||||
linter.pylint.max_public_methods = 20
|
linter.pylint.max_public_methods = 20
|
||||||
linter.pylint.max_locals = 15
|
linter.pylint.max_locals = 15
|
||||||
linter.pylint.max_nested_blocks = 5
|
linter.pylint.max_nested_blocks = 5
|
||||||
|
linter.pylint.max_module_lines = 1000
|
||||||
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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -263,6 +263,7 @@ linter.pylint.max_statements = 50
|
||||||
linter.pylint.max_public_methods = 20
|
linter.pylint.max_public_methods = 20
|
||||||
linter.pylint.max_locals = 15
|
linter.pylint.max_locals = 15
|
||||||
linter.pylint.max_nested_blocks = 5
|
linter.pylint.max_nested_blocks = 5
|
||||||
|
linter.pylint.max_module_lines = 1000
|
||||||
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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -260,6 +260,7 @@ linter.pylint.max_statements = 50
|
||||||
linter.pylint.max_public_methods = 20
|
linter.pylint.max_public_methods = 20
|
||||||
linter.pylint.max_locals = 15
|
linter.pylint.max_locals = 15
|
||||||
linter.pylint.max_nested_blocks = 5
|
linter.pylint.max_nested_blocks = 5
|
||||||
|
linter.pylint.max_module_lines = 1000
|
||||||
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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -260,6 +260,7 @@ linter.pylint.max_statements = 50
|
||||||
linter.pylint.max_public_methods = 20
|
linter.pylint.max_public_methods = 20
|
||||||
linter.pylint.max_locals = 15
|
linter.pylint.max_locals = 15
|
||||||
linter.pylint.max_nested_blocks = 5
|
linter.pylint.max_nested_blocks = 5
|
||||||
|
linter.pylint.max_module_lines = 1000
|
||||||
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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -259,6 +259,7 @@ linter.pylint.max_statements = 50
|
||||||
linter.pylint.max_public_methods = 20
|
linter.pylint.max_public_methods = 20
|
||||||
linter.pylint.max_locals = 15
|
linter.pylint.max_locals = 15
|
||||||
linter.pylint.max_nested_blocks = 5
|
linter.pylint.max_nested_blocks = 5
|
||||||
|
linter.pylint.max_module_lines = 1000
|
||||||
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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -259,6 +259,7 @@ linter.pylint.max_statements = 50
|
||||||
linter.pylint.max_public_methods = 20
|
linter.pylint.max_public_methods = 20
|
||||||
linter.pylint.max_locals = 15
|
linter.pylint.max_locals = 15
|
||||||
linter.pylint.max_nested_blocks = 5
|
linter.pylint.max_nested_blocks = 5
|
||||||
|
linter.pylint.max_module_lines = 1000
|
||||||
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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -259,6 +259,7 @@ linter.pylint.max_statements = 50
|
||||||
linter.pylint.max_public_methods = 20
|
linter.pylint.max_public_methods = 20
|
||||||
linter.pylint.max_locals = 15
|
linter.pylint.max_locals = 15
|
||||||
linter.pylint.max_nested_blocks = 5
|
linter.pylint.max_nested_blocks = 5
|
||||||
|
linter.pylint.max_module_lines = 1000
|
||||||
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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -372,6 +372,7 @@ linter.pylint.max_statements = 50
|
||||||
linter.pylint.max_public_methods = 20
|
linter.pylint.max_public_methods = 20
|
||||||
linter.pylint.max_locals = 15
|
linter.pylint.max_locals = 15
|
||||||
linter.pylint.max_nested_blocks = 5
|
linter.pylint.max_nested_blocks = 5
|
||||||
|
linter.pylint.max_module_lines = 1000
|
||||||
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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,10 @@ pub(crate) fn check_physical_lines(
|
||||||
if enforce_copyright_notice {
|
if enforce_copyright_notice {
|
||||||
missing_copyright_notice(locator, settings, context);
|
missing_copyright_notice(locator, settings, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if context.is_rule_enabled(Rule::TooManyLines) {
|
||||||
|
pylint::rules::too_many_lines(locator, settings, context);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
||||||
|
|
@ -221,6 +221,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||||
(Pylint, "C0206") => rules::pylint::rules::DictIndexMissingItems,
|
(Pylint, "C0206") => rules::pylint::rules::DictIndexMissingItems,
|
||||||
(Pylint, "C0207") => rules::pylint::rules::MissingMaxsplitArg,
|
(Pylint, "C0207") => rules::pylint::rules::MissingMaxsplitArg,
|
||||||
(Pylint, "C0208") => rules::pylint::rules::IterationOverSet,
|
(Pylint, "C0208") => rules::pylint::rules::IterationOverSet,
|
||||||
|
(Pylint, "C0302") => rules::pylint::rules::TooManyLines,
|
||||||
(Pylint, "C0414") => rules::pylint::rules::UselessImportAlias,
|
(Pylint, "C0414") => rules::pylint::rules::UselessImportAlias,
|
||||||
(Pylint, "C0415") => rules::pylint::rules::ImportOutsideTopLevel,
|
(Pylint, "C0415") => rules::pylint::rules::ImportOutsideTopLevel,
|
||||||
(Pylint, "C1802") => rules::pylint::rules::LenTest,
|
(Pylint, "C1802") => rules::pylint::rules::LenTest,
|
||||||
|
|
|
||||||
|
|
@ -259,6 +259,7 @@ impl Rule {
|
||||||
| Rule::MissingCopyrightNotice
|
| Rule::MissingCopyrightNotice
|
||||||
| Rule::MissingNewlineAtEndOfFile
|
| Rule::MissingNewlineAtEndOfFile
|
||||||
| Rule::MixedSpacesAndTabs
|
| Rule::MixedSpacesAndTabs
|
||||||
|
| Rule::TooManyLines
|
||||||
| Rule::TrailingWhitespace => LintSource::PhysicalLines,
|
| Rule::TrailingWhitespace => LintSource::PhysicalLines,
|
||||||
Rule::AmbiguousUnicodeCharacterComment
|
Rule::AmbiguousUnicodeCharacterComment
|
||||||
| Rule::BlanketTypeIgnore
|
| Rule::BlanketTypeIgnore
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
//! Settings for the `flake8-lineleak` plugin.
|
||||||
|
|
||||||
|
use std::fmt::{Display, Formatter};
|
||||||
|
|
||||||
|
use ruff_macros::CacheKey;
|
||||||
|
|
||||||
|
use crate::display_settings;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, CacheKey)]
|
||||||
|
pub struct Settings {
|
||||||
|
pub max_line_count: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Settings {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
max_line_count: 100,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Settings {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
display_settings! {
|
||||||
|
formatter = f,
|
||||||
|
namespace = "linter.flake8_lineleak",
|
||||||
|
fields = [
|
||||||
|
self.max_line_count,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -444,4 +444,90 @@ mod tests {
|
||||||
assert_diagnostics!(diagnostics);
|
assert_diagnostics!(diagnostics);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn too_many_lines_below_limit() {
|
||||||
|
use crate::test::test_snippet;
|
||||||
|
|
||||||
|
let diagnostics = test_snippet(
|
||||||
|
r"
|
||||||
|
# Line 1
|
||||||
|
# Line 2
|
||||||
|
# Line 3
|
||||||
|
import os
|
||||||
|
"
|
||||||
|
.trim(),
|
||||||
|
&LinterSettings {
|
||||||
|
preview: PreviewMode::Enabled,
|
||||||
|
pylint: pylint::settings::Settings {
|
||||||
|
max_module_lines: 1000,
|
||||||
|
..pylint::settings::Settings::default()
|
||||||
|
},
|
||||||
|
..LinterSettings::for_rules(vec![Rule::TooManyLines])
|
||||||
|
},
|
||||||
|
);
|
||||||
|
assert_diagnostics!(diagnostics);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn too_many_lines_exceeds_limit() {
|
||||||
|
use crate::test::test_snippet;
|
||||||
|
|
||||||
|
let diagnostics = test_snippet(
|
||||||
|
r"
|
||||||
|
# Line 1
|
||||||
|
# Line 2
|
||||||
|
# Line 3
|
||||||
|
# Line 4
|
||||||
|
# Line 5
|
||||||
|
# Line 6
|
||||||
|
# Line 7
|
||||||
|
# Line 8
|
||||||
|
# Line 9
|
||||||
|
# Line 10
|
||||||
|
# Line 11
|
||||||
|
import os
|
||||||
|
"
|
||||||
|
.trim(),
|
||||||
|
&LinterSettings {
|
||||||
|
preview: PreviewMode::Enabled,
|
||||||
|
pylint: pylint::settings::Settings {
|
||||||
|
max_module_lines: 10,
|
||||||
|
..pylint::settings::Settings::default()
|
||||||
|
},
|
||||||
|
..LinterSettings::for_rules(vec![Rule::TooManyLines])
|
||||||
|
},
|
||||||
|
);
|
||||||
|
assert!(!diagnostics.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn too_many_lines_at_limit() {
|
||||||
|
use crate::test::test_snippet;
|
||||||
|
|
||||||
|
let diagnostics = test_snippet(
|
||||||
|
r"
|
||||||
|
# Line 1
|
||||||
|
# Line 2
|
||||||
|
# Line 3
|
||||||
|
# Line 4
|
||||||
|
# Line 5
|
||||||
|
# Line 6
|
||||||
|
# Line 7
|
||||||
|
# Line 8
|
||||||
|
# Line 9
|
||||||
|
# Line 10
|
||||||
|
"
|
||||||
|
.trim(),
|
||||||
|
&LinterSettings {
|
||||||
|
preview: PreviewMode::Enabled,
|
||||||
|
pylint: pylint::settings::Settings {
|
||||||
|
max_module_lines: 10,
|
||||||
|
..pylint::settings::Settings::default()
|
||||||
|
},
|
||||||
|
..LinterSettings::for_rules(vec![Rule::TooManyLines])
|
||||||
|
},
|
||||||
|
);
|
||||||
|
assert_diagnostics!(diagnostics);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -83,6 +83,7 @@ pub(crate) use sys_exit_alias::*;
|
||||||
pub(crate) use too_many_arguments::*;
|
pub(crate) use too_many_arguments::*;
|
||||||
pub(crate) use too_many_boolean_expressions::*;
|
pub(crate) use too_many_boolean_expressions::*;
|
||||||
pub(crate) use too_many_branches::*;
|
pub(crate) use too_many_branches::*;
|
||||||
|
pub(crate) use too_many_lines::*;
|
||||||
pub(crate) use too_many_locals::*;
|
pub(crate) use too_many_locals::*;
|
||||||
pub(crate) use too_many_nested_blocks::*;
|
pub(crate) use too_many_nested_blocks::*;
|
||||||
pub(crate) use too_many_positional_arguments::*;
|
pub(crate) use too_many_positional_arguments::*;
|
||||||
|
|
@ -194,6 +195,7 @@ mod sys_exit_alias;
|
||||||
mod too_many_arguments;
|
mod too_many_arguments;
|
||||||
mod too_many_boolean_expressions;
|
mod too_many_boolean_expressions;
|
||||||
mod too_many_branches;
|
mod too_many_branches;
|
||||||
|
mod too_many_lines;
|
||||||
mod too_many_locals;
|
mod too_many_locals;
|
||||||
mod too_many_nested_blocks;
|
mod too_many_nested_blocks;
|
||||||
mod too_many_positional_arguments;
|
mod too_many_positional_arguments;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||||
|
use ruff_text_size::TextRange;
|
||||||
|
|
||||||
|
use crate::Locator;
|
||||||
|
use crate::Violation;
|
||||||
|
use crate::checkers::ast::LintContext;
|
||||||
|
use crate::settings::LinterSettings;
|
||||||
|
|
||||||
|
/// ## What it does
|
||||||
|
/// Checks for modules with too many lines.
|
||||||
|
///
|
||||||
|
/// By default, this rule allows up to 1000 lines, as configured by the
|
||||||
|
/// [`lint.pylint.max-module-lines`] option.
|
||||||
|
///
|
||||||
|
/// ## Why is this bad?
|
||||||
|
/// Modules with many lines are generally harder to read and understand.
|
||||||
|
/// Extracting functionality into separate modules can improve code organization
|
||||||
|
/// and maintainability.
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
/// A module with 1500 lines when `max-module-lines` is set to 1000 will trigger
|
||||||
|
/// this rule.
|
||||||
|
///
|
||||||
|
/// ## Options
|
||||||
|
/// - `lint.pylint.max-module-lines`
|
||||||
|
#[derive(ViolationMetadata)]
|
||||||
|
#[violation_metadata(preview_since = "v0.14.9")]
|
||||||
|
pub(crate) struct TooManyLines {
|
||||||
|
actual_lines: usize,
|
||||||
|
max_lines: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Violation for TooManyLines {
|
||||||
|
#[derive_message_formats]
|
||||||
|
fn message(&self) -> String {
|
||||||
|
let TooManyLines {
|
||||||
|
actual_lines,
|
||||||
|
max_lines,
|
||||||
|
} = self;
|
||||||
|
format!("Too many lines in module ({actual_lines}/{max_lines})")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// C0302
|
||||||
|
pub(crate) fn too_many_lines(locator: &Locator, settings: &LinterSettings, context: &LintContext) {
|
||||||
|
let actual_lines = locator.contents().lines().count();
|
||||||
|
let max_lines = settings.pylint.max_module_lines;
|
||||||
|
|
||||||
|
if actual_lines > max_lines {
|
||||||
|
context.report_diagnostic(
|
||||||
|
TooManyLines {
|
||||||
|
actual_lines,
|
||||||
|
max_lines,
|
||||||
|
},
|
||||||
|
TextRange::default(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -61,6 +61,7 @@ pub struct Settings {
|
||||||
pub max_public_methods: usize,
|
pub max_public_methods: usize,
|
||||||
pub max_locals: usize,
|
pub max_locals: usize,
|
||||||
pub max_nested_blocks: usize,
|
pub max_nested_blocks: usize,
|
||||||
|
pub max_module_lines: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Settings {
|
impl Default for Settings {
|
||||||
|
|
@ -77,6 +78,7 @@ impl Default for Settings {
|
||||||
max_public_methods: 20,
|
max_public_methods: 20,
|
||||||
max_locals: 15,
|
max_locals: 15,
|
||||||
max_nested_blocks: 5,
|
max_nested_blocks: 5,
|
||||||
|
max_module_lines: 1000,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -97,7 +99,8 @@ impl fmt::Display for Settings {
|
||||||
self.max_statements,
|
self.max_statements,
|
||||||
self.max_public_methods,
|
self.max_public_methods,
|
||||||
self.max_locals,
|
self.max_locals,
|
||||||
self.max_nested_blocks
|
self.max_nested_blocks,
|
||||||
|
self.max_module_lines
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
---
|
||||||
|
source: crates/ruff_linter/src/rules/pylint/mod.rs
|
||||||
|
---
|
||||||
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
---
|
||||||
|
source: crates/ruff_linter/src/rules/pylint/mod.rs
|
||||||
|
---
|
||||||
|
|
||||||
|
|
@ -3343,6 +3343,14 @@ pub struct PylintOptions {
|
||||||
example = r"max-nested-blocks = 10"
|
example = r"max-nested-blocks = 10"
|
||||||
)]
|
)]
|
||||||
pub max_nested_blocks: Option<usize>,
|
pub max_nested_blocks: Option<usize>,
|
||||||
|
|
||||||
|
/// Maximum number of lines allowed in a module (see `PLC0302`).
|
||||||
|
#[option(
|
||||||
|
default = r"1000",
|
||||||
|
value_type = "int",
|
||||||
|
example = r"max-module-lines = 1500"
|
||||||
|
)]
|
||||||
|
pub max_module_lines: Option<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PylintOptions {
|
impl PylintOptions {
|
||||||
|
|
@ -3367,6 +3375,7 @@ impl PylintOptions {
|
||||||
.unwrap_or(defaults.max_public_methods),
|
.unwrap_or(defaults.max_public_methods),
|
||||||
max_locals: self.max_locals.unwrap_or(defaults.max_locals),
|
max_locals: self.max_locals.unwrap_or(defaults.max_locals),
|
||||||
max_nested_blocks: self.max_nested_blocks.unwrap_or(defaults.max_nested_blocks),
|
max_nested_blocks: self.max_nested_blocks.unwrap_or(defaults.max_nested_blocks),
|
||||||
|
max_module_lines: self.max_module_lines.unwrap_or(defaults.max_module_lines),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2794,6 +2794,15 @@
|
||||||
"format": "uint",
|
"format": "uint",
|
||||||
"minimum": 0
|
"minimum": 0
|
||||||
},
|
},
|
||||||
|
"max-module-lines": {
|
||||||
|
"description": "Maximum number of lines allowed in a module (see `PLC0302`).",
|
||||||
|
"type": [
|
||||||
|
"integer",
|
||||||
|
"null"
|
||||||
|
],
|
||||||
|
"format": "uint",
|
||||||
|
"minimum": 0
|
||||||
|
},
|
||||||
"max-nested-blocks": {
|
"max-nested-blocks": {
|
||||||
"description": "Maximum number of nested blocks allowed within a function or method body\n(see `PLR1702`).",
|
"description": "Maximum number of nested blocks allowed within a function or method body\n(see `PLR1702`).",
|
||||||
"type": [
|
"type": [
|
||||||
|
|
@ -3590,6 +3599,9 @@
|
||||||
"PLC0206",
|
"PLC0206",
|
||||||
"PLC0207",
|
"PLC0207",
|
||||||
"PLC0208",
|
"PLC0208",
|
||||||
|
"PLC03",
|
||||||
|
"PLC030",
|
||||||
|
"PLC0302",
|
||||||
"PLC04",
|
"PLC04",
|
||||||
"PLC041",
|
"PLC041",
|
||||||
"PLC0414",
|
"PLC0414",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue