Skip to content

Commit

Permalink
add io_other_error lint
Browse files Browse the repository at this point in the history
  • Loading branch information
llogiq committed Jan 20, 2025
1 parent e359e88 commit f7e2c9c
Show file tree
Hide file tree
Showing 14 changed files with 196 additions and 30 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5682,6 +5682,7 @@ Released 2018-09-13
[`invalid_utf8_in_unchecked`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_utf8_in_unchecked
[`inverted_saturating_sub`]: https://rust-lang.github.io/rust-clippy/master/index.html#inverted_saturating_sub
[`invisible_characters`]: https://rust-lang.github.io/rust-clippy/master/index.html#invisible_characters
[`io_other_error`]: https://rust-lang.github.io/rust-clippy/master/index.html#io_other_error
[`is_digit_ascii_radix`]: https://rust-lang.github.io/rust-clippy/master/index.html#is_digit_ascii_radix
[`items_after_statements`]: https://rust-lang.github.io/rust-clippy/master/index.html#items_after_statements
[`items_after_test_module`]: https://rust-lang.github.io/rust-clippy/master/index.html#items_after_test_module
Expand Down
1 change: 1 addition & 0 deletions book/src/lint_configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -738,6 +738,7 @@ The minimum rust version that the project supports. Defaults to the `rust-versio
* [`from_over_into`](https://rust-lang.github.io/rust-clippy/master/index.html#from_over_into)
* [`if_then_some_else_none`](https://rust-lang.github.io/rust-clippy/master/index.html#if_then_some_else_none)
* [`index_refutable_slice`](https://rust-lang.github.io/rust-clippy/master/index.html#index_refutable_slice)
* [`io_other_error`](https://rust-lang.github.io/rust-clippy/master/index.html#io_other_error)
* [`iter_kv_map`](https://rust-lang.github.io/rust-clippy/master/index.html#iter_kv_map)
* [`legacy_numeric_constants`](https://rust-lang.github.io/rust-clippy/master/index.html#legacy_numeric_constants)
* [`manual_bits`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_bits)
Expand Down
1 change: 1 addition & 0 deletions clippy_config/src/conf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -607,6 +607,7 @@ define_Conf! {
from_over_into,
if_then_some_else_none,
index_refutable_slice,
io_other_error,
iter_kv_map,
legacy_numeric_constants,
manual_bits,
Expand Down
5 changes: 2 additions & 3 deletions clippy_dev/src/new_lint.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
use crate::utils::{clippy_project_root, clippy_version};
use indoc::{formatdoc, writedoc};
use std::fmt;
use std::fmt::Write as _;
use std::fs::{self, OpenOptions};
use std::io::prelude::*;
use std::io::{self, ErrorKind};
use std::path::{Path, PathBuf};
use std::{fmt, io};

struct LintData<'a> {
pass: &'a str,
Expand All @@ -25,7 +24,7 @@ impl<T> Context for io::Result<T> {
Ok(t) => Ok(t),
Err(e) => {
let message = format!("{}: {e}", text.as_ref());
Err(io::Error::new(ErrorKind::Other, message))
Err(io::Error::other(message))
},
}
}
Expand Down
1 change: 1 addition & 0 deletions clippy_lints/src/declared_lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
crate::methods::INEFFICIENT_TO_STRING_INFO,
crate::methods::INSPECT_FOR_EACH_INFO,
crate::methods::INTO_ITER_ON_REF_INFO,
crate::methods::IO_OTHER_ERROR_INFO,
crate::methods::IS_DIGIT_ASCII_RADIX_INFO,
crate::methods::ITERATOR_STEP_BY_ZERO_INFO,
crate::methods::ITER_CLONED_COLLECT_INFO,
Expand Down
31 changes: 31 additions & 0 deletions clippy_lints/src/methods/io_other_error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::msrvs::{IO_ERROR_OTHER, Msrv};
use rustc_errors::Applicability;
use rustc_hir::Expr;
use rustc_lint::LateContext;

pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, path: &Expr<'_>, args: &[Expr<'_>], msrv: &Msrv) {
if let [error_kind, error] = args
&& !expr.span.from_expansion()
&& !error_kind.span.from_expansion()
&& clippy_utils::is_expr_path_def_path(cx, path, &clippy_utils::paths::IO_ERROR_NEW)
&& clippy_utils::is_expr_path_def_path(
cx,
clippy_utils::expr_or_init(cx, error_kind),
&clippy_utils::paths::IO_ERRORKIND_OTHER,
)
&& msrv.meets(IO_ERROR_OTHER)
{
let mut applicability = Applicability::MachineApplicable;
let snip = clippy_utils::source::snippet_with_applicability(cx, error.span, "_", &mut applicability);
span_lint_and_sugg(
cx,
super::IO_OTHER_ERROR,
expr.span,
"this can be `io::Error::other`",
"use",
format!("std::io::Error::other({snip})"),
applicability,
);
}
}
26 changes: 26 additions & 0 deletions clippy_lints/src/methods/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ mod implicit_clone;
mod inefficient_to_string;
mod inspect_for_each;
mod into_iter_on_ref;
mod io_other_error;
mod is_digit_ascii_radix;
mod is_empty;
mod iter_cloned_collect;
Expand Down Expand Up @@ -4339,6 +4340,29 @@ declare_clippy_lint! {
"using `NonZero::new_unchecked()` in a `const` context"
}

declare_clippy_lint! {
/// ### What it does
/// This lint warns on calling `io::Error::new(..)` with a kind of
/// `io::ErrorKind::Other`.
///
/// ### Why is this bad?
/// Since Rust 1.74, there's the `io::Error::other(_)` shortcut.
///
/// ### Example
/// ```no_run
/// use std::io;
/// let _ = io::Error::new(io::ErrorKind::Other, "bad".to_string());
/// ```
/// Use instead:
/// ```no_run
/// let _ = std::io::Error::other("bad".to_string());
/// ```
#[clippy::version = "1.86.0"]
pub IO_OTHER_ERROR,
complexity,
"calling `std::io::Error::new(std::io::ErrorKind::Other, _)`"
}

pub struct Methods {
avoid_breaking_exported_api: bool,
msrv: Msrv,
Expand Down Expand Up @@ -4506,6 +4530,7 @@ impl_lint_pass!(Methods => [
UNNECESSARY_MAP_OR,
DOUBLE_ENDED_ITERATOR_LAST,
USELESS_NONZERO_NEW_UNCHECKED,
IO_OTHER_ERROR,
]);

/// Extracts a method call name, args, and `Span` of the method name.
Expand Down Expand Up @@ -4535,6 +4560,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
unnecessary_fallible_conversions::check_function(cx, expr, func);
manual_c_str_literals::check(cx, expr, func, args, &self.msrv);
useless_nonzero_new_unchecked::check(cx, expr, func, args, &self.msrv);
io_other_error::check(cx, expr, func, args, &self.msrv);
},
ExprKind::MethodCall(method_call, receiver, args, _) => {
let method_span = method_call.ident.span;
Expand Down
2 changes: 1 addition & 1 deletion clippy_utils/src/msrvs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ msrv_aliases! {
1,80,0 { BOX_INTO_ITER }
1,77,0 { C_STR_LITERALS }
1,76,0 { PTR_FROM_REF, OPTION_RESULT_INSPECT }
1,74,0 { REPR_RUST }
1,74,0 { REPR_RUST, IO_ERROR_OTHER }
1,73,0 { MANUAL_DIV_CEIL }
1,71,0 { TUPLE_ARRAY_CONVERSIONS, BUILD_HASHER_HASH_ONE }
1,70,0 { OPTION_RESULT_IS_VARIANT_AND, BINARY_HEAP_RETAIN }
Expand Down
2 changes: 2 additions & 0 deletions clippy_utils/src/paths.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ pub const CHILD_KILL: [&str; 4] = ["std", "process", "Child", "kill"];
pub const PANIC_ANY: [&str; 3] = ["std", "panic", "panic_any"];
pub const CHAR_IS_ASCII: [&str; 5] = ["core", "char", "methods", "<impl char>", "is_ascii"];
pub const STDIN: [&str; 4] = ["std", "io", "stdio", "Stdin"];
pub const IO_ERROR_NEW: [&str; 5] = ["std", "io", "error", "Error", "new"];
pub const IO_ERRORKIND_OTHER: [&str; 5] = ["std", "io", "error", "ErrorKind", "Other"];

// Paths in clippy itself
pub const MSRV: [&str; 3] = ["clippy_utils", "msrvs", "Msrv"];
Expand Down
7 changes: 6 additions & 1 deletion tests/ui/format_args_unfixable.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
#![warn(clippy::format_in_format_args, clippy::to_string_in_format_args)]
#![allow(unused)]
#![allow(clippy::assertions_on_constants, clippy::eq_op, clippy::uninlined_format_args)]
#![allow(
clippy::assertions_on_constants,
clippy::eq_op,
clippy::uninlined_format_args,
clippy::io_other_error
)]

use std::io::{Error, ErrorKind, Write, stdout};
use std::ops::Deref;
Expand Down
50 changes: 25 additions & 25 deletions tests/ui/format_args_unfixable.stderr
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
error: `format!` in `println!` args
--> tests/ui/format_args_unfixable.rs:26:5
--> tests/ui/format_args_unfixable.rs:31:5
|
LL | println!("error: {}", format!("something failed at {}", Location::caller()));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand All @@ -10,7 +10,7 @@ LL | println!("error: {}", format!("something failed at {}", Location::calle
= help: to override `-D warnings` add `#[allow(clippy::format_in_format_args)]`

error: `format!` in `println!` args
--> tests/ui/format_args_unfixable.rs:28:5
--> tests/ui/format_args_unfixable.rs:33:5
|
LL | println!("{}: {}", error, format!("something failed at {}", Location::caller()));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand All @@ -19,7 +19,7 @@ LL | println!("{}: {}", error, format!("something failed at {}", Location::c
= help: or consider changing `format!` to `format_args!`

error: `format!` in `println!` args
--> tests/ui/format_args_unfixable.rs:30:5
--> tests/ui/format_args_unfixable.rs:35:5
|
LL | println!("{:?}: {}", error, format!("something failed at {}", Location::caller()));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand All @@ -28,7 +28,7 @@ LL | println!("{:?}: {}", error, format!("something failed at {}", Location:
= help: or consider changing `format!` to `format_args!`

error: `format!` in `println!` args
--> tests/ui/format_args_unfixable.rs:32:5
--> tests/ui/format_args_unfixable.rs:37:5
|
LL | println!("{{}}: {}", format!("something failed at {}", Location::caller()));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand All @@ -37,7 +37,7 @@ LL | println!("{{}}: {}", format!("something failed at {}", Location::caller
= help: or consider changing `format!` to `format_args!`

error: `format!` in `println!` args
--> tests/ui/format_args_unfixable.rs:34:5
--> tests/ui/format_args_unfixable.rs:39:5
|
LL | println!(r#"error: "{}""#, format!("something failed at {}", Location::caller()));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand All @@ -46,7 +46,7 @@ LL | println!(r#"error: "{}""#, format!("something failed at {}", Location::
= help: or consider changing `format!` to `format_args!`

error: `format!` in `println!` args
--> tests/ui/format_args_unfixable.rs:36:5
--> tests/ui/format_args_unfixable.rs:41:5
|
LL | println!("error: {}", format!(r#"something failed at "{}""#, Location::caller()));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand All @@ -55,7 +55,7 @@ LL | println!("error: {}", format!(r#"something failed at "{}""#, Location::
= help: or consider changing `format!` to `format_args!`

error: `format!` in `println!` args
--> tests/ui/format_args_unfixable.rs:38:5
--> tests/ui/format_args_unfixable.rs:43:5
|
LL | println!("error: {}", format!("something failed at {} {0}", Location::caller()));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand All @@ -64,7 +64,7 @@ LL | println!("error: {}", format!("something failed at {} {0}", Location::c
= help: or consider changing `format!` to `format_args!`

error: `format!` in `format!` args
--> tests/ui/format_args_unfixable.rs:40:13
--> tests/ui/format_args_unfixable.rs:45:13
|
LL | let _ = format!("error: {}", format!("something failed at {}", Location::caller()));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand All @@ -73,7 +73,7 @@ LL | let _ = format!("error: {}", format!("something failed at {}", Location
= help: or consider changing `format!` to `format_args!`

error: `format!` in `write!` args
--> tests/ui/format_args_unfixable.rs:42:13
--> tests/ui/format_args_unfixable.rs:47:13
|
LL | let _ = write!(
| _____________^
Expand All @@ -88,7 +88,7 @@ LL | | );
= help: or consider changing `format!` to `format_args!`

error: `format!` in `writeln!` args
--> tests/ui/format_args_unfixable.rs:48:13
--> tests/ui/format_args_unfixable.rs:53:13
|
LL | let _ = writeln!(
| _____________^
Expand All @@ -103,7 +103,7 @@ LL | | );
= help: or consider changing `format!` to `format_args!`

error: `format!` in `print!` args
--> tests/ui/format_args_unfixable.rs:54:5
--> tests/ui/format_args_unfixable.rs:59:5
|
LL | print!("error: {}", format!("something failed at {}", Location::caller()));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand All @@ -112,7 +112,7 @@ LL | print!("error: {}", format!("something failed at {}", Location::caller(
= help: or consider changing `format!` to `format_args!`

error: `format!` in `eprint!` args
--> tests/ui/format_args_unfixable.rs:56:5
--> tests/ui/format_args_unfixable.rs:61:5
|
LL | eprint!("error: {}", format!("something failed at {}", Location::caller()));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand All @@ -121,7 +121,7 @@ LL | eprint!("error: {}", format!("something failed at {}", Location::caller
= help: or consider changing `format!` to `format_args!`

error: `format!` in `eprintln!` args
--> tests/ui/format_args_unfixable.rs:58:5
--> tests/ui/format_args_unfixable.rs:63:5
|
LL | eprintln!("error: {}", format!("something failed at {}", Location::caller()));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand All @@ -130,7 +130,7 @@ LL | eprintln!("error: {}", format!("something failed at {}", Location::call
= help: or consider changing `format!` to `format_args!`

error: `format!` in `format_args!` args
--> tests/ui/format_args_unfixable.rs:60:13
--> tests/ui/format_args_unfixable.rs:65:13
|
LL | let _ = format_args!("error: {}", format!("something failed at {}", Location::caller()));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand All @@ -139,7 +139,7 @@ LL | let _ = format_args!("error: {}", format!("something failed at {}", Loc
= help: or consider changing `format!` to `format_args!`

error: `format!` in `assert!` args
--> tests/ui/format_args_unfixable.rs:62:5
--> tests/ui/format_args_unfixable.rs:67:5
|
LL | assert!(true, "error: {}", format!("something failed at {}", Location::caller()));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand All @@ -148,7 +148,7 @@ LL | assert!(true, "error: {}", format!("something failed at {}", Location::
= help: or consider changing `format!` to `format_args!`

error: `format!` in `assert_eq!` args
--> tests/ui/format_args_unfixable.rs:64:5
--> tests/ui/format_args_unfixable.rs:69:5
|
LL | assert_eq!(0, 0, "error: {}", format!("something failed at {}", Location::caller()));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand All @@ -157,7 +157,7 @@ LL | assert_eq!(0, 0, "error: {}", format!("something failed at {}", Locatio
= help: or consider changing `format!` to `format_args!`

error: `format!` in `assert_ne!` args
--> tests/ui/format_args_unfixable.rs:66:5
--> tests/ui/format_args_unfixable.rs:71:5
|
LL | assert_ne!(0, 0, "error: {}", format!("something failed at {}", Location::caller()));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand All @@ -166,7 +166,7 @@ LL | assert_ne!(0, 0, "error: {}", format!("something failed at {}", Locatio
= help: or consider changing `format!` to `format_args!`

error: `format!` in `panic!` args
--> tests/ui/format_args_unfixable.rs:68:5
--> tests/ui/format_args_unfixable.rs:73:5
|
LL | panic!("error: {}", format!("something failed at {}", Location::caller()));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand All @@ -175,7 +175,7 @@ LL | panic!("error: {}", format!("something failed at {}", Location::caller(
= help: or consider changing `format!` to `format_args!`

error: `format!` in `usr_println!` args
--> tests/ui/format_args_unfixable.rs:136:5
--> tests/ui/format_args_unfixable.rs:141:5
|
LL | usr_println!(true, "error: {}", format!("boom at {}", Location::caller()));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand All @@ -184,7 +184,7 @@ LL | usr_println!(true, "error: {}", format!("boom at {}", Location::caller(
= help: or consider changing `format!` to `format_args!`

error: `format!` in `usr_println!` args
--> tests/ui/format_args_unfixable.rs:138:5
--> tests/ui/format_args_unfixable.rs:143:5
|
LL | usr_println!(true, "{}: {}", error, format!("boom at {}", Location::caller()));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand All @@ -193,7 +193,7 @@ LL | usr_println!(true, "{}: {}", error, format!("boom at {}", Location::cal
= help: or consider changing `format!` to `format_args!`

error: `format!` in `usr_println!` args
--> tests/ui/format_args_unfixable.rs:140:5
--> tests/ui/format_args_unfixable.rs:145:5
|
LL | usr_println!(true, "{:?}: {}", error, format!("boom at {}", Location::caller()));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand All @@ -202,7 +202,7 @@ LL | usr_println!(true, "{:?}: {}", error, format!("boom at {}", Location::c
= help: or consider changing `format!` to `format_args!`

error: `format!` in `usr_println!` args
--> tests/ui/format_args_unfixable.rs:142:5
--> tests/ui/format_args_unfixable.rs:147:5
|
LL | usr_println!(true, "{{}}: {}", format!("boom at {}", Location::caller()));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand All @@ -211,7 +211,7 @@ LL | usr_println!(true, "{{}}: {}", format!("boom at {}", Location::caller()
= help: or consider changing `format!` to `format_args!`

error: `format!` in `usr_println!` args
--> tests/ui/format_args_unfixable.rs:144:5
--> tests/ui/format_args_unfixable.rs:149:5
|
LL | usr_println!(true, r#"error: "{}""#, format!("boom at {}", Location::caller()));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand All @@ -220,7 +220,7 @@ LL | usr_println!(true, r#"error: "{}""#, format!("boom at {}", Location::ca
= help: or consider changing `format!` to `format_args!`

error: `format!` in `usr_println!` args
--> tests/ui/format_args_unfixable.rs:146:5
--> tests/ui/format_args_unfixable.rs:151:5
|
LL | usr_println!(true, "error: {}", format!(r#"boom at "{}""#, Location::caller()));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand All @@ -229,7 +229,7 @@ LL | usr_println!(true, "error: {}", format!(r#"boom at "{}""#, Location::ca
= help: or consider changing `format!` to `format_args!`

error: `format!` in `usr_println!` args
--> tests/ui/format_args_unfixable.rs:148:5
--> tests/ui/format_args_unfixable.rs:153:5
|
LL | usr_println!(true, "error: {}", format!("boom at {} {0}", Location::caller()));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand Down
Loading

0 comments on commit f7e2c9c

Please sign in to comment.