cargo-up 0.0.6

Upgrade your dependencies by automatically fixing your code
Documentation
use crate::{
    ra_ap_syntax::{
        ast::{self, Expr, HasName, NameOrNameRef},
        AstNode,
    },
    semver::{Error as SemVerError, Version as SemverVersion},
    utils::{normalize, INTERNAL_ERR},
    Semantics, Upgrader,
};

use anyhow::Result as AnyResult;
use paste::paste;

use std::{collections::HashMap as Map, ops::Deref};

pub(crate) type Hook<T> = Box<dyn Fn(&mut Upgrader, &T, &Semantics)>;

pub(crate) struct Hooks<T>(Map<String, Map<String, Vec<Hook<T>>>>);

impl<T> Default for Hooks<T> {
    fn default() -> Self {
        Self(Map::new())
    }
}

impl<T> Deref for Hooks<T> {
    type Target = Map<String, Map<String, Vec<Hook<T>>>>;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl<T> Hooks<T> {
    pub(crate) fn insert(&mut self, path: &str, name: &str, hook: Hook<T>) {
        let path = path.to_string();
        let name = name.to_string();

        if !self.0.contains_key(&path) {
            self.0.insert(path.clone(), Map::new());
        }

        let hook_map = self.0.get_mut(&path).expect(INTERNAL_ERR);

        if !hook_map.contains_key(&name) {
            hook_map.insert(name.clone(), vec![]);
        }

        hook_map.get_mut(&name).expect(INTERNAL_ERR).push(hook);
    }
}

macro_rules! members {
    ($($node:ident,)*) => {
        paste! {
            pub struct Version {
                pub(crate) version: SemverVersion,
                pub(crate) peers: Vec<String>,
                pub(crate) init: Option<Box<dyn Fn(&mut Upgrader, &SemverVersion) -> AnyResult<()>>>,
                $(
                    pub(crate) [<hook_ $node:snake>]: Vec<Hook<ast::$node>>,
                    pub(crate) [<hook_ $node:snake _on>]: Hooks<ast::$node>,
                )*
            }

            impl Version {
                pub fn new(version: &str) -> Result<Self, SemVerError> {
                    Ok(Self {
                        version: SemverVersion::parse(version)?,
                        peers: vec![],
                        init: None,
                        $(
                            [<hook_ $node:snake>]: Vec::new(),
                            [<hook_ $node:snake _on>]: Hooks::default(),
                        )*
                    })
                }
            }
        }
    };
}

macro_rules! methods {
    ($($node:ident,)*) => {
        paste! {
            $(
                pub fn [<hook_ $node:snake>]<F>(mut self, f: F) -> Self
                where
                    F: Fn(&mut Upgrader, &ast::$node, &Semantics) + 'static,
                {
                    self.[<hook_ $node:snake>].push(Box::new(f));
                    self
                }

                pub fn [<hook_ $node:snake _on>]<F>(mut self, path: &str, name: &str, f: F) -> Self
                where
                    F: Fn(&mut Upgrader, &ast::$node, &Semantics) + 'static
                {
                    self.[<hook_ $node:snake _on>].insert(path, name, Box::new(f));
                    self
                }
            )*
        }
    };
}

members!(
    MethodCallExpr,
    CallExpr,
    IdentPat,
    Path,
    PathExpr,
    PathPat,
    FieldExpr,
    RecordPat,
    RecordExpr,
    RecordExprField,
    RecordPatField,
    TupleStructPat,
);

impl Version {
    pub fn peers(mut self, peers: &[&str]) -> Self {
        self.peers = peers.to_vec().iter().map(|x| normalize(*x)).collect();
        self
    }

    pub fn init<F>(mut self, init: F) -> Self
    where
        F: Fn(&mut Upgrader, &SemverVersion) -> AnyResult<()> + 'static,
    {
        self.init = Some(Box::new(init));
        self
    }

    pub fn rename_structs(mut self, name: &str, map: &'static [[&str; 2]]) -> Self {
        for rename in map.into_iter() {
            self = self.hook_path_on(name, rename[0], move |u, n, _| {
                u.replace(n.segment(), rename[1]);
            })
        }

        self
    }

    pub fn rename_methods(mut self, name: &str, map: &'static [[&str; 2]]) -> Self {
        for rename in map.into_iter() {
            self = self
                .hook_method_call_expr_on(name, rename[0], move |u, n, _| {
                    u.replace(n.name_ref(), rename[1]);
                })
                .hook_path_expr_on(name, rename[0], move |u, n, _| {
                    u.replace(n.path(), rename[1]);
                });
        }

        self
    }

    pub fn rename_members(mut self, name: &str, map: &'static [[&str; 2]]) -> Self {
        for rename in map.into_iter() {
            self = self
                .hook_field_expr_on(name, rename[0], move |u, n, _| {
                    u.replace(n.name_ref(), rename[1]);
                })
                .hook_record_pat_field_on(name, rename[0], move |u, n, _| match n.field_name() {
                    Some(NameOrNameRef::Name(_)) => {
                        u.replace(n.syntax().text_range(), format!("{}: {}", rename[1], n))
                    }
                    Some(NameOrNameRef::NameRef(name_ref)) => {
                        u.replace(name_ref.syntax().text_range(), rename[1])
                    }
                    _ => {}
                })
                .hook_record_expr_field_on(name, rename[0], move |u, n, _| {
                    if let Some(name_ref) = n.name_ref() {
                        u.replace(name_ref.syntax().text_range(), rename[1]);
                    } else if let Some(Expr::PathExpr(path_expr)) = n.expr() {
                        u.replace(
                            path_expr.syntax().text_range(),
                            format!("{}: {}", rename[1], rename[0]),
                        );
                    }
                });
        }

        self
    }

    pub fn rename_variants(mut self, name: &str, map: &'static [[&str; 2]]) -> Self {
        for rename in map.into_iter() {
            self = self
                .hook_path_expr_on(name, rename[0], move |u, n, _| {
                    u.replace(n.path(), rename[1]);
                })
                .hook_path_pat_on(name, rename[0], move |u, n, _| {
                    u.replace(n.path(), rename[1]);
                })
                .hook_record_pat_on(name, rename[0], move |u, n, _| {
                    u.replace(n.path(), rename[1]);
                })
                .hook_record_expr_on(name, rename[0], move |u, n, _| {
                    u.replace(n.path(), rename[1]);
                })
                .hook_tuple_struct_pat_on(name, rename[0], move |u, n, _| {
                    u.replace(n.path(), rename[1]);
                })
                .hook_ident_pat_on(name, rename[0], move |u, n, _| {
                    u.replace(n.name(), rename[1]);
                });
        }

        self
    }
}

impl Version {
    methods!(
        MethodCallExpr,
        CallExpr,
        IdentPat,
        Path,
        PathExpr,
        PathPat,
        FieldExpr,
        RecordPat,
        RecordExpr,
        RecordExprField,
        RecordPatField,
        TupleStructPat,
    );
}