diff options
author | Aleksey Kladov <[email protected]> | 2021-06-13 19:33:54 +0100 |
---|---|---|
committer | Aleksey Kladov <[email protected]> | 2021-06-13 19:33:54 +0100 |
commit | 3478897f86cc1b3e3f83e9d4e7cedff41721fb04 (patch) | |
tree | 9700c178acb48fcc029f4120169f00a94d7308f0 /crates/ide/src | |
parent | fc30c5ccbeba2a102922da497809dd3f812544c4 (diff) |
internal: remove DiagnosticWithFix infra
Diffstat (limited to 'crates/ide/src')
-rw-r--r-- | crates/ide/src/diagnostics.rs | 514 | ||||
-rw-r--r-- | crates/ide/src/diagnostics/fixes.rs | 24 | ||||
-rw-r--r-- | crates/ide/src/diagnostics/incorrect_case.rs | 322 | ||||
-rw-r--r-- | crates/ide/src/diagnostics/unlinked_file.rs | 259 |
4 files changed, 534 insertions, 585 deletions
diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs index f084f7b06..7978c1fc2 100644 --- a/crates/ide/src/diagnostics.rs +++ b/crates/ide/src/diagnostics.rs | |||
@@ -16,20 +16,19 @@ mod no_such_field; | |||
16 | mod remove_this_semicolon; | 16 | mod remove_this_semicolon; |
17 | mod replace_filter_map_next_with_find_map; | 17 | mod replace_filter_map_next_with_find_map; |
18 | mod unimplemented_builtin_macro; | 18 | mod unimplemented_builtin_macro; |
19 | mod unlinked_file; | ||
19 | mod unresolved_extern_crate; | 20 | mod unresolved_extern_crate; |
20 | mod unresolved_import; | 21 | mod unresolved_import; |
21 | mod unresolved_macro_call; | 22 | mod unresolved_macro_call; |
22 | mod unresolved_module; | 23 | mod unresolved_module; |
23 | mod unresolved_proc_macro; | 24 | mod unresolved_proc_macro; |
24 | 25 | ||
25 | mod fixes; | ||
26 | mod field_shorthand; | 26 | mod field_shorthand; |
27 | mod unlinked_file; | ||
28 | 27 | ||
29 | use std::cell::RefCell; | 28 | use std::cell::RefCell; |
30 | 29 | ||
31 | use hir::{ | 30 | use hir::{ |
32 | diagnostics::{AnyDiagnostic, Diagnostic as _, DiagnosticCode, DiagnosticSinkBuilder}, | 31 | diagnostics::{AnyDiagnostic, DiagnosticCode, DiagnosticSinkBuilder}, |
33 | Semantics, | 32 | Semantics, |
34 | }; | 33 | }; |
35 | use ide_assists::AssistResolveStrategy; | 34 | use ide_assists::AssistResolveStrategy; |
@@ -38,15 +37,13 @@ use itertools::Itertools; | |||
38 | use rustc_hash::FxHashSet; | 37 | use rustc_hash::FxHashSet; |
39 | use syntax::{ | 38 | use syntax::{ |
40 | ast::{self, AstNode}, | 39 | ast::{self, AstNode}, |
41 | SyntaxNode, SyntaxNodePtr, TextRange, TextSize, | 40 | SyntaxNode, TextRange, |
42 | }; | 41 | }; |
43 | use text_edit::TextEdit; | 42 | use text_edit::TextEdit; |
44 | use unlinked_file::UnlinkedFile; | 43 | use unlinked_file::UnlinkedFile; |
45 | 44 | ||
46 | use crate::{Assist, AssistId, AssistKind, FileId, Label, SourceChange}; | 45 | use crate::{Assist, AssistId, AssistKind, FileId, Label, SourceChange}; |
47 | 46 | ||
48 | use self::fixes::DiagnosticWithFixes; | ||
49 | |||
50 | #[derive(Debug)] | 47 | #[derive(Debug)] |
51 | pub struct Diagnostic { | 48 | pub struct Diagnostic { |
52 | // pub name: Option<String>, | 49 | // pub name: Option<String>, |
@@ -165,19 +162,6 @@ pub(crate) fn diagnostics( | |||
165 | } | 162 | } |
166 | let res = RefCell::new(res); | 163 | let res = RefCell::new(res); |
167 | let sink_builder = DiagnosticSinkBuilder::new() | 164 | let sink_builder = DiagnosticSinkBuilder::new() |
168 | .on::<UnlinkedFile, _>(|d| { | ||
169 | // Limit diagnostic to the first few characters in the file. This matches how VS Code | ||
170 | // renders it with the full span, but on other editors, and is less invasive. | ||
171 | let range = sema.diagnostics_display_range(d.display_source()).range; | ||
172 | let range = range.intersect(TextRange::up_to(TextSize::of("..."))).unwrap_or(range); | ||
173 | |||
174 | // Override severity and mark as unused. | ||
175 | res.borrow_mut().push( | ||
176 | Diagnostic::hint(range, d.message()) | ||
177 | .with_fixes(d.fixes(&sema, resolve)) | ||
178 | .with_code(Some(d.code())), | ||
179 | ); | ||
180 | }) | ||
181 | // Only collect experimental diagnostics when they're enabled. | 165 | // Only collect experimental diagnostics when they're enabled. |
182 | .filter(|diag| !(diag.is_experimental() && config.disable_experimental)) | 166 | .filter(|diag| !(diag.is_experimental() && config.disable_experimental)) |
183 | .filter(|diag| !config.disabled.contains(diag.code().as_str())); | 167 | .filter(|diag| !config.disabled.contains(diag.code().as_str())); |
@@ -197,11 +181,9 @@ pub(crate) fn diagnostics( | |||
197 | 181 | ||
198 | let mut diags = Vec::new(); | 182 | let mut diags = Vec::new(); |
199 | let internal_diagnostics = cfg!(test); | 183 | let internal_diagnostics = cfg!(test); |
200 | match sema.to_module_def(file_id) { | 184 | let module = sema.to_module_def(file_id); |
201 | Some(m) => diags = m.diagnostics(db, &mut sink, internal_diagnostics), | 185 | if let Some(m) = module { |
202 | None => { | 186 | diags = m.diagnostics(db, &mut sink, internal_diagnostics) |
203 | sink.push(UnlinkedFile { file_id, node: SyntaxNodePtr::new(parse.tree().syntax()) }); | ||
204 | } | ||
205 | } | 187 | } |
206 | 188 | ||
207 | drop(sink); | 189 | drop(sink); |
@@ -209,6 +191,12 @@ pub(crate) fn diagnostics( | |||
209 | let mut res = res.into_inner(); | 191 | let mut res = res.into_inner(); |
210 | 192 | ||
211 | let ctx = DiagnosticsContext { config, sema, resolve }; | 193 | let ctx = DiagnosticsContext { config, sema, resolve }; |
194 | if module.is_none() { | ||
195 | let d = UnlinkedFile { file: file_id }; | ||
196 | let d = unlinked_file::unlinked_file(&ctx, &d); | ||
197 | res.push(d) | ||
198 | } | ||
199 | |||
212 | for diag in diags { | 200 | for diag in diags { |
213 | #[rustfmt::skip] | 201 | #[rustfmt::skip] |
214 | let d = match diag { | 202 | let d = match diag { |
@@ -234,16 +222,20 @@ pub(crate) fn diagnostics( | |||
234 | None => continue, | 222 | None => continue, |
235 | } | 223 | } |
236 | }; | 224 | }; |
225 | res.push(d) | ||
226 | } | ||
227 | |||
228 | res.retain(|d| { | ||
237 | if let Some(code) = d.code { | 229 | if let Some(code) = d.code { |
238 | if ctx.config.disabled.contains(code.as_str()) { | 230 | if ctx.config.disabled.contains(code.as_str()) { |
239 | continue; | 231 | return false; |
240 | } | 232 | } |
241 | } | 233 | } |
242 | if ctx.config.disable_experimental && d.experimental { | 234 | if ctx.config.disable_experimental && d.experimental { |
243 | continue; | 235 | return false; |
244 | } | 236 | } |
245 | res.push(d) | 237 | true |
246 | } | 238 | }); |
247 | 239 | ||
248 | res | 240 | res |
249 | } | 241 | } |
@@ -378,8 +370,9 @@ mod tests { | |||
378 | file_position.offset | 370 | file_position.offset |
379 | ); | 371 | ); |
380 | } | 372 | } |
373 | |||
381 | /// Checks that there's a diagnostic *without* fix at `$0`. | 374 | /// Checks that there's a diagnostic *without* fix at `$0`. |
382 | fn check_no_fix(ra_fixture: &str) { | 375 | pub(crate) fn check_no_fix(ra_fixture: &str) { |
383 | let (analysis, file_position) = fixture::position(ra_fixture); | 376 | let (analysis, file_position) = fixture::position(ra_fixture); |
384 | let diagnostic = analysis | 377 | let diagnostic = analysis |
385 | .diagnostics( | 378 | .diagnostics( |
@@ -524,142 +517,6 @@ mod a { | |||
524 | } | 517 | } |
525 | 518 | ||
526 | #[test] | 519 | #[test] |
527 | fn unlinked_file_prepend_first_item() { | ||
528 | cov_mark::check!(unlinked_file_prepend_before_first_item); | ||
529 | // Only tests the first one for `pub mod` since the rest are the same | ||
530 | check_fixes( | ||
531 | r#" | ||
532 | //- /main.rs | ||
533 | fn f() {} | ||
534 | //- /foo.rs | ||
535 | $0 | ||
536 | "#, | ||
537 | vec![ | ||
538 | r#" | ||
539 | mod foo; | ||
540 | |||
541 | fn f() {} | ||
542 | "#, | ||
543 | r#" | ||
544 | pub mod foo; | ||
545 | |||
546 | fn f() {} | ||
547 | "#, | ||
548 | ], | ||
549 | ); | ||
550 | } | ||
551 | |||
552 | #[test] | ||
553 | fn unlinked_file_append_mod() { | ||
554 | cov_mark::check!(unlinked_file_append_to_existing_mods); | ||
555 | check_fix( | ||
556 | r#" | ||
557 | //- /main.rs | ||
558 | //! Comment on top | ||
559 | |||
560 | mod preexisting; | ||
561 | |||
562 | mod preexisting2; | ||
563 | |||
564 | struct S; | ||
565 | |||
566 | mod preexisting_bottom;) | ||
567 | //- /foo.rs | ||
568 | $0 | ||
569 | "#, | ||
570 | r#" | ||
571 | //! Comment on top | ||
572 | |||
573 | mod preexisting; | ||
574 | |||
575 | mod preexisting2; | ||
576 | mod foo; | ||
577 | |||
578 | struct S; | ||
579 | |||
580 | mod preexisting_bottom;) | ||
581 | "#, | ||
582 | ); | ||
583 | } | ||
584 | |||
585 | #[test] | ||
586 | fn unlinked_file_insert_in_empty_file() { | ||
587 | cov_mark::check!(unlinked_file_empty_file); | ||
588 | check_fix( | ||
589 | r#" | ||
590 | //- /main.rs | ||
591 | //- /foo.rs | ||
592 | $0 | ||
593 | "#, | ||
594 | r#" | ||
595 | mod foo; | ||
596 | "#, | ||
597 | ); | ||
598 | } | ||
599 | |||
600 | #[test] | ||
601 | fn unlinked_file_old_style_modrs() { | ||
602 | check_fix( | ||
603 | r#" | ||
604 | //- /main.rs | ||
605 | mod submod; | ||
606 | //- /submod/mod.rs | ||
607 | // in mod.rs | ||
608 | //- /submod/foo.rs | ||
609 | $0 | ||
610 | "#, | ||
611 | r#" | ||
612 | // in mod.rs | ||
613 | mod foo; | ||
614 | "#, | ||
615 | ); | ||
616 | } | ||
617 | |||
618 | #[test] | ||
619 | fn unlinked_file_new_style_mod() { | ||
620 | check_fix( | ||
621 | r#" | ||
622 | //- /main.rs | ||
623 | mod submod; | ||
624 | //- /submod.rs | ||
625 | //- /submod/foo.rs | ||
626 | $0 | ||
627 | "#, | ||
628 | r#" | ||
629 | mod foo; | ||
630 | "#, | ||
631 | ); | ||
632 | } | ||
633 | |||
634 | #[test] | ||
635 | fn unlinked_file_with_cfg_off() { | ||
636 | cov_mark::check!(unlinked_file_skip_fix_when_mod_already_exists); | ||
637 | check_no_fix( | ||
638 | r#" | ||
639 | //- /main.rs | ||
640 | #[cfg(never)] | ||
641 | mod foo; | ||
642 | |||
643 | //- /foo.rs | ||
644 | $0 | ||
645 | "#, | ||
646 | ); | ||
647 | } | ||
648 | |||
649 | #[test] | ||
650 | fn unlinked_file_with_cfg_on() { | ||
651 | check_diagnostics( | ||
652 | r#" | ||
653 | //- /main.rs | ||
654 | #[cfg(not(never))] | ||
655 | mod foo; | ||
656 | |||
657 | //- /foo.rs | ||
658 | "#, | ||
659 | ); | ||
660 | } | ||
661 | |||
662 | #[test] | ||
663 | fn import_extern_crate_clash_with_inner_item() { | 520 | fn import_extern_crate_clash_with_inner_item() { |
664 | // This is more of a resolver test, but doesn't really work with the hir_def testsuite. | 521 | // This is more of a resolver test, but doesn't really work with the hir_def testsuite. |
665 | 522 | ||
@@ -1595,330 +1452,3 @@ fn main() { | |||
1595 | } | 1452 | } |
1596 | } | 1453 | } |
1597 | } | 1454 | } |
1598 | |||
1599 | #[cfg(test)] | ||
1600 | mod decl_check_tests { | ||
1601 | use crate::diagnostics::tests::check_diagnostics; | ||
1602 | |||
1603 | #[test] | ||
1604 | fn incorrect_function_name() { | ||
1605 | check_diagnostics( | ||
1606 | r#" | ||
1607 | fn NonSnakeCaseName() {} | ||
1608 | // ^^^^^^^^^^^^^^^^ Function `NonSnakeCaseName` should have snake_case name, e.g. `non_snake_case_name` | ||
1609 | "#, | ||
1610 | ); | ||
1611 | } | ||
1612 | |||
1613 | #[test] | ||
1614 | fn incorrect_function_params() { | ||
1615 | check_diagnostics( | ||
1616 | r#" | ||
1617 | fn foo(SomeParam: u8) {} | ||
1618 | // ^^^^^^^^^ Parameter `SomeParam` should have snake_case name, e.g. `some_param` | ||
1619 | |||
1620 | fn foo2(ok_param: &str, CAPS_PARAM: u8) {} | ||
1621 | // ^^^^^^^^^^ Parameter `CAPS_PARAM` should have snake_case name, e.g. `caps_param` | ||
1622 | "#, | ||
1623 | ); | ||
1624 | } | ||
1625 | |||
1626 | #[test] | ||
1627 | fn incorrect_variable_names() { | ||
1628 | check_diagnostics( | ||
1629 | r#" | ||
1630 | fn foo() { | ||
1631 | let SOME_VALUE = 10; | ||
1632 | // ^^^^^^^^^^ Variable `SOME_VALUE` should have snake_case name, e.g. `some_value` | ||
1633 | let AnotherValue = 20; | ||
1634 | // ^^^^^^^^^^^^ Variable `AnotherValue` should have snake_case name, e.g. `another_value` | ||
1635 | } | ||
1636 | "#, | ||
1637 | ); | ||
1638 | } | ||
1639 | |||
1640 | #[test] | ||
1641 | fn incorrect_struct_names() { | ||
1642 | check_diagnostics( | ||
1643 | r#" | ||
1644 | struct non_camel_case_name {} | ||
1645 | // ^^^^^^^^^^^^^^^^^^^ Structure `non_camel_case_name` should have CamelCase name, e.g. `NonCamelCaseName` | ||
1646 | |||
1647 | struct SCREAMING_CASE {} | ||
1648 | // ^^^^^^^^^^^^^^ Structure `SCREAMING_CASE` should have CamelCase name, e.g. `ScreamingCase` | ||
1649 | "#, | ||
1650 | ); | ||
1651 | } | ||
1652 | |||
1653 | #[test] | ||
1654 | fn no_diagnostic_for_camel_cased_acronyms_in_struct_name() { | ||
1655 | check_diagnostics( | ||
1656 | r#" | ||
1657 | struct AABB {} | ||
1658 | "#, | ||
1659 | ); | ||
1660 | } | ||
1661 | |||
1662 | #[test] | ||
1663 | fn incorrect_struct_field() { | ||
1664 | check_diagnostics( | ||
1665 | r#" | ||
1666 | struct SomeStruct { SomeField: u8 } | ||
1667 | // ^^^^^^^^^ Field `SomeField` should have snake_case name, e.g. `some_field` | ||
1668 | "#, | ||
1669 | ); | ||
1670 | } | ||
1671 | |||
1672 | #[test] | ||
1673 | fn incorrect_enum_names() { | ||
1674 | check_diagnostics( | ||
1675 | r#" | ||
1676 | enum some_enum { Val(u8) } | ||
1677 | // ^^^^^^^^^ Enum `some_enum` should have CamelCase name, e.g. `SomeEnum` | ||
1678 | |||
1679 | enum SOME_ENUM {} | ||
1680 | // ^^^^^^^^^ Enum `SOME_ENUM` should have CamelCase name, e.g. `SomeEnum` | ||
1681 | "#, | ||
1682 | ); | ||
1683 | } | ||
1684 | |||
1685 | #[test] | ||
1686 | fn no_diagnostic_for_camel_cased_acronyms_in_enum_name() { | ||
1687 | check_diagnostics( | ||
1688 | r#" | ||
1689 | enum AABB {} | ||
1690 | "#, | ||
1691 | ); | ||
1692 | } | ||
1693 | |||
1694 | #[test] | ||
1695 | fn incorrect_enum_variant_name() { | ||
1696 | check_diagnostics( | ||
1697 | r#" | ||
1698 | enum SomeEnum { SOME_VARIANT(u8) } | ||
1699 | // ^^^^^^^^^^^^ Variant `SOME_VARIANT` should have CamelCase name, e.g. `SomeVariant` | ||
1700 | "#, | ||
1701 | ); | ||
1702 | } | ||
1703 | |||
1704 | #[test] | ||
1705 | fn incorrect_const_name() { | ||
1706 | check_diagnostics( | ||
1707 | r#" | ||
1708 | const some_weird_const: u8 = 10; | ||
1709 | // ^^^^^^^^^^^^^^^^ Constant `some_weird_const` should have UPPER_SNAKE_CASE name, e.g. `SOME_WEIRD_CONST` | ||
1710 | "#, | ||
1711 | ); | ||
1712 | } | ||
1713 | |||
1714 | #[test] | ||
1715 | fn incorrect_static_name() { | ||
1716 | check_diagnostics( | ||
1717 | r#" | ||
1718 | static some_weird_const: u8 = 10; | ||
1719 | // ^^^^^^^^^^^^^^^^ Static variable `some_weird_const` should have UPPER_SNAKE_CASE name, e.g. `SOME_WEIRD_CONST` | ||
1720 | "#, | ||
1721 | ); | ||
1722 | } | ||
1723 | |||
1724 | #[test] | ||
1725 | fn fn_inside_impl_struct() { | ||
1726 | check_diagnostics( | ||
1727 | r#" | ||
1728 | struct someStruct; | ||
1729 | // ^^^^^^^^^^ Structure `someStruct` should have CamelCase name, e.g. `SomeStruct` | ||
1730 | |||
1731 | impl someStruct { | ||
1732 | fn SomeFunc(&self) { | ||
1733 | // ^^^^^^^^ Function `SomeFunc` should have snake_case name, e.g. `some_func` | ||
1734 | let WHY_VAR_IS_CAPS = 10; | ||
1735 | // ^^^^^^^^^^^^^^^ Variable `WHY_VAR_IS_CAPS` should have snake_case name, e.g. `why_var_is_caps` | ||
1736 | } | ||
1737 | } | ||
1738 | "#, | ||
1739 | ); | ||
1740 | } | ||
1741 | |||
1742 | #[test] | ||
1743 | fn no_diagnostic_for_enum_varinats() { | ||
1744 | check_diagnostics( | ||
1745 | r#" | ||
1746 | enum Option { Some, None } | ||
1747 | |||
1748 | fn main() { | ||
1749 | match Option::None { | ||
1750 | None => (), | ||
1751 | Some => (), | ||
1752 | } | ||
1753 | } | ||
1754 | "#, | ||
1755 | ); | ||
1756 | } | ||
1757 | |||
1758 | #[test] | ||
1759 | fn non_let_bind() { | ||
1760 | check_diagnostics( | ||
1761 | r#" | ||
1762 | enum Option { Some, None } | ||
1763 | |||
1764 | fn main() { | ||
1765 | match Option::None { | ||
1766 | SOME_VAR @ None => (), | ||
1767 | // ^^^^^^^^ Variable `SOME_VAR` should have snake_case name, e.g. `some_var` | ||
1768 | Some => (), | ||
1769 | } | ||
1770 | } | ||
1771 | "#, | ||
1772 | ); | ||
1773 | } | ||
1774 | |||
1775 | #[test] | ||
1776 | fn allow_attributes() { | ||
1777 | check_diagnostics( | ||
1778 | r#" | ||
1779 | #[allow(non_snake_case)] | ||
1780 | fn NonSnakeCaseName(SOME_VAR: u8) -> u8{ | ||
1781 | // cov_flags generated output from elsewhere in this file | ||
1782 | extern "C" { | ||
1783 | #[no_mangle] | ||
1784 | static lower_case: u8; | ||
1785 | } | ||
1786 | |||
1787 | let OtherVar = SOME_VAR + 1; | ||
1788 | OtherVar | ||
1789 | } | ||
1790 | |||
1791 | #[allow(nonstandard_style)] | ||
1792 | mod CheckNonstandardStyle { | ||
1793 | fn HiImABadFnName() {} | ||
1794 | } | ||
1795 | |||
1796 | #[allow(bad_style)] | ||
1797 | mod CheckBadStyle { | ||
1798 | fn HiImABadFnName() {} | ||
1799 | } | ||
1800 | |||
1801 | mod F { | ||
1802 | #![allow(non_snake_case)] | ||
1803 | fn CheckItWorksWithModAttr(BAD_NAME_HI: u8) {} | ||
1804 | } | ||
1805 | |||
1806 | #[allow(non_snake_case, non_camel_case_types)] | ||
1807 | pub struct some_type { | ||
1808 | SOME_FIELD: u8, | ||
1809 | SomeField: u16, | ||
1810 | } | ||
1811 | |||
1812 | #[allow(non_upper_case_globals)] | ||
1813 | pub const some_const: u8 = 10; | ||
1814 | |||
1815 | #[allow(non_upper_case_globals)] | ||
1816 | pub static SomeStatic: u8 = 10; | ||
1817 | "#, | ||
1818 | ); | ||
1819 | } | ||
1820 | |||
1821 | #[test] | ||
1822 | fn allow_attributes_crate_attr() { | ||
1823 | check_diagnostics( | ||
1824 | r#" | ||
1825 | #![allow(non_snake_case)] | ||
1826 | |||
1827 | mod F { | ||
1828 | fn CheckItWorksWithCrateAttr(BAD_NAME_HI: u8) {} | ||
1829 | } | ||
1830 | "#, | ||
1831 | ); | ||
1832 | } | ||
1833 | |||
1834 | #[test] | ||
1835 | #[ignore] | ||
1836 | fn bug_trait_inside_fn() { | ||
1837 | // FIXME: | ||
1838 | // This is broken, and in fact, should not even be looked at by this | ||
1839 | // lint in the first place. There's weird stuff going on in the | ||
1840 | // collection phase. | ||
1841 | // It's currently being brought in by: | ||
1842 | // * validate_func on `a` recursing into modules | ||
1843 | // * then it finds the trait and then the function while iterating | ||
1844 | // through modules | ||
1845 | // * then validate_func is called on Dirty | ||
1846 | // * ... which then proceeds to look at some unknown module taking no | ||
1847 | // attrs from either the impl or the fn a, and then finally to the root | ||
1848 | // module | ||
1849 | // | ||
1850 | // It should find the attribute on the trait, but it *doesn't even see | ||
1851 | // the trait* as far as I can tell. | ||
1852 | |||
1853 | check_diagnostics( | ||
1854 | r#" | ||
1855 | trait T { fn a(); } | ||
1856 | struct U {} | ||
1857 | impl T for U { | ||
1858 | fn a() { | ||
1859 | // this comes out of bitflags, mostly | ||
1860 | #[allow(non_snake_case)] | ||
1861 | trait __BitFlags { | ||
1862 | const HiImAlsoBad: u8 = 2; | ||
1863 | #[inline] | ||
1864 | fn Dirty(&self) -> bool { | ||
1865 | false | ||
1866 | } | ||
1867 | } | ||
1868 | |||
1869 | } | ||
1870 | } | ||
1871 | "#, | ||
1872 | ); | ||
1873 | } | ||
1874 | |||
1875 | #[test] | ||
1876 | #[ignore] | ||
1877 | fn bug_traits_arent_checked() { | ||
1878 | // FIXME: Traits and functions in traits aren't currently checked by | ||
1879 | // r-a, even though rustc will complain about them. | ||
1880 | check_diagnostics( | ||
1881 | r#" | ||
1882 | trait BAD_TRAIT { | ||
1883 | // ^^^^^^^^^ Trait `BAD_TRAIT` should have CamelCase name, e.g. `BadTrait` | ||
1884 | fn BAD_FUNCTION(); | ||
1885 | // ^^^^^^^^^^^^ Function `BAD_FUNCTION` should have snake_case name, e.g. `bad_function` | ||
1886 | fn BadFunction(); | ||
1887 | // ^^^^^^^^^^^^ Function `BadFunction` should have snake_case name, e.g. `bad_function` | ||
1888 | } | ||
1889 | "#, | ||
1890 | ); | ||
1891 | } | ||
1892 | |||
1893 | #[test] | ||
1894 | fn ignores_extern_items() { | ||
1895 | cov_mark::check!(extern_func_incorrect_case_ignored); | ||
1896 | cov_mark::check!(extern_static_incorrect_case_ignored); | ||
1897 | check_diagnostics( | ||
1898 | r#" | ||
1899 | extern { | ||
1900 | fn NonSnakeCaseName(SOME_VAR: u8) -> u8; | ||
1901 | pub static SomeStatic: u8 = 10; | ||
1902 | } | ||
1903 | "#, | ||
1904 | ); | ||
1905 | } | ||
1906 | |||
1907 | #[test] | ||
1908 | fn infinite_loop_inner_items() { | ||
1909 | check_diagnostics( | ||
1910 | r#" | ||
1911 | fn qualify() { | ||
1912 | mod foo { | ||
1913 | use super::*; | ||
1914 | } | ||
1915 | } | ||
1916 | "#, | ||
1917 | ) | ||
1918 | } | ||
1919 | |||
1920 | #[test] // Issue #8809. | ||
1921 | fn parenthesized_parameter() { | ||
1922 | check_diagnostics(r#"fn f((O): _) {}"#) | ||
1923 | } | ||
1924 | } | ||
diff --git a/crates/ide/src/diagnostics/fixes.rs b/crates/ide/src/diagnostics/fixes.rs deleted file mode 100644 index d763dca93..000000000 --- a/crates/ide/src/diagnostics/fixes.rs +++ /dev/null | |||
@@ -1,24 +0,0 @@ | |||
1 | //! Provides a way to attach fixes to the diagnostics. | ||
2 | //! The same module also has all curret custom fixes for the diagnostics implemented. | ||
3 | |||
4 | use hir::{diagnostics::Diagnostic, Semantics}; | ||
5 | use ide_assists::AssistResolveStrategy; | ||
6 | use ide_db::RootDatabase; | ||
7 | |||
8 | use crate::Assist; | ||
9 | |||
10 | /// A [Diagnostic] that potentially has some fixes available. | ||
11 | /// | ||
12 | /// [Diagnostic]: hir::diagnostics::Diagnostic | ||
13 | pub(crate) trait DiagnosticWithFixes: Diagnostic { | ||
14 | /// `resolve` determines if the diagnostic should fill in the `edit` field | ||
15 | /// of the assist. | ||
16 | /// | ||
17 | /// If `resolve` is false, the edit will be computed later, on demand, and | ||
18 | /// can be omitted. | ||
19 | fn fixes( | ||
20 | &self, | ||
21 | sema: &Semantics<RootDatabase>, | ||
22 | _resolve: &AssistResolveStrategy, | ||
23 | ) -> Option<Vec<Assist>>; | ||
24 | } | ||
diff --git a/crates/ide/src/diagnostics/incorrect_case.rs b/crates/ide/src/diagnostics/incorrect_case.rs index 56283b58b..832394400 100644 --- a/crates/ide/src/diagnostics/incorrect_case.rs +++ b/crates/ide/src/diagnostics/incorrect_case.rs | |||
@@ -163,4 +163,326 @@ impl TestStruct { | |||
163 | 163 | ||
164 | check_fix(input, expected); | 164 | check_fix(input, expected); |
165 | } | 165 | } |
166 | |||
167 | #[test] | ||
168 | fn incorrect_function_name() { | ||
169 | check_diagnostics( | ||
170 | r#" | ||
171 | fn NonSnakeCaseName() {} | ||
172 | // ^^^^^^^^^^^^^^^^ Function `NonSnakeCaseName` should have snake_case name, e.g. `non_snake_case_name` | ||
173 | "#, | ||
174 | ); | ||
175 | } | ||
176 | |||
177 | #[test] | ||
178 | fn incorrect_function_params() { | ||
179 | check_diagnostics( | ||
180 | r#" | ||
181 | fn foo(SomeParam: u8) {} | ||
182 | // ^^^^^^^^^ Parameter `SomeParam` should have snake_case name, e.g. `some_param` | ||
183 | |||
184 | fn foo2(ok_param: &str, CAPS_PARAM: u8) {} | ||
185 | // ^^^^^^^^^^ Parameter `CAPS_PARAM` should have snake_case name, e.g. `caps_param` | ||
186 | "#, | ||
187 | ); | ||
188 | } | ||
189 | |||
190 | #[test] | ||
191 | fn incorrect_variable_names() { | ||
192 | check_diagnostics( | ||
193 | r#" | ||
194 | fn foo() { | ||
195 | let SOME_VALUE = 10; | ||
196 | // ^^^^^^^^^^ Variable `SOME_VALUE` should have snake_case name, e.g. `some_value` | ||
197 | let AnotherValue = 20; | ||
198 | // ^^^^^^^^^^^^ Variable `AnotherValue` should have snake_case name, e.g. `another_value` | ||
199 | } | ||
200 | "#, | ||
201 | ); | ||
202 | } | ||
203 | |||
204 | #[test] | ||
205 | fn incorrect_struct_names() { | ||
206 | check_diagnostics( | ||
207 | r#" | ||
208 | struct non_camel_case_name {} | ||
209 | // ^^^^^^^^^^^^^^^^^^^ Structure `non_camel_case_name` should have CamelCase name, e.g. `NonCamelCaseName` | ||
210 | |||
211 | struct SCREAMING_CASE {} | ||
212 | // ^^^^^^^^^^^^^^ Structure `SCREAMING_CASE` should have CamelCase name, e.g. `ScreamingCase` | ||
213 | "#, | ||
214 | ); | ||
215 | } | ||
216 | |||
217 | #[test] | ||
218 | fn no_diagnostic_for_camel_cased_acronyms_in_struct_name() { | ||
219 | check_diagnostics( | ||
220 | r#" | ||
221 | struct AABB {} | ||
222 | "#, | ||
223 | ); | ||
224 | } | ||
225 | |||
226 | #[test] | ||
227 | fn incorrect_struct_field() { | ||
228 | check_diagnostics( | ||
229 | r#" | ||
230 | struct SomeStruct { SomeField: u8 } | ||
231 | // ^^^^^^^^^ Field `SomeField` should have snake_case name, e.g. `some_field` | ||
232 | "#, | ||
233 | ); | ||
234 | } | ||
235 | |||
236 | #[test] | ||
237 | fn incorrect_enum_names() { | ||
238 | check_diagnostics( | ||
239 | r#" | ||
240 | enum some_enum { Val(u8) } | ||
241 | // ^^^^^^^^^ Enum `some_enum` should have CamelCase name, e.g. `SomeEnum` | ||
242 | |||
243 | enum SOME_ENUM {} | ||
244 | // ^^^^^^^^^ Enum `SOME_ENUM` should have CamelCase name, e.g. `SomeEnum` | ||
245 | "#, | ||
246 | ); | ||
247 | } | ||
248 | |||
249 | #[test] | ||
250 | fn no_diagnostic_for_camel_cased_acronyms_in_enum_name() { | ||
251 | check_diagnostics( | ||
252 | r#" | ||
253 | enum AABB {} | ||
254 | "#, | ||
255 | ); | ||
256 | } | ||
257 | |||
258 | #[test] | ||
259 | fn incorrect_enum_variant_name() { | ||
260 | check_diagnostics( | ||
261 | r#" | ||
262 | enum SomeEnum { SOME_VARIANT(u8) } | ||
263 | // ^^^^^^^^^^^^ Variant `SOME_VARIANT` should have CamelCase name, e.g. `SomeVariant` | ||
264 | "#, | ||
265 | ); | ||
266 | } | ||
267 | |||
268 | #[test] | ||
269 | fn incorrect_const_name() { | ||
270 | check_diagnostics( | ||
271 | r#" | ||
272 | const some_weird_const: u8 = 10; | ||
273 | // ^^^^^^^^^^^^^^^^ Constant `some_weird_const` should have UPPER_SNAKE_CASE name, e.g. `SOME_WEIRD_CONST` | ||
274 | "#, | ||
275 | ); | ||
276 | } | ||
277 | |||
278 | #[test] | ||
279 | fn incorrect_static_name() { | ||
280 | check_diagnostics( | ||
281 | r#" | ||
282 | static some_weird_const: u8 = 10; | ||
283 | // ^^^^^^^^^^^^^^^^ Static variable `some_weird_const` should have UPPER_SNAKE_CASE name, e.g. `SOME_WEIRD_CONST` | ||
284 | "#, | ||
285 | ); | ||
286 | } | ||
287 | |||
288 | #[test] | ||
289 | fn fn_inside_impl_struct() { | ||
290 | check_diagnostics( | ||
291 | r#" | ||
292 | struct someStruct; | ||
293 | // ^^^^^^^^^^ Structure `someStruct` should have CamelCase name, e.g. `SomeStruct` | ||
294 | |||
295 | impl someStruct { | ||
296 | fn SomeFunc(&self) { | ||
297 | // ^^^^^^^^ Function `SomeFunc` should have snake_case name, e.g. `some_func` | ||
298 | let WHY_VAR_IS_CAPS = 10; | ||
299 | // ^^^^^^^^^^^^^^^ Variable `WHY_VAR_IS_CAPS` should have snake_case name, e.g. `why_var_is_caps` | ||
300 | } | ||
301 | } | ||
302 | "#, | ||
303 | ); | ||
304 | } | ||
305 | |||
306 | #[test] | ||
307 | fn no_diagnostic_for_enum_varinats() { | ||
308 | check_diagnostics( | ||
309 | r#" | ||
310 | enum Option { Some, None } | ||
311 | |||
312 | fn main() { | ||
313 | match Option::None { | ||
314 | None => (), | ||
315 | Some => (), | ||
316 | } | ||
317 | } | ||
318 | "#, | ||
319 | ); | ||
320 | } | ||
321 | |||
322 | #[test] | ||
323 | fn non_let_bind() { | ||
324 | check_diagnostics( | ||
325 | r#" | ||
326 | enum Option { Some, None } | ||
327 | |||
328 | fn main() { | ||
329 | match Option::None { | ||
330 | SOME_VAR @ None => (), | ||
331 | // ^^^^^^^^ Variable `SOME_VAR` should have snake_case name, e.g. `some_var` | ||
332 | Some => (), | ||
333 | } | ||
334 | } | ||
335 | "#, | ||
336 | ); | ||
337 | } | ||
338 | |||
339 | #[test] | ||
340 | fn allow_attributes_crate_attr() { | ||
341 | check_diagnostics( | ||
342 | r#" | ||
343 | #![allow(non_snake_case)] | ||
344 | |||
345 | mod F { | ||
346 | fn CheckItWorksWithCrateAttr(BAD_NAME_HI: u8) {} | ||
347 | } | ||
348 | "#, | ||
349 | ); | ||
350 | } | ||
351 | |||
352 | #[test] | ||
353 | #[ignore] | ||
354 | fn bug_trait_inside_fn() { | ||
355 | // FIXME: | ||
356 | // This is broken, and in fact, should not even be looked at by this | ||
357 | // lint in the first place. There's weird stuff going on in the | ||
358 | // collection phase. | ||
359 | // It's currently being brought in by: | ||
360 | // * validate_func on `a` recursing into modules | ||
361 | // * then it finds the trait and then the function while iterating | ||
362 | // through modules | ||
363 | // * then validate_func is called on Dirty | ||
364 | // * ... which then proceeds to look at some unknown module taking no | ||
365 | // attrs from either the impl or the fn a, and then finally to the root | ||
366 | // module | ||
367 | // | ||
368 | // It should find the attribute on the trait, but it *doesn't even see | ||
369 | // the trait* as far as I can tell. | ||
370 | |||
371 | check_diagnostics( | ||
372 | r#" | ||
373 | trait T { fn a(); } | ||
374 | struct U {} | ||
375 | impl T for U { | ||
376 | fn a() { | ||
377 | // this comes out of bitflags, mostly | ||
378 | #[allow(non_snake_case)] | ||
379 | trait __BitFlags { | ||
380 | const HiImAlsoBad: u8 = 2; | ||
381 | #[inline] | ||
382 | fn Dirty(&self) -> bool { | ||
383 | false | ||
384 | } | ||
385 | } | ||
386 | |||
387 | } | ||
388 | } | ||
389 | "#, | ||
390 | ); | ||
391 | } | ||
392 | |||
393 | #[test] | ||
394 | fn infinite_loop_inner_items() { | ||
395 | check_diagnostics( | ||
396 | r#" | ||
397 | fn qualify() { | ||
398 | mod foo { | ||
399 | use super::*; | ||
400 | } | ||
401 | } | ||
402 | "#, | ||
403 | ) | ||
404 | } | ||
405 | |||
406 | #[test] // Issue #8809. | ||
407 | fn parenthesized_parameter() { | ||
408 | check_diagnostics(r#"fn f((O): _) {}"#) | ||
409 | } | ||
410 | |||
411 | #[test] | ||
412 | fn ignores_extern_items() { | ||
413 | cov_mark::check!(extern_func_incorrect_case_ignored); | ||
414 | cov_mark::check!(extern_static_incorrect_case_ignored); | ||
415 | check_diagnostics( | ||
416 | r#" | ||
417 | extern { | ||
418 | fn NonSnakeCaseName(SOME_VAR: u8) -> u8; | ||
419 | pub static SomeStatic: u8 = 10; | ||
420 | } | ||
421 | "#, | ||
422 | ); | ||
423 | } | ||
424 | |||
425 | #[test] | ||
426 | #[ignore] | ||
427 | fn bug_traits_arent_checked() { | ||
428 | // FIXME: Traits and functions in traits aren't currently checked by | ||
429 | // r-a, even though rustc will complain about them. | ||
430 | check_diagnostics( | ||
431 | r#" | ||
432 | trait BAD_TRAIT { | ||
433 | // ^^^^^^^^^ Trait `BAD_TRAIT` should have CamelCase name, e.g. `BadTrait` | ||
434 | fn BAD_FUNCTION(); | ||
435 | // ^^^^^^^^^^^^ Function `BAD_FUNCTION` should have snake_case name, e.g. `bad_function` | ||
436 | fn BadFunction(); | ||
437 | // ^^^^^^^^^^^^ Function `BadFunction` should have snake_case name, e.g. `bad_function` | ||
438 | } | ||
439 | "#, | ||
440 | ); | ||
441 | } | ||
442 | |||
443 | #[test] | ||
444 | fn allow_attributes() { | ||
445 | check_diagnostics( | ||
446 | r#" | ||
447 | #[allow(non_snake_case)] | ||
448 | fn NonSnakeCaseName(SOME_VAR: u8) -> u8{ | ||
449 | // cov_flags generated output from elsewhere in this file | ||
450 | extern "C" { | ||
451 | #[no_mangle] | ||
452 | static lower_case: u8; | ||
453 | } | ||
454 | |||
455 | let OtherVar = SOME_VAR + 1; | ||
456 | OtherVar | ||
457 | } | ||
458 | |||
459 | #[allow(nonstandard_style)] | ||
460 | mod CheckNonstandardStyle { | ||
461 | fn HiImABadFnName() {} | ||
462 | } | ||
463 | |||
464 | #[allow(bad_style)] | ||
465 | mod CheckBadStyle { | ||
466 | fn HiImABadFnName() {} | ||
467 | } | ||
468 | |||
469 | mod F { | ||
470 | #![allow(non_snake_case)] | ||
471 | fn CheckItWorksWithModAttr(BAD_NAME_HI: u8) {} | ||
472 | } | ||
473 | |||
474 | #[allow(non_snake_case, non_camel_case_types)] | ||
475 | pub struct some_type { | ||
476 | SOME_FIELD: u8, | ||
477 | SomeField: u16, | ||
478 | } | ||
479 | |||
480 | #[allow(non_upper_case_globals)] | ||
481 | pub const some_const: u8 = 10; | ||
482 | |||
483 | #[allow(non_upper_case_globals)] | ||
484 | pub static SomeStatic: u8 = 10; | ||
485 | "#, | ||
486 | ); | ||
487 | } | ||
166 | } | 488 | } |
diff --git a/crates/ide/src/diagnostics/unlinked_file.rs b/crates/ide/src/diagnostics/unlinked_file.rs index 51fe0f360..a5b2e3399 100644 --- a/crates/ide/src/diagnostics/unlinked_file.rs +++ b/crates/ide/src/diagnostics/unlinked_file.rs | |||
@@ -1,11 +1,6 @@ | |||
1 | //! Diagnostic emitted for files that aren't part of any crate. | 1 | //! Diagnostic emitted for files that aren't part of any crate. |
2 | 2 | ||
3 | use hir::{ | 3 | use hir::db::DefDatabase; |
4 | db::DefDatabase, | ||
5 | diagnostics::{Diagnostic, DiagnosticCode}, | ||
6 | InFile, | ||
7 | }; | ||
8 | use ide_assists::AssistResolveStrategy; | ||
9 | use ide_db::{ | 4 | use ide_db::{ |
10 | base_db::{FileId, FileLoader, SourceDatabase, SourceDatabaseExt}, | 5 | base_db::{FileId, FileLoader, SourceDatabase, SourceDatabaseExt}, |
11 | source_change::SourceChange, | 6 | source_change::SourceChange, |
@@ -13,92 +8,77 @@ use ide_db::{ | |||
13 | }; | 8 | }; |
14 | use syntax::{ | 9 | use syntax::{ |
15 | ast::{self, ModuleItemOwner, NameOwner}, | 10 | ast::{self, ModuleItemOwner, NameOwner}, |
16 | AstNode, SyntaxNodePtr, | 11 | AstNode, TextRange, TextSize, |
17 | }; | 12 | }; |
18 | use text_edit::TextEdit; | 13 | use text_edit::TextEdit; |
19 | 14 | ||
20 | use crate::{ | 15 | use crate::{ |
21 | diagnostics::{fix, fixes::DiagnosticWithFixes}, | 16 | diagnostics::{fix, DiagnosticsContext}, |
22 | Assist, | 17 | Assist, Diagnostic, |
23 | }; | 18 | }; |
24 | 19 | ||
20 | #[derive(Debug)] | ||
21 | pub(crate) struct UnlinkedFile { | ||
22 | pub(crate) file: FileId, | ||
23 | } | ||
24 | |||
25 | // Diagnostic: unlinked-file | 25 | // Diagnostic: unlinked-file |
26 | // | 26 | // |
27 | // This diagnostic is shown for files that are not included in any crate, or files that are part of | 27 | // This diagnostic is shown for files that are not included in any crate, or files that are part of |
28 | // crates rust-analyzer failed to discover. The file will not have IDE features available. | 28 | // crates rust-analyzer failed to discover. The file will not have IDE features available. |
29 | #[derive(Debug)] | 29 | pub(super) fn unlinked_file(ctx: &DiagnosticsContext, d: &UnlinkedFile) -> Diagnostic { |
30 | pub(crate) struct UnlinkedFile { | 30 | // Limit diagnostic to the first few characters in the file. This matches how VS Code |
31 | pub(crate) file_id: FileId, | 31 | // renders it with the full span, but on other editors, and is less invasive. |
32 | pub(crate) node: SyntaxNodePtr, | 32 | let range = ctx.sema.db.parse(d.file).syntax_node().text_range(); |
33 | // FIXME: This is wrong if one of the first three characters is not ascii: `//Ы`. | ||
34 | let range = range.intersect(TextRange::up_to(TextSize::of("..."))).unwrap_or(range); | ||
35 | |||
36 | Diagnostic::new("unlinked-file", "file not included in module tree", range) | ||
37 | .with_fixes(fixes(ctx, d)) | ||
33 | } | 38 | } |
34 | 39 | ||
35 | impl Diagnostic for UnlinkedFile { | 40 | fn fixes(ctx: &DiagnosticsContext, d: &UnlinkedFile) -> Option<Vec<Assist>> { |
36 | fn code(&self) -> DiagnosticCode { | 41 | // If there's an existing module that could add `mod` or `pub mod` items to include the unlinked file, |
37 | DiagnosticCode("unlinked-file") | 42 | // suggest that as a fix. |
38 | } | ||
39 | 43 | ||
40 | fn message(&self) -> String { | 44 | let source_root = ctx.sema.db.source_root(ctx.sema.db.file_source_root(d.file)); |
41 | "file not included in module tree".to_string() | 45 | let our_path = source_root.path_for_file(&d.file)?; |
42 | } | 46 | let module_name = our_path.name_and_extension()?.0; |
43 | 47 | ||
44 | fn display_source(&self) -> InFile<SyntaxNodePtr> { | 48 | // Candidates to look for: |
45 | InFile::new(self.file_id.into(), self.node.clone()) | 49 | // - `mod.rs` in the same folder |
46 | } | 50 | // - we also check `main.rs` and `lib.rs` |
51 | // - `$dir.rs` in the parent folder, where `$dir` is the directory containing `self.file_id` | ||
52 | let parent = our_path.parent()?; | ||
53 | let mut paths = vec![parent.join("mod.rs")?, parent.join("lib.rs")?, parent.join("main.rs")?]; | ||
47 | 54 | ||
48 | fn as_any(&self) -> &(dyn std::any::Any + Send + 'static) { | 55 | // `submod/bla.rs` -> `submod.rs` |
49 | self | 56 | if let Some(newmod) = (|| { |
57 | let name = parent.name_and_extension()?.0; | ||
58 | parent.parent()?.join(&format!("{}.rs", name)) | ||
59 | })() { | ||
60 | paths.push(newmod); | ||
50 | } | 61 | } |
51 | } | ||
52 | 62 | ||
53 | impl DiagnosticWithFixes for UnlinkedFile { | 63 | for path in &paths { |
54 | fn fixes( | 64 | if let Some(parent_id) = source_root.file_for_path(path) { |
55 | &self, | 65 | for krate in ctx.sema.db.relevant_crates(*parent_id).iter() { |
56 | sema: &hir::Semantics<RootDatabase>, | 66 | let crate_def_map = ctx.sema.db.crate_def_map(*krate); |
57 | _resolve: &AssistResolveStrategy, | 67 | for (_, module) in crate_def_map.modules() { |
58 | ) -> Option<Vec<Assist>> { | 68 | if module.origin.is_inline() { |
59 | // If there's an existing module that could add `mod` or `pub mod` items to include the unlinked file, | 69 | // We don't handle inline `mod parent {}`s, they use different paths. |
60 | // suggest that as a fix. | 70 | continue; |
61 | 71 | } | |
62 | let source_root = sema.db.source_root(sema.db.file_source_root(self.file_id)); | ||
63 | let our_path = source_root.path_for_file(&self.file_id)?; | ||
64 | let module_name = our_path.name_and_extension()?.0; | ||
65 | |||
66 | // Candidates to look for: | ||
67 | // - `mod.rs` in the same folder | ||
68 | // - we also check `main.rs` and `lib.rs` | ||
69 | // - `$dir.rs` in the parent folder, where `$dir` is the directory containing `self.file_id` | ||
70 | let parent = our_path.parent()?; | ||
71 | let mut paths = | ||
72 | vec![parent.join("mod.rs")?, parent.join("lib.rs")?, parent.join("main.rs")?]; | ||
73 | |||
74 | // `submod/bla.rs` -> `submod.rs` | ||
75 | if let Some(newmod) = (|| { | ||
76 | let name = parent.name_and_extension()?.0; | ||
77 | parent.parent()?.join(&format!("{}.rs", name)) | ||
78 | })() { | ||
79 | paths.push(newmod); | ||
80 | } | ||
81 | 72 | ||
82 | for path in &paths { | 73 | if module.origin.file_id() == Some(*parent_id) { |
83 | if let Some(parent_id) = source_root.file_for_path(path) { | 74 | return make_fixes(ctx.sema.db, *parent_id, module_name, d.file); |
84 | for krate in sema.db.relevant_crates(*parent_id).iter() { | ||
85 | let crate_def_map = sema.db.crate_def_map(*krate); | ||
86 | for (_, module) in crate_def_map.modules() { | ||
87 | if module.origin.is_inline() { | ||
88 | // We don't handle inline `mod parent {}`s, they use different paths. | ||
89 | continue; | ||
90 | } | ||
91 | |||
92 | if module.origin.file_id() == Some(*parent_id) { | ||
93 | return make_fixes(sema.db, *parent_id, module_name, self.file_id); | ||
94 | } | ||
95 | } | 75 | } |
96 | } | 76 | } |
97 | } | 77 | } |
98 | } | 78 | } |
99 | |||
100 | None | ||
101 | } | 79 | } |
80 | |||
81 | None | ||
102 | } | 82 | } |
103 | 83 | ||
104 | fn make_fixes( | 84 | fn make_fixes( |
@@ -181,3 +161,144 @@ fn make_fixes( | |||
181 | ), | 161 | ), |
182 | ]) | 162 | ]) |
183 | } | 163 | } |
164 | |||
165 | #[cfg(test)] | ||
166 | mod tests { | ||
167 | use crate::diagnostics::tests::{check_diagnostics, check_fix, check_fixes, check_no_fix}; | ||
168 | |||
169 | #[test] | ||
170 | fn unlinked_file_prepend_first_item() { | ||
171 | cov_mark::check!(unlinked_file_prepend_before_first_item); | ||
172 | // Only tests the first one for `pub mod` since the rest are the same | ||
173 | check_fixes( | ||
174 | r#" | ||
175 | //- /main.rs | ||
176 | fn f() {} | ||
177 | //- /foo.rs | ||
178 | $0 | ||
179 | "#, | ||
180 | vec![ | ||
181 | r#" | ||
182 | mod foo; | ||
183 | |||
184 | fn f() {} | ||
185 | "#, | ||
186 | r#" | ||
187 | pub mod foo; | ||
188 | |||
189 | fn f() {} | ||
190 | "#, | ||
191 | ], | ||
192 | ); | ||
193 | } | ||
194 | |||
195 | #[test] | ||
196 | fn unlinked_file_append_mod() { | ||
197 | cov_mark::check!(unlinked_file_append_to_existing_mods); | ||
198 | check_fix( | ||
199 | r#" | ||
200 | //- /main.rs | ||
201 | //! Comment on top | ||
202 | |||
203 | mod preexisting; | ||
204 | |||
205 | mod preexisting2; | ||
206 | |||
207 | struct S; | ||
208 | |||
209 | mod preexisting_bottom;) | ||
210 | //- /foo.rs | ||
211 | $0 | ||
212 | "#, | ||
213 | r#" | ||
214 | //! Comment on top | ||
215 | |||
216 | mod preexisting; | ||
217 | |||
218 | mod preexisting2; | ||
219 | mod foo; | ||
220 | |||
221 | struct S; | ||
222 | |||
223 | mod preexisting_bottom;) | ||
224 | "#, | ||
225 | ); | ||
226 | } | ||
227 | |||
228 | #[test] | ||
229 | fn unlinked_file_insert_in_empty_file() { | ||
230 | cov_mark::check!(unlinked_file_empty_file); | ||
231 | check_fix( | ||
232 | r#" | ||
233 | //- /main.rs | ||
234 | //- /foo.rs | ||
235 | $0 | ||
236 | "#, | ||
237 | r#" | ||
238 | mod foo; | ||
239 | "#, | ||
240 | ); | ||
241 | } | ||
242 | |||
243 | #[test] | ||
244 | fn unlinked_file_old_style_modrs() { | ||
245 | check_fix( | ||
246 | r#" | ||
247 | //- /main.rs | ||
248 | mod submod; | ||
249 | //- /submod/mod.rs | ||
250 | // in mod.rs | ||
251 | //- /submod/foo.rs | ||
252 | $0 | ||
253 | "#, | ||
254 | r#" | ||
255 | // in mod.rs | ||
256 | mod foo; | ||
257 | "#, | ||
258 | ); | ||
259 | } | ||
260 | |||
261 | #[test] | ||
262 | fn unlinked_file_new_style_mod() { | ||
263 | check_fix( | ||
264 | r#" | ||
265 | //- /main.rs | ||
266 | mod submod; | ||
267 | //- /submod.rs | ||
268 | //- /submod/foo.rs | ||
269 | $0 | ||
270 | "#, | ||
271 | r#" | ||
272 | mod foo; | ||
273 | "#, | ||
274 | ); | ||
275 | } | ||
276 | |||
277 | #[test] | ||
278 | fn unlinked_file_with_cfg_off() { | ||
279 | cov_mark::check!(unlinked_file_skip_fix_when_mod_already_exists); | ||
280 | check_no_fix( | ||
281 | r#" | ||
282 | //- /main.rs | ||
283 | #[cfg(never)] | ||
284 | mod foo; | ||
285 | |||
286 | //- /foo.rs | ||
287 | $0 | ||
288 | "#, | ||
289 | ); | ||
290 | } | ||
291 | |||
292 | #[test] | ||
293 | fn unlinked_file_with_cfg_on() { | ||
294 | check_diagnostics( | ||
295 | r#" | ||
296 | //- /main.rs | ||
297 | #[cfg(not(never))] | ||
298 | mod foo; | ||
299 | |||
300 | //- /foo.rs | ||
301 | "#, | ||
302 | ); | ||
303 | } | ||
304 | } | ||