aboutsummaryrefslogtreecommitdiff
path: root/crates/ide/src/diagnostics.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide/src/diagnostics.rs')
-rw-r--r--crates/ide/src/diagnostics.rs530
1 files changed, 24 insertions, 506 deletions
diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs
index 814e64ae4..7978c1fc2 100644
--- a/crates/ide/src/diagnostics.rs
+++ b/crates/ide/src/diagnostics.rs
@@ -6,6 +6,7 @@
6 6
7mod break_outside_of_loop; 7mod break_outside_of_loop;
8mod inactive_code; 8mod inactive_code;
9mod incorrect_case;
9mod macro_error; 10mod macro_error;
10mod mismatched_arg_count; 11mod mismatched_arg_count;
11mod missing_fields; 12mod missing_fields;
@@ -15,20 +16,19 @@ mod no_such_field;
15mod remove_this_semicolon; 16mod remove_this_semicolon;
16mod replace_filter_map_next_with_find_map; 17mod replace_filter_map_next_with_find_map;
17mod unimplemented_builtin_macro; 18mod unimplemented_builtin_macro;
19mod unlinked_file;
18mod unresolved_extern_crate; 20mod unresolved_extern_crate;
19mod unresolved_import; 21mod unresolved_import;
20mod unresolved_macro_call; 22mod unresolved_macro_call;
21mod unresolved_module; 23mod unresolved_module;
22mod unresolved_proc_macro; 24mod unresolved_proc_macro;
23 25
24mod fixes;
25mod field_shorthand; 26mod field_shorthand;
26mod unlinked_file;
27 27
28use std::cell::RefCell; 28use std::cell::RefCell;
29 29
30use hir::{ 30use hir::{
31 diagnostics::{AnyDiagnostic, Diagnostic as _, DiagnosticCode, DiagnosticSinkBuilder}, 31 diagnostics::{AnyDiagnostic, DiagnosticCode, DiagnosticSinkBuilder},
32 Semantics, 32 Semantics,
33}; 33};
34use ide_assists::AssistResolveStrategy; 34use ide_assists::AssistResolveStrategy;
@@ -37,15 +37,13 @@ use itertools::Itertools;
37use rustc_hash::FxHashSet; 37use rustc_hash::FxHashSet;
38use syntax::{ 38use syntax::{
39 ast::{self, AstNode}, 39 ast::{self, AstNode},
40 SyntaxNode, SyntaxNodePtr, TextRange, TextSize, 40 SyntaxNode, TextRange,
41}; 41};
42use text_edit::TextEdit; 42use text_edit::TextEdit;
43use unlinked_file::UnlinkedFile; 43use unlinked_file::UnlinkedFile;
44 44
45use crate::{Assist, AssistId, AssistKind, FileId, Label, SourceChange}; 45use crate::{Assist, AssistId, AssistKind, FileId, Label, SourceChange};
46 46
47use self::fixes::DiagnosticWithFixes;
48
49#[derive(Debug)] 47#[derive(Debug)]
50pub struct Diagnostic { 48pub struct Diagnostic {
51 // pub name: Option<String>, 49 // pub name: Option<String>,
@@ -135,7 +133,6 @@ pub struct DiagnosticsConfig {
135struct DiagnosticsContext<'a> { 133struct DiagnosticsContext<'a> {
136 config: &'a DiagnosticsConfig, 134 config: &'a DiagnosticsConfig,
137 sema: Semantics<'a, RootDatabase>, 135 sema: Semantics<'a, RootDatabase>,
138 #[allow(unused)]
139 resolve: &'a AssistResolveStrategy, 136 resolve: &'a AssistResolveStrategy,
140} 137}
141 138
@@ -165,22 +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::<hir::diagnostics::IncorrectCase, _>(|d| {
169 res.borrow_mut().push(warning_with_fix(d, &sema, resolve));
170 })
171 .on::<UnlinkedFile, _>(|d| {
172 // Limit diagnostic to the first few characters in the file. This matches how VS Code
173 // renders it with the full span, but on other editors, and is less invasive.
174 let range = sema.diagnostics_display_range(d.display_source()).range;
175 let range = range.intersect(TextRange::up_to(TextSize::of("..."))).unwrap_or(range);
176
177 // Override severity and mark as unused.
178 res.borrow_mut().push(
179 Diagnostic::hint(range, d.message())
180 .with_fixes(d.fixes(&sema, resolve))
181 .with_code(Some(d.code())),
182 );
183 })
184 // Only collect experimental diagnostics when they're enabled. 165 // Only collect experimental diagnostics when they're enabled.
185 .filter(|diag| !(diag.is_experimental() && config.disable_experimental)) 166 .filter(|diag| !(diag.is_experimental() && config.disable_experimental))
186 .filter(|diag| !config.disabled.contains(diag.code().as_str())); 167 .filter(|diag| !config.disabled.contains(diag.code().as_str()));
@@ -200,11 +181,9 @@ pub(crate) fn diagnostics(
200 181
201 let mut diags = Vec::new(); 182 let mut diags = Vec::new();
202 let internal_diagnostics = cfg!(test); 183 let internal_diagnostics = cfg!(test);
203 match sema.to_module_def(file_id) { 184 let module = sema.to_module_def(file_id);
204 Some(m) => diags = m.diagnostics(db, &mut sink, internal_diagnostics), 185 if let Some(m) = module {
205 None => { 186 diags = m.diagnostics(db, &mut sink, internal_diagnostics)
206 sink.push(UnlinkedFile { file_id, node: SyntaxNodePtr::new(parse.tree().syntax()) });
207 }
208 } 187 }
209 188
210 drop(sink); 189 drop(sink);
@@ -212,10 +191,17 @@ pub(crate) fn diagnostics(
212 let mut res = res.into_inner(); 191 let mut res = res.into_inner();
213 192
214 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
215 for diag in diags { 200 for diag in diags {
216 #[rustfmt::skip] 201 #[rustfmt::skip]
217 let d = match diag { 202 let d = match diag {
218 AnyDiagnostic::BreakOutsideOfLoop(d) => break_outside_of_loop::break_outside_of_loop(&ctx, &d), 203 AnyDiagnostic::BreakOutsideOfLoop(d) => break_outside_of_loop::break_outside_of_loop(&ctx, &d),
204 AnyDiagnostic::IncorrectCase(d) => incorrect_case::incorrect_case(&ctx, &d),
219 AnyDiagnostic::MacroError(d) => macro_error::macro_error(&ctx, &d), 205 AnyDiagnostic::MacroError(d) => macro_error::macro_error(&ctx, &d),
220 AnyDiagnostic::MismatchedArgCount(d) => mismatched_arg_count::mismatched_arg_count(&ctx, &d), 206 AnyDiagnostic::MismatchedArgCount(d) => mismatched_arg_count::mismatched_arg_count(&ctx, &d),
221 AnyDiagnostic::MissingFields(d) => missing_fields::missing_fields(&ctx, &d), 207 AnyDiagnostic::MissingFields(d) => missing_fields::missing_fields(&ctx, &d),
@@ -236,30 +222,24 @@ pub(crate) fn diagnostics(
236 None => continue, 222 None => continue,
237 } 223 }
238 }; 224 };
225 res.push(d)
226 }
227
228 res.retain(|d| {
239 if let Some(code) = d.code { 229 if let Some(code) = d.code {
240 if ctx.config.disabled.contains(code.as_str()) { 230 if ctx.config.disabled.contains(code.as_str()) {
241 continue; 231 return false;
242 } 232 }
243 } 233 }
244 if ctx.config.disable_experimental && d.experimental { 234 if ctx.config.disable_experimental && d.experimental {
245 continue; 235 return false;
246 } 236 }
247 res.push(d) 237 true
248 } 238 });
249 239
250 res 240 res
251} 241}
252 242
253fn warning_with_fix<D: DiagnosticWithFixes>(
254 d: &D,
255 sema: &Semantics<RootDatabase>,
256 resolve: &AssistResolveStrategy,
257) -> Diagnostic {
258 Diagnostic::hint(sema.diagnostics_display_range(d.display_source()).range, d.message())
259 .with_fixes(d.fixes(sema, resolve))
260 .with_code(Some(d.code()))
261}
262
263fn check_unnecessary_braces_in_use_statement( 243fn check_unnecessary_braces_in_use_statement(
264 acc: &mut Vec<Diagnostic>, 244 acc: &mut Vec<Diagnostic>,
265 file_id: FileId, 245 file_id: FileId,
@@ -390,8 +370,9 @@ mod tests {
390 file_position.offset 370 file_position.offset
391 ); 371 );
392 } 372 }
373
393 /// Checks that there's a diagnostic *without* fix at `$0`. 374 /// Checks that there's a diagnostic *without* fix at `$0`.
394 fn check_no_fix(ra_fixture: &str) { 375 pub(crate) fn check_no_fix(ra_fixture: &str) {
395 let (analysis, file_position) = fixture::position(ra_fixture); 376 let (analysis, file_position) = fixture::position(ra_fixture);
396 let diagnostic = analysis 377 let diagnostic = analysis
397 .diagnostics( 378 .diagnostics(
@@ -536,142 +517,6 @@ mod a {
536 } 517 }
537 518
538 #[test] 519 #[test]
539 fn unlinked_file_prepend_first_item() {
540 cov_mark::check!(unlinked_file_prepend_before_first_item);
541 // Only tests the first one for `pub mod` since the rest are the same
542 check_fixes(
543 r#"
544//- /main.rs
545fn f() {}
546//- /foo.rs
547$0
548"#,
549 vec![
550 r#"
551mod foo;
552
553fn f() {}
554"#,
555 r#"
556pub mod foo;
557
558fn f() {}
559"#,
560 ],
561 );
562 }
563
564 #[test]
565 fn unlinked_file_append_mod() {
566 cov_mark::check!(unlinked_file_append_to_existing_mods);
567 check_fix(
568 r#"
569//- /main.rs
570//! Comment on top
571
572mod preexisting;
573
574mod preexisting2;
575
576struct S;
577
578mod preexisting_bottom;)
579//- /foo.rs
580$0
581"#,
582 r#"
583//! Comment on top
584
585mod preexisting;
586
587mod preexisting2;
588mod foo;
589
590struct S;
591
592mod preexisting_bottom;)
593"#,
594 );
595 }
596
597 #[test]
598 fn unlinked_file_insert_in_empty_file() {
599 cov_mark::check!(unlinked_file_empty_file);
600 check_fix(
601 r#"
602//- /main.rs
603//- /foo.rs
604$0
605"#,
606 r#"
607mod foo;
608"#,
609 );
610 }
611
612 #[test]
613 fn unlinked_file_old_style_modrs() {
614 check_fix(
615 r#"
616//- /main.rs
617mod submod;
618//- /submod/mod.rs
619// in mod.rs
620//- /submod/foo.rs
621$0
622"#,
623 r#"
624// in mod.rs
625mod foo;
626"#,
627 );
628 }
629
630 #[test]
631 fn unlinked_file_new_style_mod() {
632 check_fix(
633 r#"
634//- /main.rs
635mod submod;
636//- /submod.rs
637//- /submod/foo.rs
638$0
639"#,
640 r#"
641mod foo;
642"#,
643 );
644 }
645
646 #[test]
647 fn unlinked_file_with_cfg_off() {
648 cov_mark::check!(unlinked_file_skip_fix_when_mod_already_exists);
649 check_no_fix(
650 r#"
651//- /main.rs
652#[cfg(never)]
653mod foo;
654
655//- /foo.rs
656$0
657"#,
658 );
659 }
660
661 #[test]
662 fn unlinked_file_with_cfg_on() {
663 check_diagnostics(
664 r#"
665//- /main.rs
666#[cfg(not(never))]
667mod foo;
668
669//- /foo.rs
670"#,
671 );
672 }
673
674 #[test]
675 fn import_extern_crate_clash_with_inner_item() { 520 fn import_extern_crate_clash_with_inner_item() {
676 // 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.
677 522
@@ -1607,330 +1452,3 @@ fn main() {
1607 } 1452 }
1608 } 1453 }
1609} 1454}
1610
1611#[cfg(test)]
1612mod decl_check_tests {
1613 use crate::diagnostics::tests::check_diagnostics;
1614
1615 #[test]
1616 fn incorrect_function_name() {
1617 check_diagnostics(
1618 r#"
1619fn NonSnakeCaseName() {}
1620// ^^^^^^^^^^^^^^^^ Function `NonSnakeCaseName` should have snake_case name, e.g. `non_snake_case_name`
1621"#,
1622 );
1623 }
1624
1625 #[test]
1626 fn incorrect_function_params() {
1627 check_diagnostics(
1628 r#"
1629fn foo(SomeParam: u8) {}
1630 // ^^^^^^^^^ Parameter `SomeParam` should have snake_case name, e.g. `some_param`
1631
1632fn foo2(ok_param: &str, CAPS_PARAM: u8) {}
1633 // ^^^^^^^^^^ Parameter `CAPS_PARAM` should have snake_case name, e.g. `caps_param`
1634"#,
1635 );
1636 }
1637
1638 #[test]
1639 fn incorrect_variable_names() {
1640 check_diagnostics(
1641 r#"
1642fn foo() {
1643 let SOME_VALUE = 10;
1644 // ^^^^^^^^^^ Variable `SOME_VALUE` should have snake_case name, e.g. `some_value`
1645 let AnotherValue = 20;
1646 // ^^^^^^^^^^^^ Variable `AnotherValue` should have snake_case name, e.g. `another_value`
1647}
1648"#,
1649 );
1650 }
1651
1652 #[test]
1653 fn incorrect_struct_names() {
1654 check_diagnostics(
1655 r#"
1656struct non_camel_case_name {}
1657 // ^^^^^^^^^^^^^^^^^^^ Structure `non_camel_case_name` should have CamelCase name, e.g. `NonCamelCaseName`
1658
1659struct SCREAMING_CASE {}
1660 // ^^^^^^^^^^^^^^ Structure `SCREAMING_CASE` should have CamelCase name, e.g. `ScreamingCase`
1661"#,
1662 );
1663 }
1664
1665 #[test]
1666 fn no_diagnostic_for_camel_cased_acronyms_in_struct_name() {
1667 check_diagnostics(
1668 r#"
1669struct AABB {}
1670"#,
1671 );
1672 }
1673
1674 #[test]
1675 fn incorrect_struct_field() {
1676 check_diagnostics(
1677 r#"
1678struct SomeStruct { SomeField: u8 }
1679 // ^^^^^^^^^ Field `SomeField` should have snake_case name, e.g. `some_field`
1680"#,
1681 );
1682 }
1683
1684 #[test]
1685 fn incorrect_enum_names() {
1686 check_diagnostics(
1687 r#"
1688enum some_enum { Val(u8) }
1689 // ^^^^^^^^^ Enum `some_enum` should have CamelCase name, e.g. `SomeEnum`
1690
1691enum SOME_ENUM {}
1692 // ^^^^^^^^^ Enum `SOME_ENUM` should have CamelCase name, e.g. `SomeEnum`
1693"#,
1694 );
1695 }
1696
1697 #[test]
1698 fn no_diagnostic_for_camel_cased_acronyms_in_enum_name() {
1699 check_diagnostics(
1700 r#"
1701enum AABB {}
1702"#,
1703 );
1704 }
1705
1706 #[test]
1707 fn incorrect_enum_variant_name() {
1708 check_diagnostics(
1709 r#"
1710enum SomeEnum { SOME_VARIANT(u8) }
1711 // ^^^^^^^^^^^^ Variant `SOME_VARIANT` should have CamelCase name, e.g. `SomeVariant`
1712"#,
1713 );
1714 }
1715
1716 #[test]
1717 fn incorrect_const_name() {
1718 check_diagnostics(
1719 r#"
1720const some_weird_const: u8 = 10;
1721 // ^^^^^^^^^^^^^^^^ Constant `some_weird_const` should have UPPER_SNAKE_CASE name, e.g. `SOME_WEIRD_CONST`
1722"#,
1723 );
1724 }
1725
1726 #[test]
1727 fn incorrect_static_name() {
1728 check_diagnostics(
1729 r#"
1730static some_weird_const: u8 = 10;
1731 // ^^^^^^^^^^^^^^^^ Static variable `some_weird_const` should have UPPER_SNAKE_CASE name, e.g. `SOME_WEIRD_CONST`
1732"#,
1733 );
1734 }
1735
1736 #[test]
1737 fn fn_inside_impl_struct() {
1738 check_diagnostics(
1739 r#"
1740struct someStruct;
1741 // ^^^^^^^^^^ Structure `someStruct` should have CamelCase name, e.g. `SomeStruct`
1742
1743impl someStruct {
1744 fn SomeFunc(&self) {
1745 // ^^^^^^^^ Function `SomeFunc` should have snake_case name, e.g. `some_func`
1746 let WHY_VAR_IS_CAPS = 10;
1747 // ^^^^^^^^^^^^^^^ Variable `WHY_VAR_IS_CAPS` should have snake_case name, e.g. `why_var_is_caps`
1748 }
1749}
1750"#,
1751 );
1752 }
1753
1754 #[test]
1755 fn no_diagnostic_for_enum_varinats() {
1756 check_diagnostics(
1757 r#"
1758enum Option { Some, None }
1759
1760fn main() {
1761 match Option::None {
1762 None => (),
1763 Some => (),
1764 }
1765}
1766"#,
1767 );
1768 }
1769
1770 #[test]
1771 fn non_let_bind() {
1772 check_diagnostics(
1773 r#"
1774enum Option { Some, None }
1775
1776fn main() {
1777 match Option::None {
1778 SOME_VAR @ None => (),
1779 // ^^^^^^^^ Variable `SOME_VAR` should have snake_case name, e.g. `some_var`
1780 Some => (),
1781 }
1782}
1783"#,
1784 );
1785 }
1786
1787 #[test]
1788 fn allow_attributes() {
1789 check_diagnostics(
1790 r#"
1791#[allow(non_snake_case)]
1792fn NonSnakeCaseName(SOME_VAR: u8) -> u8{
1793 // cov_flags generated output from elsewhere in this file
1794 extern "C" {
1795 #[no_mangle]
1796 static lower_case: u8;
1797 }
1798
1799 let OtherVar = SOME_VAR + 1;
1800 OtherVar
1801}
1802
1803#[allow(nonstandard_style)]
1804mod CheckNonstandardStyle {
1805 fn HiImABadFnName() {}
1806}
1807
1808#[allow(bad_style)]
1809mod CheckBadStyle {
1810 fn HiImABadFnName() {}
1811}
1812
1813mod F {
1814 #![allow(non_snake_case)]
1815 fn CheckItWorksWithModAttr(BAD_NAME_HI: u8) {}
1816}
1817
1818#[allow(non_snake_case, non_camel_case_types)]
1819pub struct some_type {
1820 SOME_FIELD: u8,
1821 SomeField: u16,
1822}
1823
1824#[allow(non_upper_case_globals)]
1825pub const some_const: u8 = 10;
1826
1827#[allow(non_upper_case_globals)]
1828pub static SomeStatic: u8 = 10;
1829 "#,
1830 );
1831 }
1832
1833 #[test]
1834 fn allow_attributes_crate_attr() {
1835 check_diagnostics(
1836 r#"
1837#![allow(non_snake_case)]
1838
1839mod F {
1840 fn CheckItWorksWithCrateAttr(BAD_NAME_HI: u8) {}
1841}
1842 "#,
1843 );
1844 }
1845
1846 #[test]
1847 #[ignore]
1848 fn bug_trait_inside_fn() {
1849 // FIXME:
1850 // This is broken, and in fact, should not even be looked at by this
1851 // lint in the first place. There's weird stuff going on in the
1852 // collection phase.
1853 // It's currently being brought in by:
1854 // * validate_func on `a` recursing into modules
1855 // * then it finds the trait and then the function while iterating
1856 // through modules
1857 // * then validate_func is called on Dirty
1858 // * ... which then proceeds to look at some unknown module taking no
1859 // attrs from either the impl or the fn a, and then finally to the root
1860 // module
1861 //
1862 // It should find the attribute on the trait, but it *doesn't even see
1863 // the trait* as far as I can tell.
1864
1865 check_diagnostics(
1866 r#"
1867trait T { fn a(); }
1868struct U {}
1869impl T for U {
1870 fn a() {
1871 // this comes out of bitflags, mostly
1872 #[allow(non_snake_case)]
1873 trait __BitFlags {
1874 const HiImAlsoBad: u8 = 2;
1875 #[inline]
1876 fn Dirty(&self) -> bool {
1877 false
1878 }
1879 }
1880
1881 }
1882}
1883 "#,
1884 );
1885 }
1886
1887 #[test]
1888 #[ignore]
1889 fn bug_traits_arent_checked() {
1890 // FIXME: Traits and functions in traits aren't currently checked by
1891 // r-a, even though rustc will complain about them.
1892 check_diagnostics(
1893 r#"
1894trait BAD_TRAIT {
1895 // ^^^^^^^^^ Trait `BAD_TRAIT` should have CamelCase name, e.g. `BadTrait`
1896 fn BAD_FUNCTION();
1897 // ^^^^^^^^^^^^ Function `BAD_FUNCTION` should have snake_case name, e.g. `bad_function`
1898 fn BadFunction();
1899 // ^^^^^^^^^^^^ Function `BadFunction` should have snake_case name, e.g. `bad_function`
1900}
1901 "#,
1902 );
1903 }
1904
1905 #[test]
1906 fn ignores_extern_items() {
1907 cov_mark::check!(extern_func_incorrect_case_ignored);
1908 cov_mark::check!(extern_static_incorrect_case_ignored);
1909 check_diagnostics(
1910 r#"
1911extern {
1912 fn NonSnakeCaseName(SOME_VAR: u8) -> u8;
1913 pub static SomeStatic: u8 = 10;
1914}
1915 "#,
1916 );
1917 }
1918
1919 #[test]
1920 fn infinite_loop_inner_items() {
1921 check_diagnostics(
1922 r#"
1923fn qualify() {
1924 mod foo {
1925 use super::*;
1926 }
1927}
1928 "#,
1929 )
1930 }
1931
1932 #[test] // Issue #8809.
1933 fn parenthesized_parameter() {
1934 check_diagnostics(r#"fn f((O): _) {}"#)
1935 }
1936}