├── .gitignore ├── Cargo.toml ├── .rustfmt.toml ├── README.md ├── src └── main.rs └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cargo-check-tip" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | cargo_metadata = "0.15.2" 8 | codespan-reporting = "0.11.1" 9 | colored = "2.0.0" 10 | regex = "1.7.1" 11 | serde = { version = "1.0.152", features = ["derive"] } 12 | serde_json = "1.0.91" 13 | serde_regex = "1.1.0" 14 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | unstable_features = true 2 | newline_style = "Unix" 3 | group_imports = "StdExternalCrate" 4 | wrap_comments = true 5 | fn_args_layout = "compressed" 6 | force_multiline_blocks = true 7 | format_strings = true 8 | match_block_trailing_comma = true 9 | imports_granularity = "Module" 10 | normalize_comments = true 11 | reorder_impl_items = true 12 | use_field_init_shorthand = true 13 | where_single_line = true 14 | chain_width = 60 15 | 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Install 2 | 3 | ```shell 4 | $ cargo install --git https://github.com/emi2k01/cargo-check-tip.git 5 | ``` 6 | 7 | ## Usage 8 | 9 | ```shell 10 | $ cargo check-tip 11 | ``` 12 | 13 | ### For library developers 14 | 15 | In your `Cargo.toml` 16 | 17 | ```toml 18 | [package.metadata.tips] 19 | tip = "Functions must be async" 20 | code_pattern = "(E0456|E0987)" # Rust code error pattern 21 | message_pattern = "the trait bound.*" # Error message pattern 22 | span_pattern = "the trait bound.*" # Span pattern, used to mark where the tip will point to 23 | ``` 24 | 25 | #### Rust error anatomy 26 | ![Error anatomy](https://user-images.githubusercontent.com/78516649/215917091-c792874d-755e-4893-bc1d-f661f2584675.png) 27 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use std::iter::repeat; 2 | use std::process::{Command, Stdio}; 3 | 4 | use cargo_metadata::diagnostic::{DiagnosticLevel, DiagnosticSpan}; 5 | use cargo_metadata::MetadataCommand; 6 | use colored::Colorize; 7 | use regex::Regex; 8 | use serde::Deserialize; 9 | 10 | #[derive(Deserialize)] 11 | struct OurMetadata { 12 | tips: Vec, 13 | } 14 | 15 | #[derive(Deserialize)] 16 | struct Tip { 17 | #[serde(with = "serde_regex")] 18 | code_pattern: Regex, 19 | #[serde(with = "serde_regex")] 20 | message_pattern: Regex, 21 | #[serde(with = "serde_regex")] 22 | span_pattern: Regex, 23 | tip: String, 24 | } 25 | 26 | fn main() { 27 | Command::new("cargo") 28 | .args(&["check"]) 29 | .spawn() 30 | .unwrap() 31 | .wait() 32 | .unwrap(); 33 | 34 | let metadata = MetadataCommand::new().exec().unwrap(); 35 | let mut our_metadata = OurMetadata { tips: Vec::new() }; 36 | 37 | for package in &metadata.packages { 38 | let tips_field = package.metadata["tips"].clone(); 39 | if tips_field.is_array() { 40 | our_metadata.tips = serde_json::from_value(tips_field).unwrap(); 41 | } 42 | } 43 | 44 | let tips = our_metadata.tips; 45 | 46 | let mut command = Command::new("cargo") 47 | .args(&["check", "--message-format=json"]) 48 | .stdout(Stdio::piped()) 49 | .stderr(Stdio::piped()) 50 | .spawn() 51 | .unwrap(); 52 | 53 | let reader = std::io::BufReader::new(command.stdout.take().unwrap()); 54 | let mut diagnostics: Vec<(DiagnosticSpan, String)> = Vec::new(); 55 | for message in cargo_metadata::Message::parse_stream(reader) { 56 | let message = message.unwrap(); 57 | match message { 58 | cargo_metadata::Message::CompilerMessage(compiler_message) => { 59 | if compiler_message.message.level == DiagnosticLevel::Error { 60 | for mapping in &tips { 61 | let mut is_code_match = false; 62 | let is_message_match = mapping 63 | .message_pattern 64 | .is_match(&compiler_message.message.message); 65 | if let Some(code) = compiler_message.message.code.as_ref().map(|c| &c.code) 66 | { 67 | is_code_match = mapping.code_pattern.is_match(code); 68 | } 69 | if is_message_match || is_code_match { 70 | let span = compiler_message.message.spans.iter().find(|span| { 71 | span.label 72 | .as_ref() 73 | .map(|l| mapping.span_pattern.is_match(l)) 74 | .unwrap_or(false) 75 | }); 76 | if let Some(span) = span { 77 | diagnostics.push((span.clone(), mapping.tip.clone())); 78 | } 79 | } 80 | } 81 | } 82 | }, 83 | _ => {}, 84 | } 85 | } 86 | for (diagnostic, tip) in diagnostics { 87 | let line_range = diagnostic.line_start..=diagnostic.line_end; 88 | let lines = line_range.zip(diagnostic.text.into_iter()); 89 | for (line_number, line) in lines { 90 | let mut line_gutter = line_number.to_string(); 91 | line_gutter.extend(repeat(' ').take((3 - line_gutter.len()).max(3))); 92 | line_gutter.push_str("| "); 93 | 94 | let mut col_separator = String::new(); 95 | col_separator.extend(repeat(' ').take(line_gutter.len() - 3)); 96 | println!( 97 | "{col_separator}{} {}:{}", 98 | "-->".bright_blue().bold(), 99 | diagnostic.file_name, 100 | diagnostic.line_start, 101 | ); 102 | 103 | let line_content = line.text; 104 | println!("{}{line_content}", line_gutter.bright_blue().bold()); 105 | 106 | let mut pad = String::new(); 107 | pad.extend(repeat(' ').take(line_gutter.len())); 108 | pad.extend(repeat(' ').take(line.highlight_start - 1)); 109 | print!("{pad}"); 110 | 111 | let mut highlight = String::new(); 112 | highlight.extend(repeat('^').take(line.highlight_end - line.highlight_start)); 113 | println!("{}", format!("{highlight} {tip}").bright_cyan().bold()); 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "0.7.20" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "atty" 16 | version = "0.2.14" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 19 | dependencies = [ 20 | "hermit-abi", 21 | "libc", 22 | "winapi", 23 | ] 24 | 25 | [[package]] 26 | name = "camino" 27 | version = "1.1.2" 28 | source = "registry+https://github.com/rust-lang/crates.io-index" 29 | checksum = "c77df041dc383319cc661b428b6961a005db4d6808d5e12536931b1ca9556055" 30 | dependencies = [ 31 | "serde", 32 | ] 33 | 34 | [[package]] 35 | name = "cargo-check-tip" 36 | version = "0.1.0" 37 | dependencies = [ 38 | "cargo_metadata", 39 | "codespan-reporting", 40 | "colored", 41 | "regex", 42 | "serde", 43 | "serde_json", 44 | "serde_regex", 45 | ] 46 | 47 | [[package]] 48 | name = "cargo-platform" 49 | version = "0.1.2" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "cbdb825da8a5df079a43676dbe042702f1707b1109f713a01420fbb4cc71fa27" 52 | dependencies = [ 53 | "serde", 54 | ] 55 | 56 | [[package]] 57 | name = "cargo_metadata" 58 | version = "0.15.2" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | checksum = "982a0cf6a99c350d7246035613882e376d58cebe571785abc5da4f648d53ac0a" 61 | dependencies = [ 62 | "camino", 63 | "cargo-platform", 64 | "semver", 65 | "serde", 66 | "serde_json", 67 | "thiserror", 68 | ] 69 | 70 | [[package]] 71 | name = "codespan-reporting" 72 | version = "0.11.1" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" 75 | dependencies = [ 76 | "termcolor", 77 | "unicode-width", 78 | ] 79 | 80 | [[package]] 81 | name = "colored" 82 | version = "2.0.0" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd" 85 | dependencies = [ 86 | "atty", 87 | "lazy_static", 88 | "winapi", 89 | ] 90 | 91 | [[package]] 92 | name = "hermit-abi" 93 | version = "0.1.19" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 96 | dependencies = [ 97 | "libc", 98 | ] 99 | 100 | [[package]] 101 | name = "itoa" 102 | version = "1.0.5" 103 | source = "registry+https://github.com/rust-lang/crates.io-index" 104 | checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" 105 | 106 | [[package]] 107 | name = "lazy_static" 108 | version = "1.4.0" 109 | source = "registry+https://github.com/rust-lang/crates.io-index" 110 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 111 | 112 | [[package]] 113 | name = "libc" 114 | version = "0.2.139" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" 117 | 118 | [[package]] 119 | name = "memchr" 120 | version = "2.5.0" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 123 | 124 | [[package]] 125 | name = "proc-macro2" 126 | version = "1.0.50" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2" 129 | dependencies = [ 130 | "unicode-ident", 131 | ] 132 | 133 | [[package]] 134 | name = "quote" 135 | version = "1.0.23" 136 | source = "registry+https://github.com/rust-lang/crates.io-index" 137 | checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" 138 | dependencies = [ 139 | "proc-macro2", 140 | ] 141 | 142 | [[package]] 143 | name = "regex" 144 | version = "1.7.1" 145 | source = "registry+https://github.com/rust-lang/crates.io-index" 146 | checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" 147 | dependencies = [ 148 | "aho-corasick", 149 | "memchr", 150 | "regex-syntax", 151 | ] 152 | 153 | [[package]] 154 | name = "regex-syntax" 155 | version = "0.6.28" 156 | source = "registry+https://github.com/rust-lang/crates.io-index" 157 | checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" 158 | 159 | [[package]] 160 | name = "ryu" 161 | version = "1.0.12" 162 | source = "registry+https://github.com/rust-lang/crates.io-index" 163 | checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" 164 | 165 | [[package]] 166 | name = "semver" 167 | version = "1.0.16" 168 | source = "registry+https://github.com/rust-lang/crates.io-index" 169 | checksum = "58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a" 170 | dependencies = [ 171 | "serde", 172 | ] 173 | 174 | [[package]] 175 | name = "serde" 176 | version = "1.0.152" 177 | source = "registry+https://github.com/rust-lang/crates.io-index" 178 | checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" 179 | dependencies = [ 180 | "serde_derive", 181 | ] 182 | 183 | [[package]] 184 | name = "serde_derive" 185 | version = "1.0.152" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" 188 | dependencies = [ 189 | "proc-macro2", 190 | "quote", 191 | "syn", 192 | ] 193 | 194 | [[package]] 195 | name = "serde_json" 196 | version = "1.0.91" 197 | source = "registry+https://github.com/rust-lang/crates.io-index" 198 | checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883" 199 | dependencies = [ 200 | "itoa", 201 | "ryu", 202 | "serde", 203 | ] 204 | 205 | [[package]] 206 | name = "serde_regex" 207 | version = "1.1.0" 208 | source = "registry+https://github.com/rust-lang/crates.io-index" 209 | checksum = "a8136f1a4ea815d7eac4101cfd0b16dc0cb5e1fe1b8609dfd728058656b7badf" 210 | dependencies = [ 211 | "regex", 212 | "serde", 213 | ] 214 | 215 | [[package]] 216 | name = "syn" 217 | version = "1.0.107" 218 | source = "registry+https://github.com/rust-lang/crates.io-index" 219 | checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" 220 | dependencies = [ 221 | "proc-macro2", 222 | "quote", 223 | "unicode-ident", 224 | ] 225 | 226 | [[package]] 227 | name = "termcolor" 228 | version = "1.2.0" 229 | source = "registry+https://github.com/rust-lang/crates.io-index" 230 | checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" 231 | dependencies = [ 232 | "winapi-util", 233 | ] 234 | 235 | [[package]] 236 | name = "thiserror" 237 | version = "1.0.38" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" 240 | dependencies = [ 241 | "thiserror-impl", 242 | ] 243 | 244 | [[package]] 245 | name = "thiserror-impl" 246 | version = "1.0.38" 247 | source = "registry+https://github.com/rust-lang/crates.io-index" 248 | checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" 249 | dependencies = [ 250 | "proc-macro2", 251 | "quote", 252 | "syn", 253 | ] 254 | 255 | [[package]] 256 | name = "unicode-ident" 257 | version = "1.0.6" 258 | source = "registry+https://github.com/rust-lang/crates.io-index" 259 | checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" 260 | 261 | [[package]] 262 | name = "unicode-width" 263 | version = "0.1.10" 264 | source = "registry+https://github.com/rust-lang/crates.io-index" 265 | checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" 266 | 267 | [[package]] 268 | name = "winapi" 269 | version = "0.3.9" 270 | source = "registry+https://github.com/rust-lang/crates.io-index" 271 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 272 | dependencies = [ 273 | "winapi-i686-pc-windows-gnu", 274 | "winapi-x86_64-pc-windows-gnu", 275 | ] 276 | 277 | [[package]] 278 | name = "winapi-i686-pc-windows-gnu" 279 | version = "0.4.0" 280 | source = "registry+https://github.com/rust-lang/crates.io-index" 281 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 282 | 283 | [[package]] 284 | name = "winapi-util" 285 | version = "0.1.5" 286 | source = "registry+https://github.com/rust-lang/crates.io-index" 287 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 288 | dependencies = [ 289 | "winapi", 290 | ] 291 | 292 | [[package]] 293 | name = "winapi-x86_64-pc-windows-gnu" 294 | version = "0.4.0" 295 | source = "registry+https://github.com/rust-lang/crates.io-index" 296 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 297 | --------------------------------------------------------------------------------