Repository: dtolnay/cargo-expand (language: rust)

Issue:
Switch to using syn-select
dtolnay/request-for-implementation#1 asked for a crate to handle syntactic filtering by path in a single file. [syn-select](https://crates.io/crates/syn-select) is now available, matching the test cases specified in the original ticket.


Discussion / hints:

Fix patch:
diff --git a/.travis.yml b/.travis.yml
index 0919b7c..29658ef 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -6,7 +6,7 @@ rust:
   - stable
   - beta
   - nightly
-  - 1.31.0
+  - 1.32.0
 
 script:
   - cargo check
diff --git a/Cargo.lock b/Cargo.lock
index 6eecee0..921bd61 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -135,6 +135,7 @@ dependencies = [
  "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
  "structopt 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
  "syn 0.15.26 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn-select 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "tempfile 3.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "toolchain_find 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
@@ -1035,6 +1036,14 @@ dependencies = [
  "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "syn-select"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "syn 0.15.26 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "synstructure"
 version = "0.10.1"
@@ -1383,6 +1392,7 @@ dependencies = [
 "checksum structopt 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "670ad348dc73012fcf78c71f06f9d942232cdd4c859d4b6975e27836c3efc0c3"
 "checksum structopt-derive 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "ef98172b1a00b0bec738508d3726540edcbd186d50dfd326f2b1febbb3559f04"
 "checksum syn 0.15.26 (registry+https://github.com/rust-lang/crates.io-index)" = "f92e629aa1d9c827b2bb8297046c1ccffc57c99b947a680d3ccff1f136a3bee9"
+"checksum syn-select 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "eae797fe9aa6ee5b478f3ae7b2ca1db303443853bcf65d6192e7f134124333a8"
 "checksum synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "73687139bf99285483c96ac0add482c3776528beac1d97d444f6e91f203a2015"
 "checksum syntect 3.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e02dd9df97a68a2d005ace28ff24c610abfc3ce17afcfdb22a077645dabb599a"
 "checksum tempfile 3.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "7e91405c14320e5c79b3d148e1c86f40749a36e490642202a31689cb1a3452b2"
diff --git a/Cargo.toml b/Cargo.toml
index 1896212..983cd23 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -17,6 +17,7 @@ prettyprint = "0.3"
 proc-macro2 = "0.4"
 quote = { version = "0.6", default-features = false }
 structopt = "0.2"
+syn-select = "0.1.4"
 tempfile = "3.0"
 toolchain_find = "0.1"
 
diff --git a/src/filter.rs b/src/filter.rs
deleted file mode 100644
index 8334fd0..0000000
--- a/src/filter.rs
+++ /dev/null
@@ -1,168 +0,0 @@
-use std::fmt::{self, Display};
-use std::mem;
-use std::str::FromStr;
-
-use proc_macro2::Span;
-use syn::{File, Ident, Item, ItemConst, ItemFn, ItemMod, ItemType, TraitItem, Visibility};
-
-#[derive(Debug)]
-pub struct Filter {
-    segments: Vec<Ident>,
-}
-
-impl FromStr for Filter {
-    type Err = String;
-
-    fn from_str(input: &str) -> Result<Self, Self::Err> {
-        let mut segments = Vec::new();
-        for segment in input.split("::") {
-            match syn::parse_str(segment) {
-                Ok(ident) => segments.push(ident),
-                Err(_) => return Err(format!("`{}` is not an identifier", segment)),
-            }
-        }
-        if segments.is_empty() {
-            return Err("empty path".to_owned());
-        }
-        Ok(Filter { segments })
-    }
-}
-
-impl Display for Filter {
-    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
-        for (i, segment) in self.segments.iter().enumerate() {
-            if i > 0 {
-                formatter.write_str("::")?;
-            }
-            Display::fmt(segment, formatter)?;
-        }
-        Ok(())
-    }
-}
-
-pub fn filter(syntax_tree: &mut File, filter: &Filter) {
-    syntax_tree.shebang = None;
-    syntax_tree.attrs.clear();
-
-    let items = mem::replace(&mut syntax_tree.items, Vec::new());
-    let root_mod = ItemMod {
-        attrs: Vec::new(),
-        vis: Visibility::Inherited,
-        mod_token: Default::default(),
-        ident: Ident::new("root", Span::call_site()),
-        content: Some((Default::default(), items)),
-        semi: None,
-    };
-    let mut items = vec![Item::Mod(root_mod)];
-
-    for segment in &filter.segments {
-        items = items
-            .into_iter()
-            .flat_map(contents_of_item)
-            .filter(|item| name_of_item(item) == Some(segment))
-            .collect();
-    }
-
-    if items.len() == 1 {
-        items = match items.pop().unwrap() {
-            Item::Mod(ItemMod {
-                content: Some((_, nested)),
-                ..
-            }) => nested,
-            other => vec![other],
-        };
-    }
-
-    syntax_tree.items = items;
-}
-
-fn contents_of_item(item: Item) -> Vec<Item> {
-    match item {
-        Item::ExternCrate(_) => Vec::new(),
-        Item::Use(_) => Vec::new(),
-        Item::Static(_) => Vec::new(),
-        Item::Const(_) => Vec::new(),
-        Item::Fn(_) => Vec::new(),
-        Item::Mod(item_mod) => match item_mod.content {
-            Some((_, nested)) => nested,
-            None => Vec::new(),
-        },
-        Item::ForeignMod(_) => Vec::new(),
-        Item::Type(_) => Vec::new(),
-        Item::Existential(_) => Vec::new(),
-        Item::Struct(_) => Vec::new(),
-        Item::Enum(_) => Vec::new(),
-        Item::Union(_) => Vec::new(),
-        Item::Trait(item_trait) => item_trait
-            .items
-            .into_iter()
-            .filter_map(|trait_item| match trait_item {
-                TraitItem::Const(item) => Some(Item::Const(ItemConst {
-                    attrs: item.attrs,
-                    vis: Visibility::Inherited,
-                    const_token: item.const_token,
-                    ident: item.ident,
-                    colon_token: item.colon_token,
-                    ty: Box::new(item.ty),
-                    eq_token: item.default.as_ref()?.0,
-                    expr: Box::new(item.default?.1),
-                    semi_token: item.semi_token,
-                })),
-                TraitItem::Method(item) => Some(Item::Fn(ItemFn {
-                    attrs: item.attrs,
-                    vis: Visibility::Inherited,
-                    constness: item.sig.constness,
-                    unsafety: item.sig.unsafety,
-                    asyncness: item.sig.asyncness,
-                    abi: item.sig.abi,
-                    ident: item.sig.ident,
-                    decl: Box::new(item.sig.decl),
-                    block: Box::new(item.default?),
-                })),
-                TraitItem::Type(item) => Some(Item::Type(ItemType {
-                    attrs: item.attrs,
-                    vis: Visibility::Inherited,
-                    type_token: item.type_token,
-                    ident: item.ident,
-                    generics: item.generics,
-                    eq_token: item.default.as_ref()?.0,
-                    ty: Box::new(item.default?.1),
-                    semi_token: item.semi_token,
-                })),
-                TraitItem::Macro(_) => None,
-                TraitItem::Verbatim(_) => None,
-            })
-            .collect(),
-        Item::TraitAlias(_) => Vec::new(),
-        Item::Impl(_) => Vec::new(),
-        Item::Macro(_) => Vec::new(),
-        Item::Macro2(_) => Vec::new(),
-        Item::Verbatim(_) => Vec::new(),
-    }
-}
-
-fn name_of_item(item: &Item) -> Option<&Ident> {
-    match item {
-        Item::ExternCrate(item) => match &item.rename {
-            Some((_, rename)) => Some(rename),
-            None => Some(&item.ident),
-        },
-        Item::Use(_) => None,
-        Item::Static(item) => Some(&item.ident),
-        Item::Const(item) => Some(&item.ident),
-        Item::Fn(item) => Some(&item.ident),
-        Item::Mod(item) => Some(&item.ident),
-        Item::ForeignMod(_) => None,
-        Item::Type(item) => Some(&item.ident),
-        Item::Existential(item) => Some(&item.ident),
-        Item::Struct(item) => Some(&item.ident),
-        Item::Enum(item) => Some(&item.ident),
-        Item::Union(item) => Some(&item.ident),
-        Item::Trait(item) => Some(&item.ident),
-        Item::TraitAlias(item) => Some(&item.ident),
-        Item::Impl(_) => None,
-        Item::Macro(item) => item.ident.as_ref(),
-        Item::Macro2(item) => Some(&item.ident),
-        Item::Verbatim(_) => None,
-    }
-}
diff --git a/src/main.rs b/src/main.rs
index ca0ce3f..22e6aa3 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,6 +1,5 @@
 mod edit;
 mod error;
-mod filter;
 mod opts;
 
 use std::env;
@@ -141,7 +140,9 @@ fn cargo_expand() -> Result<i32> {
         if let Ok(mut syntax_tree) = syn::parse_file(&content) {
             edit::remove_macro_rules(&mut syntax_tree);
             if let Some(filter) = args.item {
-                filter::filter(&mut syntax_tree, &filter);
+                syntax_tree.shebang = None;
+                syntax_tree.attrs.clear();
+                syntax_tree.items = filter.apply_to(&syntax_tree);
                 if syntax_tree.items.is_empty() {
                     eprintln!("WARNING: no such item: {}", filter);
                     return Ok(1);
diff --git a/src/opts.rs b/src/opts.rs
index 57d5f23..e6164ec 100644
--- a/src/opts.rs
+++ b/src/opts.rs
@@ -2,8 +2,7 @@ use std::path::PathBuf;
 
 use structopt::clap::AppSettings;
 use structopt::StructOpt;
-
-use crate::filter::Filter;
+use syn_select::Selector;
 
 #[derive(StructOpt)]
 #[structopt(bin_name = "cargo")]
@@ -97,5 +96,5 @@ pub struct Args {
 
     /// Local path to module or other named item to expand, e.g. os::unix::ffi
     #[structopt(value_name = "ITEM")]
-    pub item: Option<Filter>,
+    pub item: Option<Selector>,
 }


Test patch: