Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5f77b420cd | ||
|
|
90f9e60517 | ||
|
|
320737f6e4 | ||
|
|
dfba1416b2 | ||
|
|
2ca3f35bd1 | ||
|
|
b1c40d5fa7 | ||
|
|
a129e27b3e | ||
|
|
ad7daa008e |
24
.github/workflows/release.yaml
vendored
24
.github/workflows/release.yaml
vendored
@@ -16,7 +16,7 @@ env:
|
||||
PYTHON_VERSION: "3.7" # to build abi3 wheels
|
||||
|
||||
jobs:
|
||||
macos:
|
||||
macos-x86_64:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@@ -39,6 +39,25 @@ jobs:
|
||||
- name: Install built wheel - x86_64
|
||||
run: |
|
||||
pip install dist/${{ env.PACKAGE_NAME }}-*.whl --force-reinstall
|
||||
- name: Upload wheels
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: wheels
|
||||
path: dist
|
||||
macos-universal:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
architecture: x64
|
||||
- name: Install Rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
profile: minimal
|
||||
default: true
|
||||
- name: Build wheels - universal2
|
||||
uses: messense/maturin-action@v1
|
||||
with:
|
||||
@@ -261,7 +280,8 @@ jobs:
|
||||
name: Release
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- macos
|
||||
- macos-universal
|
||||
- macos-x86_64
|
||||
- windows
|
||||
- linux
|
||||
- linux-cross
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
repos:
|
||||
- repo: https://github.com/charliermarsh/ruff
|
||||
rev: v0.0.36
|
||||
rev: v0.0.37
|
||||
hooks:
|
||||
- id: lint
|
||||
|
||||
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -1744,7 +1744,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.36"
|
||||
version = "0.0.37"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.0.36"
|
||||
version = "0.0.37"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
|
||||
22
README.md
22
README.md
@@ -57,7 +57,7 @@ ruff also works with [Pre-Commit](https://pre-commit.com) (requires Cargo on sys
|
||||
```yaml
|
||||
repos:
|
||||
- repo: https://github.com/charliermarsh/ruff
|
||||
rev: v0.0.36
|
||||
rev: v0.0.37
|
||||
hooks:
|
||||
- id: lint
|
||||
```
|
||||
@@ -86,7 +86,7 @@ ruff path/to/code/ --select F401 F403
|
||||
See `ruff --help` for more:
|
||||
|
||||
```shell
|
||||
ruff (v0.0.36)
|
||||
ruff (v0.0.37)
|
||||
An extremely fast Python linter.
|
||||
|
||||
USAGE:
|
||||
@@ -123,17 +123,15 @@ ruff's goal is to achieve feature-parity with Flake8 when used (1) without any p
|
||||
(2) alongside Black, and (3) on Python 3 code. (Using Black obviates the need for many of Flake8's
|
||||
stylistic checks; limiting to Python 3 obviates the need for certain compatibility checks.)
|
||||
|
||||
Under those conditions, Flake8 implements about 58 rules, give or take. At time of writing, ruff
|
||||
implements 36 rules. (Note that these 36 rules likely cover a disproportionate share of errors:
|
||||
Under those conditions, Flake8 implements about 60 rules, give or take. At time of writing, ruff
|
||||
implements 38 rules. (Note that these 38 rules likely cover a disproportionate share of errors:
|
||||
unused imports, undefined variables, etc.)
|
||||
|
||||
Of the unimplemented rules, ruff is missing:
|
||||
The unimplemented rules are tracked in #170, and include:
|
||||
|
||||
- 15 rules related to string `.format` calls.
|
||||
- 3 rules related to syntax errors in doctests and annotations.
|
||||
- 2 rules related to redefining unused names.
|
||||
|
||||
...along with a variety of others that don't fit neatly into any specific category.
|
||||
- 14 rules related to string `.format` calls.
|
||||
- 1 rule related to parsing and syntax.
|
||||
- 6 logical rules.
|
||||
|
||||
Beyond rule-set parity, ruff suffers from the following limitations vis-à-vis Flake8:
|
||||
|
||||
@@ -158,8 +156,9 @@ Beyond rule-set parity, ruff suffers from the following limitations vis-à-vis F
|
||||
| E743 | AmbiguousFunctionName | ambiguous function name '...' |
|
||||
| E902 | IOError | No such file or directory: `...` |
|
||||
| F401 | UnusedImport | `...` imported but unused |
|
||||
| F403 | ImportStarUsage | Unable to detect undefined names |
|
||||
| F403 | ImportStarUsage | `from ... import *` used; unable to detect undefined names |
|
||||
| F404 | LateFutureImport | from __future__ imports must occur at the beginning of the file |
|
||||
| F406 | ImportStarNotPermitted | `from ... import *` only allowed at module level |
|
||||
| F407 | FutureFeatureNotDefined | future feature '...' is not defined |
|
||||
| F541 | FStringMissingPlaceholders | f-string without any placeholders |
|
||||
| F601 | MultiValueRepeatedKeyLiteral | Dictionary key literal repeated |
|
||||
@@ -173,6 +172,7 @@ Beyond rule-set parity, ruff suffers from the following limitations vis-à-vis F
|
||||
| F704 | YieldOutsideFunction | a `yield` or `yield from` statement outside of a function/method |
|
||||
| F706 | ReturnOutsideFunction | a `return` statement outside of a function/method |
|
||||
| F707 | DefaultExceptNotLast | an `except:` block as not the last exception handler |
|
||||
| F722 | ForwardAnnotationSyntaxError | syntax error in forward annotation '...' |
|
||||
| F821 | UndefinedName | Undefined name `...` |
|
||||
| F822 | UndefinedExport | Undefined name `...` in `__all__` |
|
||||
| F823 | UndefinedLocal | Local variable `...` referenced before assignment |
|
||||
|
||||
@@ -13,11 +13,13 @@ fn main() {
|
||||
CheckKind::DoNotAssignLambda,
|
||||
CheckKind::DoNotUseBareExcept,
|
||||
CheckKind::DuplicateArgumentName,
|
||||
CheckKind::ForwardAnnotationSyntaxError("...".to_string()),
|
||||
CheckKind::FStringMissingPlaceholders,
|
||||
CheckKind::FutureFeatureNotDefined("...".to_string()),
|
||||
CheckKind::IOError("...".to_string()),
|
||||
CheckKind::IfTuple,
|
||||
CheckKind::ImportStarUsage,
|
||||
CheckKind::ImportStarNotPermitted("...".to_string()),
|
||||
CheckKind::ImportStarUsage("...".to_string()),
|
||||
CheckKind::LateFutureImport,
|
||||
CheckKind::LineTooLong(89, 88),
|
||||
CheckKind::ModuleImportNotAtTopOfFile,
|
||||
|
||||
@@ -19,10 +19,12 @@ classifiers = [
|
||||
]
|
||||
author = "Charlie Marsh"
|
||||
author_email = "charlie.r.marsh@gmail.com"
|
||||
url = "https://github.com/charliermarsh/ruff"
|
||||
description = "An extremely fast Python linter, written in Rust."
|
||||
requires-python = ">=3.7"
|
||||
|
||||
[project.urls]
|
||||
repository = "https://github.com/charliermarsh/ruff"
|
||||
|
||||
[build-system]
|
||||
requires = ["maturin>=0.13,<0.14"]
|
||||
build-backend = "maturin"
|
||||
|
||||
9
resources/test/fixtures/F406.py
vendored
Normal file
9
resources/test/fixtures/F406.py
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
from F634 import *
|
||||
|
||||
|
||||
def f():
|
||||
from F634 import *
|
||||
|
||||
|
||||
class F:
|
||||
from F634 import *
|
||||
10
resources/test/fixtures/F722.py
vendored
Normal file
10
resources/test/fixtures/F722.py
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
class A:
|
||||
pass
|
||||
|
||||
|
||||
def f() -> "A":
|
||||
pass
|
||||
|
||||
|
||||
def g() -> "///":
|
||||
pass
|
||||
2
resources/test/fixtures/pyproject.toml
vendored
2
resources/test/fixtures/pyproject.toml
vendored
@@ -17,6 +17,7 @@ select = [
|
||||
"F401",
|
||||
"F403",
|
||||
"F404",
|
||||
"F406",
|
||||
"F407",
|
||||
"F541",
|
||||
"F601",
|
||||
@@ -30,6 +31,7 @@ select = [
|
||||
"F704",
|
||||
"F706",
|
||||
"F707",
|
||||
"F722",
|
||||
"F821",
|
||||
"F822",
|
||||
"F823",
|
||||
|
||||
@@ -448,8 +448,25 @@ where
|
||||
);
|
||||
|
||||
if self.settings.select.contains(&CheckCode::F403) {
|
||||
self.checks
|
||||
.push(Check::new(CheckKind::ImportStarUsage, stmt.location));
|
||||
self.checks.push(Check::new(
|
||||
CheckKind::ImportStarUsage(
|
||||
module.clone().unwrap_or_else(|| "module".to_string()),
|
||||
),
|
||||
stmt.location,
|
||||
));
|
||||
}
|
||||
|
||||
if self.settings.select.contains(&CheckCode::F406) {
|
||||
let scope = &self.scopes
|
||||
[*(self.scope_stack.last().expect("No current scope found."))];
|
||||
if !matches!(scope.kind, ScopeKind::Module) {
|
||||
self.checks.push(Check::new(
|
||||
CheckKind::ImportStarNotPermitted(
|
||||
module.clone().unwrap_or_else(|| "module".to_string()),
|
||||
),
|
||||
stmt.location,
|
||||
));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let binding = Binding {
|
||||
@@ -1164,6 +1181,11 @@ impl<'a> Checker<'a> {
|
||||
if let Ok(mut expr) = parser::parse_expression(expression, path) {
|
||||
relocate_expr(&mut expr, location);
|
||||
allocator.push(expr);
|
||||
} else if self.settings.select.contains(&CheckCode::F722) {
|
||||
self.checks.push(Check::new(
|
||||
CheckKind::ForwardAnnotationSyntaxError(expression.to_string()),
|
||||
location,
|
||||
));
|
||||
}
|
||||
}
|
||||
for expr in allocator {
|
||||
|
||||
@@ -23,6 +23,7 @@ pub enum CheckCode {
|
||||
F401,
|
||||
F403,
|
||||
F404,
|
||||
F406,
|
||||
F407,
|
||||
F541,
|
||||
F601,
|
||||
@@ -36,6 +37,7 @@ pub enum CheckCode {
|
||||
F704,
|
||||
F706,
|
||||
F707,
|
||||
F722,
|
||||
F821,
|
||||
F822,
|
||||
F823,
|
||||
@@ -66,6 +68,7 @@ impl FromStr for CheckCode {
|
||||
"F401" => Ok(CheckCode::F401),
|
||||
"F403" => Ok(CheckCode::F403),
|
||||
"F404" => Ok(CheckCode::F404),
|
||||
"F406" => Ok(CheckCode::F406),
|
||||
"F407" => Ok(CheckCode::F407),
|
||||
"F541" => Ok(CheckCode::F541),
|
||||
"F601" => Ok(CheckCode::F601),
|
||||
@@ -110,6 +113,7 @@ impl CheckCode {
|
||||
CheckCode::F401 => "F401",
|
||||
CheckCode::F403 => "F403",
|
||||
CheckCode::F404 => "F404",
|
||||
CheckCode::F406 => "F406",
|
||||
CheckCode::F407 => "F407",
|
||||
CheckCode::F541 => "F541",
|
||||
CheckCode::F601 => "F601",
|
||||
@@ -123,6 +127,7 @@ impl CheckCode {
|
||||
CheckCode::F704 => "F704",
|
||||
CheckCode::F706 => "F706",
|
||||
CheckCode::F707 => "F707",
|
||||
CheckCode::F722 => "F722",
|
||||
CheckCode::F821 => "F821",
|
||||
CheckCode::F822 => "F822",
|
||||
CheckCode::F823 => "F823",
|
||||
@@ -134,45 +139,12 @@ impl CheckCode {
|
||||
}
|
||||
}
|
||||
|
||||
/// The source for the check (either the AST, or the physical lines).
|
||||
/// The source for the check (either the AST, the filesystem, or the physical lines).
|
||||
pub fn lint_source(&self) -> &'static LintSource {
|
||||
match self {
|
||||
CheckCode::E402 => &LintSource::AST,
|
||||
CheckCode::E501 => &LintSource::Lines,
|
||||
CheckCode::E711 => &LintSource::AST,
|
||||
CheckCode::E712 => &LintSource::AST,
|
||||
CheckCode::E713 => &LintSource::AST,
|
||||
CheckCode::E714 => &LintSource::AST,
|
||||
CheckCode::E722 => &LintSource::AST,
|
||||
CheckCode::E731 => &LintSource::AST,
|
||||
CheckCode::E741 => &LintSource::AST,
|
||||
CheckCode::E742 => &LintSource::AST,
|
||||
CheckCode::E743 => &LintSource::AST,
|
||||
CheckCode::E902 => &LintSource::FileSystem,
|
||||
CheckCode::F401 => &LintSource::AST,
|
||||
CheckCode::F403 => &LintSource::AST,
|
||||
CheckCode::F404 => &LintSource::AST,
|
||||
CheckCode::F407 => &LintSource::AST,
|
||||
CheckCode::F541 => &LintSource::AST,
|
||||
CheckCode::F601 => &LintSource::AST,
|
||||
CheckCode::F602 => &LintSource::AST,
|
||||
CheckCode::F621 => &LintSource::AST,
|
||||
CheckCode::F622 => &LintSource::AST,
|
||||
CheckCode::F631 => &LintSource::AST,
|
||||
CheckCode::F634 => &LintSource::AST,
|
||||
CheckCode::F701 => &LintSource::AST,
|
||||
CheckCode::F702 => &LintSource::AST,
|
||||
CheckCode::F704 => &LintSource::AST,
|
||||
CheckCode::F706 => &LintSource::AST,
|
||||
CheckCode::F707 => &LintSource::AST,
|
||||
CheckCode::F821 => &LintSource::AST,
|
||||
CheckCode::F822 => &LintSource::AST,
|
||||
CheckCode::F823 => &LintSource::AST,
|
||||
CheckCode::F831 => &LintSource::AST,
|
||||
CheckCode::F841 => &LintSource::AST,
|
||||
CheckCode::F901 => &LintSource::AST,
|
||||
CheckCode::R001 => &LintSource::AST,
|
||||
CheckCode::R002 => &LintSource::AST,
|
||||
_ => &LintSource::AST,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -202,11 +174,13 @@ pub enum CheckKind {
|
||||
DoNotAssignLambda,
|
||||
DoNotUseBareExcept,
|
||||
DuplicateArgumentName,
|
||||
ForwardAnnotationSyntaxError(String),
|
||||
FStringMissingPlaceholders,
|
||||
FutureFeatureNotDefined(String),
|
||||
IOError(String),
|
||||
IfTuple,
|
||||
ImportStarUsage,
|
||||
ImportStarNotPermitted(String),
|
||||
ImportStarUsage(String),
|
||||
LateFutureImport,
|
||||
LineTooLong(usize, usize),
|
||||
ModuleImportNotAtTopOfFile,
|
||||
@@ -244,11 +218,13 @@ impl CheckKind {
|
||||
CheckKind::DoNotAssignLambda => "DoNotAssignLambda",
|
||||
CheckKind::DoNotUseBareExcept => "DoNotUseBareExcept",
|
||||
CheckKind::DuplicateArgumentName => "DuplicateArgumentName",
|
||||
CheckKind::ForwardAnnotationSyntaxError(_) => "ForwardAnnotationSyntaxError",
|
||||
CheckKind::FStringMissingPlaceholders => "FStringMissingPlaceholders",
|
||||
CheckKind::FutureFeatureNotDefined(_) => "FutureFeatureNotDefined",
|
||||
CheckKind::IOError(_) => "IOError",
|
||||
CheckKind::IfTuple => "IfTuple",
|
||||
CheckKind::ImportStarUsage => "ImportStarUsage",
|
||||
CheckKind::ImportStarNotPermitted(_) => "ImportStarNotPermitted",
|
||||
CheckKind::ImportStarUsage(_) => "ImportStarUsage",
|
||||
CheckKind::LateFutureImport => "LateFutureImport",
|
||||
CheckKind::LineTooLong(_, _) => "LineTooLong",
|
||||
CheckKind::ModuleImportNotAtTopOfFile => "ModuleImportNotAtTopOfFile",
|
||||
@@ -288,11 +264,13 @@ impl CheckKind {
|
||||
CheckKind::DoNotAssignLambda => &CheckCode::E731,
|
||||
CheckKind::DoNotUseBareExcept => &CheckCode::E722,
|
||||
CheckKind::DuplicateArgumentName => &CheckCode::F831,
|
||||
CheckKind::ForwardAnnotationSyntaxError(_) => &CheckCode::F722,
|
||||
CheckKind::FStringMissingPlaceholders => &CheckCode::F541,
|
||||
CheckKind::FutureFeatureNotDefined(_) => &CheckCode::F407,
|
||||
CheckKind::IOError(_) => &CheckCode::E902,
|
||||
CheckKind::IfTuple => &CheckCode::F634,
|
||||
CheckKind::ImportStarUsage => &CheckCode::F403,
|
||||
CheckKind::ImportStarNotPermitted(_) => &CheckCode::F406,
|
||||
CheckKind::ImportStarUsage(_) => &CheckCode::F403,
|
||||
CheckKind::LateFutureImport => &CheckCode::F404,
|
||||
CheckKind::LineTooLong(_, _) => &CheckCode::E501,
|
||||
CheckKind::ModuleImportNotAtTopOfFile => &CheckCode::E402,
|
||||
@@ -344,6 +322,9 @@ impl CheckKind {
|
||||
CheckKind::DuplicateArgumentName => {
|
||||
"Duplicate argument name in function definition".to_string()
|
||||
}
|
||||
CheckKind::ForwardAnnotationSyntaxError(body) => {
|
||||
format!("syntax error in forward annotation '{body}'")
|
||||
}
|
||||
CheckKind::FStringMissingPlaceholders => {
|
||||
"f-string without any placeholders".to_string()
|
||||
}
|
||||
@@ -354,7 +335,12 @@ impl CheckKind {
|
||||
format!("No such file or directory: `{name}`")
|
||||
}
|
||||
CheckKind::IfTuple => "If test is a tuple, which is always `True`".to_string(),
|
||||
CheckKind::ImportStarUsage => "Unable to detect undefined names".to_string(),
|
||||
CheckKind::ImportStarNotPermitted(name) => {
|
||||
format!("`from {name} import *` only allowed at module level")
|
||||
}
|
||||
CheckKind::ImportStarUsage(name) => {
|
||||
format!("`from {name} import *` used; unable to detect undefined names")
|
||||
}
|
||||
CheckKind::LateFutureImport => {
|
||||
"from __future__ imports must occur at the beginning of the file".to_string()
|
||||
}
|
||||
|
||||
@@ -598,12 +598,12 @@ mod tests {
|
||||
actual.sort_by_key(|check| check.location);
|
||||
let expected = vec![
|
||||
Check {
|
||||
kind: CheckKind::ImportStarUsage,
|
||||
kind: CheckKind::ImportStarUsage("F634".to_string()),
|
||||
location: Location::new(1, 1),
|
||||
fix: None,
|
||||
},
|
||||
Check {
|
||||
kind: CheckKind::ImportStarUsage,
|
||||
kind: CheckKind::ImportStarUsage("F634".to_string()),
|
||||
location: Location::new(2, 1),
|
||||
fix: None,
|
||||
},
|
||||
@@ -641,6 +641,38 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn f406() -> Result<()> {
|
||||
let mut actual = check_path(
|
||||
Path::new("./resources/test/fixtures/F406.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::F406]),
|
||||
},
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
actual.sort_by_key(|check| check.location);
|
||||
let expected = vec![
|
||||
Check {
|
||||
kind: CheckKind::ImportStarNotPermitted("F634".to_string()),
|
||||
location: Location::new(5, 5),
|
||||
fix: None,
|
||||
},
|
||||
Check {
|
||||
kind: CheckKind::ImportStarNotPermitted("F634".to_string()),
|
||||
location: Location::new(9, 5),
|
||||
fix: None,
|
||||
},
|
||||
];
|
||||
assert_eq!(actual.len(), expected.len());
|
||||
for i in 0..actual.len() {
|
||||
assert_eq!(actual[i], expected[i]);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn f407() -> Result<()> {
|
||||
let mut actual = check_path(
|
||||
@@ -1041,6 +1073,31 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn f722() -> Result<()> {
|
||||
let mut actual = check_path(
|
||||
Path::new("./resources/test/fixtures/F722.py"),
|
||||
&settings::Settings {
|
||||
line_length: 88,
|
||||
exclude: vec![],
|
||||
select: BTreeSet::from([CheckCode::F722]),
|
||||
},
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
actual.sort_by_key(|check| check.location);
|
||||
let expected = vec![Check {
|
||||
kind: CheckKind::ForwardAnnotationSyntaxError("///".to_string()),
|
||||
location: Location::new(9, 13),
|
||||
fix: None,
|
||||
}];
|
||||
assert_eq!(actual.len(), expected.len());
|
||||
for i in 0..actual.len() {
|
||||
assert_eq!(actual[i], expected[i]);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn f821() -> Result<()> {
|
||||
let mut actual = check_path(
|
||||
|
||||
@@ -274,6 +274,7 @@ other-attribute = 1
|
||||
CheckCode::F401,
|
||||
CheckCode::F403,
|
||||
CheckCode::F404,
|
||||
CheckCode::F406,
|
||||
CheckCode::F407,
|
||||
CheckCode::F541,
|
||||
CheckCode::F601,
|
||||
@@ -287,6 +288,7 @@ other-attribute = 1
|
||||
CheckCode::F704,
|
||||
CheckCode::F706,
|
||||
CheckCode::F707,
|
||||
CheckCode::F722,
|
||||
CheckCode::F821,
|
||||
CheckCode::F822,
|
||||
CheckCode::F823,
|
||||
|
||||
@@ -58,6 +58,7 @@ impl Settings {
|
||||
CheckCode::E902,
|
||||
CheckCode::F401,
|
||||
CheckCode::F403,
|
||||
CheckCode::F406,
|
||||
CheckCode::F407,
|
||||
CheckCode::F541,
|
||||
CheckCode::F601,
|
||||
@@ -71,6 +72,7 @@ impl Settings {
|
||||
CheckCode::F704,
|
||||
CheckCode::F706,
|
||||
CheckCode::F707,
|
||||
CheckCode::F722,
|
||||
CheckCode::F821,
|
||||
CheckCode::F822,
|
||||
CheckCode::F823,
|
||||
|
||||
Reference in New Issue
Block a user