diff options
| author | bors <bors@rust-lang.org> | 2022-07-18 10:24:53 +0000 |
|---|---|---|
| committer | bors <bors@rust-lang.org> | 2022-07-18 10:24:53 +0000 |
| commit | 8e379cec62b63d4e61a8f0b062e8be3ebfcd4a4b (patch) | |
| tree | 6aaeeaf45c2d7f6f9786981f110a49c37ff35b9a | |
| parent | 897a7ec4b826f85ec1626870e734490701138097 (diff) | |
| parent | b5aa3b389e93d77cb769bf417555489e999c401e (diff) | |
| download | rust-8e379cec62b63d4e61a8f0b062e8be3ebfcd4a4b.tar.gz rust-8e379cec62b63d4e61a8f0b062e8be3ebfcd4a4b.zip | |
Auto merge of #12286 - harpsword:fix_generate_constant, r=Veykril
fix: “Generate constant” ignores the path prefix of the identifier fix #12022 add abilities to generate constant with prefix path, even these mods in path are not exist. ## some examples https://user-images.githubusercontent.com/10148927/168710096-59d5c594-0e4a-4ba7-bfb3-21c4b99409ea.mov https://user-images.githubusercontent.com/10148927/168710111-cd6d3df4-58eb-4358-ae9e-791bfc408efa.mov https://user-images.githubusercontent.com/10148927/168710115-601923b7-2164-4b9a-85a9-fbb4b29796a1.mov https://user-images.githubusercontent.com/10148927/168710118-3d4bc9c1-758b-4e6f-9709-568fa920937d.mov
| -rw-r--r-- | crates/ide-assists/src/handlers/generate_constant.rs | 166 |
1 files changed, 151 insertions, 15 deletions
diff --git a/crates/ide-assists/src/handlers/generate_constant.rs b/crates/ide-assists/src/handlers/generate_constant.rs index c4b759d5f3c..8932a91ebe6 100644 --- a/crates/ide-assists/src/handlers/generate_constant.rs +++ b/crates/ide-assists/src/handlers/generate_constant.rs @@ -1,12 +1,13 @@ use crate::assist_context::{AssistContext, Assists}; -use hir::HirDisplay; +use hir::{HasVisibility, HirDisplay, Module}; use ide_db::{ assists::{AssistId, AssistKind}, - defs::NameRefClass, + base_db::{FileId, Upcast}, + defs::{Definition, NameRefClass}, }; use syntax::{ - ast::{self, edit::IndentLevel}, - AstNode, + ast::{self, edit::IndentLevel, NameRef}, + AstNode, Direction, SyntaxKind, TextSize, }; // Assist: generate_constant @@ -32,13 +33,6 @@ use syntax::{ pub(crate) fn generate_constant(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { let constant_token = ctx.find_node_at_offset::<ast::NameRef>()?; - let expr = constant_token.syntax().ancestors().find_map(ast::Expr::cast)?; - let statement = expr.syntax().ancestors().find_map(ast::Stmt::cast)?; - let ty = ctx.sema.type_of_expr(&expr)?; - let scope = ctx.sema.scope(statement.syntax())?; - let module = scope.module(); - let type_name = ty.original().display_source_code(ctx.db(), module.into()).ok()?; - let indent = IndentLevel::from_node(statement.syntax()); if constant_token.to_string().chars().any(|it| !(it.is_uppercase() || it == '_')) { cov_mark::hit!(not_constant_name); return None; @@ -47,20 +41,106 @@ pub(crate) fn generate_constant(acc: &mut Assists, ctx: &AssistContext) -> Optio cov_mark::hit!(already_defined); return None; } + let expr = constant_token.syntax().ancestors().find_map(ast::Expr::cast)?; + let statement = expr.syntax().ancestors().find_map(ast::Stmt::cast)?; + let ty = ctx.sema.type_of_expr(&expr)?; + let scope = ctx.sema.scope(statement.syntax())?; + let constant_module = scope.module(); + let type_name = ty.original().display_source_code(ctx.db(), constant_module.into()).ok()?; let target = statement.syntax().parent()?.text_range(); + let path = constant_token.syntax().ancestors().find_map(ast::Path::cast)?; + + let name_refs = path.segments().map(|s| s.name_ref()); + let mut outer_exists = false; + let mut not_exist_name_ref = Vec::new(); + let mut current_module = constant_module; + for name_ref in name_refs { + let name_ref_value = name_ref?; + let name_ref_class = NameRefClass::classify(&ctx.sema, &name_ref_value); + match name_ref_class { + Some(NameRefClass::Definition(Definition::Module(m))) => { + if !m.visibility(ctx.sema.db).is_visible_from(ctx.sema.db, constant_module.into()) { + return None; + } + outer_exists = true; + current_module = m; + } + Some(_) => { + return None; + } + None => { + not_exist_name_ref.push(name_ref_value); + } + } + } + let (offset, indent, file_id, post_string) = + target_data_for_generate_constant(ctx, current_module, constant_module).unwrap_or_else( + || { + let indent = IndentLevel::from_node(statement.syntax()); + (statement.syntax().text_range().start(), indent, None, format!("\n{}", indent)) + }, + ); + + let text = get_text_for_generate_constant(not_exist_name_ref, indent, outer_exists, type_name)?; acc.add( AssistId("generate_constant", AssistKind::QuickFix), "Generate constant", target, |builder| { - builder.insert( - statement.syntax().text_range().start(), - format!("const {}: {} = $0;\n{}", constant_token, type_name, indent), - ); + if let Some(file_id) = file_id { + builder.edit_file(file_id); + } + builder.insert(offset, format!("{}{}", text, post_string)); }, ) } +fn get_text_for_generate_constant( + mut not_exist_name_ref: Vec<NameRef>, + indent: IndentLevel, + outer_exists: bool, + type_name: String, +) -> Option<String> { + let constant_token = not_exist_name_ref.pop()?; + let vis = if not_exist_name_ref.len() == 0 && !outer_exists { "" } else { "\npub " }; + let mut text = format!("{}const {}: {} = $0;", vis, constant_token, type_name); + while let Some(name_ref) = not_exist_name_ref.pop() { + let vis = if not_exist_name_ref.len() == 0 && !outer_exists { "" } else { "\npub " }; + text = text.replace("\n", "\n "); + text = format!("{}mod {} {{{}\n}}", vis, name_ref.to_string(), text); + } + Some(text.replace("\n", &format!("\n{}", indent))) +} + +fn target_data_for_generate_constant( + ctx: &AssistContext, + current_module: Module, + constant_module: Module, +) -> Option<(TextSize, IndentLevel, Option<FileId>, String)> { + if current_module == constant_module { + // insert in current file + return None; + } + let in_file_source = current_module.definition_source(ctx.sema.db); + let file_id = in_file_source.file_id.original_file(ctx.sema.db.upcast()); + match in_file_source.value { + hir::ModuleSource::Module(module_node) => { + let indent = IndentLevel::from_node(module_node.syntax()); + let l_curly_token = module_node.item_list()?.l_curly_token()?; + let offset = l_curly_token.text_range().end(); + + let siblings_has_newline = l_curly_token + .siblings_with_tokens(Direction::Next) + .find(|it| it.kind() == SyntaxKind::WHITESPACE && it.to_string().contains("\n")) + .is_some(); + let post_string = + if siblings_has_newline { format!("{}", indent) } else { format!("\n{}", indent) }; + Some((offset, indent + 1, Some(file_id), post_string)) + } + _ => Some((TextSize::from(0), 0.into(), Some(file_id), "\n".into())), + } +} + #[cfg(test)] mod tests { use super::*; @@ -116,4 +196,60 @@ fn main() { }"#, ); } + + #[test] + fn test_constant_with_path() { + check_assist( + generate_constant, + r#"mod foo {} +fn bar() -> i32 { + foo::A_CON$0STANT +}"#, + r#"mod foo { + pub const A_CONSTANT: i32 = $0; +} +fn bar() -> i32 { + foo::A_CONSTANT +}"#, + ); + } + + #[test] + fn test_constant_with_longer_path() { + check_assist( + generate_constant, + r#"mod foo { + pub mod goo {} +} +fn bar() -> i32 { + foo::goo::A_CON$0STANT +}"#, + r#"mod foo { + pub mod goo { + pub const A_CONSTANT: i32 = $0; + } +} +fn bar() -> i32 { + foo::goo::A_CONSTANT +}"#, + ); + } + + #[test] + fn test_constant_with_not_exist_longer_path() { + check_assist( + generate_constant, + r#"fn bar() -> i32 { + foo::goo::A_CON$0STANT +}"#, + r#"mod foo { + pub mod goo { + pub const A_CONSTANT: i32 = $0; + } +} +fn bar() -> i32 { + foo::goo::A_CONSTANT +}"#, + ); + } } |
