diff options
-rw-r--r-- | lib/src/lib.rs | 43 | ||||
-rw-r--r-- | lib/src/lints.rs | 1 | ||||
-rw-r--r-- | lib/src/lints/legacy_let_syntax.rs | 55 | ||||
-rw-r--r-- | lib/src/make.rs | 45 |
4 files changed, 129 insertions, 15 deletions
diff --git a/lib/src/lib.rs b/lib/src/lib.rs index ab4a14a..f0f26dd 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs | |||
@@ -4,7 +4,7 @@ mod make; | |||
4 | pub use lints::LINTS; | 4 | pub use lints::LINTS; |
5 | 5 | ||
6 | use rnix::{SyntaxElement, SyntaxKind, TextRange}; | 6 | use rnix::{SyntaxElement, SyntaxKind, TextRange}; |
7 | use std::{default::Default, convert::Into}; | 7 | use std::{convert::Into, default::Default}; |
8 | 8 | ||
9 | /// Report generated by a lint | 9 | /// Report generated by a lint |
10 | #[derive(Debug, Default)] | 10 | #[derive(Debug, Default)] |
@@ -32,11 +32,16 @@ impl Report { | |||
32 | self | 32 | self |
33 | } | 33 | } |
34 | /// Add a diagnostic with a fix to this report | 34 | /// Add a diagnostic with a fix to this report |
35 | pub fn suggest<S: AsRef<str>>(mut self, at: TextRange, message: S, suggestion: Suggestion) -> Self { | 35 | pub fn suggest<S: AsRef<str>>( |
36 | self.diagnostics.push(Diagnostic::suggest(at, message, suggestion)); | 36 | mut self, |
37 | at: TextRange, | ||
38 | message: S, | ||
39 | suggestion: Suggestion, | ||
40 | ) -> Self { | ||
41 | self.diagnostics | ||
42 | .push(Diagnostic::suggest(at, message, suggestion)); | ||
37 | self | 43 | self |
38 | } | 44 | } |
39 | |||
40 | } | 45 | } |
41 | 46 | ||
42 | /// Mapping from a bytespan to an error message. | 47 | /// Mapping from a bytespan to an error message. |
@@ -51,11 +56,19 @@ pub struct Diagnostic { | |||
51 | impl Diagnostic { | 56 | impl Diagnostic { |
52 | /// Construct a diagnostic. | 57 | /// Construct a diagnostic. |
53 | pub fn new(at: TextRange, message: String) -> Self { | 58 | pub fn new(at: TextRange, message: String) -> Self { |
54 | Self { at, message, suggestion: None } | 59 | Self { |
60 | at, | ||
61 | message, | ||
62 | suggestion: None, | ||
63 | } | ||
55 | } | 64 | } |
56 | /// Construct a diagnostic with a fix. | 65 | /// Construct a diagnostic with a fix. |
57 | pub fn suggest<S: AsRef<str>>(at: TextRange, message: S, suggestion: Suggestion) -> Self { | 66 | pub fn suggest<S: AsRef<str>>(at: TextRange, message: S, suggestion: Suggestion) -> Self { |
58 | Self { at, message: message.as_ref().into(), suggestion: Some(suggestion) } | 67 | Self { |
68 | at, | ||
69 | message: message.as_ref().into(), | ||
70 | suggestion: Some(suggestion), | ||
71 | } | ||
59 | } | 72 | } |
60 | } | 73 | } |
61 | 74 | ||
@@ -72,7 +85,7 @@ impl Suggestion { | |||
72 | pub fn new<E: Into<SyntaxElement>>(at: TextRange, fix: E) -> Self { | 85 | pub fn new<E: Into<SyntaxElement>>(at: TextRange, fix: E) -> Self { |
73 | Self { | 86 | Self { |
74 | at, | 87 | at, |
75 | fix: fix.into() | 88 | fix: fix.into(), |
76 | } | 89 | } |
77 | } | 90 | } |
78 | } | 91 | } |
@@ -86,10 +99,18 @@ pub trait Rule { | |||
86 | /// Contains information about the lint itself. Do not implement manually, | 99 | /// Contains information about the lint itself. Do not implement manually, |
87 | /// look at the `lint` attribute macro instead for implementing rules | 100 | /// look at the `lint` attribute macro instead for implementing rules |
88 | pub trait Metadata { | 101 | pub trait Metadata { |
89 | fn name() -> &'static str where Self: Sized; | 102 | fn name() -> &'static str |
90 | fn note() -> &'static str where Self: Sized; | 103 | where |
91 | fn code() -> u32 where Self: Sized; | 104 | Self: Sized; |
92 | fn report() -> Report where Self: Sized; | 105 | fn note() -> &'static str |
106 | where | ||
107 | Self: Sized; | ||
108 | fn code() -> u32 | ||
109 | where | ||
110 | Self: Sized; | ||
111 | fn report() -> Report | ||
112 | where | ||
113 | Self: Sized; | ||
93 | fn match_with(&self, with: &SyntaxKind) -> bool; | 114 | fn match_with(&self, with: &SyntaxKind) -> bool; |
94 | fn match_kind(&self) -> SyntaxKind; | 115 | fn match_kind(&self) -> SyntaxKind; |
95 | } | 116 | } |
diff --git a/lib/src/lints.rs b/lib/src/lints.rs index 30c1678..98aa404 100644 --- a/lib/src/lints.rs +++ b/lib/src/lints.rs | |||
@@ -5,4 +5,5 @@ lint_map! { | |||
5 | empty_let_in, | 5 | empty_let_in, |
6 | manual_inherit, | 6 | manual_inherit, |
7 | manual_inherit_from, | 7 | manual_inherit_from, |
8 | legacy_let_syntax, | ||
8 | } | 9 | } |
diff --git a/lib/src/lints/legacy_let_syntax.rs b/lib/src/lints/legacy_let_syntax.rs new file mode 100644 index 0000000..c5588e2 --- /dev/null +++ b/lib/src/lints/legacy_let_syntax.rs | |||
@@ -0,0 +1,55 @@ | |||
1 | use crate::{make, Lint, Metadata, Report, Rule, Suggestion}; | ||
2 | |||
3 | use if_chain::if_chain; | ||
4 | use macros::lint; | ||
5 | use rnix::{ | ||
6 | types::{EntryHolder, Ident, Key, LegacyLet, TokenWrapper, TypedNode}, | ||
7 | NodeOrToken, SyntaxElement, SyntaxKind, | ||
8 | }; | ||
9 | |||
10 | #[lint( | ||
11 | name = "legacy let syntax", | ||
12 | note = "Using undocumented `let` syntax", | ||
13 | code = 5, | ||
14 | match_with = SyntaxKind::NODE_LEGACY_LET | ||
15 | )] | ||
16 | struct ManualInherit; | ||
17 | |||
18 | impl Rule for ManualInherit { | ||
19 | fn validate(&self, node: &SyntaxElement) -> Option<Report> { | ||
20 | if_chain! { | ||
21 | if let NodeOrToken::Node(node) = node; | ||
22 | if let Some(legacy_let) = LegacyLet::cast(node.clone()); | ||
23 | |||
24 | if legacy_let | ||
25 | .entries() | ||
26 | .find(|kv| matches!(kv.key(), Some(k) if key_is_ident(&k, "body"))) | ||
27 | .is_some(); | ||
28 | |||
29 | then { | ||
30 | let inherits = legacy_let.inherits(); | ||
31 | let entries = legacy_let.entries(); | ||
32 | let attrset = make::attrset(inherits, entries, true); | ||
33 | let parenthesized = make::parenthesize(&attrset.node()); | ||
34 | let selected = make::select(parenthesized.node(), &make::ident("body").node()); | ||
35 | |||
36 | let at = node.text_range(); | ||
37 | let message = "Prefer `rec` over undocumented `let` syntax"; | ||
38 | let replacement = selected.node().clone(); | ||
39 | |||
40 | Some(Self::report().suggest(at, message, Suggestion::new(at, replacement))) | ||
41 | } else { | ||
42 | None | ||
43 | } | ||
44 | } | ||
45 | } | ||
46 | } | ||
47 | |||
48 | fn key_is_ident(key_path: &Key, ident: &str) -> bool { | ||
49 | if let Some(key_node) = key_path.path().next() { | ||
50 | if let Some(key) = Ident::cast(key_node) { | ||
51 | return key.as_str() == ident; | ||
52 | } | ||
53 | } | ||
54 | false | ||
55 | } | ||
diff --git a/lib/src/make.rs b/lib/src/make.rs index cf371a2..2a6450c 100644 --- a/lib/src/make.rs +++ b/lib/src/make.rs | |||
@@ -1,13 +1,20 @@ | |||
1 | use std::iter::IntoIterator; | 1 | use std::{fmt::Write, iter::IntoIterator}; |
2 | 2 | ||
3 | use rnix::{SyntaxNode, types::{TypedNode, self, TokenWrapper}}; | 3 | use rnix::{ |
4 | types::{self, TokenWrapper, TypedNode}, | ||
5 | SyntaxNode, | ||
6 | }; | ||
4 | 7 | ||
5 | fn ast_from_text<N: TypedNode>(text: &str) -> N { | 8 | fn ast_from_text<N: TypedNode>(text: &str) -> N { |
6 | let parse = rnix::parse(text); | 9 | let parse = rnix::parse(text); |
7 | let node = match parse.node().descendants().find_map(N::cast) { | 10 | let node = match parse.node().descendants().find_map(N::cast) { |
8 | Some(it) => it, | 11 | Some(it) => it, |
9 | None => { | 12 | None => { |
10 | panic!("Failed to make ast node `{}` from text `{}`", std::any::type_name::<N>(), text) | 13 | panic!( |
14 | "Failed to make ast node `{}` from text `{}`", | ||
15 | std::any::type_name::<N>(), | ||
16 | text | ||
17 | ) | ||
11 | } | 18 | } |
12 | }; | 19 | }; |
13 | node | 20 | node |
@@ -30,7 +37,10 @@ pub fn inherit_stmt<'a>(nodes: impl IntoIterator<Item = &'a types::Ident>) -> ty | |||
30 | ast_from_text(&format!("{{ inherit {}; }}", inherited_idents)) | 37 | ast_from_text(&format!("{{ inherit {}; }}", inherited_idents)) |
31 | } | 38 | } |
32 | 39 | ||
33 | pub fn inherit_from_stmt<'a>(from: SyntaxNode, nodes: impl IntoIterator<Item = &'a types::Ident>) -> types::Inherit { | 40 | pub fn inherit_from_stmt<'a>( |
41 | from: SyntaxNode, | ||
42 | nodes: impl IntoIterator<Item = &'a types::Ident>, | ||
43 | ) -> types::Inherit { | ||
34 | let inherited_idents = nodes | 44 | let inherited_idents = nodes |
35 | .into_iter() | 45 | .into_iter() |
36 | .map(|i| i.as_str().to_owned()) | 46 | .map(|i| i.as_str().to_owned()) |
@@ -38,3 +48,30 @@ pub fn inherit_from_stmt<'a>(from: SyntaxNode, nodes: impl IntoIterator<Item = & | |||
38 | .join(" "); | 48 | .join(" "); |
39 | ast_from_text(&format!("{{ inherit ({}) {}; }}", from, inherited_idents)) | 49 | ast_from_text(&format!("{{ inherit ({}) {}; }}", from, inherited_idents)) |
40 | } | 50 | } |
51 | |||
52 | pub fn attrset( | ||
53 | inherits: impl IntoIterator<Item = types::Inherit>, | ||
54 | entries: impl IntoIterator<Item = types::KeyValue>, | ||
55 | recursive: bool, | ||
56 | ) -> types::AttrSet { | ||
57 | let mut buffer = String::new(); | ||
58 | |||
59 | writeln!(buffer, "{}{{", if recursive { "rec " } else { "" }).unwrap(); | ||
60 | for inherit in inherits.into_iter() { | ||
61 | writeln!(buffer, " {}", inherit.node().text()).unwrap(); | ||
62 | } | ||
63 | for entry in entries.into_iter() { | ||
64 | writeln!(buffer, " {}", entry.node().text()).unwrap(); | ||
65 | } | ||
66 | write!(buffer, "}}").unwrap(); | ||
67 | |||
68 | ast_from_text(&buffer) | ||
69 | } | ||
70 | |||
71 | pub fn select(set: &SyntaxNode, index: &SyntaxNode) -> types::Select { | ||
72 | ast_from_text(&format!("{}.{}", set, index)) | ||
73 | } | ||
74 | |||
75 | pub fn ident(text: &str) -> types::Ident { | ||
76 | ast_from_text(text) | ||
77 | } | ||