1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
//! Core traits for rule definitions and rule context.
//! As well as an internal prelude to make imports for rules easier.

#![allow(unused_variables, unused_imports)]

use crate::{Diagnostic, DiagnosticBuilder};
use codespan_reporting::diagnostic::Severity;
use dyn_clone::DynClone;
use rslint_parser::{SyntaxNode, SyntaxNodeExt, SyntaxToken};
use serde::{Deserialize, Serialize};
use std::borrow::Borrow;
use std::fmt::Debug;
use std::marker::{Send, Sync};
use std::ops::{Deref, DerefMut, Drop};
use std::rc::Rc;

/// The main type of rule run by the runner. The rule takes individual
/// nodes inside of a Concrete Syntax Tree and checks them.
/// It may also take individual syntax tokens.
/// Rule must be all be [`Send`] + [`Sync`], because rules are run in parallel.
///
/// # Rule Level Configuration
/// Rules do not know about the lint level they were configured for, the runner
/// runs the rules, then maps any error/warning diagnostics to their appropriate severity.
/// This saves on boilerplate code for getting the appropriate diagnostic builder type and config.
///
/// # Guidelines
/// This is a list of guidelines and tips you should generally follow when implementing a rule:
/// - Do not use text based equality, it is inaccurate, instead use [`lexical_eq`](SyntaxNodeExt::lexical_eq).
/// - Avoid using `text_range` on nodes, it is inaccurate because it may include whitespace, instead use [`trimmed_range`](SyntaxNodeExt::trimmed_range).
/// - Avoid using `text` on nodes for the same reason as the previous, use [`trimmed_text`](SyntaxNodeExt::trimmed_text).
/// - If you can offer better diagnostics and more context around a rule error, __always__ do it! It is a central goal
/// of the project to offer very helpful diagnostics.
/// - Do not be afraid to clone syntax nodes, ast nodes, and syntax tokens. They are all backed by an [`Rc`](std::rc::Rc) around Node data.
/// therefore they can be cheaply cloned (but if you can, have your functions take a reference since Rc cloning is not zero cost).
/// - Do not try to rely on the result of other rules, it is impossible because rules are run at the same time.
/// - Do not rely on file data of different files. There is a separate rule type for this.
/// - Do not unwrap pieces of an AST node (sometimes it is ok because they are guaranteed to be there), since that will cause panics
/// with error recovery.
/// - Do not use node or string coloring outside of diagnostic notes, it messes with termcolor and ends up looking horrible.
#[typetag::serde]
pub trait CstRule: Rule {
    /// Check an individual node in the syntax tree.
    /// You can use the `match_ast` macro to make matching a node to an ast node easier.
    /// The reason this uses nodes and not a visitor is because nodes are more flexible,
    /// converting them to an AST node has zero cost and you can easily traverse surrounding nodes.
    /// Defaults to doing nothing.
    ///
    /// The return type is `Option<()>` to allow usage of `?` on the properties of AST nodes which are all optional.
    #[inline]
    fn check_node(&self, node: &SyntaxNode, ctx: &mut RuleCtx) -> Option<()> {
        None
    }

    /// Check an individual token in the syntax tree.
    /// Defaults to doing nothing.
    #[inline]
    fn check_token(&self, token: &SyntaxToken, ctx: &mut RuleCtx) -> Option<()> {
        None
    }

    /// Check the root of the tree one time.
    /// This method is guaranteed to only be called once.
    /// The root's kind will be either `SCRIPT` or `MODULE`.
    /// Defaults to doing nothing.
    #[inline]
    fn check_root(&self, root: &SyntaxNode, ctx: &mut RuleCtx) -> Option<()> {
        None
    }
}

/// A generic trait which describes things common to a rule regardless on what they run on.
///
/// Each rule should have a `new` function for easy instantiation. We however do not require this
/// for the purposes of allowing more complex rules to instantiate themselves in a different way.
/// However the rules must be easily instantiated because of rule groups.
pub trait Rule: Debug + DynClone + Send + Sync {
    /// A unique, kebab-case name for the rule.
    fn name(&self) -> &'static str;
    /// The name of the group this rule belongs to.
    fn group(&self) -> &'static str;
}

dyn_clone::clone_trait_object!(Rule);
dyn_clone::clone_trait_object!(CstRule);

/// The level configured for a rule.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum RuleLevel {
    Warning,
    Error,
}

/// Context given to a rule when running it.
// This is passed by reference and not by Arc, which is very important,
// Arcs are very expensive to copy, and for 50 rules running on 50 files we will have a total of
// 2500 copies, which is non ideal at best.
#[derive(Debug, Clone)]
pub struct RuleCtx {
    /// The file id of the file being linted.
    pub file_id: usize,
    /// Whether the linter is run with the `--verbose` option.
    /// Which dictates whether the linter should include more (potentially spammy) context in diagnostics.
    pub verbose: bool,
    /// An empty vector of diagnostics which the rule adds to.
    pub diagnostics: Vec<Diagnostic>,
}

impl RuleCtx {
    /// Make a new diagnostic builder.
    pub fn err(&mut self, code: impl AsRef<str>, message: impl AsRef<str>) -> DiagnosticBuilder {
        DiagnosticBuilder::error(self.file_id, code.as_ref(), message.as_ref())
    }

    pub fn add_err(&mut self, diagnostic: impl Into<Diagnostic>) {
        self.diagnostics.push(diagnostic.into())
    }
}

/// The result of running a single rule on a syntax tree.
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct RuleResult {
    pub diagnostics: Vec<Diagnostic>,
}

impl RuleResult {
    /// Get the result of running this rule.
    pub fn outcome(&self) -> Outcome {
        Outcome::from(&self.diagnostics)
    }

    pub fn merge(self, other: RuleResult) -> RuleResult {
        RuleResult {
            diagnostics: [self.diagnostics, other.diagnostics].concat(),
        }
    }
}

/// The overall result of running a single rule or linting a file.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Outcome {
    /// Running the rule resulted in one or more errors.
    /// The rule result may have also included warnings or notes.
    Failure,
    /// Running the rule resulted in one or more warnings.
    /// May also include notes.
    Warning,
    /// Running the rule resulted in no errors or warnings.  
    /// May include note diagnostics (which are very rare).
    Success,
}

impl<T> From<T> for Outcome
where
    T: IntoIterator,
    T::Item: Borrow<Diagnostic>,
{
    fn from(diagnostics: T) -> Self {
        let mut outcome = Outcome::Success;
        for diagnostic in diagnostics {
            match diagnostic.borrow().severity {
                Severity::Error | Severity::Bug => outcome = Outcome::Failure,
                Severity::Warning if outcome != Outcome::Failure => outcome = Outcome::Warning,
                _ => {}
            }
        }
        outcome
    }
}

impl Outcome {
    pub fn merge(outcomes: impl IntoIterator<Item = impl Borrow<Outcome>>) -> Outcome {
        let mut overall = Outcome::Success;
        for outcome in outcomes {
            match outcome.borrow() {
                Outcome::Failure => overall = Outcome::Failure,
                Outcome::Warning if overall != Outcome::Failure => overall = Outcome::Warning,
                _ => {}
            }
        }
        overall
    }
}

/// A macro to easily generate rule boilerplate code.
///
/// ```ignore
/// declare_lint! {
///     /// A description of the rule here
///     /// This will be used as the doc for the rule struct
///     RuleName,
///     // The name of the group this rule belongs to.
///     groupname,
///     // Make sure this is kebab-case and unique.
///     "rule-name",
///     /// A description of the attribute here, used for config docs.
///     pub config_attr: u8,
///     pub another_attr: String
/// }
/// ```
///
/// # Rule name and docs
///
/// The macro's first argument is an identifier for the rule structure.
/// This should always be a PascalCase name. You will have to either derive Default for the struct
/// or implement it manually.
///
/// The macro also accepts any doc comments for the rule name. These comments
/// are then used by an xtask script to generate markdown files for user facing docs.
/// Each rule doc should include an `Incorrect Code Examples` header. It may also optionally
/// include a `Correct Code Examples`. Do not include a `Config` header, it is autogenerated
/// from config field docs.
///
/// # Config
///
/// After the rule code, the macro accepts fields for the struct. Any field which is
/// public will be used for config, you can however disable this by using `#[serde(skip)]`.
/// Every public (config) field should have a doc comment, the doc comments will be used for
/// user facing documentation. Therefore try to be non technical and non rust specific with the doc comments.
/// **All config fields will be renamed to camelCase**
///
///
/// This will generate a rule struct with `RuleName`,
/// and use the optional config attributes defined for the config of the rule.
/// You must make sure each config field is Deserializable.
#[macro_export]
macro_rules! declare_lint {
    (
        $(#[$outer:meta])*
        // The rule struct name
        $name:ident,
        $group:ident,
        // A unique kebab-case name for the rule
        $code:expr
        $(,
            // Any fields for the rule
            $(
                $(#[$inner:meta])*
                $visibility:vis $key:ident : $val:ty
            ),* $(,)?
        )?
    ) => {
        use $crate::Rule;
        use serde::{Deserialize, Serialize};

        $(#[$outer])*
        #[serde(rename_all = "camelCase")]
        #[derive(Debug, Clone, Deserialize, Serialize)]
        pub struct $name {
            $(
                $(
                    $(#[$inner])*
                    pub $key: $val
                ),
            *)?
        }

        impl $name {
            pub fn new() -> Self {
                Self::default()
            }
        }

        impl Rule for $name {
            fn name(&self) -> &'static str {
                $code
            }

            fn group(&self) -> &'static str {
                stringify!($group)
            }
        }
    };
}