aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/src/lib.rs43
-rw-r--r--lib/src/lints.rs1
-rw-r--r--lib/src/lints/legacy_let_syntax.rs55
-rw-r--r--lib/src/make.rs45
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;
4pub use lints::LINTS; 4pub use lints::LINTS;
5 5
6use rnix::{SyntaxElement, SyntaxKind, TextRange}; 6use rnix::{SyntaxElement, SyntaxKind, TextRange};
7use std::{default::Default, convert::Into}; 7use 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 {
51impl Diagnostic { 56impl 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
88pub trait Metadata { 101pub 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 @@
1use crate::{make, Lint, Metadata, Report, Rule, Suggestion};
2
3use if_chain::if_chain;
4use macros::lint;
5use 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)]
16struct ManualInherit;
17
18impl 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
48fn 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 @@
1use std::iter::IntoIterator; 1use std::{fmt::Write, iter::IntoIterator};
2 2
3use rnix::{SyntaxNode, types::{TypedNode, self, TokenWrapper}}; 3use rnix::{
4 types::{self, TokenWrapper, TypedNode},
5 SyntaxNode,
6};
4 7
5fn ast_from_text<N: TypedNode>(text: &str) -> N { 8fn 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
33pub fn inherit_from_stmt<'a>(from: SyntaxNode, nodes: impl IntoIterator<Item = &'a types::Ident>) -> types::Inherit { 40pub 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
52pub 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
71pub fn select(set: &SyntaxNode, index: &SyntaxNode) -> types::Select {
72 ast_from_text(&format!("{}.{}", set, index))
73}
74
75pub fn ident(text: &str) -> types::Ident {
76 ast_from_text(text)
77}