#diagnostics #clippy #warnings #comments #git #messages #pair

app rust-diagnostics

一个将Clippy诊断嵌入Rust代码的工具

12个版本

0.1.11 2023年2月2日
0.1.10 2023年2月2日
0.1.9 2023年1月22日
0.1.6 2022年11月24日

开发工具类别中排名396

每月下载量36

Apache-2.0

95KB
2K SLoC

rust-diagnostics

这是一个工具,可以将代码片段的诊断作为注释插入到Rust代码中,并检查警告/错误在git提交历史中是如何被修复的。

Rust编译器在控制台显示许多诊断信息,使用文件名和行号来指示它们的精确位置。没有IDE,程序员需要来回切换命令控制台和编辑器。

此工具将诊断消息直接插入,这可能能够启用基于转换器的机器学习方法来分析Rust诊断语义。

通过额外的参数,此工具还可以检查在修订版r1中发现的警告是如何由修订版r2手动修复的。

目前我们已将此工具与clippygit2-rs集成。

安装

cargo install rust-diagnostics

用法

以下是命令的完整语法。

rust-diagnostics [--patch <commit_id> [--confirm] [--pair] [--function] [--single] [--location] [--mixed] ]

运行示例

为了说明其用法,我们使用一个小示例,我们可以称之为一个abc项目。

rm -rf abc
cargo init --bin abc
cat > abc/src/main.rs <<EOF
fn main() {
    let s = std::fs::read_to_string("Cargo.toml").unwrap();
    println!("{s}");
}
EOF

将警告信息插入Rust代码

rust-diagnostics的默认函数(即,没有任何参数)将警告信息插入Rust代码。例如,

cd abc
rust-diagnostics

该命令调用clippy来报告所有警告

    Checking abc v0.1.0 (...)
    Finished dev [unoptimized + debuginfo] target(s) in 0.06s
There are 1 warnings in 1 files.

结果,还创建了一个新的文件夹diagnostics,其中包含一个文件src/main.rs


fn main() {
    let s = /*#[Warning(clippy::unwrap_used)*/std::fs::read_to_string("Cargo.toml").unwrap()/*
#[Warning(clippy::unwrap_used)
note: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#unwrap_used
if this value is an `Err`, it will panic
requested on the command line with `-W clippy::unwrap-used`*/;
    println!("The configuration file is: {s}");
}

如我们所见,相关的警告已经被两个注释标记,在违规代码之前和之后。之前的注释表示警告类型,这里是clippy::unwrap_used。之后的注释也指示了由cargo clippy报告的一些附加注释,提供了警告类型的详细信息和解决该问题的提示。在这个例子中,unwrap_used没有自动修复。

分析变更历史中手动修复的警告

上述工具的扩展功能很有用,它可以检查更改历史中有多少警告被修复,是自动通过 cargo clippy --fix 实现的,还是通过手动补丁修复的。如果手动修复是重复的,那么它对学习语言很有帮助,无论是手动还是通过机器学习。

为此,我们通过在 git 仓库中做些修改来重新启动示例。

rm -rf abc
cargo init --vcs git --bin abc
cd abc
cat > src/main.rs <<EOF
fn main() {
    let s = std::fs::read_to_string("Cargo.toml").unwrap();
    println!("{s}");
}
EOF
git commit -am "r1"
cat > src/main.rs <<EOF
fn main() {
    if let Ok(s) = std::fs::read_to_string("Cargo.toml") {
        println!("{s}");
    }
}
EOF
git commit -am "r2"

这里使用 --vcs git 选项是为了让示例项目包含一些更改历史,以便说明与 git 仓库分析相关的功能。

如果你检查代码并想知道修订版 r2 是否修复了修订版 r1 的警告,你可以使用 git log -p 来首先识别修订的提交 ID。

commit 839164fa28d71a9c00009c9e25bc84dce6caa286             .......... (r2)
Author: ...
Date:   ...

    update

diff --git a/src/main.rs b/src/main.rs
index 36d2d89..6175ab1 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,5 +1,6 @@
 
 fn main() {
-    let s = std::fs::read_to_string("Cargo.toml").unwrap();
-    println!("{s}");
+    if let Ok(s) = std::fs::read_to_string("Cargo.toml") {
+        println!("{s}");
+    }
 }

commit 6fafc98041f47155bc51c5ddc55b8e8b0b7548bf           .......... (r1)
Author: ...
Date:   ...

    init

diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..36d2d89
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,5 @@
+
+fn main() {
+    let s = std::fs::read_to_string("Cargo.toml").unwrap();
+    println!("{s}");
+}


然后运行以下两个命令,我们可以检查 r1 的警告是否被 r2 修复。

git checkout $r1
rust-diagnostics --patch $r2 --confirm

输出 diagnostics.log 包含 $r1 的警告数量以及 $r1..$r2 之间影响修复列表中警告的块。

例如,输出将与 git diff 格式的输出相同

There are 1 warnings in 1 files.
##[Warning(clippy::unwrap_used)
@@ -3,2 +3,3 @@ fn main() {
-    let s = std::fs::read_to_string("Cargo.toml").unwrap();
-    println!("{s}");
+    if let Ok(s) = std::fs::read_to_string("Cargo.toml") {
+        println!("{s}");
+    }

注意,这里我们移除了所有上下文行,就像 -U0 选项的 git-diff 命令一样,这样就可以得到更精确的补丁功能上下文。

使用 --pair 选项生成一对

使用 --pair 选项将补丁转换为变化前后的代码对

git checkout $r1
rust-diagnostics --patch $r2 --confirm --pair

例如,diagnostics.log 将包含

There are 1 warnings in 1 files.
##[Warning(clippy::unwrap_used)
@@ -3,2 +3,3 @@ fn main() {
    let s = std::fs::read_to_string("Cargo.toml").unwrap();
    println!("{s}");
=== 19a3477889393ea2cdd0edcb5e6ab30c ===
    if let Ok(s) = std::fs::read_to_string("Cargo.toml") {
        println!("{s}");
    }

注意。为了避免与现有代码冲突,我们在对偶的分隔符中使用哈希键 19a3477889393ea2cdd0edcb5e6ab30c,它是由以下命令创建的

echo rust-diagnostics | md5sum 

使用 --function 选项生成函数上下文

对偶可能过于简略,我们使用 --function 选项来打印补丁周围的函数作为其上下文

git checkout $r1
rust-diagnostics --patch $r2 --confirm --pair --function

例如,它将打印以下内容

There are 1 warnings in 1 files.
##[Warning(clippy::unwrap_used)
@@ -3,2 +3,3 @@ fn main() {
fn main() {
    let s = std::fs::read_to_string("Cargo.toml").unwrap();
    println!("{s}");
}
=== 19a3477889393ea2cdd0edcb5e6ab30c ===
@@ -3,2 +3,3 @@ fn main() {
fn main() {
    if let Ok(s) = std::fs::read_to_string("Cargo.toml") {
        println!("{s}");
    }
}

使用 --location 选项生成标记上下文

此选项可以将根据 clippy 生成的警告位置及其修复提示插入到原始上下文中。

git checkout $r1
rust-diagnostics --patch $r2 --confirm --pair --function --location

例如,它将打印以下内容

There are 1 warnings in 1 files.
##[Warning(clippy::unwrap_used)
fn main() {
    let s = /*#[Warning(clippy::unwrap_used)*/std::fs::read_to_string("Cargo.toml").unwrap()/*
#[Warning(clippy::unwrap_used)
note: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#unwrap_used
if this value is an `Err`, it will panic
requested on the command line with `-W clippy::unwrap-used`*/;
    println!("{s}");
}
=== 19a3477889393ea2cdd0edcb5e6ab30c ===
fn main() {
    if let Ok(s) = std::fs::read_to_string("Cargo.toml") {
        println!("{s}");
    }
}

使用 --mixed 选项生成混合上下文和补丁

此选项可以将上下文与实际补丁配对。

git checkout $r1
rust-diagnostics --patch $r2 --confirm --pair --function --location --mixed

例如,它将打印以下内容

There are 1 warnings in 1 files.
##[Warning(clippy::unwrap_used)
fn main() {
    let s = /*#[Warning(clippy::unwrap_used)*/std::fs::read_to_string("Cargo.toml").unwrap()/*
#[Warning(clippy::unwrap_used)
note: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#unwrap_used
if this value is an `Err`, it will panic
requested on the command line with `-W clippy::unwrap-used`*/;
    println!("{s}");
}
=== 19a3477889393ea2cdd0edcb5e6ab30c ===
-    let s = std::fs::read_to_string("Cargo.toml").unwrap();
-    println!("{s}");
+    if let Ok(s) = std::fs::read_to_string("Cargo.toml") {
+        println!("{s}");
+    }

注意,我们不保留标题,因为如果我们已经知道警告的位置,行号就不再重要了,而插入的标记提示已经移动了原始行号。

计算警告数量

另一种计算警告数量的方法是使用 "cargo lintcheck"。

致谢

  • David Wood 提出了我们可以使用 --message-format=json 选项从 Rust 编译器获取诊断信息,这大大节省了修改 Rust 编译器的努力。现在我们的解决方案在一定程度上独立于 Rust 编译器的实现;
  • Mara Bos 提供了一些关于如何使用 if-let 语句解决 unwrap() 警告的提示;
  • Amanieu d'Antras 对实践中某些 clippy 规则的必要性进行了解释,他还提高了底层 BTreeMap 的性能。
  • Josh Triplett 实现了底层的 git2-rs,它用 Rust 包装了 libgit2 库。
  • 李春苗博士实现了重构规则 unwrapped_used.txl,以自动修复相应的警告。
  • Nghi Bui 博士 提出了一种创建混合对的想法。

依赖项

~207MB
~5.5M SLoC