diff --git a/Cargo.toml b/Cargo.toml index 118b775..4d8c5cc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,9 +14,13 @@ serde = "1.0.80" serde_json = "1.0.33" console = "0.6.2" syn = { version = "=0.15.18", default-features = false, features = ["full", "extra-traits", "parsing"] } -quote = "0.6.10" +quote = { version = "0.6.10", default-features = false } clap = "2.32.0" -proc-macro2 = "0.4.24" +proc-macro2 = { version = "0.4.24", default-features = false } [dev-dependencies] assert_cmd = "0.10.2" + +[features] +default = [] +proc_macro_spans = [] diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..7f6c99a --- /dev/null +++ b/build.rs @@ -0,0 +1,6 @@ +fn main() { + match std::env::var("CARGO_CFG_PROCMACRO2_SEMVER_EXEMPT") { + Ok(_) => println!("cargo:rustc-cfg=feature=\"proc_macro_spans\""), + Err(_) => {} + } +} diff --git a/src/check.rs b/src/check.rs index 7c7292d..bc3df2c 100644 --- a/src/check.rs +++ b/src/check.rs @@ -1,9 +1,14 @@ use proc_macro2; use proc_macro2::TokenTree; use quote::quote; +use std::fmt; use std::fs::File; use std::io::Read; use std::path::PathBuf; +use syn::spanned::Spanned; + +#[cfg(feature = "proc_macro_spans")] +use std::io::BufRead; use ext::*; @@ -90,7 +95,70 @@ pub enum SourceOffense { /// Only valid for entry point file (main.rs / lib.rs). MissingNoStdAttribute, /// Source code contains an explicit `use std::` statement. - UseStdStatement, + UseStdStatement(UseStdStmt), +} + +#[derive(Debug)] +pub struct UseStdStmt { + src_path: PathBuf, + span: proc_macro2::Span, +} + +impl PartialEq for UseStdStmt { + fn eq(&self, other: &UseStdStmt) -> bool { + self.src_path == other.src_path + } +} +impl Eq for UseStdStmt {} + +#[cfg(feature = "proc_macro_spans")] +impl fmt::Display for UseStdStmt { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let file = File::open(&self.src_path).unwrap(); + let file = std::io::BufReader::new(file); + let line = file + .lines() + .skip(self.span.start().line - 1) + .next() + .unwrap() + .unwrap(); + + writeln!( + f, + " --> {src}:{line}:{column}", + src = self + .src_path + .strip_prefix(std::env::current_dir().unwrap()) + .unwrap() + .display(), + line = self.span.start().line, + column = self.span.start().column + )?; + writeln!(f, " |")?; + + writeln!( + f, + "{line_num:<4}|{line}", + line_num = self.span.start().line, + line = line + )?; + writeln!(f, " |") + } +} + +#[cfg(not(feature = "proc_macro_spans"))] +impl fmt::Display for UseStdStmt { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + writeln!( + f, + " --> {src}", + src = self + .src_path + .strip_prefix(std::env::current_dir().unwrap()) + .unwrap() + .display(), + ) + } } pub fn get_crate_support_from_source(src_path: &PathBuf) -> CrateSupport { @@ -116,27 +184,31 @@ pub fn get_crate_support_from_source(src_path: &PathBuf) -> CrateSupport { let mut offenses = vec![]; - let use_statements: Vec<_> = syntax.items.iter().filter_map(|item| match item { - syn::Item::Use(item) => Some(item), - _ => None, - }).collect(); + let use_statements: Vec<_> = syntax + .items + .iter() + .filter_map(|item| match item { + syn::Item::Use(item) => Some(item), + _ => None, + }) + .collect(); - let mut has_use_std = false; let std_ident: syn::Ident = syn::parse_quote!(std); for use_statement in &use_statements { match use_statement.tree { syn::UseTree::Path(ref first_path) => { let first_ident = &first_path.ident; if first_ident == &std_ident { - has_use_std = true; + let stmt = UseStdStmt { + src_path: src_path.clone(), + span: use_statement.tree.span(), + }; + offenses.push(SourceOffense::UseStdStatement(stmt)); } - }, + } _ => unimplemented!(), } } - if has_use_std { - offenses.push(SourceOffense::UseStdStatement); - } let always_no_std: syn::Attribute = syn::parse_quote!(#![no_std]); let contains_always_no_std = syntax.attrs.contains(&always_no_std); diff --git a/src/main.rs b/src/main.rs index 04b079c..91d6fb5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -111,9 +111,10 @@ fn check_and_print_package( match offense { SourceOffense::MissingNoStdAttribute => { println!(" - Did not find a #![no_std] attribute or a simple conditional attribute like #[cfg_attr(not(feature = \"std\"), no_std)] in the crate source. Crate most likely doesn't support no_std without changes."); - }, - SourceOffense::UseStdStatement => { + } + SourceOffense::UseStdStatement(stmt) => { println!(" - Source code contains an explicit `use std::` statement."); + println!("{}", stmt); } } }