2021-07-03 16:42:59 -05:00
|
|
|
use hir::AsAssocItem;
|
2022-03-06 12:01:30 -06:00
|
|
|
use ide_db::{
|
|
|
|
helpers::mod_path_to_ast,
|
|
|
|
imports::insert_use::{insert_use, ImportScope},
|
2021-07-03 16:42:59 -05:00
|
|
|
};
|
2021-07-10 10:03:24 -05:00
|
|
|
use syntax::{
|
|
|
|
ast::{self, make},
|
|
|
|
match_ast, ted, AstNode, SyntaxNode,
|
|
|
|
};
|
2019-10-27 08:46:49 -05:00
|
|
|
|
2020-11-24 15:25:13 -06:00
|
|
|
use crate::{AssistContext, AssistId, AssistKind, Assists};
|
2019-10-27 08:46:49 -05:00
|
|
|
|
2020-02-07 15:35:34 -06:00
|
|
|
// Assist: replace_qualified_name_with_use
|
2019-10-27 09:49:39 -05:00
|
|
|
//
|
2020-02-07 15:35:34 -06:00
|
|
|
// Adds a use statement for a given fully-qualified name.
|
2019-10-27 09:49:39 -05:00
|
|
|
//
|
|
|
|
// ```
|
2021-07-03 16:42:59 -05:00
|
|
|
// # mod std { pub mod collections { pub struct HashMap<T, U>(T, U); } }
|
2021-01-06 14:15:48 -06:00
|
|
|
// fn process(map: std::collections::$0HashMap<String, String>) {}
|
2019-10-27 09:49:39 -05:00
|
|
|
// ```
|
|
|
|
// ->
|
|
|
|
// ```
|
|
|
|
// use std::collections::HashMap;
|
|
|
|
//
|
2021-07-03 16:42:59 -05:00
|
|
|
// # mod std { pub mod collections { pub struct HashMap<T, U>(T, U); } }
|
2019-10-27 09:49:39 -05:00
|
|
|
// fn process(map: HashMap<String, String>) {}
|
|
|
|
// ```
|
2020-05-06 11:45:35 -05:00
|
|
|
pub(crate) fn replace_qualified_name_with_use(
|
|
|
|
acc: &mut Assists,
|
|
|
|
ctx: &AssistContext,
|
|
|
|
) -> Option<()> {
|
2019-10-27 08:46:49 -05:00
|
|
|
let path: ast::Path = ctx.find_node_at_offset()?;
|
|
|
|
// We don't want to mess with use statements
|
2021-07-03 16:42:59 -05:00
|
|
|
if path.syntax().ancestors().find_map(ast::UseTree::cast).is_some() {
|
|
|
|
cov_mark::hit!(not_applicable_in_use);
|
2019-10-27 08:46:49 -05:00
|
|
|
return None;
|
|
|
|
}
|
2021-07-03 16:42:59 -05:00
|
|
|
|
2020-08-15 11:50:41 -05:00
|
|
|
if path.qualifier().is_none() {
|
2021-03-08 14:19:44 -06:00
|
|
|
cov_mark::hit!(dont_import_trivial_paths);
|
2019-10-27 08:46:49 -05:00
|
|
|
return None;
|
|
|
|
}
|
|
|
|
|
2021-07-10 10:03:24 -05:00
|
|
|
// only offer replacement for non assoc items
|
|
|
|
match ctx.sema.resolve_path(&path)? {
|
|
|
|
hir::PathResolution::Def(def) if def.as_assoc_item(ctx.sema.db).is_none() => (),
|
|
|
|
_ => return None,
|
|
|
|
}
|
|
|
|
// then search for an import for the first path segment of what we want to replace
|
|
|
|
// that way it is less likely that we import the item from a different location due re-exports
|
|
|
|
let module = match ctx.sema.resolve_path(&path.first_qualifier_or_self())? {
|
|
|
|
hir::PathResolution::Def(module @ hir::ModuleDef::Module(_)) => module,
|
2021-07-03 16:42:59 -05:00
|
|
|
_ => return None,
|
|
|
|
};
|
|
|
|
|
2021-07-20 11:57:12 -05:00
|
|
|
let starts_with_name_ref = !matches!(
|
|
|
|
path.first_segment().and_then(|it| it.kind()),
|
|
|
|
Some(
|
|
|
|
ast::PathSegmentKind::CrateKw
|
|
|
|
| ast::PathSegmentKind::SuperKw
|
|
|
|
| ast::PathSegmentKind::SelfKw
|
|
|
|
)
|
|
|
|
);
|
|
|
|
let path_to_qualifier = starts_with_name_ref
|
|
|
|
.then(|| {
|
2022-03-31 04:12:08 -05:00
|
|
|
ctx.sema.scope(path.syntax())?.module().find_use_path_prefixed(
|
|
|
|
ctx.sema.db,
|
|
|
|
module,
|
|
|
|
ctx.config.insert_use.prefix_kind,
|
|
|
|
)
|
2021-07-20 11:57:12 -05:00
|
|
|
})
|
|
|
|
.flatten();
|
|
|
|
|
2021-11-03 15:12:36 -05:00
|
|
|
let scope = ImportScope::find_insert_use_container(path.syntax(), &ctx.sema)?;
|
2021-07-10 10:03:24 -05:00
|
|
|
let target = path.syntax().text_range();
|
2020-05-06 11:45:35 -05:00
|
|
|
acc.add(
|
2020-07-02 16:48:35 -05:00
|
|
|
AssistId("replace_qualified_name_with_use", AssistKind::RefactorRewrite),
|
2020-02-07 15:35:34 -06:00
|
|
|
"Replace qualified path with use",
|
2020-05-06 05:51:28 -05:00
|
|
|
target,
|
2020-05-06 11:45:35 -05:00
|
|
|
|builder| {
|
2020-06-13 12:05:31 -05:00
|
|
|
// Now that we've brought the name into scope, re-qualify all paths that could be
|
|
|
|
// affected (that is, all paths inside the node we added the `use` to).
|
2021-06-18 16:53:41 -05:00
|
|
|
let scope = match scope {
|
|
|
|
ImportScope::File(it) => ImportScope::File(builder.make_mut(it)),
|
|
|
|
ImportScope::Module(it) => ImportScope::Module(builder.make_mut(it)),
|
|
|
|
ImportScope::Block(it) => ImportScope::Block(builder.make_mut(it)),
|
|
|
|
};
|
2022-01-28 02:15:23 -06:00
|
|
|
shorten_paths(scope.as_syntax_node(), &path);
|
|
|
|
let path = drop_generic_args(&path);
|
2021-07-10 10:03:24 -05:00
|
|
|
// stick the found import in front of the to be replaced path
|
2021-07-20 11:57:12 -05:00
|
|
|
let path = match path_to_qualifier.and_then(|it| mod_path_to_ast(&it).qualifier()) {
|
2021-07-10 10:03:24 -05:00
|
|
|
Some(qualifier) => make::path_concat(qualifier, path),
|
|
|
|
None => path,
|
|
|
|
};
|
2021-06-18 16:53:41 -05:00
|
|
|
insert_use(&scope, path, &ctx.config.insert_use);
|
2020-02-28 14:53:20 -06:00
|
|
|
},
|
|
|
|
)
|
2019-02-11 14:57:38 -06:00
|
|
|
}
|
|
|
|
|
2022-01-28 02:15:23 -06:00
|
|
|
fn drop_generic_args(path: &ast::Path) -> ast::Path {
|
|
|
|
let path = path.clone_for_update();
|
|
|
|
if let Some(segment) = path.segment() {
|
|
|
|
if let Some(generic_args) = segment.generic_arg_list() {
|
|
|
|
ted::remove(generic_args.syntax());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
path
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Mutates `node` to shorten `path` in all descendants of `node`.
|
2021-04-20 12:28:18 -05:00
|
|
|
fn shorten_paths(node: &SyntaxNode, path: &ast::Path) {
|
2020-06-13 12:05:31 -05:00
|
|
|
for child in node.children() {
|
|
|
|
match_ast! {
|
|
|
|
match child {
|
|
|
|
// Don't modify `use` items, as this can break the `use` item when injecting a new
|
|
|
|
// import into the use tree.
|
2021-12-14 05:44:31 -06:00
|
|
|
ast::Use(_) => continue,
|
2020-06-13 12:05:31 -05:00
|
|
|
// Don't descend into submodules, they don't have the same `use` items in scope.
|
2021-07-03 16:42:59 -05:00
|
|
|
// FIXME: This isn't true due to `super::*` imports?
|
2021-12-14 05:44:31 -06:00
|
|
|
ast::Module(_) => continue,
|
2021-04-20 12:28:18 -05:00
|
|
|
ast::Path(p) => if maybe_replace_path(p.clone(), path.clone()).is_none() {
|
|
|
|
shorten_paths(p.syntax(), path);
|
2020-06-13 12:05:31 -05:00
|
|
|
},
|
2021-04-20 12:28:18 -05:00
|
|
|
_ => shorten_paths(&child, path),
|
2020-06-13 12:05:31 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-19 19:05:22 -05:00
|
|
|
fn maybe_replace_path(path: ast::Path, target: ast::Path) -> Option<()> {
|
2021-07-03 16:42:59 -05:00
|
|
|
if !path_eq_no_generics(path.clone(), target) {
|
2020-06-13 12:05:31 -05:00
|
|
|
return None;
|
|
|
|
}
|
|
|
|
|
2020-06-15 17:49:59 -05:00
|
|
|
// Shorten `path`, leaving only its last segment.
|
|
|
|
if let Some(parent) = path.qualifier() {
|
2021-04-19 19:05:22 -05:00
|
|
|
ted::remove(parent.syntax());
|
2020-06-15 17:49:59 -05:00
|
|
|
}
|
|
|
|
if let Some(double_colon) = path.coloncolon_token() {
|
2021-04-19 19:05:22 -05:00
|
|
|
ted::remove(&double_colon);
|
2020-06-13 12:05:31 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
Some(())
|
|
|
|
}
|
|
|
|
|
2021-07-03 16:42:59 -05:00
|
|
|
fn path_eq_no_generics(lhs: ast::Path, rhs: ast::Path) -> bool {
|
2020-06-15 17:49:59 -05:00
|
|
|
let mut lhs_curr = lhs;
|
|
|
|
let mut rhs_curr = rhs;
|
|
|
|
loop {
|
2021-07-03 16:42:59 -05:00
|
|
|
match lhs_curr.segment().zip(rhs_curr.segment()) {
|
|
|
|
Some((lhs, rhs))
|
|
|
|
if lhs.coloncolon_token().is_some() == rhs.coloncolon_token().is_some()
|
|
|
|
&& lhs
|
|
|
|
.name_ref()
|
|
|
|
.zip(rhs.name_ref())
|
2021-09-13 11:50:19 -05:00
|
|
|
.map_or(false, |(lhs, rhs)| lhs.text() == rhs.text()) => {}
|
2020-06-15 17:49:59 -05:00
|
|
|
_ => return false,
|
|
|
|
}
|
|
|
|
|
|
|
|
match (lhs_curr.qualifier(), rhs_curr.qualifier()) {
|
|
|
|
(Some(lhs), Some(rhs)) => {
|
|
|
|
lhs_curr = lhs;
|
|
|
|
rhs_curr = rhs;
|
|
|
|
}
|
|
|
|
(None, None) => return true,
|
|
|
|
_ => return false,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-06 08:34:37 -06:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
2020-05-06 03:16:55 -05:00
|
|
|
use crate::tests::{check_assist, check_assist_not_applicable};
|
2019-02-06 08:34:37 -06:00
|
|
|
|
2019-10-27 09:26:51 -05:00
|
|
|
use super::*;
|
|
|
|
|
2020-10-13 18:39:58 -05:00
|
|
|
#[test]
|
|
|
|
fn test_replace_already_imported() {
|
|
|
|
check_assist(
|
|
|
|
replace_qualified_name_with_use,
|
2021-07-03 16:42:59 -05:00
|
|
|
r"
|
|
|
|
mod std { pub mod fs { pub struct Path; } }
|
|
|
|
use std::fs;
|
2020-10-13 18:39:58 -05:00
|
|
|
|
|
|
|
fn main() {
|
2021-01-06 14:15:48 -06:00
|
|
|
std::f$0s::Path
|
2020-10-13 18:39:58 -05:00
|
|
|
}",
|
2021-07-03 16:42:59 -05:00
|
|
|
r"
|
|
|
|
mod std { pub mod fs { pub struct Path; } }
|
|
|
|
use std::fs;
|
2020-10-13 18:39:58 -05:00
|
|
|
|
|
|
|
fn main() {
|
|
|
|
fs::Path
|
|
|
|
}",
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2019-02-06 08:34:37 -06:00
|
|
|
#[test]
|
2020-02-07 15:35:34 -06:00
|
|
|
fn test_replace_add_use_no_anchor() {
|
2019-02-06 08:34:37 -06:00
|
|
|
check_assist(
|
2020-02-07 15:35:34 -06:00
|
|
|
replace_qualified_name_with_use,
|
2020-06-15 15:39:26 -05:00
|
|
|
r"
|
2021-07-03 16:42:59 -05:00
|
|
|
mod std { pub mod fs { pub struct Path; } }
|
|
|
|
std::fs::Path$0
|
2019-02-06 08:34:37 -06:00
|
|
|
",
|
2020-06-15 15:39:26 -05:00
|
|
|
r"
|
2021-07-03 16:42:59 -05:00
|
|
|
use std::fs::Path;
|
2019-02-06 08:34:37 -06:00
|
|
|
|
2021-07-03 16:42:59 -05:00
|
|
|
mod std { pub mod fs { pub struct Path; } }
|
|
|
|
Path
|
2019-02-06 08:34:37 -06:00
|
|
|
",
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2021-07-03 16:42:59 -05:00
|
|
|
fn test_replace_add_use_no_anchor_middle_segment() {
|
2019-02-06 08:34:37 -06:00
|
|
|
check_assist(
|
2020-02-07 15:35:34 -06:00
|
|
|
replace_qualified_name_with_use,
|
2020-06-15 15:39:26 -05:00
|
|
|
r"
|
2021-07-03 16:42:59 -05:00
|
|
|
mod std { pub mod fs { pub struct Path; } }
|
|
|
|
std::fs$0::Path
|
2019-02-06 08:34:37 -06:00
|
|
|
",
|
2020-06-15 15:39:26 -05:00
|
|
|
r"
|
2021-07-03 16:42:59 -05:00
|
|
|
use std::fs;
|
2019-02-06 08:34:37 -06:00
|
|
|
|
2021-07-03 16:42:59 -05:00
|
|
|
mod std { pub mod fs { pub struct Path; } }
|
|
|
|
fs::Path
|
2019-02-06 08:34:37 -06:00
|
|
|
",
|
|
|
|
);
|
|
|
|
}
|
2022-01-13 13:08:04 -06:00
|
|
|
|
2019-02-06 08:34:37 -06:00
|
|
|
#[test]
|
2020-08-15 11:50:41 -05:00
|
|
|
fn dont_import_trivial_paths() {
|
2021-03-08 14:19:44 -06:00
|
|
|
cov_mark::check!(dont_import_trivial_paths);
|
2021-07-03 16:42:59 -05:00
|
|
|
check_assist_not_applicable(replace_qualified_name_with_use, r"impl foo$0 for () {}");
|
2019-02-11 14:57:38 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2020-02-07 15:35:34 -06:00
|
|
|
fn test_replace_not_applicable_in_use() {
|
2021-07-03 16:42:59 -05:00
|
|
|
cov_mark::check!(not_applicable_in_use);
|
|
|
|
check_assist_not_applicable(replace_qualified_name_with_use, r"use std::fmt$0;");
|
2020-06-13 12:05:31 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn replaces_all_affected_paths() {
|
|
|
|
check_assist(
|
|
|
|
replace_qualified_name_with_use,
|
2020-06-15 15:39:26 -05:00
|
|
|
r"
|
2021-07-03 16:42:59 -05:00
|
|
|
mod std { pub mod fmt { pub trait Debug {} } }
|
2020-06-13 12:05:31 -05:00
|
|
|
fn main() {
|
2021-01-06 14:15:48 -06:00
|
|
|
std::fmt::Debug$0;
|
2020-06-13 12:05:31 -05:00
|
|
|
let x: std::fmt::Debug = std::fmt::Debug;
|
|
|
|
}
|
|
|
|
",
|
2020-06-15 15:39:26 -05:00
|
|
|
r"
|
2020-06-13 12:05:31 -05:00
|
|
|
use std::fmt::Debug;
|
|
|
|
|
2021-07-03 16:42:59 -05:00
|
|
|
mod std { pub mod fmt { pub trait Debug {} } }
|
2020-06-13 12:05:31 -05:00
|
|
|
fn main() {
|
|
|
|
Debug;
|
|
|
|
let x: Debug = Debug;
|
|
|
|
}
|
|
|
|
",
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn does_not_replace_in_submodules() {
|
|
|
|
check_assist(
|
|
|
|
replace_qualified_name_with_use,
|
2020-06-15 15:39:26 -05:00
|
|
|
r"
|
2021-07-03 16:42:59 -05:00
|
|
|
mod std { pub mod fmt { pub trait Debug {} } }
|
2020-06-13 12:05:31 -05:00
|
|
|
fn main() {
|
2021-01-06 14:15:48 -06:00
|
|
|
std::fmt::Debug$0;
|
2020-06-13 12:05:31 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
mod sub {
|
|
|
|
fn f() {
|
|
|
|
std::fmt::Debug;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
",
|
2020-06-15 15:39:26 -05:00
|
|
|
r"
|
2020-06-13 12:05:31 -05:00
|
|
|
use std::fmt::Debug;
|
|
|
|
|
2021-07-03 16:42:59 -05:00
|
|
|
mod std { pub mod fmt { pub trait Debug {} } }
|
2020-06-13 12:05:31 -05:00
|
|
|
fn main() {
|
|
|
|
Debug;
|
|
|
|
}
|
|
|
|
|
|
|
|
mod sub {
|
|
|
|
fn f() {
|
|
|
|
std::fmt::Debug;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
",
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn does_not_replace_in_use() {
|
|
|
|
check_assist(
|
|
|
|
replace_qualified_name_with_use,
|
2020-06-15 15:39:26 -05:00
|
|
|
r"
|
2021-07-03 16:42:59 -05:00
|
|
|
mod std { pub mod fmt { pub trait Display {} } }
|
2020-06-13 12:05:31 -05:00
|
|
|
use std::fmt::Display;
|
|
|
|
|
|
|
|
fn main() {
|
2021-01-06 14:15:48 -06:00
|
|
|
std::fmt$0;
|
2020-06-13 12:05:31 -05:00
|
|
|
}
|
|
|
|
",
|
2020-06-15 15:39:26 -05:00
|
|
|
r"
|
2021-07-03 16:42:59 -05:00
|
|
|
mod std { pub mod fmt { pub trait Display {} } }
|
2021-11-19 03:06:36 -06:00
|
|
|
use std::fmt::{Display, self};
|
2020-06-13 12:05:31 -05:00
|
|
|
|
|
|
|
fn main() {
|
|
|
|
fmt;
|
2020-08-03 14:17:05 -05:00
|
|
|
}
|
|
|
|
",
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2021-07-03 16:42:59 -05:00
|
|
|
fn does_not_replace_assoc_item_path() {
|
|
|
|
check_assist_not_applicable(
|
2020-08-03 14:17:05 -05:00
|
|
|
replace_qualified_name_with_use,
|
|
|
|
r"
|
2021-07-03 16:42:59 -05:00
|
|
|
pub struct Foo;
|
|
|
|
impl Foo {
|
|
|
|
pub fn foo() {}
|
2020-08-03 14:17:05 -05:00
|
|
|
}
|
|
|
|
|
2021-07-03 16:42:59 -05:00
|
|
|
fn main() {
|
|
|
|
Foo::foo$0();
|
2019-02-12 14:14:51 -06:00
|
|
|
}
|
2021-07-10 10:03:24 -05:00
|
|
|
",
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn replace_reuses_path_qualifier() {
|
|
|
|
check_assist(
|
|
|
|
replace_qualified_name_with_use,
|
|
|
|
r"
|
|
|
|
pub mod foo {
|
2021-07-20 11:57:12 -05:00
|
|
|
pub struct Foo;
|
2021-07-10 10:03:24 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
mod bar {
|
|
|
|
pub use super::foo::Foo as Bar;
|
|
|
|
}
|
|
|
|
|
|
|
|
fn main() {
|
|
|
|
foo::Foo$0;
|
|
|
|
}
|
|
|
|
",
|
|
|
|
r"
|
|
|
|
use foo::Foo;
|
|
|
|
|
|
|
|
pub mod foo {
|
2021-07-20 11:57:12 -05:00
|
|
|
pub struct Foo;
|
2021-07-10 10:03:24 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
mod bar {
|
|
|
|
pub use super::foo::Foo as Bar;
|
|
|
|
}
|
|
|
|
|
|
|
|
fn main() {
|
|
|
|
Foo;
|
|
|
|
}
|
2022-01-05 16:46:58 -06:00
|
|
|
",
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn replace_does_not_always_try_to_replace_by_full_item_path() {
|
|
|
|
check_assist(
|
|
|
|
replace_qualified_name_with_use,
|
|
|
|
r"
|
|
|
|
use std::mem;
|
|
|
|
|
|
|
|
mod std {
|
|
|
|
pub mod mem {
|
|
|
|
pub fn drop<T>(_: T) {}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn main() {
|
|
|
|
mem::drop$0(0);
|
|
|
|
}
|
|
|
|
",
|
|
|
|
r"
|
|
|
|
use std::mem::{self, drop};
|
|
|
|
|
|
|
|
mod std {
|
|
|
|
pub mod mem {
|
|
|
|
pub fn drop<T>(_: T) {}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn main() {
|
|
|
|
drop(0);
|
|
|
|
}
|
2022-01-28 02:15:23 -06:00
|
|
|
",
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn replace_should_drop_generic_args_in_use() {
|
|
|
|
check_assist(
|
|
|
|
replace_qualified_name_with_use,
|
|
|
|
r"
|
|
|
|
mod std {
|
|
|
|
pub mod mem {
|
|
|
|
pub fn drop<T>(_: T) {}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn main() {
|
|
|
|
std::mem::drop::<usize>$0(0);
|
|
|
|
}
|
|
|
|
",
|
|
|
|
r"
|
|
|
|
use std::mem::drop;
|
|
|
|
|
|
|
|
mod std {
|
|
|
|
pub mod mem {
|
|
|
|
pub fn drop<T>(_: T) {}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn main() {
|
|
|
|
drop::<usize>(0);
|
|
|
|
}
|
2021-07-03 16:42:59 -05:00
|
|
|
",
|
2019-02-12 14:14:51 -06:00
|
|
|
);
|
|
|
|
}
|
2019-02-06 08:34:37 -06:00
|
|
|
}
|