aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ide')
-rw-r--r--crates/ra_ide/src/completion.rs32
-rw-r--r--crates/ra_ide/src/completion/complete_attribute.rs293
-rw-r--r--crates/ra_ide/src/completion/completion_context.rs8
-rw-r--r--crates/ra_ide/src/display/short_label.rs6
-rw-r--r--crates/ra_ide/src/folding_ranges.rs2
-rw-r--r--crates/ra_ide/src/hover.rs11
-rw-r--r--crates/ra_ide/src/join_lines.rs3
-rw-r--r--crates/ra_ide/src/syntax_tree.rs77
8 files changed, 354 insertions, 78 deletions
diff --git a/crates/ra_ide/src/completion.rs b/crates/ra_ide/src/completion.rs
index 4ca0fdf4f..a0e06faa2 100644
--- a/crates/ra_ide/src/completion.rs
+++ b/crates/ra_ide/src/completion.rs
@@ -65,21 +65,23 @@ pub(crate) fn completions(
65 let ctx = CompletionContext::new(db, position, config)?; 65 let ctx = CompletionContext::new(db, position, config)?;
66 66
67 let mut acc = Completions::default(); 67 let mut acc = Completions::default();
68 68 if ctx.attribute_under_caret.is_some() {
69 complete_fn_param::complete_fn_param(&mut acc, &ctx); 69 complete_attribute::complete_attribute(&mut acc, &ctx);
70 complete_keyword::complete_expr_keyword(&mut acc, &ctx); 70 } else {
71 complete_keyword::complete_use_tree_keyword(&mut acc, &ctx); 71 complete_fn_param::complete_fn_param(&mut acc, &ctx);
72 complete_snippet::complete_expr_snippet(&mut acc, &ctx); 72 complete_keyword::complete_expr_keyword(&mut acc, &ctx);
73 complete_snippet::complete_item_snippet(&mut acc, &ctx); 73 complete_keyword::complete_use_tree_keyword(&mut acc, &ctx);
74 complete_qualified_path::complete_qualified_path(&mut acc, &ctx); 74 complete_snippet::complete_expr_snippet(&mut acc, &ctx);
75 complete_unqualified_path::complete_unqualified_path(&mut acc, &ctx); 75 complete_snippet::complete_item_snippet(&mut acc, &ctx);
76 complete_dot::complete_dot(&mut acc, &ctx); 76 complete_qualified_path::complete_qualified_path(&mut acc, &ctx);
77 complete_record::complete_record(&mut acc, &ctx); 77 complete_unqualified_path::complete_unqualified_path(&mut acc, &ctx);
78 complete_pattern::complete_pattern(&mut acc, &ctx); 78 complete_dot::complete_dot(&mut acc, &ctx);
79 complete_postfix::complete_postfix(&mut acc, &ctx); 79 complete_record::complete_record(&mut acc, &ctx);
80 complete_macro_in_item_position::complete_macro_in_item_position(&mut acc, &ctx); 80 complete_pattern::complete_pattern(&mut acc, &ctx);
81 complete_trait_impl::complete_trait_impl(&mut acc, &ctx); 81 complete_postfix::complete_postfix(&mut acc, &ctx);
82 complete_attribute::complete_attribute(&mut acc, &ctx); 82 complete_macro_in_item_position::complete_macro_in_item_position(&mut acc, &ctx);
83 complete_trait_impl::complete_trait_impl(&mut acc, &ctx);
84 }
83 85
84 Some(acc) 86 Some(acc)
85} 87}
diff --git a/crates/ra_ide/src/completion/complete_attribute.rs b/crates/ra_ide/src/completion/complete_attribute.rs
index 8bf952798..20e6edc17 100644
--- a/crates/ra_ide/src/completion/complete_attribute.rs
+++ b/crates/ra_ide/src/completion/complete_attribute.rs
@@ -5,23 +5,26 @@
5 5
6use super::completion_context::CompletionContext; 6use super::completion_context::CompletionContext;
7use super::completion_item::{CompletionItem, CompletionItemKind, CompletionKind, Completions}; 7use super::completion_item::{CompletionItem, CompletionItemKind, CompletionKind, Completions};
8use ast::AttrInput;
8use ra_syntax::{ 9use ra_syntax::{
9 ast::{Attr, AttrKind}, 10 ast::{self, AttrKind},
10 AstNode, 11 AstNode, SyntaxKind,
11}; 12};
13use rustc_hash::FxHashSet;
12 14
13pub(super) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext) { 15pub(super) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> {
14 if !ctx.is_attribute { 16 let attribute = ctx.attribute_under_caret.as_ref()?;
15 return;
16 }
17 17
18 let is_inner = ctx 18 match (attribute.path(), attribute.input()) {
19 .original_token 19 (Some(path), Some(AttrInput::TokenTree(token_tree))) if path.to_string() == "derive" => {
20 .ancestors() 20 complete_derive(acc, ctx, token_tree)
21 .find_map(Attr::cast) 21 }
22 .map(|attr| attr.kind() == AttrKind::Inner) 22 _ => complete_attribute_start(acc, ctx, attribute),
23 .unwrap_or(false); 23 }
24 Some(())
25}
24 26
27fn complete_attribute_start(acc: &mut Completions, ctx: &CompletionContext, attribute: &ast::Attr) {
25 for attr_completion in ATTRIBUTES { 28 for attr_completion in ATTRIBUTES {
26 let mut item = CompletionItem::new( 29 let mut item = CompletionItem::new(
27 CompletionKind::Attribute, 30 CompletionKind::Attribute,
@@ -37,7 +40,7 @@ pub(super) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext)
37 _ => {} 40 _ => {}
38 } 41 }
39 42
40 if is_inner || !attr_completion.should_be_inner { 43 if attribute.kind() == AttrKind::Inner || !attr_completion.should_be_inner {
41 acc.add(item); 44 acc.add(item);
42 } 45 }
43 } 46 }
@@ -126,6 +129,106 @@ const ATTRIBUTES: &[AttrCompletion] = &[
126 }, 129 },
127]; 130];
128 131
132fn complete_derive(acc: &mut Completions, ctx: &CompletionContext, derive_input: ast::TokenTree) {
133 if let Ok(existing_derives) = parse_derive_input(derive_input) {
134 for derive_completion in DEFAULT_DERIVE_COMPLETIONS
135 .into_iter()
136 .filter(|completion| !existing_derives.contains(completion.label))
137 {
138 let mut label = derive_completion.label.to_owned();
139 for dependency in derive_completion
140 .dependencies
141 .into_iter()
142 .filter(|&&dependency| !existing_derives.contains(dependency))
143 {
144 label.push_str(", ");
145 label.push_str(dependency);
146 }
147 acc.add(
148 CompletionItem::new(CompletionKind::Attribute, ctx.source_range(), label)
149 .kind(CompletionItemKind::Attribute),
150 );
151 }
152
153 for custom_derive_name in get_derive_names_in_scope(ctx).difference(&existing_derives) {
154 acc.add(
155 CompletionItem::new(
156 CompletionKind::Attribute,
157 ctx.source_range(),
158 custom_derive_name,
159 )
160 .kind(CompletionItemKind::Attribute),
161 );
162 }
163 }
164}
165
166fn parse_derive_input(derive_input: ast::TokenTree) -> Result<FxHashSet<String>, ()> {
167 match (derive_input.left_delimiter_token(), derive_input.right_delimiter_token()) {
168 (Some(left_paren), Some(right_paren))
169 if left_paren.kind() == SyntaxKind::L_PAREN
170 && right_paren.kind() == SyntaxKind::R_PAREN =>
171 {
172 let mut input_derives = FxHashSet::default();
173 let mut current_derive = String::new();
174 for token in derive_input
175 .syntax()
176 .children_with_tokens()
177 .filter_map(|token| token.into_token())
178 .skip_while(|token| token != &left_paren)
179 .skip(1)
180 .take_while(|token| token != &right_paren)
181 {
182 if SyntaxKind::COMMA == token.kind() {
183 if !current_derive.is_empty() {
184 input_derives.insert(current_derive);
185 current_derive = String::new();
186 }
187 } else {
188 current_derive.push_str(token.to_string().trim());
189 }
190 }
191
192 if !current_derive.is_empty() {
193 input_derives.insert(current_derive);
194 }
195 Ok(input_derives)
196 }
197 _ => Err(()),
198 }
199}
200
201fn get_derive_names_in_scope(ctx: &CompletionContext) -> FxHashSet<String> {
202 let mut result = FxHashSet::default();
203 ctx.scope().process_all_names(&mut |name, scope_def| {
204 if let hir::ScopeDef::MacroDef(mac) = scope_def {
205 if mac.is_derive_macro() {
206 result.insert(name.to_string());
207 }
208 }
209 });
210 result
211}
212
213struct DeriveCompletion {
214 label: &'static str,
215 dependencies: &'static [&'static str],
216}
217
218/// Standard Rust derives and the information about their dependencies
219/// (the dependencies are needed so that the main derive don't break the compilation when added)
220const DEFAULT_DERIVE_COMPLETIONS: &[DeriveCompletion] = &[
221 DeriveCompletion { label: "Clone", dependencies: &[] },
222 DeriveCompletion { label: "Copy", dependencies: &["Clone"] },
223 DeriveCompletion { label: "Debug", dependencies: &[] },
224 DeriveCompletion { label: "Default", dependencies: &[] },
225 DeriveCompletion { label: "Hash", dependencies: &[] },
226 DeriveCompletion { label: "PartialEq", dependencies: &[] },
227 DeriveCompletion { label: "Eq", dependencies: &["PartialEq"] },
228 DeriveCompletion { label: "PartialOrd", dependencies: &["PartialEq"] },
229 DeriveCompletion { label: "Ord", dependencies: &["PartialOrd", "Eq", "PartialEq"] },
230];
231
129#[cfg(test)] 232#[cfg(test)]
130mod tests { 233mod tests {
131 use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind}; 234 use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind};
@@ -136,6 +239,170 @@ mod tests {
136 } 239 }
137 240
138 #[test] 241 #[test]
242 fn empty_derive_completion() {
243 assert_debug_snapshot!(
244 do_attr_completion(
245 r"
246 #[derive(<|>)]
247 struct Test {}
248 ",
249 ),
250 @r###"
251 [
252 CompletionItem {
253 label: "Clone",
254 source_range: 30..30,
255 delete: 30..30,
256 insert: "Clone",
257 kind: Attribute,
258 },
259 CompletionItem {
260 label: "Copy, Clone",
261 source_range: 30..30,
262 delete: 30..30,
263 insert: "Copy, Clone",
264 kind: Attribute,
265 },
266 CompletionItem {
267 label: "Debug",
268 source_range: 30..30,
269 delete: 30..30,
270 insert: "Debug",
271 kind: Attribute,
272 },
273 CompletionItem {
274 label: "Default",
275 source_range: 30..30,
276 delete: 30..30,
277 insert: "Default",
278 kind: Attribute,
279 },
280 CompletionItem {
281 label: "Eq, PartialEq",
282 source_range: 30..30,
283 delete: 30..30,
284 insert: "Eq, PartialEq",
285 kind: Attribute,
286 },
287 CompletionItem {
288 label: "Hash",
289 source_range: 30..30,
290 delete: 30..30,
291 insert: "Hash",
292 kind: Attribute,
293 },
294 CompletionItem {
295 label: "Ord, PartialOrd, Eq, PartialEq",
296 source_range: 30..30,
297 delete: 30..30,
298 insert: "Ord, PartialOrd, Eq, PartialEq",
299 kind: Attribute,
300 },
301 CompletionItem {
302 label: "PartialEq",
303 source_range: 30..30,
304 delete: 30..30,
305 insert: "PartialEq",
306 kind: Attribute,
307 },
308 CompletionItem {
309 label: "PartialOrd, PartialEq",
310 source_range: 30..30,
311 delete: 30..30,
312 insert: "PartialOrd, PartialEq",
313 kind: Attribute,
314 },
315 ]
316 "###
317 );
318 }
319
320 #[test]
321 fn no_completion_for_incorrect_derive() {
322 assert_debug_snapshot!(
323 do_attr_completion(
324 r"
325 #[derive{<|>)]
326 struct Test {}
327 ",
328 ),
329 @"[]"
330 );
331 }
332
333 #[test]
334 fn derive_with_input_completion() {
335 assert_debug_snapshot!(
336 do_attr_completion(
337 r"
338 #[derive(serde::Serialize, PartialEq, <|>)]
339 struct Test {}
340 ",
341 ),
342 @r###"
343 [
344 CompletionItem {
345 label: "Clone",
346 source_range: 59..59,
347 delete: 59..59,
348 insert: "Clone",
349 kind: Attribute,
350 },
351 CompletionItem {
352 label: "Copy, Clone",
353 source_range: 59..59,
354 delete: 59..59,
355 insert: "Copy, Clone",
356 kind: Attribute,
357 },
358 CompletionItem {
359 label: "Debug",
360 source_range: 59..59,
361 delete: 59..59,
362 insert: "Debug",
363 kind: Attribute,
364 },
365 CompletionItem {
366 label: "Default",
367 source_range: 59..59,
368 delete: 59..59,
369 insert: "Default",
370 kind: Attribute,
371 },
372 CompletionItem {
373 label: "Eq",
374 source_range: 59..59,
375 delete: 59..59,
376 insert: "Eq",
377 kind: Attribute,
378 },
379 CompletionItem {
380 label: "Hash",
381 source_range: 59..59,
382 delete: 59..59,
383 insert: "Hash",
384 kind: Attribute,
385 },
386 CompletionItem {
387 label: "Ord, PartialOrd, Eq",
388 source_range: 59..59,
389 delete: 59..59,
390 insert: "Ord, PartialOrd, Eq",
391 kind: Attribute,
392 },
393 CompletionItem {
394 label: "PartialOrd",
395 source_range: 59..59,
396 delete: 59..59,
397 insert: "PartialOrd",
398 kind: Attribute,
399 },
400 ]
401 "###
402 );
403 }
404
405 #[test]
139 fn test_attribute_completion() { 406 fn test_attribute_completion() {
140 assert_debug_snapshot!( 407 assert_debug_snapshot!(
141 do_attr_completion( 408 do_attr_completion(
diff --git a/crates/ra_ide/src/completion/completion_context.rs b/crates/ra_ide/src/completion/completion_context.rs
index 118fceb2e..dd87bd119 100644
--- a/crates/ra_ide/src/completion/completion_context.rs
+++ b/crates/ra_ide/src/completion/completion_context.rs
@@ -58,7 +58,7 @@ pub(crate) struct CompletionContext<'a> {
58 pub(super) is_macro_call: bool, 58 pub(super) is_macro_call: bool,
59 pub(super) is_path_type: bool, 59 pub(super) is_path_type: bool,
60 pub(super) has_type_args: bool, 60 pub(super) has_type_args: bool,
61 pub(super) is_attribute: bool, 61 pub(super) attribute_under_caret: Option<ast::Attr>,
62} 62}
63 63
64impl<'a> CompletionContext<'a> { 64impl<'a> CompletionContext<'a> {
@@ -116,7 +116,7 @@ impl<'a> CompletionContext<'a> {
116 is_path_type: false, 116 is_path_type: false,
117 has_type_args: false, 117 has_type_args: false,
118 dot_receiver_is_ambiguous_float_literal: false, 118 dot_receiver_is_ambiguous_float_literal: false,
119 is_attribute: false, 119 attribute_under_caret: None,
120 }; 120 };
121 121
122 let mut original_file = original_file.syntax().clone(); 122 let mut original_file = original_file.syntax().clone();
@@ -200,6 +200,7 @@ impl<'a> CompletionContext<'a> {
200 Some(ty) 200 Some(ty)
201 }) 201 })
202 .flatten(); 202 .flatten();
203 self.attribute_under_caret = find_node_at_offset(&file_with_fake_ident, offset);
203 204
204 // First, let's try to complete a reference to some declaration. 205 // First, let's try to complete a reference to some declaration.
205 if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(&file_with_fake_ident, offset) { 206 if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(&file_with_fake_ident, offset) {
@@ -318,7 +319,6 @@ impl<'a> CompletionContext<'a> {
318 .and_then(|it| it.syntax().parent().and_then(ast::CallExpr::cast)) 319 .and_then(|it| it.syntax().parent().and_then(ast::CallExpr::cast))
319 .is_some(); 320 .is_some();
320 self.is_macro_call = path.syntax().parent().and_then(ast::MacroCall::cast).is_some(); 321 self.is_macro_call = path.syntax().parent().and_then(ast::MacroCall::cast).is_some();
321 self.is_attribute = path.syntax().parent().and_then(ast::Attr::cast).is_some();
322 322
323 self.is_path_type = path.syntax().parent().and_then(ast::PathType::cast).is_some(); 323 self.is_path_type = path.syntax().parent().and_then(ast::PathType::cast).is_some();
324 self.has_type_args = segment.type_arg_list().is_some(); 324 self.has_type_args = segment.type_arg_list().is_some();
@@ -344,7 +344,7 @@ impl<'a> CompletionContext<'a> {
344 stmt.syntax().text_range() == name_ref.syntax().text_range(), 344 stmt.syntax().text_range() == name_ref.syntax().text_range(),
345 ); 345 );
346 } 346 }
347 if let Some(block) = ast::Block::cast(node) { 347 if let Some(block) = ast::BlockExpr::cast(node) {
348 return Some( 348 return Some(
349 block.expr().map(|e| e.syntax().text_range()) 349 block.expr().map(|e| e.syntax().text_range())
350 == Some(name_ref.syntax().text_range()), 350 == Some(name_ref.syntax().text_range()),
diff --git a/crates/ra_ide/src/display/short_label.rs b/crates/ra_ide/src/display/short_label.rs
index 4b081bf6c..d37260e96 100644
--- a/crates/ra_ide/src/display/short_label.rs
+++ b/crates/ra_ide/src/display/short_label.rs
@@ -33,7 +33,11 @@ impl ShortLabel for ast::EnumDef {
33 33
34impl ShortLabel for ast::TraitDef { 34impl ShortLabel for ast::TraitDef {
35 fn short_label(&self) -> Option<String> { 35 fn short_label(&self) -> Option<String> {
36 short_label_from_node(self, "trait ") 36 if self.unsafe_token().is_some() {
37 short_label_from_node(self, "unsafe trait ")
38 } else {
39 short_label_from_node(self, "trait ")
40 }
37 } 41 }
38} 42}
39 43
diff --git a/crates/ra_ide/src/folding_ranges.rs b/crates/ra_ide/src/folding_ranges.rs
index 4379005aa..8657377de 100644
--- a/crates/ra_ide/src/folding_ranges.rs
+++ b/crates/ra_ide/src/folding_ranges.rs
@@ -88,7 +88,7 @@ fn fold_kind(kind: SyntaxKind) -> Option<FoldKind> {
88 | ITEM_LIST 88 | ITEM_LIST
89 | EXTERN_ITEM_LIST 89 | EXTERN_ITEM_LIST
90 | USE_TREE_LIST 90 | USE_TREE_LIST
91 | BLOCK 91 | BLOCK_EXPR
92 | MATCH_ARM_LIST 92 | MATCH_ARM_LIST
93 | ENUM_VARIANT_LIST 93 | ENUM_VARIANT_LIST
94 | TOKEN_TREE => Some(FoldKind::Block), 94 | TOKEN_TREE => Some(FoldKind::Block),
diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs
index a62f598f0..54d318858 100644
--- a/crates/ra_ide/src/hover.rs
+++ b/crates/ra_ide/src/hover.rs
@@ -869,4 +869,15 @@ fn func(foo: i32) { if true { <|>foo; }; }
869 &[r#"pub(crate) async unsafe extern "C" fn foo()"#], 869 &[r#"pub(crate) async unsafe extern "C" fn foo()"#],
870 ); 870 );
871 } 871 }
872
873 #[test]
874 fn test_hover_trait_show_qualifiers() {
875 check_hover_result(
876 "
877 //- /lib.rs
878 unsafe trait foo<|>() {}
879 ",
880 &["unsafe trait foo"],
881 );
882 }
872} 883}
diff --git a/crates/ra_ide/src/join_lines.rs b/crates/ra_ide/src/join_lines.rs
index d0def7eaa..63fd6b3e4 100644
--- a/crates/ra_ide/src/join_lines.rs
+++ b/crates/ra_ide/src/join_lines.rs
@@ -129,8 +129,7 @@ fn has_comma_after(node: &SyntaxNode) -> bool {
129} 129}
130 130
131fn join_single_expr_block(edit: &mut TextEditBuilder, token: &SyntaxToken) -> Option<()> { 131fn join_single_expr_block(edit: &mut TextEditBuilder, token: &SyntaxToken) -> Option<()> {
132 let block = ast::Block::cast(token.parent())?; 132 let block_expr = ast::BlockExpr::cast(token.parent())?;
133 let block_expr = ast::BlockExpr::cast(block.syntax().parent()?)?;
134 if !block_expr.is_standalone() { 133 if !block_expr.is_standalone() {
135 return None; 134 return None;
136 } 135 }
diff --git a/crates/ra_ide/src/syntax_tree.rs b/crates/ra_ide/src/syntax_tree.rs
index bf97f8c56..86c70ff83 100644
--- a/crates/ra_ide/src/syntax_tree.rs
+++ b/crates/ra_ide/src/syntax_tree.rs
@@ -120,9 +120,8 @@ [email protected]
120 [email protected] ")" 120 [email protected] ")"
121 [email protected] " " 121 [email protected] " "
122 [email protected] 122 [email protected]
123 [email protected] 123 [email protected] "{"
124 [email protected] "{" 124 [email protected] "}"
125 [email protected] "}"
126"# 125"#
127 .trim() 126 .trim()
128 ); 127 );
@@ -153,26 +152,25 @@ [email protected]
153 [email protected] ")" 152 [email protected] ")"
154 [email protected] " " 153 [email protected] " "
155 [email protected] 154 [email protected]
156 [email protected] 155 [email protected] "{"
157 [email protected] "{" 156 [email protected] "\n "
158 [email protected] "\n " 157 [email protected]
159 [email protected] 158 [email protected]
160 [email protected] 159 [email protected]
161 [email protected] 160 [email protected]
162 [email protected] 161 [email protected]
163 [email protected] 162 [email protected] "assert"
164 [email protected] "assert" 163 [email protected] "!"
165 [email protected] "!" 164 [email protected]
166 [email protected] 165 [email protected] "("
167 [email protected] "(" 166 [email protected] "\"\n fn foo() {\n ..."
168 [email protected] "\"\n fn foo() {\n ..." 167 [email protected] ","
169 [email protected] "," 168 [email protected] " "
170 [email protected] " " 169 [email protected] "\"\""
171 [email protected] "\"\"" 170 [email protected] ")"
172 [email protected] ")" 171 [email protected] ";"
173 [email protected] ";" 172 [email protected] "\n"
174 [email protected] "\n" 173 [email protected] "}"
175 [email protected] "}"
176"# 174"#
177 .trim() 175 .trim()
178 ); 176 );
@@ -196,9 +194,8 @@ [email protected]
196 [email protected] ")" 194 [email protected] ")"
197 [email protected] " " 195 [email protected] " "
198 [email protected] 196 [email protected]
199 [email protected] 197 [email protected] "{"
200 [email protected] "{" 198 [email protected] "}"
201 [email protected] "}"
202"# 199"#
203 .trim() 200 .trim()
204 ); 201 );
@@ -265,10 +262,9 @@ [email protected]
265 [email protected] ")" 262 [email protected] ")"
266 [email protected] " " 263 [email protected] " "
267 [email protected] 264 [email protected]
268 [email protected] 265 [email protected] "{"
269 [email protected] "{" 266 [email protected] "\n"
270 [email protected] "\n" 267 [email protected] "}"
271 [email protected] "}"
272"# 268"#
273 .trim() 269 .trim()
274 ); 270 );
@@ -300,10 +296,9 @@ [email protected]
300 [email protected] ")" 296 [email protected] ")"
301 [email protected] " " 297 [email protected] " "
302 [email protected] 298 [email protected]
303 [email protected] 299 [email protected] "{"
304 [email protected] "{" 300 [email protected] "\n"
305 [email protected] "\n" 301 [email protected] "}"
306 [email protected] "}"
307"# 302"#
308 .trim() 303 .trim()
309 ); 304 );
@@ -334,10 +329,9 @@ [email protected]
334 [email protected] ")" 329 [email protected] ")"
335 [email protected] " " 330 [email protected] " "
336 [email protected] 331 [email protected]
337 [email protected] 332 [email protected] "{"
338 [email protected] "{" 333 [email protected] "\n"
339 [email protected] "\n" 334 [email protected] "}"
340 [email protected] "}"
341 [email protected] "\n" 335 [email protected] "\n"
342 [email protected] 336 [email protected]
343 [email protected] "fn" 337 [email protected] "fn"
@@ -349,10 +343,9 @@ [email protected]
349 [email protected] ")" 343 [email protected] ")"
350 [email protected] " " 344 [email protected] " "
351 [email protected] 345 [email protected]
352 [email protected] 346 [email protected] "{"
353 [email protected] "{" 347 [email protected] "\n"
354 [email protected] "\n" 348 [email protected] "}"
355 [email protected] "}"
356"# 349"#
357 .trim() 350 .trim()
358 ); 351 );