aboutsummaryrefslogtreecommitdiff
path: root/crates/ide
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide')
-rw-r--r--crates/ide/src/diagnostics.rs514
-rw-r--r--crates/ide/src/diagnostics/fixes.rs24
-rw-r--r--crates/ide/src/diagnostics/incorrect_case.rs322
-rw-r--r--crates/ide/src/diagnostics/unlinked_file.rs259
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;
16mod remove_this_semicolon; 16mod remove_this_semicolon;
17mod replace_filter_map_next_with_find_map; 17mod replace_filter_map_next_with_find_map;
18mod unimplemented_builtin_macro; 18mod unimplemented_builtin_macro;
19mod unlinked_file;
19mod unresolved_extern_crate; 20mod unresolved_extern_crate;
20mod unresolved_import; 21mod unresolved_import;
21mod unresolved_macro_call; 22mod unresolved_macro_call;
22mod unresolved_module; 23mod unresolved_module;
23mod unresolved_proc_macro; 24mod unresolved_proc_macro;
24 25
25mod fixes;
26mod field_shorthand; 26mod field_shorthand;
27mod unlinked_file;
28 27
29use std::cell::RefCell; 28use std::cell::RefCell;
30 29
31use hir::{ 30use hir::{
32 diagnostics::{AnyDiagnostic, Diagnostic as _, DiagnosticCode, DiagnosticSinkBuilder}, 31 diagnostics::{AnyDiagnostic, DiagnosticCode, DiagnosticSinkBuilder},
33 Semantics, 32 Semantics,
34}; 33};
35use ide_assists::AssistResolveStrategy; 34use ide_assists::AssistResolveStrategy;
@@ -38,15 +37,13 @@ use itertools::Itertools;
38use rustc_hash::FxHashSet; 37use rustc_hash::FxHashSet;
39use syntax::{ 38use syntax::{
40 ast::{self, AstNode}, 39 ast::{self, AstNode},
41 SyntaxNode, SyntaxNodePtr, TextRange, TextSize, 40 SyntaxNode, TextRange,
42}; 41};
43use text_edit::TextEdit; 42use text_edit::TextEdit;
44use unlinked_file::UnlinkedFile; 43use unlinked_file::UnlinkedFile;
45 44
46use crate::{Assist, AssistId, AssistKind, FileId, Label, SourceChange}; 45use crate::{Assist, AssistId, AssistKind, FileId, Label, SourceChange};
47 46
48use self::fixes::DiagnosticWithFixes;
49
50#[derive(Debug)] 47#[derive(Debug)]
51pub struct Diagnostic { 48pub 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
533fn f() {}
534//- /foo.rs
535$0
536"#,
537 vec![
538 r#"
539mod foo;
540
541fn f() {}
542"#,
543 r#"
544pub mod foo;
545
546fn 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
560mod preexisting;
561
562mod preexisting2;
563
564struct S;
565
566mod preexisting_bottom;)
567//- /foo.rs
568$0
569"#,
570 r#"
571//! Comment on top
572
573mod preexisting;
574
575mod preexisting2;
576mod foo;
577
578struct S;
579
580mod 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#"
595mod foo;
596"#,
597 );
598 }
599
600 #[test]
601 fn unlinked_file_old_style_modrs() {
602 check_fix(
603 r#"
604//- /main.rs
605mod submod;
606//- /submod/mod.rs
607// in mod.rs
608//- /submod/foo.rs
609$0
610"#,
611 r#"
612// in mod.rs
613mod foo;
614"#,
615 );
616 }
617
618 #[test]
619 fn unlinked_file_new_style_mod() {
620 check_fix(
621 r#"
622//- /main.rs
623mod submod;
624//- /submod.rs
625//- /submod/foo.rs
626$0
627"#,
628 r#"
629mod 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)]
641mod 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))]
655mod 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)]
1600mod decl_check_tests {
1601 use crate::diagnostics::tests::check_diagnostics;
1602
1603 #[test]
1604 fn incorrect_function_name() {
1605 check_diagnostics(
1606 r#"
1607fn 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#"
1617fn foo(SomeParam: u8) {}
1618 // ^^^^^^^^^ Parameter `SomeParam` should have snake_case name, e.g. `some_param`
1619
1620fn 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#"
1630fn 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#"
1644struct non_camel_case_name {}
1645 // ^^^^^^^^^^^^^^^^^^^ Structure `non_camel_case_name` should have CamelCase name, e.g. `NonCamelCaseName`
1646
1647struct 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#"
1657struct AABB {}
1658"#,
1659 );
1660 }
1661
1662 #[test]
1663 fn incorrect_struct_field() {
1664 check_diagnostics(
1665 r#"
1666struct 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#"
1676enum some_enum { Val(u8) }
1677 // ^^^^^^^^^ Enum `some_enum` should have CamelCase name, e.g. `SomeEnum`
1678
1679enum 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#"
1689enum AABB {}
1690"#,
1691 );
1692 }
1693
1694 #[test]
1695 fn incorrect_enum_variant_name() {
1696 check_diagnostics(
1697 r#"
1698enum 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#"
1708const 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#"
1718static 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#"
1728struct someStruct;
1729 // ^^^^^^^^^^ Structure `someStruct` should have CamelCase name, e.g. `SomeStruct`
1730
1731impl 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#"
1746enum Option { Some, None }
1747
1748fn 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#"
1762enum Option { Some, None }
1763
1764fn 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)]
1780fn 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)]
1792mod CheckNonstandardStyle {
1793 fn HiImABadFnName() {}
1794}
1795
1796#[allow(bad_style)]
1797mod CheckBadStyle {
1798 fn HiImABadFnName() {}
1799}
1800
1801mod F {
1802 #![allow(non_snake_case)]
1803 fn CheckItWorksWithModAttr(BAD_NAME_HI: u8) {}
1804}
1805
1806#[allow(non_snake_case, non_camel_case_types)]
1807pub struct some_type {
1808 SOME_FIELD: u8,
1809 SomeField: u16,
1810}
1811
1812#[allow(non_upper_case_globals)]
1813pub const some_const: u8 = 10;
1814
1815#[allow(non_upper_case_globals)]
1816pub 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
1827mod 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#"
1855trait T { fn a(); }
1856struct U {}
1857impl 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#"
1882trait 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#"
1899extern {
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#"
1911fn 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
4use hir::{diagnostics::Diagnostic, Semantics};
5use ide_assists::AssistResolveStrategy;
6use ide_db::RootDatabase;
7
8use crate::Assist;
9
10/// A [Diagnostic] that potentially has some fixes available.
11///
12/// [Diagnostic]: hir::diagnostics::Diagnostic
13pub(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#"
171fn 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#"
181fn foo(SomeParam: u8) {}
182 // ^^^^^^^^^ Parameter `SomeParam` should have snake_case name, e.g. `some_param`
183
184fn 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#"
194fn 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#"
208struct non_camel_case_name {}
209 // ^^^^^^^^^^^^^^^^^^^ Structure `non_camel_case_name` should have CamelCase name, e.g. `NonCamelCaseName`
210
211struct 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#"
221struct AABB {}
222"#,
223 );
224 }
225
226 #[test]
227 fn incorrect_struct_field() {
228 check_diagnostics(
229 r#"
230struct 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#"
240enum some_enum { Val(u8) }
241 // ^^^^^^^^^ Enum `some_enum` should have CamelCase name, e.g. `SomeEnum`
242
243enum 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#"
253enum AABB {}
254"#,
255 );
256 }
257
258 #[test]
259 fn incorrect_enum_variant_name() {
260 check_diagnostics(
261 r#"
262enum 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#"
272const 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#"
282static 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#"
292struct someStruct;
293 // ^^^^^^^^^^ Structure `someStruct` should have CamelCase name, e.g. `SomeStruct`
294
295impl 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#"
310enum Option { Some, None }
311
312fn 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#"
326enum Option { Some, None }
327
328fn 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
345mod 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#"
373trait T { fn a(); }
374struct U {}
375impl 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#"
397fn 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#"
417extern {
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#"
432trait 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)]
448fn 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)]
460mod CheckNonstandardStyle {
461 fn HiImABadFnName() {}
462}
463
464#[allow(bad_style)]
465mod CheckBadStyle {
466 fn HiImABadFnName() {}
467}
468
469mod F {
470 #![allow(non_snake_case)]
471 fn CheckItWorksWithModAttr(BAD_NAME_HI: u8) {}
472}
473
474#[allow(non_snake_case, non_camel_case_types)]
475pub struct some_type {
476 SOME_FIELD: u8,
477 SomeField: u16,
478}
479
480#[allow(non_upper_case_globals)]
481pub const some_const: u8 = 10;
482
483#[allow(non_upper_case_globals)]
484pub 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
3use hir::{ 3use hir::db::DefDatabase;
4 db::DefDatabase,
5 diagnostics::{Diagnostic, DiagnosticCode},
6 InFile,
7};
8use ide_assists::AssistResolveStrategy;
9use ide_db::{ 4use 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};
14use syntax::{ 9use syntax::{
15 ast::{self, ModuleItemOwner, NameOwner}, 10 ast::{self, ModuleItemOwner, NameOwner},
16 AstNode, SyntaxNodePtr, 11 AstNode, TextRange, TextSize,
17}; 12};
18use text_edit::TextEdit; 13use text_edit::TextEdit;
19 14
20use crate::{ 15use crate::{
21 diagnostics::{fix, fixes::DiagnosticWithFixes}, 16 diagnostics::{fix, DiagnosticsContext},
22 Assist, 17 Assist, Diagnostic,
23}; 18};
24 19
20#[derive(Debug)]
21pub(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)] 29pub(super) fn unlinked_file(ctx: &DiagnosticsContext, d: &UnlinkedFile) -> Diagnostic {
30pub(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
35impl Diagnostic for UnlinkedFile { 40fn 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
53impl 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
104fn make_fixes( 84fn make_fixes(
@@ -181,3 +161,144 @@ fn make_fixes(
181 ), 161 ),
182 ]) 162 ])
183} 163}
164
165#[cfg(test)]
166mod 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
176fn f() {}
177//- /foo.rs
178$0
179"#,
180 vec![
181 r#"
182mod foo;
183
184fn f() {}
185"#,
186 r#"
187pub mod foo;
188
189fn 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
203mod preexisting;
204
205mod preexisting2;
206
207struct S;
208
209mod preexisting_bottom;)
210//- /foo.rs
211$0
212"#,
213 r#"
214//! Comment on top
215
216mod preexisting;
217
218mod preexisting2;
219mod foo;
220
221struct S;
222
223mod 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#"
238mod foo;
239"#,
240 );
241 }
242
243 #[test]
244 fn unlinked_file_old_style_modrs() {
245 check_fix(
246 r#"
247//- /main.rs
248mod submod;
249//- /submod/mod.rs
250// in mod.rs
251//- /submod/foo.rs
252$0
253"#,
254 r#"
255// in mod.rs
256mod foo;
257"#,
258 );
259 }
260
261 #[test]
262 fn unlinked_file_new_style_mod() {
263 check_fix(
264 r#"
265//- /main.rs
266mod submod;
267//- /submod.rs
268//- /submod/foo.rs
269$0
270"#,
271 r#"
272mod 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)]
284mod 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))]
298mod foo;
299
300//- /foo.rs
301"#,
302 );
303 }
304}