diff options
Diffstat (limited to 'crates/ra_ide')
-rw-r--r-- | crates/ra_ide/Cargo.toml | 2 | ||||
-rw-r--r-- | crates/ra_ide/src/completion/complete_attribute.rs | 282 | ||||
-rw-r--r-- | crates/ra_ide/src/diagnostics.rs | 156 | ||||
-rw-r--r-- | crates/ra_ide/src/folding_ranges.rs | 14 | ||||
-rw-r--r-- | crates/ra_ide/src/goto_implementation.rs | 10 | ||||
-rw-r--r-- | crates/ra_ide/src/lib.rs | 12 | ||||
-rw-r--r-- | crates/ra_ide/src/runnables.rs | 164 | ||||
-rw-r--r-- | crates/ra_ide/src/ssr.rs | 39 | ||||
-rw-r--r-- | crates/ra_ide/src/syntax_highlighting/html.rs | 10 | ||||
-rw-r--r-- | crates/ra_ide/test_data/rainbow_highlighting.html | 12 |
10 files changed, 534 insertions, 167 deletions
diff --git a/crates/ra_ide/Cargo.toml b/crates/ra_ide/Cargo.toml index 6f8107491..f4181c4eb 100644 --- a/crates/ra_ide/Cargo.toml +++ b/crates/ra_ide/Cargo.toml | |||
@@ -17,7 +17,7 @@ indexmap = "1.3.2" | |||
17 | itertools = "0.9.0" | 17 | itertools = "0.9.0" |
18 | log = "0.4.8" | 18 | log = "0.4.8" |
19 | rustc-hash = "1.1.0" | 19 | rustc-hash = "1.1.0" |
20 | rand = { version = "0.7.3", features = ["small_rng"] } | 20 | oorandom = "11.1.2" |
21 | 21 | ||
22 | stdx = { path = "../stdx" } | 22 | stdx = { path = "../stdx" } |
23 | 23 | ||
diff --git a/crates/ra_ide/src/completion/complete_attribute.rs b/crates/ra_ide/src/completion/complete_attribute.rs index d268c92be..109c5e9a8 100644 --- a/crates/ra_ide/src/completion/complete_attribute.rs +++ b/crates/ra_ide/src/completion/complete_attribute.rs | |||
@@ -13,13 +13,19 @@ use crate::completion::{ | |||
13 | 13 | ||
14 | pub(super) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { | 14 | pub(super) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { |
15 | let attribute = ctx.attribute_under_caret.as_ref()?; | 15 | let attribute = ctx.attribute_under_caret.as_ref()?; |
16 | |||
17 | match (attribute.path(), attribute.input()) { | 16 | match (attribute.path(), attribute.input()) { |
18 | (Some(path), Some(ast::AttrInput::TokenTree(token_tree))) | 17 | (Some(path), Some(ast::AttrInput::TokenTree(token_tree))) |
19 | if path.to_string() == "derive" => | 18 | if path.to_string() == "derive" => |
20 | { | 19 | { |
21 | complete_derive(acc, ctx, token_tree) | 20 | complete_derive(acc, ctx, token_tree) |
22 | } | 21 | } |
22 | (Some(path), Some(ast::AttrInput::TokenTree(token_tree))) | ||
23 | if ["allow", "warn", "deny", "forbid"] | ||
24 | .iter() | ||
25 | .any(|lint_level| lint_level == &path.to_string()) => | ||
26 | { | ||
27 | complete_lint(acc, ctx, token_tree) | ||
28 | } | ||
23 | (_, Some(ast::AttrInput::TokenTree(_token_tree))) => {} | 29 | (_, Some(ast::AttrInput::TokenTree(_token_tree))) => {} |
24 | _ => complete_attribute_start(acc, ctx, attribute), | 30 | _ => complete_attribute_start(acc, ctx, attribute), |
25 | } | 31 | } |
@@ -125,7 +131,7 @@ const ATTRIBUTES: &[AttrCompletion] = &[ | |||
125 | ]; | 131 | ]; |
126 | 132 | ||
127 | fn complete_derive(acc: &mut Completions, ctx: &CompletionContext, derive_input: ast::TokenTree) { | 133 | fn complete_derive(acc: &mut Completions, ctx: &CompletionContext, derive_input: ast::TokenTree) { |
128 | if let Ok(existing_derives) = parse_derive_input(derive_input) { | 134 | if let Ok(existing_derives) = parse_comma_sep_input(derive_input) { |
129 | for derive_completion in DEFAULT_DERIVE_COMPLETIONS | 135 | for derive_completion in DEFAULT_DERIVE_COMPLETIONS |
130 | .into_iter() | 136 | .into_iter() |
131 | .filter(|completion| !existing_derives.contains(completion.label)) | 137 | .filter(|completion| !existing_derives.contains(completion.label)) |
@@ -158,7 +164,26 @@ fn complete_derive(acc: &mut Completions, ctx: &CompletionContext, derive_input: | |||
158 | } | 164 | } |
159 | } | 165 | } |
160 | 166 | ||
161 | fn parse_derive_input(derive_input: ast::TokenTree) -> Result<FxHashSet<String>, ()> { | 167 | fn complete_lint(acc: &mut Completions, ctx: &CompletionContext, derive_input: ast::TokenTree) { |
168 | if let Ok(existing_lints) = parse_comma_sep_input(derive_input) { | ||
169 | for lint_completion in DEFAULT_LINT_COMPLETIONS | ||
170 | .into_iter() | ||
171 | .filter(|completion| !existing_lints.contains(completion.label)) | ||
172 | { | ||
173 | acc.add( | ||
174 | CompletionItem::new( | ||
175 | CompletionKind::Attribute, | ||
176 | ctx.source_range(), | ||
177 | lint_completion.label, | ||
178 | ) | ||
179 | .kind(CompletionItemKind::Attribute) | ||
180 | .detail(lint_completion.description), | ||
181 | ); | ||
182 | } | ||
183 | } | ||
184 | } | ||
185 | |||
186 | fn parse_comma_sep_input(derive_input: ast::TokenTree) -> Result<FxHashSet<String>, ()> { | ||
162 | match (derive_input.left_delimiter_token(), derive_input.right_delimiter_token()) { | 187 | match (derive_input.left_delimiter_token(), derive_input.right_delimiter_token()) { |
163 | (Some(left_paren), Some(right_paren)) | 188 | (Some(left_paren), Some(right_paren)) |
164 | if left_paren.kind() == SyntaxKind::L_PAREN | 189 | if left_paren.kind() == SyntaxKind::L_PAREN |
@@ -212,6 +237,7 @@ struct DeriveCompletion { | |||
212 | 237 | ||
213 | /// Standard Rust derives and the information about their dependencies | 238 | /// Standard Rust derives and the information about their dependencies |
214 | /// (the dependencies are needed so that the main derive don't break the compilation when added) | 239 | /// (the dependencies are needed so that the main derive don't break the compilation when added) |
240 | #[rustfmt::skip] | ||
215 | const DEFAULT_DERIVE_COMPLETIONS: &[DeriveCompletion] = &[ | 241 | const DEFAULT_DERIVE_COMPLETIONS: &[DeriveCompletion] = &[ |
216 | DeriveCompletion { label: "Clone", dependencies: &[] }, | 242 | DeriveCompletion { label: "Clone", dependencies: &[] }, |
217 | DeriveCompletion { label: "Copy", dependencies: &["Clone"] }, | 243 | DeriveCompletion { label: "Copy", dependencies: &["Clone"] }, |
@@ -224,6 +250,130 @@ const DEFAULT_DERIVE_COMPLETIONS: &[DeriveCompletion] = &[ | |||
224 | DeriveCompletion { label: "Ord", dependencies: &["PartialOrd", "Eq", "PartialEq"] }, | 250 | DeriveCompletion { label: "Ord", dependencies: &["PartialOrd", "Eq", "PartialEq"] }, |
225 | ]; | 251 | ]; |
226 | 252 | ||
253 | struct LintCompletion { | ||
254 | label: &'static str, | ||
255 | description: &'static str, | ||
256 | } | ||
257 | |||
258 | #[rustfmt::skip] | ||
259 | const DEFAULT_LINT_COMPLETIONS: &[LintCompletion] = &[ | ||
260 | LintCompletion { label: "absolute_paths_not_starting_with_crate", description: r#"fully qualified paths that start with a module name instead of `crate`, `self`, or an extern crate name"# }, | ||
261 | LintCompletion { label: "anonymous_parameters", description: r#"detects anonymous parameters"# }, | ||
262 | LintCompletion { label: "box_pointers", description: r#"use of owned (Box type) heap memory"# }, | ||
263 | LintCompletion { label: "deprecated_in_future", description: r#"detects use of items that will be deprecated in a future version"# }, | ||
264 | LintCompletion { label: "elided_lifetimes_in_paths", description: r#"hidden lifetime parameters in types are deprecated"# }, | ||
265 | LintCompletion { label: "explicit_outlives_requirements", description: r#"outlives requirements can be inferred"# }, | ||
266 | LintCompletion { label: "indirect_structural_match", description: r#"pattern with const indirectly referencing non-structural-match type"# }, | ||
267 | LintCompletion { label: "keyword_idents", description: r#"detects edition keywords being used as an identifier"# }, | ||
268 | LintCompletion { label: "macro_use_extern_crate", description: r#"the `#[macro_use]` attribute is now deprecated in favor of using macros via the module system"# }, | ||
269 | LintCompletion { label: "meta_variable_misuse", description: r#"possible meta-variable misuse at macro definition"# }, | ||
270 | LintCompletion { label: "missing_copy_implementations", description: r#"detects potentially-forgotten implementations of `Copy`"# }, | ||
271 | LintCompletion { label: "missing_crate_level_docs", description: r#"detects crates with no crate-level documentation"# }, | ||
272 | LintCompletion { label: "missing_debug_implementations", description: r#"detects missing implementations of Debug"# }, | ||
273 | LintCompletion { label: "missing_docs", description: r#"detects missing documentation for public members"# }, | ||
274 | LintCompletion { label: "missing_doc_code_examples", description: r#"detects publicly-exported items without code samples in their documentation"# }, | ||
275 | LintCompletion { label: "non_ascii_idents", description: r#"detects non-ASCII identifiers"# }, | ||
276 | LintCompletion { label: "private_doc_tests", description: r#"detects code samples in docs of private items not documented by rustdoc"# }, | ||
277 | LintCompletion { label: "single_use_lifetimes", description: r#"detects lifetime parameters that are only used once"# }, | ||
278 | LintCompletion { label: "trivial_casts", description: r#"detects trivial casts which could be removed"# }, | ||
279 | LintCompletion { label: "trivial_numeric_casts", description: r#"detects trivial casts of numeric types which could be removed"# }, | ||
280 | LintCompletion { label: "unaligned_references", description: r#"detects unaligned references to fields of packed structs"# }, | ||
281 | LintCompletion { label: "unreachable_pub", description: r#"`pub` items not reachable from crate root"# }, | ||
282 | LintCompletion { label: "unsafe_code", description: r#"usage of `unsafe` code"# }, | ||
283 | LintCompletion { label: "unsafe_op_in_unsafe_fn", description: r#"unsafe operations in unsafe functions without an explicit unsafe block are deprecated"# }, | ||
284 | LintCompletion { label: "unstable_features", description: r#"enabling unstable features (deprecated. do not use)"# }, | ||
285 | LintCompletion { label: "unused_crate_dependencies", description: r#"crate dependencies that are never used"# }, | ||
286 | LintCompletion { label: "unused_extern_crates", description: r#"extern crates that are never used"# }, | ||
287 | LintCompletion { label: "unused_import_braces", description: r#"unnecessary braces around an imported item"# }, | ||
288 | LintCompletion { label: "unused_lifetimes", description: r#"detects lifetime parameters that are never used"# }, | ||
289 | LintCompletion { label: "unused_qualifications", description: r#"detects unnecessarily qualified names"# }, | ||
290 | LintCompletion { label: "unused_results", description: r#"unused result of an expression in a statement"# }, | ||
291 | LintCompletion { label: "variant_size_differences", description: r#"detects enums with widely varying variant sizes"# }, | ||
292 | LintCompletion { label: "array_into_iter", description: r#"detects calling `into_iter` on arrays"# }, | ||
293 | LintCompletion { label: "asm_sub_register", description: r#"using only a subset of a register for inline asm inputs"# }, | ||
294 | LintCompletion { label: "bare_trait_objects", description: r#"suggest using `dyn Trait` for trait objects"# }, | ||
295 | LintCompletion { label: "bindings_with_variant_name", description: r#"detects pattern bindings with the same name as one of the matched variants"# }, | ||
296 | LintCompletion { label: "cenum_impl_drop_cast", description: r#"a C-like enum implementing Drop is cast"# }, | ||
297 | LintCompletion { label: "clashing_extern_declarations", description: r#"detects when an extern fn has been declared with the same name but different types"# }, | ||
298 | LintCompletion { label: "coherence_leak_check", description: r#"distinct impls distinguished only by the leak-check code"# }, | ||
299 | LintCompletion { label: "confusable_idents", description: r#"detects visually confusable pairs between identifiers"# }, | ||
300 | LintCompletion { label: "dead_code", description: r#"detect unused, unexported items"# }, | ||
301 | LintCompletion { label: "deprecated", description: r#"detects use of deprecated items"# }, | ||
302 | LintCompletion { label: "ellipsis_inclusive_range_patterns", description: r#"`...` range patterns are deprecated"# }, | ||
303 | LintCompletion { label: "exported_private_dependencies", description: r#"public interface leaks type from a private dependency"# }, | ||
304 | LintCompletion { label: "illegal_floating_point_literal_pattern", description: r#"floating-point literals cannot be used in patterns"# }, | ||
305 | LintCompletion { label: "improper_ctypes", description: r#"proper use of libc types in foreign modules"# }, | ||
306 | LintCompletion { label: "improper_ctypes_definitions", description: r#"proper use of libc types in foreign item definitions"# }, | ||
307 | LintCompletion { label: "incomplete_features", description: r#"incomplete features that may function improperly in some or all cases"# }, | ||
308 | LintCompletion { label: "inline_no_sanitize", description: r#"detects incompatible use of `#[inline(always)]` and `#[no_sanitize(...)]`"# }, | ||
309 | LintCompletion { label: "intra_doc_link_resolution_failure", description: r#"failures in resolving intra-doc link targets"# }, | ||
310 | LintCompletion { label: "invalid_codeblock_attributes", description: r#"codeblock attribute looks a lot like a known one"# }, | ||
311 | LintCompletion { label: "invalid_value", description: r#"an invalid value is being created (such as a NULL reference)"# }, | ||
312 | LintCompletion { label: "irrefutable_let_patterns", description: r#"detects irrefutable patterns in if-let and while-let statements"# }, | ||
313 | LintCompletion { label: "late_bound_lifetime_arguments", description: r#"detects generic lifetime arguments in path segments with late bound lifetime parameters"# }, | ||
314 | LintCompletion { label: "mixed_script_confusables", description: r#"detects Unicode scripts whose mixed script confusables codepoints are solely used"# }, | ||
315 | LintCompletion { label: "mutable_borrow_reservation_conflict", description: r#"reservation of a two-phased borrow conflicts with other shared borrows"# }, | ||
316 | LintCompletion { label: "non_camel_case_types", description: r#"types, variants, traits and type parameters should have camel case names"# }, | ||
317 | LintCompletion { label: "non_shorthand_field_patterns", description: r#"using `Struct { x: x }` instead of `Struct { x }` in a pattern"# }, | ||
318 | LintCompletion { label: "non_snake_case", description: r#"variables, methods, functions, lifetime parameters and modules should have snake case names"# }, | ||
319 | LintCompletion { label: "non_upper_case_globals", description: r#"static constants should have uppercase identifiers"# }, | ||
320 | LintCompletion { label: "no_mangle_generic_items", description: r#"generic items must be mangled"# }, | ||
321 | LintCompletion { label: "overlapping_patterns", description: r#"detects overlapping patterns"# }, | ||
322 | LintCompletion { label: "path_statements", description: r#"path statements with no effect"# }, | ||
323 | LintCompletion { label: "private_in_public", description: r#"detect private items in public interfaces not caught by the old implementation"# }, | ||
324 | LintCompletion { label: "proc_macro_derive_resolution_fallback", description: r#"detects proc macro derives using inaccessible names from parent modules"# }, | ||
325 | LintCompletion { label: "redundant_semicolons", description: r#"detects unnecessary trailing semicolons"# }, | ||
326 | LintCompletion { label: "renamed_and_removed_lints", description: r#"lints that have been renamed or removed"# }, | ||
327 | LintCompletion { label: "safe_packed_borrows", description: r#"safe borrows of fields of packed structs were erroneously allowed"# }, | ||
328 | LintCompletion { label: "stable_features", description: r#"stable features found in `#[feature]` directive"# }, | ||
329 | LintCompletion { label: "trivial_bounds", description: r#"these bounds don't depend on an type parameters"# }, | ||
330 | LintCompletion { label: "type_alias_bounds", description: r#"bounds in type aliases are not enforced"# }, | ||
331 | LintCompletion { label: "tyvar_behind_raw_pointer", description: r#"raw pointer to an inference variable"# }, | ||
332 | LintCompletion { label: "uncommon_codepoints", description: r#"detects uncommon Unicode codepoints in identifiers"# }, | ||
333 | LintCompletion { label: "unconditional_recursion", description: r#"functions that cannot return without calling themselves"# }, | ||
334 | LintCompletion { label: "unknown_lints", description: r#"unrecognized lint attribute"# }, | ||
335 | LintCompletion { label: "unnameable_test_items", description: r#"detects an item that cannot be named being marked as `#[test_case]`"# }, | ||
336 | LintCompletion { label: "unreachable_code", description: r#"detects unreachable code paths"# }, | ||
337 | LintCompletion { label: "unreachable_patterns", description: r#"detects unreachable patterns"# }, | ||
338 | LintCompletion { label: "unstable_name_collisions", description: r#"detects name collision with an existing but unstable method"# }, | ||
339 | LintCompletion { label: "unused_allocation", description: r#"detects unnecessary allocations that can be eliminated"# }, | ||
340 | LintCompletion { label: "unused_assignments", description: r#"detect assignments that will never be read"# }, | ||
341 | LintCompletion { label: "unused_attributes", description: r#"detects attributes that were not used by the compiler"# }, | ||
342 | LintCompletion { label: "unused_braces", description: r#"unnecessary braces around an expression"# }, | ||
343 | LintCompletion { label: "unused_comparisons", description: r#"comparisons made useless by limits of the types involved"# }, | ||
344 | LintCompletion { label: "unused_doc_comments", description: r#"detects doc comments that aren't used by rustdoc"# }, | ||
345 | LintCompletion { label: "unused_features", description: r#"unused features found in crate-level `#[feature]` directives"# }, | ||
346 | LintCompletion { label: "unused_imports", description: r#"imports that are never used"# }, | ||
347 | LintCompletion { label: "unused_labels", description: r#"detects labels that are never used"# }, | ||
348 | LintCompletion { label: "unused_macros", description: r#"detects macros that were not used"# }, | ||
349 | LintCompletion { label: "unused_must_use", description: r#"unused result of a type flagged as `#[must_use]`"# }, | ||
350 | LintCompletion { label: "unused_mut", description: r#"detect mut variables which don't need to be mutable"# }, | ||
351 | LintCompletion { label: "unused_parens", description: r#"`if`, `match`, `while` and `return` do not need parentheses"# }, | ||
352 | LintCompletion { label: "unused_unsafe", description: r#"unnecessary use of an `unsafe` block"# }, | ||
353 | LintCompletion { label: "unused_variables", description: r#"detect variables which are not used in any way"# }, | ||
354 | LintCompletion { label: "warnings", description: r#"mass-change the level for lints which produce warnings"# }, | ||
355 | LintCompletion { label: "where_clauses_object_safety", description: r#"checks the object safety of where clauses"# }, | ||
356 | LintCompletion { label: "while_true", description: r#"suggest using `loop { }` instead of `while true { }`"# }, | ||
357 | LintCompletion { label: "ambiguous_associated_items", description: r#"ambiguous associated items"# }, | ||
358 | LintCompletion { label: "arithmetic_overflow", description: r#"arithmetic operation overflows"# }, | ||
359 | LintCompletion { label: "conflicting_repr_hints", description: r#"conflicts between `#[repr(..)]` hints that were previously accepted and used in practice"# }, | ||
360 | LintCompletion { label: "const_err", description: r#"constant evaluation detected erroneous expression"# }, | ||
361 | LintCompletion { label: "ill_formed_attribute_input", description: r#"ill-formed attribute inputs that were previously accepted and used in practice"# }, | ||
362 | LintCompletion { label: "incomplete_include", description: r#"trailing content in included file"# }, | ||
363 | LintCompletion { label: "invalid_type_param_default", description: r#"type parameter default erroneously allowed in invalid location"# }, | ||
364 | LintCompletion { label: "macro_expanded_macro_exports_accessed_by_absolute_paths", description: r#"macro-expanded `macro_export` macros from the current crate cannot be referred to by absolute paths"# }, | ||
365 | LintCompletion { label: "missing_fragment_specifier", description: r#"detects missing fragment specifiers in unused `macro_rules!` patterns"# }, | ||
366 | LintCompletion { label: "mutable_transmutes", description: r#"mutating transmuted &mut T from &T may cause undefined behavior"# }, | ||
367 | LintCompletion { label: "no_mangle_const_items", description: r#"const items will not have their symbols exported"# }, | ||
368 | LintCompletion { label: "order_dependent_trait_objects", description: r#"trait-object types were treated as different depending on marker-trait order"# }, | ||
369 | LintCompletion { label: "overflowing_literals", description: r#"literal out of range for its type"# }, | ||
370 | LintCompletion { label: "patterns_in_fns_without_body", description: r#"patterns in functions without body were erroneously allowed"# }, | ||
371 | LintCompletion { label: "pub_use_of_private_extern_crate", description: r#"detect public re-exports of private extern crates"# }, | ||
372 | LintCompletion { label: "soft_unstable", description: r#"a feature gate that doesn't break dependent crates"# }, | ||
373 | LintCompletion { label: "unconditional_panic", description: r#"operation will cause a panic at runtime"# }, | ||
374 | LintCompletion { label: "unknown_crate_types", description: r#"unknown crate type found in `#[crate_type]` directive"# }, | ||
375 | ]; | ||
376 | |||
227 | #[cfg(test)] | 377 | #[cfg(test)] |
228 | mod tests { | 378 | mod tests { |
229 | use expect::{expect, Expect}; | 379 | use expect::{expect, Expect}; |
@@ -257,6 +407,130 @@ struct Test {} | |||
257 | } | 407 | } |
258 | 408 | ||
259 | #[test] | 409 | #[test] |
410 | fn empty_lint_completion() { | ||
411 | check( | ||
412 | r#"#[allow(<|>)]"#, | ||
413 | expect![[r#" | ||
414 | at absolute_paths_not_starting_with_crate fully qualified paths that start with a module name instead of `crate`, `self`, or an extern crate name | ||
415 | at ambiguous_associated_items ambiguous associated items | ||
416 | at anonymous_parameters detects anonymous parameters | ||
417 | at arithmetic_overflow arithmetic operation overflows | ||
418 | at array_into_iter detects calling `into_iter` on arrays | ||
419 | at asm_sub_register using only a subset of a register for inline asm inputs | ||
420 | at bare_trait_objects suggest using `dyn Trait` for trait objects | ||
421 | at bindings_with_variant_name detects pattern bindings with the same name as one of the matched variants | ||
422 | at box_pointers use of owned (Box type) heap memory | ||
423 | at cenum_impl_drop_cast a C-like enum implementing Drop is cast | ||
424 | at clashing_extern_declarations detects when an extern fn has been declared with the same name but different types | ||
425 | at coherence_leak_check distinct impls distinguished only by the leak-check code | ||
426 | at conflicting_repr_hints conflicts between `#[repr(..)]` hints that were previously accepted and used in practice | ||
427 | at confusable_idents detects visually confusable pairs between identifiers | ||
428 | at const_err constant evaluation detected erroneous expression | ||
429 | at dead_code detect unused, unexported items | ||
430 | at deprecated detects use of deprecated items | ||
431 | at deprecated_in_future detects use of items that will be deprecated in a future version | ||
432 | at elided_lifetimes_in_paths hidden lifetime parameters in types are deprecated | ||
433 | at ellipsis_inclusive_range_patterns `...` range patterns are deprecated | ||
434 | at explicit_outlives_requirements outlives requirements can be inferred | ||
435 | at exported_private_dependencies public interface leaks type from a private dependency | ||
436 | at ill_formed_attribute_input ill-formed attribute inputs that were previously accepted and used in practice | ||
437 | at illegal_floating_point_literal_pattern floating-point literals cannot be used in patterns | ||
438 | at improper_ctypes proper use of libc types in foreign modules | ||
439 | at improper_ctypes_definitions proper use of libc types in foreign item definitions | ||
440 | at incomplete_features incomplete features that may function improperly in some or all cases | ||
441 | at incomplete_include trailing content in included file | ||
442 | at indirect_structural_match pattern with const indirectly referencing non-structural-match type | ||
443 | at inline_no_sanitize detects incompatible use of `#[inline(always)]` and `#[no_sanitize(...)]` | ||
444 | at intra_doc_link_resolution_failure failures in resolving intra-doc link targets | ||
445 | at invalid_codeblock_attributes codeblock attribute looks a lot like a known one | ||
446 | at invalid_type_param_default type parameter default erroneously allowed in invalid location | ||
447 | at invalid_value an invalid value is being created (such as a NULL reference) | ||
448 | at irrefutable_let_patterns detects irrefutable patterns in if-let and while-let statements | ||
449 | at keyword_idents detects edition keywords being used as an identifier | ||
450 | at late_bound_lifetime_arguments detects generic lifetime arguments in path segments with late bound lifetime parameters | ||
451 | at macro_expanded_macro_exports_accessed_by_absolute_paths macro-expanded `macro_export` macros from the current crate cannot be referred to by absolute paths | ||
452 | at macro_use_extern_crate the `#[macro_use]` attribute is now deprecated in favor of using macros via the module system | ||
453 | at meta_variable_misuse possible meta-variable misuse at macro definition | ||
454 | at missing_copy_implementations detects potentially-forgotten implementations of `Copy` | ||
455 | at missing_crate_level_docs detects crates with no crate-level documentation | ||
456 | at missing_debug_implementations detects missing implementations of Debug | ||
457 | at missing_doc_code_examples detects publicly-exported items without code samples in their documentation | ||
458 | at missing_docs detects missing documentation for public members | ||
459 | at missing_fragment_specifier detects missing fragment specifiers in unused `macro_rules!` patterns | ||
460 | at mixed_script_confusables detects Unicode scripts whose mixed script confusables codepoints are solely used | ||
461 | at mutable_borrow_reservation_conflict reservation of a two-phased borrow conflicts with other shared borrows | ||
462 | at mutable_transmutes mutating transmuted &mut T from &T may cause undefined behavior | ||
463 | at no_mangle_const_items const items will not have their symbols exported | ||
464 | at no_mangle_generic_items generic items must be mangled | ||
465 | at non_ascii_idents detects non-ASCII identifiers | ||
466 | at non_camel_case_types types, variants, traits and type parameters should have camel case names | ||
467 | at non_shorthand_field_patterns using `Struct { x: x }` instead of `Struct { x }` in a pattern | ||
468 | at non_snake_case variables, methods, functions, lifetime parameters and modules should have snake case names | ||
469 | at non_upper_case_globals static constants should have uppercase identifiers | ||
470 | at order_dependent_trait_objects trait-object types were treated as different depending on marker-trait order | ||
471 | at overflowing_literals literal out of range for its type | ||
472 | at overlapping_patterns detects overlapping patterns | ||
473 | at path_statements path statements with no effect | ||
474 | at patterns_in_fns_without_body patterns in functions without body were erroneously allowed | ||
475 | at private_doc_tests detects code samples in docs of private items not documented by rustdoc | ||
476 | at private_in_public detect private items in public interfaces not caught by the old implementation | ||
477 | at proc_macro_derive_resolution_fallback detects proc macro derives using inaccessible names from parent modules | ||
478 | at pub_use_of_private_extern_crate detect public re-exports of private extern crates | ||
479 | at redundant_semicolons detects unnecessary trailing semicolons | ||
480 | at renamed_and_removed_lints lints that have been renamed or removed | ||
481 | at safe_packed_borrows safe borrows of fields of packed structs were erroneously allowed | ||
482 | at single_use_lifetimes detects lifetime parameters that are only used once | ||
483 | at soft_unstable a feature gate that doesn't break dependent crates | ||
484 | at stable_features stable features found in `#[feature]` directive | ||
485 | at trivial_bounds these bounds don't depend on an type parameters | ||
486 | at trivial_casts detects trivial casts which could be removed | ||
487 | at trivial_numeric_casts detects trivial casts of numeric types which could be removed | ||
488 | at type_alias_bounds bounds in type aliases are not enforced | ||
489 | at tyvar_behind_raw_pointer raw pointer to an inference variable | ||
490 | at unaligned_references detects unaligned references to fields of packed structs | ||
491 | at uncommon_codepoints detects uncommon Unicode codepoints in identifiers | ||
492 | at unconditional_panic operation will cause a panic at runtime | ||
493 | at unconditional_recursion functions that cannot return without calling themselves | ||
494 | at unknown_crate_types unknown crate type found in `#[crate_type]` directive | ||
495 | at unknown_lints unrecognized lint attribute | ||
496 | at unnameable_test_items detects an item that cannot be named being marked as `#[test_case]` | ||
497 | at unreachable_code detects unreachable code paths | ||
498 | at unreachable_patterns detects unreachable patterns | ||
499 | at unreachable_pub `pub` items not reachable from crate root | ||
500 | at unsafe_code usage of `unsafe` code | ||
501 | at unsafe_op_in_unsafe_fn unsafe operations in unsafe functions without an explicit unsafe block are deprecated | ||
502 | at unstable_features enabling unstable features (deprecated. do not use) | ||
503 | at unstable_name_collisions detects name collision with an existing but unstable method | ||
504 | at unused_allocation detects unnecessary allocations that can be eliminated | ||
505 | at unused_assignments detect assignments that will never be read | ||
506 | at unused_attributes detects attributes that were not used by the compiler | ||
507 | at unused_braces unnecessary braces around an expression | ||
508 | at unused_comparisons comparisons made useless by limits of the types involved | ||
509 | at unused_crate_dependencies crate dependencies that are never used | ||
510 | at unused_doc_comments detects doc comments that aren't used by rustdoc | ||
511 | at unused_extern_crates extern crates that are never used | ||
512 | at unused_features unused features found in crate-level `#[feature]` directives | ||
513 | at unused_import_braces unnecessary braces around an imported item | ||
514 | at unused_imports imports that are never used | ||
515 | at unused_labels detects labels that are never used | ||
516 | at unused_lifetimes detects lifetime parameters that are never used | ||
517 | at unused_macros detects macros that were not used | ||
518 | at unused_must_use unused result of a type flagged as `#[must_use]` | ||
519 | at unused_mut detect mut variables which don't need to be mutable | ||
520 | at unused_parens `if`, `match`, `while` and `return` do not need parentheses | ||
521 | at unused_qualifications detects unnecessarily qualified names | ||
522 | at unused_results unused result of an expression in a statement | ||
523 | at unused_unsafe unnecessary use of an `unsafe` block | ||
524 | at unused_variables detect variables which are not used in any way | ||
525 | at variant_size_differences detects enums with widely varying variant sizes | ||
526 | at warnings mass-change the level for lints which produce warnings | ||
527 | at where_clauses_object_safety checks the object safety of where clauses | ||
528 | at while_true suggest using `loop { }` instead of `while true { }` | ||
529 | "#]], | ||
530 | ) | ||
531 | } | ||
532 | |||
533 | #[test] | ||
260 | fn no_completion_for_incorrect_derive() { | 534 | fn no_completion_for_incorrect_derive() { |
261 | check( | 535 | check( |
262 | r#" | 536 | r#" |
@@ -325,7 +599,7 @@ struct Test {} | |||
325 | 599 | ||
326 | #[test] | 600 | #[test] |
327 | fn test_attribute_completion_inside_nested_attr() { | 601 | fn test_attribute_completion_inside_nested_attr() { |
328 | check(r#"#[allow(<|>)]"#, expect![[]]) | 602 | check(r#"#[cfg(<|>)]"#, expect![[]]) |
329 | } | 603 | } |
330 | 604 | ||
331 | #[test] | 605 | #[test] |
diff --git a/crates/ra_ide/src/diagnostics.rs b/crates/ra_ide/src/diagnostics.rs index e029af0dc..897177d05 100644 --- a/crates/ra_ide/src/diagnostics.rs +++ b/crates/ra_ide/src/diagnostics.rs | |||
@@ -7,7 +7,7 @@ | |||
7 | use std::cell::RefCell; | 7 | use std::cell::RefCell; |
8 | 8 | ||
9 | use hir::{ | 9 | use hir::{ |
10 | diagnostics::{AstDiagnostic, Diagnostic as _, DiagnosticSink}, | 10 | diagnostics::{AstDiagnostic, Diagnostic as _, DiagnosticSinkBuilder}, |
11 | HasSource, HirDisplay, Semantics, VariantDef, | 11 | HasSource, HirDisplay, Semantics, VariantDef, |
12 | }; | 12 | }; |
13 | use itertools::Itertools; | 13 | use itertools::Itertools; |
@@ -29,7 +29,11 @@ pub enum Severity { | |||
29 | WeakWarning, | 29 | WeakWarning, |
30 | } | 30 | } |
31 | 31 | ||
32 | pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic> { | 32 | pub(crate) fn diagnostics( |
33 | db: &RootDatabase, | ||
34 | file_id: FileId, | ||
35 | enable_experimental: bool, | ||
36 | ) -> Vec<Diagnostic> { | ||
33 | let _p = profile("diagnostics"); | 37 | let _p = profile("diagnostics"); |
34 | let sema = Semantics::new(db); | 38 | let sema = Semantics::new(db); |
35 | let parse = db.parse(file_id); | 39 | let parse = db.parse(file_id); |
@@ -48,79 +52,85 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic> | |||
48 | check_struct_shorthand_initialization(&mut res, file_id, &node); | 52 | check_struct_shorthand_initialization(&mut res, file_id, &node); |
49 | } | 53 | } |
50 | let res = RefCell::new(res); | 54 | let res = RefCell::new(res); |
51 | let mut sink = DiagnosticSink::new(|d| { | 55 | let mut sink = DiagnosticSinkBuilder::new() |
52 | res.borrow_mut().push(Diagnostic { | 56 | .on::<hir::diagnostics::UnresolvedModule, _>(|d| { |
53 | message: d.message(), | 57 | let original_file = d.source().file_id.original_file(db); |
54 | range: sema.diagnostics_range(d).range, | 58 | let fix = Fix::new( |
55 | severity: Severity::Error, | 59 | "Create module", |
56 | fix: None, | 60 | FileSystemEdit::CreateFile { anchor: original_file, dst: d.candidate.clone() } |
57 | }) | 61 | .into(), |
58 | }) | 62 | ); |
59 | .on::<hir::diagnostics::UnresolvedModule, _>(|d| { | 63 | res.borrow_mut().push(Diagnostic { |
60 | let original_file = d.source().file_id.original_file(db); | 64 | range: sema.diagnostics_range(d).range, |
61 | let fix = Fix::new( | 65 | message: d.message(), |
62 | "Create module", | 66 | severity: Severity::Error, |
63 | FileSystemEdit::CreateFile { anchor: original_file, dst: d.candidate.clone() }.into(), | 67 | fix: Some(fix), |
64 | ); | 68 | }) |
65 | res.borrow_mut().push(Diagnostic { | ||
66 | range: sema.diagnostics_range(d).range, | ||
67 | message: d.message(), | ||
68 | severity: Severity::Error, | ||
69 | fix: Some(fix), | ||
70 | }) | 69 | }) |
71 | }) | 70 | .on::<hir::diagnostics::MissingFields, _>(|d| { |
72 | .on::<hir::diagnostics::MissingFields, _>(|d| { | 71 | // Note that although we could add a diagnostics to |
73 | // Note that although we could add a diagnostics to | 72 | // fill the missing tuple field, e.g : |
74 | // fill the missing tuple field, e.g : | 73 | // `struct A(usize);` |
75 | // `struct A(usize);` | 74 | // `let a = A { 0: () }` |
76 | // `let a = A { 0: () }` | 75 | // but it is uncommon usage and it should not be encouraged. |
77 | // but it is uncommon usage and it should not be encouraged. | 76 | let fix = if d.missed_fields.iter().any(|it| it.as_tuple_index().is_some()) { |
78 | let fix = if d.missed_fields.iter().any(|it| it.as_tuple_index().is_some()) { | 77 | None |
79 | None | 78 | } else { |
80 | } else { | 79 | let mut field_list = d.ast(db); |
81 | let mut field_list = d.ast(db); | 80 | for f in d.missed_fields.iter() { |
82 | for f in d.missed_fields.iter() { | 81 | let field = |
83 | let field = | 82 | make::record_field(make::name_ref(&f.to_string()), Some(make::expr_unit())); |
84 | make::record_field(make::name_ref(&f.to_string()), Some(make::expr_unit())); | 83 | field_list = field_list.append_field(&field); |
85 | field_list = field_list.append_field(&field); | 84 | } |
86 | } | 85 | |
87 | 86 | let edit = { | |
88 | let edit = { | 87 | let mut builder = TextEditBuilder::default(); |
89 | let mut builder = TextEditBuilder::default(); | 88 | algo::diff(&d.ast(db).syntax(), &field_list.syntax()) |
90 | algo::diff(&d.ast(db).syntax(), &field_list.syntax()).into_text_edit(&mut builder); | 89 | .into_text_edit(&mut builder); |
91 | builder.finish() | 90 | builder.finish() |
91 | }; | ||
92 | Some(Fix::new("Fill struct fields", SourceFileEdit { file_id, edit }.into())) | ||
92 | }; | 93 | }; |
93 | Some(Fix::new("Fill struct fields", SourceFileEdit { file_id, edit }.into())) | ||
94 | }; | ||
95 | 94 | ||
96 | res.borrow_mut().push(Diagnostic { | 95 | res.borrow_mut().push(Diagnostic { |
97 | range: sema.diagnostics_range(d).range, | 96 | range: sema.diagnostics_range(d).range, |
98 | message: d.message(), | 97 | message: d.message(), |
99 | severity: Severity::Error, | 98 | severity: Severity::Error, |
100 | fix, | 99 | fix, |
100 | }) | ||
101 | }) | 101 | }) |
102 | }) | 102 | .on::<hir::diagnostics::MissingOkInTailExpr, _>(|d| { |
103 | .on::<hir::diagnostics::MissingOkInTailExpr, _>(|d| { | 103 | let node = d.ast(db); |
104 | let node = d.ast(db); | 104 | let replacement = format!("Ok({})", node.syntax()); |
105 | let replacement = format!("Ok({})", node.syntax()); | 105 | let edit = TextEdit::replace(node.syntax().text_range(), replacement); |
106 | let edit = TextEdit::replace(node.syntax().text_range(), replacement); | 106 | let source_change = SourceFileEdit { file_id, edit }.into(); |
107 | let source_change = SourceFileEdit { file_id, edit }.into(); | 107 | let fix = Fix::new("Wrap with ok", source_change); |
108 | let fix = Fix::new("Wrap with ok", source_change); | 108 | res.borrow_mut().push(Diagnostic { |
109 | res.borrow_mut().push(Diagnostic { | 109 | range: sema.diagnostics_range(d).range, |
110 | range: sema.diagnostics_range(d).range, | 110 | message: d.message(), |
111 | message: d.message(), | 111 | severity: Severity::Error, |
112 | severity: Severity::Error, | 112 | fix: Some(fix), |
113 | fix: Some(fix), | 113 | }) |
114 | }) | 114 | }) |
115 | }) | 115 | .on::<hir::diagnostics::NoSuchField, _>(|d| { |
116 | .on::<hir::diagnostics::NoSuchField, _>(|d| { | 116 | res.borrow_mut().push(Diagnostic { |
117 | res.borrow_mut().push(Diagnostic { | 117 | range: sema.diagnostics_range(d).range, |
118 | range: sema.diagnostics_range(d).range, | 118 | message: d.message(), |
119 | message: d.message(), | 119 | severity: Severity::Error, |
120 | severity: Severity::Error, | 120 | fix: missing_struct_field_fix(&sema, file_id, d), |
121 | fix: missing_struct_field_fix(&sema, file_id, d), | 121 | }) |
122 | }) | 122 | }) |
123 | }); | 123 | // Only collect experimental diagnostics when they're enabled. |
124 | .filter(|diag| !diag.is_experimental() || enable_experimental) | ||
125 | // Diagnostics not handled above get no fix and default treatment. | ||
126 | .build(|d| { | ||
127 | res.borrow_mut().push(Diagnostic { | ||
128 | message: d.message(), | ||
129 | range: sema.diagnostics_range(d).range, | ||
130 | severity: Severity::Error, | ||
131 | fix: None, | ||
132 | }) | ||
133 | }); | ||
124 | 134 | ||
125 | if let Some(m) = sema.to_module_def(file_id) { | 135 | if let Some(m) = sema.to_module_def(file_id) { |
126 | m.diagnostics(db, &mut sink); | 136 | m.diagnostics(db, &mut sink); |
@@ -298,7 +308,7 @@ mod tests { | |||
298 | let after = trim_indent(ra_fixture_after); | 308 | let after = trim_indent(ra_fixture_after); |
299 | 309 | ||
300 | let (analysis, file_position) = analysis_and_position(ra_fixture_before); | 310 | let (analysis, file_position) = analysis_and_position(ra_fixture_before); |
301 | let diagnostic = analysis.diagnostics(file_position.file_id).unwrap().pop().unwrap(); | 311 | let diagnostic = analysis.diagnostics(file_position.file_id, true).unwrap().pop().unwrap(); |
302 | let mut fix = diagnostic.fix.unwrap(); | 312 | let mut fix = diagnostic.fix.unwrap(); |
303 | let edit = fix.source_change.source_file_edits.pop().unwrap().edit; | 313 | let edit = fix.source_change.source_file_edits.pop().unwrap().edit; |
304 | let target_file_contents = analysis.file_text(file_position.file_id).unwrap(); | 314 | let target_file_contents = analysis.file_text(file_position.file_id).unwrap(); |
@@ -324,7 +334,7 @@ mod tests { | |||
324 | let ra_fixture_after = &trim_indent(ra_fixture_after); | 334 | let ra_fixture_after = &trim_indent(ra_fixture_after); |
325 | let (analysis, file_pos) = analysis_and_position(ra_fixture_before); | 335 | let (analysis, file_pos) = analysis_and_position(ra_fixture_before); |
326 | let current_file_id = file_pos.file_id; | 336 | let current_file_id = file_pos.file_id; |
327 | let diagnostic = analysis.diagnostics(current_file_id).unwrap().pop().unwrap(); | 337 | let diagnostic = analysis.diagnostics(current_file_id, true).unwrap().pop().unwrap(); |
328 | let mut fix = diagnostic.fix.unwrap(); | 338 | let mut fix = diagnostic.fix.unwrap(); |
329 | let edit = fix.source_change.source_file_edits.pop().unwrap(); | 339 | let edit = fix.source_change.source_file_edits.pop().unwrap(); |
330 | let changed_file_id = edit.file_id; | 340 | let changed_file_id = edit.file_id; |
@@ -345,14 +355,14 @@ mod tests { | |||
345 | let analysis = mock.analysis(); | 355 | let analysis = mock.analysis(); |
346 | let diagnostics = files | 356 | let diagnostics = files |
347 | .into_iter() | 357 | .into_iter() |
348 | .flat_map(|file_id| analysis.diagnostics(file_id).unwrap()) | 358 | .flat_map(|file_id| analysis.diagnostics(file_id, true).unwrap()) |
349 | .collect::<Vec<_>>(); | 359 | .collect::<Vec<_>>(); |
350 | assert_eq!(diagnostics.len(), 0, "unexpected diagnostics:\n{:#?}", diagnostics); | 360 | assert_eq!(diagnostics.len(), 0, "unexpected diagnostics:\n{:#?}", diagnostics); |
351 | } | 361 | } |
352 | 362 | ||
353 | fn check_expect(ra_fixture: &str, expect: Expect) { | 363 | fn check_expect(ra_fixture: &str, expect: Expect) { |
354 | let (analysis, file_id) = single_file(ra_fixture); | 364 | let (analysis, file_id) = single_file(ra_fixture); |
355 | let diagnostics = analysis.diagnostics(file_id).unwrap(); | 365 | let diagnostics = analysis.diagnostics(file_id, true).unwrap(); |
356 | expect.assert_debug_eq(&diagnostics) | 366 | expect.assert_debug_eq(&diagnostics) |
357 | } | 367 | } |
358 | 368 | ||
diff --git a/crates/ra_ide/src/folding_ranges.rs b/crates/ra_ide/src/folding_ranges.rs index e7ec9953f..315808890 100644 --- a/crates/ra_ide/src/folding_ranges.rs +++ b/crates/ra_ide/src/folding_ranges.rs | |||
@@ -84,7 +84,7 @@ fn fold_kind(kind: SyntaxKind) -> Option<FoldKind> { | |||
84 | match kind { | 84 | match kind { |
85 | COMMENT => Some(FoldKind::Comment), | 85 | COMMENT => Some(FoldKind::Comment), |
86 | USE_ITEM => Some(FoldKind::Imports), | 86 | USE_ITEM => Some(FoldKind::Imports), |
87 | ARG_LIST => Some(FoldKind::ArgList), | 87 | ARG_LIST | PARAM_LIST => Some(FoldKind::ArgList), |
88 | RECORD_FIELD_DEF_LIST | 88 | RECORD_FIELD_DEF_LIST |
89 | | RECORD_FIELD_PAT_LIST | 89 | | RECORD_FIELD_PAT_LIST |
90 | | RECORD_FIELD_LIST | 90 | | RECORD_FIELD_LIST |
@@ -386,4 +386,16 @@ const _: S = S <fold block>{ | |||
386 | "#, | 386 | "#, |
387 | ) | 387 | ) |
388 | } | 388 | } |
389 | |||
390 | #[test] | ||
391 | fn fold_multiline_params() { | ||
392 | check( | ||
393 | r#" | ||
394 | fn foo<fold arglist>( | ||
395 | x: i32, | ||
396 | y: String, | ||
397 | )</fold> {} | ||
398 | "#, | ||
399 | ) | ||
400 | } | ||
389 | } | 401 | } |
diff --git a/crates/ra_ide/src/goto_implementation.rs b/crates/ra_ide/src/goto_implementation.rs index 3ee048f28..16a61d071 100644 --- a/crates/ra_ide/src/goto_implementation.rs +++ b/crates/ra_ide/src/goto_implementation.rs | |||
@@ -23,7 +23,7 @@ pub(crate) fn goto_implementation( | |||
23 | 23 | ||
24 | let krate = sema.to_module_def(position.file_id)?.krate(); | 24 | let krate = sema.to_module_def(position.file_id)?.krate(); |
25 | 25 | ||
26 | if let Some(nominal_def) = find_node_at_offset::<ast::NominalDef>(&syntax, position.offset) { | 26 | if let Some(nominal_def) = find_node_at_offset::<ast::AdtDef>(&syntax, position.offset) { |
27 | return Some(RangeInfo::new( | 27 | return Some(RangeInfo::new( |
28 | nominal_def.syntax().text_range(), | 28 | nominal_def.syntax().text_range(), |
29 | impls_for_def(&sema, &nominal_def, krate)?, | 29 | impls_for_def(&sema, &nominal_def, krate)?, |
@@ -40,13 +40,13 @@ pub(crate) fn goto_implementation( | |||
40 | 40 | ||
41 | fn impls_for_def( | 41 | fn impls_for_def( |
42 | sema: &Semantics<RootDatabase>, | 42 | sema: &Semantics<RootDatabase>, |
43 | node: &ast::NominalDef, | 43 | node: &ast::AdtDef, |
44 | krate: Crate, | 44 | krate: Crate, |
45 | ) -> Option<Vec<NavigationTarget>> { | 45 | ) -> Option<Vec<NavigationTarget>> { |
46 | let ty = match node { | 46 | let ty = match node { |
47 | ast::NominalDef::StructDef(def) => sema.to_def(def)?.ty(sema.db), | 47 | ast::AdtDef::StructDef(def) => sema.to_def(def)?.ty(sema.db), |
48 | ast::NominalDef::EnumDef(def) => sema.to_def(def)?.ty(sema.db), | 48 | ast::AdtDef::EnumDef(def) => sema.to_def(def)?.ty(sema.db), |
49 | ast::NominalDef::UnionDef(def) => sema.to_def(def)?.ty(sema.db), | 49 | ast::AdtDef::UnionDef(def) => sema.to_def(def)?.ty(sema.db), |
50 | }; | 50 | }; |
51 | 51 | ||
52 | let impls = ImplDef::all_in_crate(sema.db, krate); | 52 | let impls = ImplDef::all_in_crate(sema.db, krate); |
diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs index dc9192d42..0fede0d87 100644 --- a/crates/ra_ide/src/lib.rs +++ b/crates/ra_ide/src/lib.rs | |||
@@ -487,8 +487,12 @@ impl Analysis { | |||
487 | } | 487 | } |
488 | 488 | ||
489 | /// Computes the set of diagnostics for the given file. | 489 | /// Computes the set of diagnostics for the given file. |
490 | pub fn diagnostics(&self, file_id: FileId) -> Cancelable<Vec<Diagnostic>> { | 490 | pub fn diagnostics( |
491 | self.with_db(|db| diagnostics::diagnostics(db, file_id)) | 491 | &self, |
492 | file_id: FileId, | ||
493 | enable_experimental: bool, | ||
494 | ) -> Cancelable<Vec<Diagnostic>> { | ||
495 | self.with_db(|db| diagnostics::diagnostics(db, file_id, enable_experimental)) | ||
492 | } | 496 | } |
493 | 497 | ||
494 | /// Returns the edit required to rename reference at the position to the new | 498 | /// Returns the edit required to rename reference at the position to the new |
@@ -505,9 +509,11 @@ impl Analysis { | |||
505 | &self, | 509 | &self, |
506 | query: &str, | 510 | query: &str, |
507 | parse_only: bool, | 511 | parse_only: bool, |
512 | position: FilePosition, | ||
513 | selections: Vec<FileRange>, | ||
508 | ) -> Cancelable<Result<SourceChange, SsrError>> { | 514 | ) -> Cancelable<Result<SourceChange, SsrError>> { |
509 | self.with_db(|db| { | 515 | self.with_db(|db| { |
510 | let edits = ssr::parse_search_replace(query, parse_only, db)?; | 516 | let edits = ssr::parse_search_replace(query, parse_only, db, position, selections)?; |
511 | Ok(SourceChange::from(edits)) | 517 | Ok(SourceChange::from(edits)) |
512 | }) | 518 | }) |
513 | } | 519 | } |
diff --git a/crates/ra_ide/src/runnables.rs b/crates/ra_ide/src/runnables.rs index 95a35a28d..45e0a7d85 100644 --- a/crates/ra_ide/src/runnables.rs +++ b/crates/ra_ide/src/runnables.rs | |||
@@ -220,15 +220,7 @@ fn runnable_mod( | |||
220 | module: ast::Module, | 220 | module: ast::Module, |
221 | file_id: FileId, | 221 | file_id: FileId, |
222 | ) -> Option<Runnable> { | 222 | ) -> Option<Runnable> { |
223 | let has_test_function = module | 223 | if !has_test_function_or_multiple_test_submodules(&module) { |
224 | .item_list()? | ||
225 | .items() | ||
226 | .filter_map(|it| match it { | ||
227 | ast::ModuleItem::FnDef(it) => Some(it), | ||
228 | _ => None, | ||
229 | }) | ||
230 | .any(|f| has_test_related_attribute(&f)); | ||
231 | if !has_test_function { | ||
232 | return None; | 224 | return None; |
233 | } | 225 | } |
234 | let module_def = sema.to_def(&module)?; | 226 | let module_def = sema.to_def(&module)?; |
@@ -246,6 +238,34 @@ fn runnable_mod( | |||
246 | Some(Runnable { nav, kind: RunnableKind::TestMod { path }, cfg_exprs }) | 238 | Some(Runnable { nav, kind: RunnableKind::TestMod { path }, cfg_exprs }) |
247 | } | 239 | } |
248 | 240 | ||
241 | // We could create runnables for modules with number_of_test_submodules > 0, | ||
242 | // but that bloats the runnables for no real benefit, since all tests can be run by the submodule already | ||
243 | fn has_test_function_or_multiple_test_submodules(module: &ast::Module) -> bool { | ||
244 | if let Some(item_list) = module.item_list() { | ||
245 | let mut number_of_test_submodules = 0; | ||
246 | |||
247 | for item in item_list.items() { | ||
248 | match item { | ||
249 | ast::ModuleItem::FnDef(f) => { | ||
250 | if has_test_related_attribute(&f) { | ||
251 | return true; | ||
252 | } | ||
253 | } | ||
254 | ast::ModuleItem::Module(submodule) => { | ||
255 | if has_test_function_or_multiple_test_submodules(&submodule) { | ||
256 | number_of_test_submodules += 1; | ||
257 | } | ||
258 | } | ||
259 | _ => (), | ||
260 | } | ||
261 | } | ||
262 | |||
263 | number_of_test_submodules > 1 | ||
264 | } else { | ||
265 | false | ||
266 | } | ||
267 | } | ||
268 | |||
249 | #[cfg(test)] | 269 | #[cfg(test)] |
250 | mod tests { | 270 | mod tests { |
251 | use expect::{expect, Expect}; | 271 | use expect::{expect, Expect}; |
@@ -571,19 +591,33 @@ mod test_mod { | |||
571 | } | 591 | } |
572 | 592 | ||
573 | #[test] | 593 | #[test] |
574 | fn test_runnables_one_depth_layer_module() { | 594 | fn only_modules_with_test_functions_or_more_than_one_test_submodule_have_runners() { |
575 | check( | 595 | check( |
576 | r#" | 596 | r#" |
577 | //- /lib.rs | 597 | //- /lib.rs |
578 | <|> | 598 | <|> |
579 | mod foo { | 599 | mod root_tests { |
580 | mod test_mod { | 600 | mod nested_tests_0 { |
581 | #[test] | 601 | mod nested_tests_1 { |
582 | fn test_foo1() {} | 602 | #[test] |
603 | fn nested_test_11() {} | ||
604 | |||
605 | #[test] | ||
606 | fn nested_test_12() {} | ||
607 | } | ||
608 | |||
609 | mod nested_tests_2 { | ||
610 | #[test] | ||
611 | fn nested_test_2() {} | ||
612 | } | ||
613 | |||
614 | mod nested_tests_3 {} | ||
583 | } | 615 | } |
616 | |||
617 | mod nested_tests_4 {} | ||
584 | } | 618 | } |
585 | "#, | 619 | "#, |
586 | &[&TEST, &TEST], | 620 | &[&TEST, &TEST, &TEST, &TEST, &TEST, &TEST], |
587 | expect![[r#" | 621 | expect![[r#" |
588 | [ | 622 | [ |
589 | Runnable { | 623 | Runnable { |
@@ -591,18 +625,18 @@ mod foo { | |||
591 | file_id: FileId( | 625 | file_id: FileId( |
592 | 1, | 626 | 1, |
593 | ), | 627 | ), |
594 | full_range: 15..77, | 628 | full_range: 22..323, |
595 | focus_range: Some( | 629 | focus_range: Some( |
596 | 19..27, | 630 | 26..40, |
597 | ), | 631 | ), |
598 | name: "test_mod", | 632 | name: "nested_tests_0", |
599 | kind: MODULE, | 633 | kind: MODULE, |
600 | container_name: None, | 634 | container_name: None, |
601 | description: None, | 635 | description: None, |
602 | docs: None, | 636 | docs: None, |
603 | }, | 637 | }, |
604 | kind: TestMod { | 638 | kind: TestMod { |
605 | path: "foo::test_mod", | 639 | path: "root_tests::nested_tests_0", |
606 | }, | 640 | }, |
607 | cfg_exprs: [], | 641 | cfg_exprs: [], |
608 | }, | 642 | }, |
@@ -611,11 +645,31 @@ mod foo { | |||
611 | file_id: FileId( | 645 | file_id: FileId( |
612 | 1, | 646 | 1, |
613 | ), | 647 | ), |
614 | full_range: 38..71, | 648 | full_range: 51..192, |
615 | focus_range: Some( | 649 | focus_range: Some( |
616 | 57..66, | 650 | 55..69, |
617 | ), | 651 | ), |
618 | name: "test_foo1", | 652 | name: "nested_tests_1", |
653 | kind: MODULE, | ||
654 | container_name: None, | ||
655 | description: None, | ||
656 | docs: None, | ||
657 | }, | ||
658 | kind: TestMod { | ||
659 | path: "root_tests::nested_tests_0::nested_tests_1", | ||
660 | }, | ||
661 | cfg_exprs: [], | ||
662 | }, | ||
663 | Runnable { | ||
664 | nav: NavigationTarget { | ||
665 | file_id: FileId( | ||
666 | 1, | ||
667 | ), | ||
668 | full_range: 84..126, | ||
669 | focus_range: Some( | ||
670 | 107..121, | ||
671 | ), | ||
672 | name: "nested_test_11", | ||
619 | kind: FN_DEF, | 673 | kind: FN_DEF, |
620 | container_name: None, | 674 | container_name: None, |
621 | description: None, | 675 | description: None, |
@@ -623,7 +677,7 @@ mod foo { | |||
623 | }, | 677 | }, |
624 | kind: Test { | 678 | kind: Test { |
625 | test_id: Path( | 679 | test_id: Path( |
626 | "foo::test_mod::test_foo1", | 680 | "root_tests::nested_tests_0::nested_tests_1::nested_test_11", |
627 | ), | 681 | ), |
628 | attr: TestAttr { | 682 | attr: TestAttr { |
629 | ignore: false, | 683 | ignore: false, |
@@ -631,46 +685,48 @@ mod foo { | |||
631 | }, | 685 | }, |
632 | cfg_exprs: [], | 686 | cfg_exprs: [], |
633 | }, | 687 | }, |
634 | ] | ||
635 | "#]], | ||
636 | ); | ||
637 | } | ||
638 | |||
639 | #[test] | ||
640 | fn test_runnables_multiple_depth_module() { | ||
641 | check( | ||
642 | r#" | ||
643 | //- /lib.rs | ||
644 | <|> | ||
645 | mod foo { | ||
646 | mod bar { | ||
647 | mod test_mod { | ||
648 | #[test] | ||
649 | fn test_foo1() {} | ||
650 | } | ||
651 | } | ||
652 | } | ||
653 | "#, | ||
654 | &[&TEST, &TEST], | ||
655 | expect![[r#" | ||
656 | [ | ||
657 | Runnable { | 688 | Runnable { |
658 | nav: NavigationTarget { | 689 | nav: NavigationTarget { |
659 | file_id: FileId( | 690 | file_id: FileId( |
660 | 1, | 691 | 1, |
661 | ), | 692 | ), |
662 | full_range: 33..107, | 693 | full_range: 140..182, |
663 | focus_range: Some( | 694 | focus_range: Some( |
664 | 37..45, | 695 | 163..177, |
665 | ), | 696 | ), |
666 | name: "test_mod", | 697 | name: "nested_test_12", |
698 | kind: FN_DEF, | ||
699 | container_name: None, | ||
700 | description: None, | ||
701 | docs: None, | ||
702 | }, | ||
703 | kind: Test { | ||
704 | test_id: Path( | ||
705 | "root_tests::nested_tests_0::nested_tests_1::nested_test_12", | ||
706 | ), | ||
707 | attr: TestAttr { | ||
708 | ignore: false, | ||
709 | }, | ||
710 | }, | ||
711 | cfg_exprs: [], | ||
712 | }, | ||
713 | Runnable { | ||
714 | nav: NavigationTarget { | ||
715 | file_id: FileId( | ||
716 | 1, | ||
717 | ), | ||
718 | full_range: 202..286, | ||
719 | focus_range: Some( | ||
720 | 206..220, | ||
721 | ), | ||
722 | name: "nested_tests_2", | ||
667 | kind: MODULE, | 723 | kind: MODULE, |
668 | container_name: None, | 724 | container_name: None, |
669 | description: None, | 725 | description: None, |
670 | docs: None, | 726 | docs: None, |
671 | }, | 727 | }, |
672 | kind: TestMod { | 728 | kind: TestMod { |
673 | path: "foo::bar::test_mod", | 729 | path: "root_tests::nested_tests_0::nested_tests_2", |
674 | }, | 730 | }, |
675 | cfg_exprs: [], | 731 | cfg_exprs: [], |
676 | }, | 732 | }, |
@@ -679,11 +735,11 @@ mod foo { | |||
679 | file_id: FileId( | 735 | file_id: FileId( |
680 | 1, | 736 | 1, |
681 | ), | 737 | ), |
682 | full_range: 60..97, | 738 | full_range: 235..276, |
683 | focus_range: Some( | 739 | focus_range: Some( |
684 | 83..92, | 740 | 258..271, |
685 | ), | 741 | ), |
686 | name: "test_foo1", | 742 | name: "nested_test_2", |
687 | kind: FN_DEF, | 743 | kind: FN_DEF, |
688 | container_name: None, | 744 | container_name: None, |
689 | description: None, | 745 | description: None, |
@@ -691,7 +747,7 @@ mod foo { | |||
691 | }, | 747 | }, |
692 | kind: Test { | 748 | kind: Test { |
693 | test_id: Path( | 749 | test_id: Path( |
694 | "foo::bar::test_mod::test_foo1", | 750 | "root_tests::nested_tests_0::nested_tests_2::nested_test_2", |
695 | ), | 751 | ), |
696 | attr: TestAttr { | 752 | attr: TestAttr { |
697 | ignore: false, | 753 | ignore: false, |
diff --git a/crates/ra_ide/src/ssr.rs b/crates/ra_ide/src/ssr.rs index b3e9e5dfe..4348b43be 100644 --- a/crates/ra_ide/src/ssr.rs +++ b/crates/ra_ide/src/ssr.rs | |||
@@ -1,5 +1,5 @@ | |||
1 | use ra_db::SourceDatabaseExt; | 1 | use ra_db::{FilePosition, FileRange}; |
2 | use ra_ide_db::{symbol_index::SymbolsDatabase, RootDatabase}; | 2 | use ra_ide_db::RootDatabase; |
3 | 3 | ||
4 | use crate::SourceFileEdit; | 4 | use crate::SourceFileEdit; |
5 | use ra_ssr::{MatchFinder, SsrError, SsrRule}; | 5 | use ra_ssr::{MatchFinder, SsrError, SsrRule}; |
@@ -11,6 +11,22 @@ use ra_ssr::{MatchFinder, SsrError, SsrRule}; | |||
11 | // A `$<name>` placeholder in the search pattern will match any AST node and `$<name>` will reference it in the replacement. | 11 | // A `$<name>` placeholder in the search pattern will match any AST node and `$<name>` will reference it in the replacement. |
12 | // Within a macro call, a placeholder will match up until whatever token follows the placeholder. | 12 | // Within a macro call, a placeholder will match up until whatever token follows the placeholder. |
13 | // | 13 | // |
14 | // All paths in both the search pattern and the replacement template must resolve in the context | ||
15 | // in which this command is invoked. Paths in the search pattern will then match the code if they | ||
16 | // resolve to the same item, even if they're written differently. For example if we invoke the | ||
17 | // command in the module `foo` with a pattern of `Bar`, then code in the parent module that refers | ||
18 | // to `foo::Bar` will match. | ||
19 | // | ||
20 | // Paths in the replacement template will be rendered appropriately for the context in which the | ||
21 | // replacement occurs. For example if our replacement template is `foo::Bar` and we match some | ||
22 | // code in the `foo` module, we'll insert just `Bar`. | ||
23 | // | ||
24 | // Method calls should generally be written in UFCS form. e.g. `foo::Bar::baz($s, $a)` will match | ||
25 | // `$s.baz($a)`, provided the method call `baz` resolves to the method `foo::Bar::baz`. | ||
26 | // | ||
27 | // The scope of the search / replace will be restricted to the current selection if any, otherwise | ||
28 | // it will apply to the whole workspace. | ||
29 | // | ||
14 | // Placeholders may be given constraints by writing them as `${<name>:<constraint1>:<constraint2>...}`. | 30 | // Placeholders may be given constraints by writing them as `${<name>:<constraint1>:<constraint2>...}`. |
15 | // | 31 | // |
16 | // Supported constraints: | 32 | // Supported constraints: |
@@ -43,21 +59,14 @@ pub fn parse_search_replace( | |||
43 | rule: &str, | 59 | rule: &str, |
44 | parse_only: bool, | 60 | parse_only: bool, |
45 | db: &RootDatabase, | 61 | db: &RootDatabase, |
62 | resolve_context: FilePosition, | ||
63 | selections: Vec<FileRange>, | ||
46 | ) -> Result<Vec<SourceFileEdit>, SsrError> { | 64 | ) -> Result<Vec<SourceFileEdit>, SsrError> { |
47 | let mut edits = vec![]; | ||
48 | let rule: SsrRule = rule.parse()?; | 65 | let rule: SsrRule = rule.parse()?; |
66 | let mut match_finder = MatchFinder::in_context(db, resolve_context, selections); | ||
67 | match_finder.add_rule(rule)?; | ||
49 | if parse_only { | 68 | if parse_only { |
50 | return Ok(edits); | 69 | return Ok(Vec::new()); |
51 | } | ||
52 | let mut match_finder = MatchFinder::new(db); | ||
53 | match_finder.add_rule(rule); | ||
54 | for &root in db.local_roots().iter() { | ||
55 | let sr = db.source_root(root); | ||
56 | for file_id in sr.iter() { | ||
57 | if let Some(edit) = match_finder.edits_for_file(file_id) { | ||
58 | edits.push(SourceFileEdit { file_id, edit }); | ||
59 | } | ||
60 | } | ||
61 | } | 70 | } |
62 | Ok(edits) | 71 | Ok(match_finder.edits()) |
63 | } | 72 | } |
diff --git a/crates/ra_ide/src/syntax_highlighting/html.rs b/crates/ra_ide/src/syntax_highlighting/html.rs index 0be55bca9..a5e7d2867 100644 --- a/crates/ra_ide/src/syntax_highlighting/html.rs +++ b/crates/ra_ide/src/syntax_highlighting/html.rs | |||
@@ -1,5 +1,6 @@ | |||
1 | //! Renders a bit of code as HTML. | 1 | //! Renders a bit of code as HTML. |
2 | 2 | ||
3 | use oorandom::Rand32; | ||
3 | use ra_db::SourceDatabase; | 4 | use ra_db::SourceDatabase; |
4 | use ra_syntax::{AstNode, TextRange, TextSize}; | 5 | use ra_syntax::{AstNode, TextRange, TextSize}; |
5 | 6 | ||
@@ -9,13 +10,12 @@ pub(crate) fn highlight_as_html(db: &RootDatabase, file_id: FileId, rainbow: boo | |||
9 | let parse = db.parse(file_id); | 10 | let parse = db.parse(file_id); |
10 | 11 | ||
11 | fn rainbowify(seed: u64) -> String { | 12 | fn rainbowify(seed: u64) -> String { |
12 | use rand::prelude::*; | 13 | let mut rng = Rand32::new(seed); |
13 | let mut rng = SmallRng::seed_from_u64(seed); | ||
14 | format!( | 14 | format!( |
15 | "hsl({h},{s}%,{l}%)", | 15 | "hsl({h},{s}%,{l}%)", |
16 | h = rng.gen_range::<u16, _, _>(0, 361), | 16 | h = rng.rand_range(0..361), |
17 | s = rng.gen_range::<u16, _, _>(42, 99), | 17 | s = rng.rand_range(42..99), |
18 | l = rng.gen_range::<u16, _, _>(40, 91), | 18 | l = rng.rand_range(40..91), |
19 | ) | 19 | ) |
20 | } | 20 | } |
21 | 21 | ||
diff --git a/crates/ra_ide/test_data/rainbow_highlighting.html b/crates/ra_ide/test_data/rainbow_highlighting.html index 08d83302c..401e87a73 100644 --- a/crates/ra_ide/test_data/rainbow_highlighting.html +++ b/crates/ra_ide/test_data/rainbow_highlighting.html | |||
@@ -36,14 +36,14 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
36 | .unresolved_reference { color: #FC5555; text-decoration: wavy underline; } | 36 | .unresolved_reference { color: #FC5555; text-decoration: wavy underline; } |
37 | </style> | 37 | </style> |
38 | <pre><code><span class="keyword">fn</span> <span class="function declaration">main</span><span class="punctuation">(</span><span class="punctuation">)</span> <span class="punctuation">{</span> | 38 | <pre><code><span class="keyword">fn</span> <span class="function declaration">main</span><span class="punctuation">(</span><span class="punctuation">)</span> <span class="punctuation">{</span> |
39 | <span class="keyword">let</span> <span class="variable declaration" data-binding-hash="8121853618659664005" style="color: hsl(261,57%,61%);">hello</span> <span class="operator">=</span> <span class="string_literal">"hello"</span><span class="punctuation">;</span> | 39 | <span class="keyword">let</span> <span class="variable declaration" data-binding-hash="8121853618659664005" style="color: hsl(273,88%,88%);">hello</span> <span class="operator">=</span> <span class="string_literal">"hello"</span><span class="punctuation">;</span> |
40 | <span class="keyword">let</span> <span class="variable declaration" data-binding-hash="2705725358298919760" style="color: hsl(17,51%,74%);">x</span> <span class="operator">=</span> <span class="variable" data-binding-hash="8121853618659664005" style="color: hsl(261,57%,61%);">hello</span><span class="punctuation">.</span><span class="unresolved_reference">to_string</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span> | 40 | <span class="keyword">let</span> <span class="variable declaration" data-binding-hash="2705725358298919760" style="color: hsl(76,47%,83%);">x</span> <span class="operator">=</span> <span class="variable" data-binding-hash="8121853618659664005" style="color: hsl(273,88%,88%);">hello</span><span class="punctuation">.</span><span class="unresolved_reference">to_string</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span> |
41 | <span class="keyword">let</span> <span class="variable declaration" data-binding-hash="3365759661443752373" style="color: hsl(127,76%,66%);">y</span> <span class="operator">=</span> <span class="variable" data-binding-hash="8121853618659664005" style="color: hsl(261,57%,61%);">hello</span><span class="punctuation">.</span><span class="unresolved_reference">to_string</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span> | 41 | <span class="keyword">let</span> <span class="variable declaration" data-binding-hash="3365759661443752373" style="color: hsl(15,86%,51%);">y</span> <span class="operator">=</span> <span class="variable" data-binding-hash="8121853618659664005" style="color: hsl(273,88%,88%);">hello</span><span class="punctuation">.</span><span class="unresolved_reference">to_string</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span> |
42 | 42 | ||
43 | <span class="keyword">let</span> <span class="variable declaration" data-binding-hash="794745962933817518" style="color: hsl(19,74%,76%);">x</span> <span class="operator">=</span> <span class="string_literal">"other color please!"</span><span class="punctuation">;</span> | 43 | <span class="keyword">let</span> <span class="variable declaration" data-binding-hash="794745962933817518" style="color: hsl(127,71%,87%);">x</span> <span class="operator">=</span> <span class="string_literal">"other color please!"</span><span class="punctuation">;</span> |
44 | <span class="keyword">let</span> <span class="variable declaration" data-binding-hash="6717528807933952652" style="color: hsl(85,49%,84%);">y</span> <span class="operator">=</span> <span class="variable" data-binding-hash="794745962933817518" style="color: hsl(19,74%,76%);">x</span><span class="punctuation">.</span><span class="unresolved_reference">to_string</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span> | 44 | <span class="keyword">let</span> <span class="variable declaration" data-binding-hash="6717528807933952652" style="color: hsl(90,74%,79%);">y</span> <span class="operator">=</span> <span class="variable" data-binding-hash="794745962933817518" style="color: hsl(127,71%,87%);">x</span><span class="punctuation">.</span><span class="unresolved_reference">to_string</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span> |
45 | <span class="punctuation">}</span> | 45 | <span class="punctuation">}</span> |
46 | 46 | ||
47 | <span class="keyword">fn</span> <span class="function declaration">bar</span><span class="punctuation">(</span><span class="punctuation">)</span> <span class="punctuation">{</span> | 47 | <span class="keyword">fn</span> <span class="function declaration">bar</span><span class="punctuation">(</span><span class="punctuation">)</span> <span class="punctuation">{</span> |
48 | <span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable declaration mutable" data-binding-hash="8121853618659664005" style="color: hsl(261,57%,61%);">hello</span> <span class="operator">=</span> <span class="string_literal">"hello"</span><span class="punctuation">;</span> | 48 | <span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable declaration mutable" data-binding-hash="8121853618659664005" style="color: hsl(273,88%,88%);">hello</span> <span class="operator">=</span> <span class="string_literal">"hello"</span><span class="punctuation">;</span> |
49 | <span class="punctuation">}</span></code></pre> \ No newline at end of file | 49 | <span class="punctuation">}</span></code></pre> \ No newline at end of file |