Skip to content

Commit b5aa3b3

Browse files
committed
fix: “Generate constant” ignores the path prefix of the identifier
1 parent 897a7ec commit b5aa3b3

File tree

1 file changed

+151
-15
lines changed

1 file changed

+151
-15
lines changed

crates/ide-assists/src/handlers/generate_constant.rs

+151-15
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
use crate::assist_context::{AssistContext, Assists};
2-
use hir::HirDisplay;
2+
use hir::{HasVisibility, HirDisplay, Module};
33
use ide_db::{
44
assists::{AssistId, AssistKind},
5-
defs::NameRefClass,
5+
base_db::{FileId, Upcast},
6+
defs::{Definition, NameRefClass},
67
};
78
use syntax::{
8-
ast::{self, edit::IndentLevel},
9-
AstNode,
9+
ast::{self, edit::IndentLevel, NameRef},
10+
AstNode, Direction, SyntaxKind, TextSize,
1011
};
1112

1213
// Assist: generate_constant
@@ -32,13 +33,6 @@ use syntax::{
3233

3334
pub(crate) fn generate_constant(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
3435
let constant_token = ctx.find_node_at_offset::<ast::NameRef>()?;
35-
let expr = constant_token.syntax().ancestors().find_map(ast::Expr::cast)?;
36-
let statement = expr.syntax().ancestors().find_map(ast::Stmt::cast)?;
37-
let ty = ctx.sema.type_of_expr(&expr)?;
38-
let scope = ctx.sema.scope(statement.syntax())?;
39-
let module = scope.module();
40-
let type_name = ty.original().display_source_code(ctx.db(), module.into()).ok()?;
41-
let indent = IndentLevel::from_node(statement.syntax());
4236
if constant_token.to_string().chars().any(|it| !(it.is_uppercase() || it == '_')) {
4337
cov_mark::hit!(not_constant_name);
4438
return None;
@@ -47,20 +41,106 @@ pub(crate) fn generate_constant(acc: &mut Assists, ctx: &AssistContext) -> Optio
4741
cov_mark::hit!(already_defined);
4842
return None;
4943
}
44+
let expr = constant_token.syntax().ancestors().find_map(ast::Expr::cast)?;
45+
let statement = expr.syntax().ancestors().find_map(ast::Stmt::cast)?;
46+
let ty = ctx.sema.type_of_expr(&expr)?;
47+
let scope = ctx.sema.scope(statement.syntax())?;
48+
let constant_module = scope.module();
49+
let type_name = ty.original().display_source_code(ctx.db(), constant_module.into()).ok()?;
5050
let target = statement.syntax().parent()?.text_range();
51+
let path = constant_token.syntax().ancestors().find_map(ast::Path::cast)?;
52+
53+
let name_refs = path.segments().map(|s| s.name_ref());
54+
let mut outer_exists = false;
55+
let mut not_exist_name_ref = Vec::new();
56+
let mut current_module = constant_module;
57+
for name_ref in name_refs {
58+
let name_ref_value = name_ref?;
59+
let name_ref_class = NameRefClass::classify(&ctx.sema, &name_ref_value);
60+
match name_ref_class {
61+
Some(NameRefClass::Definition(Definition::Module(m))) => {
62+
if !m.visibility(ctx.sema.db).is_visible_from(ctx.sema.db, constant_module.into()) {
63+
return None;
64+
}
65+
outer_exists = true;
66+
current_module = m;
67+
}
68+
Some(_) => {
69+
return None;
70+
}
71+
None => {
72+
not_exist_name_ref.push(name_ref_value);
73+
}
74+
}
75+
}
76+
let (offset, indent, file_id, post_string) =
77+
target_data_for_generate_constant(ctx, current_module, constant_module).unwrap_or_else(
78+
|| {
79+
let indent = IndentLevel::from_node(statement.syntax());
80+
(statement.syntax().text_range().start(), indent, None, format!("\n{}", indent))
81+
},
82+
);
83+
84+
let text = get_text_for_generate_constant(not_exist_name_ref, indent, outer_exists, type_name)?;
5185
acc.add(
5286
AssistId("generate_constant", AssistKind::QuickFix),
5387
"Generate constant",
5488
target,
5589
|builder| {
56-
builder.insert(
57-
statement.syntax().text_range().start(),
58-
format!("const {}: {} = $0;\n{}", constant_token, type_name, indent),
59-
);
90+
if let Some(file_id) = file_id {
91+
builder.edit_file(file_id);
92+
}
93+
builder.insert(offset, format!("{}{}", text, post_string));
6094
},
6195
)
6296
}
6397

98+
fn get_text_for_generate_constant(
99+
mut not_exist_name_ref: Vec<NameRef>,
100+
indent: IndentLevel,
101+
outer_exists: bool,
102+
type_name: String,
103+
) -> Option<String> {
104+
let constant_token = not_exist_name_ref.pop()?;
105+
let vis = if not_exist_name_ref.len() == 0 && !outer_exists { "" } else { "\npub " };
106+
let mut text = format!("{}const {}: {} = $0;", vis, constant_token, type_name);
107+
while let Some(name_ref) = not_exist_name_ref.pop() {
108+
let vis = if not_exist_name_ref.len() == 0 && !outer_exists { "" } else { "\npub " };
109+
text = text.replace("\n", "\n ");
110+
text = format!("{}mod {} {{{}\n}}", vis, name_ref.to_string(), text);
111+
}
112+
Some(text.replace("\n", &format!("\n{}", indent)))
113+
}
114+
115+
fn target_data_for_generate_constant(
116+
ctx: &AssistContext,
117+
current_module: Module,
118+
constant_module: Module,
119+
) -> Option<(TextSize, IndentLevel, Option<FileId>, String)> {
120+
if current_module == constant_module {
121+
// insert in current file
122+
return None;
123+
}
124+
let in_file_source = current_module.definition_source(ctx.sema.db);
125+
let file_id = in_file_source.file_id.original_file(ctx.sema.db.upcast());
126+
match in_file_source.value {
127+
hir::ModuleSource::Module(module_node) => {
128+
let indent = IndentLevel::from_node(module_node.syntax());
129+
let l_curly_token = module_node.item_list()?.l_curly_token()?;
130+
let offset = l_curly_token.text_range().end();
131+
132+
let siblings_has_newline = l_curly_token
133+
.siblings_with_tokens(Direction::Next)
134+
.find(|it| it.kind() == SyntaxKind::WHITESPACE && it.to_string().contains("\n"))
135+
.is_some();
136+
let post_string =
137+
if siblings_has_newline { format!("{}", indent) } else { format!("\n{}", indent) };
138+
Some((offset, indent + 1, Some(file_id), post_string))
139+
}
140+
_ => Some((TextSize::from(0), 0.into(), Some(file_id), "\n".into())),
141+
}
142+
}
143+
64144
#[cfg(test)]
65145
mod tests {
66146
use super::*;
@@ -113,6 +193,62 @@ impl S {
113193
}
114194
fn main() {
115195
let v = S::new(capa$0city);
196+
}"#,
197+
);
198+
}
199+
200+
#[test]
201+
fn test_constant_with_path() {
202+
check_assist(
203+
generate_constant,
204+
r#"mod foo {}
205+
fn bar() -> i32 {
206+
foo::A_CON$0STANT
207+
}"#,
208+
r#"mod foo {
209+
pub const A_CONSTANT: i32 = $0;
210+
}
211+
fn bar() -> i32 {
212+
foo::A_CONSTANT
213+
}"#,
214+
);
215+
}
216+
217+
#[test]
218+
fn test_constant_with_longer_path() {
219+
check_assist(
220+
generate_constant,
221+
r#"mod foo {
222+
pub mod goo {}
223+
}
224+
fn bar() -> i32 {
225+
foo::goo::A_CON$0STANT
226+
}"#,
227+
r#"mod foo {
228+
pub mod goo {
229+
pub const A_CONSTANT: i32 = $0;
230+
}
231+
}
232+
fn bar() -> i32 {
233+
foo::goo::A_CONSTANT
234+
}"#,
235+
);
236+
}
237+
238+
#[test]
239+
fn test_constant_with_not_exist_longer_path() {
240+
check_assist(
241+
generate_constant,
242+
r#"fn bar() -> i32 {
243+
foo::goo::A_CON$0STANT
244+
}"#,
245+
r#"mod foo {
246+
pub mod goo {
247+
pub const A_CONSTANT: i32 = $0;
248+
}
249+
}
250+
fn bar() -> i32 {
251+
foo::goo::A_CONSTANT
116252
}"#,
117253
);
118254
}

0 commit comments

Comments
 (0)