diff options
-rw-r--r-- | crates/assists/src/handlers/generate_enum_match_method.rs | 221 | ||||
-rw-r--r-- | crates/assists/src/handlers/generate_new.rs | 64 | ||||
-rw-r--r-- | crates/assists/src/lib.rs | 2 | ||||
-rw-r--r-- | crates/assists/src/tests/generated.rs | 28 | ||||
-rw-r--r-- | crates/assists/src/utils.rs | 75 | ||||
-rw-r--r-- | crates/hir_def/src/nameres/path_resolution.rs | 7 | ||||
-rw-r--r-- | crates/hir_ty/src/diagnostics/decl_check/case_conv.rs | 34 | ||||
-rw-r--r-- | docs/dev/README.md | 8 |
8 files changed, 355 insertions, 84 deletions
diff --git a/crates/assists/src/handlers/generate_enum_match_method.rs b/crates/assists/src/handlers/generate_enum_match_method.rs new file mode 100644 index 000000000..ee89d4208 --- /dev/null +++ b/crates/assists/src/handlers/generate_enum_match_method.rs | |||
@@ -0,0 +1,221 @@ | |||
1 | use stdx::{format_to, to_lower_snake_case}; | ||
2 | use syntax::ast::{self, AstNode, NameOwner}; | ||
3 | use syntax::{ast::VisibilityOwner, T}; | ||
4 | use test_utils::mark; | ||
5 | |||
6 | use crate::{utils::find_struct_impl, AssistContext, AssistId, AssistKind, Assists}; | ||
7 | |||
8 | // Assist: generate_enum_match_method | ||
9 | // | ||
10 | // Generate an `is_` method for an enum variant. | ||
11 | // | ||
12 | // ``` | ||
13 | // enum Version { | ||
14 | // Undefined, | ||
15 | // Minor$0, | ||
16 | // Major, | ||
17 | // } | ||
18 | // ``` | ||
19 | // -> | ||
20 | // ``` | ||
21 | // enum Version { | ||
22 | // Undefined, | ||
23 | // Minor, | ||
24 | // Major, | ||
25 | // } | ||
26 | // | ||
27 | // impl Version { | ||
28 | // /// Returns `true` if the version is [`Minor`]. | ||
29 | // fn is_minor(&self) -> bool { | ||
30 | // matches!(self, Self::Minor) | ||
31 | // } | ||
32 | // } | ||
33 | // ``` | ||
34 | pub(crate) fn generate_enum_match_method(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | ||
35 | let variant = ctx.find_node_at_offset::<ast::Variant>()?; | ||
36 | let variant_name = variant.name()?; | ||
37 | let parent_enum = variant.parent_enum(); | ||
38 | if !matches!(variant.kind(), ast::StructKind::Unit) { | ||
39 | mark::hit!(test_gen_enum_match_on_non_unit_variant_not_implemented); | ||
40 | return None; | ||
41 | } | ||
42 | |||
43 | let enum_lowercase_name = to_lower_snake_case(&parent_enum.name()?.to_string()); | ||
44 | let fn_name = to_lower_snake_case(&variant_name.to_string()); | ||
45 | |||
46 | // Return early if we've found an existing new fn | ||
47 | let impl_def = find_struct_impl( | ||
48 | &ctx, | ||
49 | &ast::AdtDef::Enum(parent_enum.clone()), | ||
50 | format!("is_{}", fn_name).as_str(), | ||
51 | )?; | ||
52 | |||
53 | let target = variant.syntax().text_range(); | ||
54 | acc.add( | ||
55 | AssistId("generate_enum_match_method", AssistKind::Generate), | ||
56 | "Generate an `is_` method for an enum variant", | ||
57 | target, | ||
58 | |builder| { | ||
59 | let mut buf = String::with_capacity(512); | ||
60 | |||
61 | if impl_def.is_some() { | ||
62 | buf.push('\n'); | ||
63 | } | ||
64 | |||
65 | let vis = parent_enum.visibility().map_or(String::new(), |v| format!("{} ", v)); | ||
66 | |||
67 | format_to!( | ||
68 | buf, | ||
69 | " /// Returns `true` if the {} is [`{}`]. | ||
70 | {}fn is_{}(&self) -> bool {{ | ||
71 | matches!(self, Self::{}) | ||
72 | }}", | ||
73 | enum_lowercase_name, | ||
74 | variant_name, | ||
75 | vis, | ||
76 | fn_name, | ||
77 | variant_name | ||
78 | ); | ||
79 | |||
80 | let start_offset = impl_def | ||
81 | .and_then(|impl_def| { | ||
82 | buf.push('\n'); | ||
83 | let start = impl_def | ||
84 | .syntax() | ||
85 | .descendants_with_tokens() | ||
86 | .find(|t| t.kind() == T!['{'])? | ||
87 | .text_range() | ||
88 | .end(); | ||
89 | |||
90 | Some(start) | ||
91 | }) | ||
92 | .unwrap_or_else(|| { | ||
93 | buf = generate_impl_text(&parent_enum, &buf); | ||
94 | parent_enum.syntax().text_range().end() | ||
95 | }); | ||
96 | |||
97 | builder.insert(start_offset, buf); | ||
98 | }, | ||
99 | ) | ||
100 | } | ||
101 | |||
102 | // Generates the surrounding `impl Type { <code> }` including type and lifetime | ||
103 | // parameters | ||
104 | fn generate_impl_text(strukt: &ast::Enum, code: &str) -> String { | ||
105 | let mut buf = String::with_capacity(code.len()); | ||
106 | buf.push_str("\n\nimpl "); | ||
107 | buf.push_str(strukt.name().unwrap().text()); | ||
108 | format_to!(buf, " {{\n{}\n}}", code); | ||
109 | buf | ||
110 | } | ||
111 | |||
112 | #[cfg(test)] | ||
113 | mod tests { | ||
114 | use test_utils::mark; | ||
115 | |||
116 | use crate::tests::{check_assist, check_assist_not_applicable}; | ||
117 | |||
118 | use super::*; | ||
119 | |||
120 | fn check_not_applicable(ra_fixture: &str) { | ||
121 | check_assist_not_applicable(generate_enum_match_method, ra_fixture) | ||
122 | } | ||
123 | |||
124 | #[test] | ||
125 | fn test_generate_enum_match_from_variant() { | ||
126 | check_assist( | ||
127 | generate_enum_match_method, | ||
128 | r#" | ||
129 | enum Variant { | ||
130 | Undefined, | ||
131 | Minor$0, | ||
132 | Major, | ||
133 | }"#, | ||
134 | r#"enum Variant { | ||
135 | Undefined, | ||
136 | Minor, | ||
137 | Major, | ||
138 | } | ||
139 | |||
140 | impl Variant { | ||
141 | /// Returns `true` if the variant is [`Minor`]. | ||
142 | fn is_minor(&self) -> bool { | ||
143 | matches!(self, Self::Minor) | ||
144 | } | ||
145 | }"#, | ||
146 | ); | ||
147 | } | ||
148 | |||
149 | #[test] | ||
150 | fn test_generate_enum_match_already_implemented() { | ||
151 | check_not_applicable( | ||
152 | r#" | ||
153 | enum Variant { | ||
154 | Undefined, | ||
155 | Minor$0, | ||
156 | Major, | ||
157 | } | ||
158 | |||
159 | impl Variant { | ||
160 | fn is_minor(&self) -> bool { | ||
161 | matches!(self, Self::Minor) | ||
162 | } | ||
163 | }"#, | ||
164 | ); | ||
165 | } | ||
166 | |||
167 | #[test] | ||
168 | fn test_add_from_impl_no_element() { | ||
169 | mark::check!(test_gen_enum_match_on_non_unit_variant_not_implemented); | ||
170 | check_not_applicable( | ||
171 | r#" | ||
172 | enum Variant { | ||
173 | Undefined, | ||
174 | Minor(u32)$0, | ||
175 | Major, | ||
176 | }"#, | ||
177 | ); | ||
178 | } | ||
179 | |||
180 | #[test] | ||
181 | fn test_generate_enum_match_from_variant_with_one_variant() { | ||
182 | check_assist( | ||
183 | generate_enum_match_method, | ||
184 | r#"enum Variant { Undefi$0ned }"#, | ||
185 | r#" | ||
186 | enum Variant { Undefined } | ||
187 | |||
188 | impl Variant { | ||
189 | /// Returns `true` if the variant is [`Undefined`]. | ||
190 | fn is_undefined(&self) -> bool { | ||
191 | matches!(self, Self::Undefined) | ||
192 | } | ||
193 | }"#, | ||
194 | ); | ||
195 | } | ||
196 | |||
197 | #[test] | ||
198 | fn test_generate_enum_match_from_variant_with_visibility_marker() { | ||
199 | check_assist( | ||
200 | generate_enum_match_method, | ||
201 | r#" | ||
202 | pub(crate) enum Variant { | ||
203 | Undefined, | ||
204 | Minor$0, | ||
205 | Major, | ||
206 | }"#, | ||
207 | r#"pub(crate) enum Variant { | ||
208 | Undefined, | ||
209 | Minor, | ||
210 | Major, | ||
211 | } | ||
212 | |||
213 | impl Variant { | ||
214 | /// Returns `true` if the variant is [`Minor`]. | ||
215 | pub(crate) fn is_minor(&self) -> bool { | ||
216 | matches!(self, Self::Minor) | ||
217 | } | ||
218 | }"#, | ||
219 | ); | ||
220 | } | ||
221 | } | ||
diff --git a/crates/assists/src/handlers/generate_new.rs b/crates/assists/src/handlers/generate_new.rs index b7390855a..84832273f 100644 --- a/crates/assists/src/handlers/generate_new.rs +++ b/crates/assists/src/handlers/generate_new.rs | |||
@@ -1,4 +1,3 @@ | |||
1 | use hir::Adt; | ||
2 | use itertools::Itertools; | 1 | use itertools::Itertools; |
3 | use stdx::format_to; | 2 | use stdx::format_to; |
4 | use syntax::{ | 3 | use syntax::{ |
@@ -6,7 +5,7 @@ use syntax::{ | |||
6 | SmolStr, T, | 5 | SmolStr, T, |
7 | }; | 6 | }; |
8 | 7 | ||
9 | use crate::{AssistContext, AssistId, AssistKind, Assists}; | 8 | use crate::{utils::find_struct_impl, AssistContext, AssistId, AssistKind, Assists}; |
10 | 9 | ||
11 | // Assist: generate_new | 10 | // Assist: generate_new |
12 | // | 11 | // |
@@ -38,7 +37,7 @@ pub(crate) fn generate_new(acc: &mut Assists, ctx: &AssistContext) -> Option<()> | |||
38 | }; | 37 | }; |
39 | 38 | ||
40 | // Return early if we've found an existing new fn | 39 | // Return early if we've found an existing new fn |
41 | let impl_def = find_struct_impl(&ctx, &strukt)?; | 40 | let impl_def = find_struct_impl(&ctx, &ast::AdtDef::Struct(strukt.clone()), "new")?; |
42 | 41 | ||
43 | let target = strukt.syntax().text_range(); | 42 | let target = strukt.syntax().text_range(); |
44 | acc.add(AssistId("generate_new", AssistKind::Generate), "Generate `new`", target, |builder| { | 43 | acc.add(AssistId("generate_new", AssistKind::Generate), "Generate `new`", target, |builder| { |
@@ -111,65 +110,6 @@ fn generate_impl_text(strukt: &ast::Struct, code: &str) -> String { | |||
111 | buf | 110 | buf |
112 | } | 111 | } |
113 | 112 | ||
114 | // Uses a syntax-driven approach to find any impl blocks for the struct that | ||
115 | // exist within the module/file | ||
116 | // | ||
117 | // Returns `None` if we've found an existing `new` fn | ||
118 | // | ||
119 | // FIXME: change the new fn checking to a more semantic approach when that's more | ||
120 | // viable (e.g. we process proc macros, etc) | ||
121 | fn find_struct_impl(ctx: &AssistContext, strukt: &ast::Struct) -> Option<Option<ast::Impl>> { | ||
122 | let db = ctx.db(); | ||
123 | let module = strukt.syntax().ancestors().find(|node| { | ||
124 | ast::Module::can_cast(node.kind()) || ast::SourceFile::can_cast(node.kind()) | ||
125 | })?; | ||
126 | |||
127 | let struct_def = ctx.sema.to_def(strukt)?; | ||
128 | |||
129 | let block = module.descendants().filter_map(ast::Impl::cast).find_map(|impl_blk| { | ||
130 | let blk = ctx.sema.to_def(&impl_blk)?; | ||
131 | |||
132 | // FIXME: handle e.g. `struct S<T>; impl<U> S<U> {}` | ||
133 | // (we currently use the wrong type parameter) | ||
134 | // also we wouldn't want to use e.g. `impl S<u32>` | ||
135 | let same_ty = match blk.target_ty(db).as_adt() { | ||
136 | Some(def) => def == Adt::Struct(struct_def), | ||
137 | None => false, | ||
138 | }; | ||
139 | let not_trait_impl = blk.target_trait(db).is_none(); | ||
140 | |||
141 | if !(same_ty && not_trait_impl) { | ||
142 | None | ||
143 | } else { | ||
144 | Some(impl_blk) | ||
145 | } | ||
146 | }); | ||
147 | |||
148 | if let Some(ref impl_blk) = block { | ||
149 | if has_new_fn(impl_blk) { | ||
150 | return None; | ||
151 | } | ||
152 | } | ||
153 | |||
154 | Some(block) | ||
155 | } | ||
156 | |||
157 | fn has_new_fn(imp: &ast::Impl) -> bool { | ||
158 | if let Some(il) = imp.assoc_item_list() { | ||
159 | for item in il.assoc_items() { | ||
160 | if let ast::AssocItem::Fn(f) = item { | ||
161 | if let Some(name) = f.name() { | ||
162 | if name.text().eq_ignore_ascii_case("new") { | ||
163 | return true; | ||
164 | } | ||
165 | } | ||
166 | } | ||
167 | } | ||
168 | } | ||
169 | |||
170 | false | ||
171 | } | ||
172 | |||
173 | #[cfg(test)] | 113 | #[cfg(test)] |
174 | mod tests { | 114 | mod tests { |
175 | use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target}; | 115 | use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target}; |
diff --git a/crates/assists/src/lib.rs b/crates/assists/src/lib.rs index 062a902ab..83fbf6986 100644 --- a/crates/assists/src/lib.rs +++ b/crates/assists/src/lib.rs | |||
@@ -127,6 +127,7 @@ mod handlers { | |||
127 | mod flip_trait_bound; | 127 | mod flip_trait_bound; |
128 | mod generate_default_from_enum_variant; | 128 | mod generate_default_from_enum_variant; |
129 | mod generate_derive; | 129 | mod generate_derive; |
130 | mod generate_enum_match_method; | ||
130 | mod generate_from_impl_for_enum; | 131 | mod generate_from_impl_for_enum; |
131 | mod generate_function; | 132 | mod generate_function; |
132 | mod generate_impl; | 133 | mod generate_impl; |
@@ -185,6 +186,7 @@ mod handlers { | |||
185 | flip_trait_bound::flip_trait_bound, | 186 | flip_trait_bound::flip_trait_bound, |
186 | generate_default_from_enum_variant::generate_default_from_enum_variant, | 187 | generate_default_from_enum_variant::generate_default_from_enum_variant, |
187 | generate_derive::generate_derive, | 188 | generate_derive::generate_derive, |
189 | generate_enum_match_method::generate_enum_match_method, | ||
188 | generate_from_impl_for_enum::generate_from_impl_for_enum, | 190 | generate_from_impl_for_enum::generate_from_impl_for_enum, |
189 | generate_function::generate_function, | 191 | generate_function::generate_function, |
190 | generate_impl::generate_impl, | 192 | generate_impl::generate_impl, |
diff --git a/crates/assists/src/tests/generated.rs b/crates/assists/src/tests/generated.rs index e84f208a3..0dbb05f2a 100644 --- a/crates/assists/src/tests/generated.rs +++ b/crates/assists/src/tests/generated.rs | |||
@@ -460,6 +460,34 @@ struct Point { | |||
460 | } | 460 | } |
461 | 461 | ||
462 | #[test] | 462 | #[test] |
463 | fn doctest_generate_enum_match_method() { | ||
464 | check_doc_test( | ||
465 | "generate_enum_match_method", | ||
466 | r#####" | ||
467 | enum Version { | ||
468 | Undefined, | ||
469 | Minor$0, | ||
470 | Major, | ||
471 | } | ||
472 | "#####, | ||
473 | r#####" | ||
474 | enum Version { | ||
475 | Undefined, | ||
476 | Minor, | ||
477 | Major, | ||
478 | } | ||
479 | |||
480 | impl Version { | ||
481 | /// Returns `true` if the version is [`Minor`]. | ||
482 | fn is_minor(&self) -> bool { | ||
483 | matches!(self, Self::Minor) | ||
484 | } | ||
485 | } | ||
486 | "#####, | ||
487 | ) | ||
488 | } | ||
489 | |||
490 | #[test] | ||
463 | fn doctest_generate_from_impl_for_enum() { | 491 | fn doctest_generate_from_impl_for_enum() { |
464 | check_doc_test( | 492 | check_doc_test( |
465 | "generate_from_impl_for_enum", | 493 | "generate_from_impl_for_enum", |
diff --git a/crates/assists/src/utils.rs b/crates/assists/src/utils.rs index 4e762e18b..3842558d8 100644 --- a/crates/assists/src/utils.rs +++ b/crates/assists/src/utils.rs | |||
@@ -2,7 +2,7 @@ | |||
2 | 2 | ||
3 | use std::ops; | 3 | use std::ops; |
4 | 4 | ||
5 | use hir::HasSource; | 5 | use hir::{Adt, HasSource}; |
6 | use ide_db::{helpers::SnippetCap, RootDatabase}; | 6 | use ide_db::{helpers::SnippetCap, RootDatabase}; |
7 | use itertools::Itertools; | 7 | use itertools::Itertools; |
8 | use syntax::{ | 8 | use syntax::{ |
@@ -15,7 +15,10 @@ use syntax::{ | |||
15 | SyntaxNode, TextSize, T, | 15 | SyntaxNode, TextSize, T, |
16 | }; | 16 | }; |
17 | 17 | ||
18 | use crate::ast_transform::{self, AstTransform, QualifyPaths, SubstituteTypeParams}; | 18 | use crate::{ |
19 | assist_context::AssistContext, | ||
20 | ast_transform::{self, AstTransform, QualifyPaths, SubstituteTypeParams}, | ||
21 | }; | ||
19 | 22 | ||
20 | pub(crate) fn unwrap_trivial_block(block: ast::BlockExpr) -> ast::Expr { | 23 | pub(crate) fn unwrap_trivial_block(block: ast::BlockExpr) -> ast::Expr { |
21 | extract_trivial_expression(&block) | 24 | extract_trivial_expression(&block) |
@@ -267,3 +270,71 @@ pub(crate) fn does_pat_match_variant(pat: &ast::Pat, var: &ast::Pat) -> bool { | |||
267 | 270 | ||
268 | pat_head == var_head | 271 | pat_head == var_head |
269 | } | 272 | } |
273 | |||
274 | // Uses a syntax-driven approach to find any impl blocks for the struct that | ||
275 | // exist within the module/file | ||
276 | // | ||
277 | // Returns `None` if we've found an existing `new` fn | ||
278 | // | ||
279 | // FIXME: change the new fn checking to a more semantic approach when that's more | ||
280 | // viable (e.g. we process proc macros, etc) | ||
281 | pub(crate) fn find_struct_impl( | ||
282 | ctx: &AssistContext, | ||
283 | strukt: &ast::AdtDef, | ||
284 | name: &str, | ||
285 | ) -> Option<Option<ast::Impl>> { | ||
286 | let db = ctx.db(); | ||
287 | let module = strukt.syntax().ancestors().find(|node| { | ||
288 | ast::Module::can_cast(node.kind()) || ast::SourceFile::can_cast(node.kind()) | ||
289 | })?; | ||
290 | |||
291 | let struct_def = match strukt { | ||
292 | ast::AdtDef::Enum(e) => Adt::Enum(ctx.sema.to_def(e)?), | ||
293 | ast::AdtDef::Struct(s) => Adt::Struct(ctx.sema.to_def(s)?), | ||
294 | ast::AdtDef::Union(u) => Adt::Union(ctx.sema.to_def(u)?), | ||
295 | }; | ||
296 | |||
297 | let block = module.descendants().filter_map(ast::Impl::cast).find_map(|impl_blk| { | ||
298 | let blk = ctx.sema.to_def(&impl_blk)?; | ||
299 | |||
300 | // FIXME: handle e.g. `struct S<T>; impl<U> S<U> {}` | ||
301 | // (we currently use the wrong type parameter) | ||
302 | // also we wouldn't want to use e.g. `impl S<u32>` | ||
303 | |||
304 | let same_ty = match blk.target_ty(db).as_adt() { | ||
305 | Some(def) => def == struct_def, | ||
306 | None => false, | ||
307 | }; | ||
308 | let not_trait_impl = blk.target_trait(db).is_none(); | ||
309 | |||
310 | if !(same_ty && not_trait_impl) { | ||
311 | None | ||
312 | } else { | ||
313 | Some(impl_blk) | ||
314 | } | ||
315 | }); | ||
316 | |||
317 | if let Some(ref impl_blk) = block { | ||
318 | if has_fn(impl_blk, name) { | ||
319 | return None; | ||
320 | } | ||
321 | } | ||
322 | |||
323 | Some(block) | ||
324 | } | ||
325 | |||
326 | fn has_fn(imp: &ast::Impl, rhs_name: &str) -> bool { | ||
327 | if let Some(il) = imp.assoc_item_list() { | ||
328 | for item in il.assoc_items() { | ||
329 | if let ast::AssocItem::Fn(f) = item { | ||
330 | if let Some(name) = f.name() { | ||
331 | if name.text().eq_ignore_ascii_case(rhs_name) { | ||
332 | return true; | ||
333 | } | ||
334 | } | ||
335 | } | ||
336 | } | ||
337 | } | ||
338 | |||
339 | false | ||
340 | } | ||
diff --git a/crates/hir_def/src/nameres/path_resolution.rs b/crates/hir_def/src/nameres/path_resolution.rs index f2b59172d..036e389b0 100644 --- a/crates/hir_def/src/nameres/path_resolution.rs +++ b/crates/hir_def/src/nameres/path_resolution.rs | |||
@@ -108,7 +108,6 @@ impl DefMap { | |||
108 | shadow: BuiltinShadowMode, | 108 | shadow: BuiltinShadowMode, |
109 | ) -> ResolvePathResult { | 109 | ) -> ResolvePathResult { |
110 | let mut result = ResolvePathResult::empty(ReachedFixedPoint::No); | 110 | let mut result = ResolvePathResult::empty(ReachedFixedPoint::No); |
111 | result.segment_index = Some(usize::max_value()); | ||
112 | 111 | ||
113 | let mut arc; | 112 | let mut arc; |
114 | let mut current_map = self; | 113 | let mut current_map = self; |
@@ -128,7 +127,11 @@ impl DefMap { | |||
128 | } | 127 | } |
129 | // FIXME: this doesn't seem right; what if the different namespace resolutions come from different crates? | 128 | // FIXME: this doesn't seem right; what if the different namespace resolutions come from different crates? |
130 | result.krate = result.krate.or(new.krate); | 129 | result.krate = result.krate.or(new.krate); |
131 | result.segment_index = result.segment_index.min(new.segment_index); | 130 | result.segment_index = match (result.segment_index, new.segment_index) { |
131 | (Some(idx), None) => Some(idx), | ||
132 | (Some(old), Some(new)) => Some(old.max(new)), | ||
133 | (None, new) => new, | ||
134 | }; | ||
132 | 135 | ||
133 | match ¤t_map.block { | 136 | match ¤t_map.block { |
134 | Some(block) => { | 137 | Some(block) => { |
diff --git a/crates/hir_ty/src/diagnostics/decl_check/case_conv.rs b/crates/hir_ty/src/diagnostics/decl_check/case_conv.rs index 14e4d92f0..3ab36caf2 100644 --- a/crates/hir_ty/src/diagnostics/decl_check/case_conv.rs +++ b/crates/hir_ty/src/diagnostics/decl_check/case_conv.rs | |||
@@ -5,7 +5,7 @@ | |||
5 | // from file /compiler/rustc_lint/src/nonstandard_style.rs | 5 | // from file /compiler/rustc_lint/src/nonstandard_style.rs |
6 | 6 | ||
7 | /// Converts an identifier to an UpperCamelCase form. | 7 | /// Converts an identifier to an UpperCamelCase form. |
8 | /// Returns `None` if the string is already is UpperCamelCase. | 8 | /// Returns `None` if the string is already in UpperCamelCase. |
9 | pub(crate) fn to_camel_case(ident: &str) -> Option<String> { | 9 | pub(crate) fn to_camel_case(ident: &str) -> Option<String> { |
10 | if is_camel_case(ident) { | 10 | if is_camel_case(ident) { |
11 | return None; | 11 | return None; |
@@ -17,7 +17,7 @@ pub(crate) fn to_camel_case(ident: &str) -> Option<String> { | |||
17 | .split('_') | 17 | .split('_') |
18 | .filter(|component| !component.is_empty()) | 18 | .filter(|component| !component.is_empty()) |
19 | .map(|component| { | 19 | .map(|component| { |
20 | let mut camel_cased_component = String::new(); | 20 | let mut camel_cased_component = String::with_capacity(component.len()); |
21 | 21 | ||
22 | let mut new_word = true; | 22 | let mut new_word = true; |
23 | let mut prev_is_lower_case = true; | 23 | let mut prev_is_lower_case = true; |
@@ -30,9 +30,9 @@ pub(crate) fn to_camel_case(ident: &str) -> Option<String> { | |||
30 | } | 30 | } |
31 | 31 | ||
32 | if new_word { | 32 | if new_word { |
33 | camel_cased_component.push_str(&c.to_uppercase().to_string()); | 33 | camel_cased_component.extend(c.to_uppercase()); |
34 | } else { | 34 | } else { |
35 | camel_cased_component.push_str(&c.to_lowercase().to_string()); | 35 | camel_cased_component.extend(c.to_lowercase()); |
36 | } | 36 | } |
37 | 37 | ||
38 | prev_is_lower_case = c.is_lowercase(); | 38 | prev_is_lower_case = c.is_lowercase(); |
@@ -41,16 +41,16 @@ pub(crate) fn to_camel_case(ident: &str) -> Option<String> { | |||
41 | 41 | ||
42 | camel_cased_component | 42 | camel_cased_component |
43 | }) | 43 | }) |
44 | .fold((String::new(), None), |(acc, prev): (String, Option<String>), next| { | 44 | .fold((String::new(), None), |(acc, prev): (_, Option<String>), next| { |
45 | // separate two components with an underscore if their boundary cannot | 45 | // separate two components with an underscore if their boundary cannot |
46 | // be distinguished using a uppercase/lowercase case distinction | 46 | // be distinguished using a uppercase/lowercase case distinction |
47 | let join = if let Some(prev) = prev { | 47 | let join = prev |
48 | let l = prev.chars().last().unwrap(); | 48 | .and_then(|prev| { |
49 | let f = next.chars().next().unwrap(); | 49 | let f = next.chars().next()?; |
50 | !char_has_case(l) && !char_has_case(f) | 50 | let l = prev.chars().last()?; |
51 | } else { | 51 | Some(!char_has_case(l) && !char_has_case(f)) |
52 | false | 52 | }) |
53 | }; | 53 | .unwrap_or(false); |
54 | (acc + if join { "_" } else { "" } + &next, Some(next)) | 54 | (acc + if join { "_" } else { "" } + &next, Some(next)) |
55 | }) | 55 | }) |
56 | .0; | 56 | .0; |
@@ -92,14 +92,12 @@ fn is_camel_case(name: &str) -> bool { | |||
92 | let mut fst = None; | 92 | let mut fst = None; |
93 | // start with a non-lowercase letter rather than non-uppercase | 93 | // start with a non-lowercase letter rather than non-uppercase |
94 | // ones (some scripts don't have a concept of upper/lowercase) | 94 | // ones (some scripts don't have a concept of upper/lowercase) |
95 | !name.chars().next().unwrap().is_lowercase() | 95 | name.chars().next().map_or(true, |c| !c.is_lowercase()) |
96 | && !name.contains("__") | 96 | && !name.contains("__") |
97 | && !name.chars().any(|snd| { | 97 | && !name.chars().any(|snd| { |
98 | let ret = match (fst, snd) { | 98 | let ret = match fst { |
99 | (None, _) => false, | 99 | None => false, |
100 | (Some(fst), snd) => { | 100 | Some(fst) => char_has_case(fst) && snd == '_' || char_has_case(snd) && fst == '_', |
101 | char_has_case(fst) && snd == '_' || char_has_case(snd) && fst == '_' | ||
102 | } | ||
103 | }; | 101 | }; |
104 | fst = Some(snd); | 102 | fst = Some(snd); |
105 | 103 | ||
diff --git a/docs/dev/README.md b/docs/dev/README.md index 9c0af68e3..9b9b18102 100644 --- a/docs/dev/README.md +++ b/docs/dev/README.md | |||
@@ -114,6 +114,14 @@ npm run lint | |||
114 | 114 | ||
115 | Do see [./style.md](./style.md). | 115 | Do see [./style.md](./style.md). |
116 | 116 | ||
117 | # How to ... | ||
118 | |||
119 | * ... add an assist? [#7535](https://github.com/rust-analyzer/rust-analyzer/pull/7535) | ||
120 | * ... add a new protocol extension? [#4569](https://github.com/rust-analyzer/rust-analyzer/pull/4569) | ||
121 | * ... add a new configuration option? [#7451](https://github.com/rust-analyzer/rust-analyzer/pull/7451) | ||
122 | * ... add a new completion? [#6964](https://github.com/rust-analyzer/rust-analyzer/pull/6964) | ||
123 | * ... allow new syntax in the parser? [#7338](https://github.com/rust-analyzer/rust-analyzer/pull/7338) | ||
124 | |||
117 | # Logging | 125 | # Logging |
118 | 126 | ||
119 | Logging is done by both rust-analyzer and VS Code, so it might be tricky to | 127 | Logging is done by both rust-analyzer and VS Code, so it might be tricky to |