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
//! Directives used to configure or ignore rules.
//! These take place of comments over nodes or comments at the top level.
//!
//! Directives can contain multiple commands separated by `-`. For example:
//!
//! ```text
//! // rslint-ignore for-direction, no-await-in-loop - deny no-empty -- because why not
//!   |      |                                     |  |            |    |             |
//!   |      +-------------------------------------+  +------------+    +-------------+
//!   |                      command                     command            comment   |
//!   +-------------------------------------------------------------------------------+
//!                                      Directive
//! ```

mod parser;

pub use self::parser::*;

use crate::{rule_tests, CstRule, CstRuleStore, Diagnostic, SyntaxNode};
use rslint_parser::util::*;

// TODO: More complex warnings, things like ignoring node directives because of file level directives

/// Apply file level directives to a store and add their respective diagnostics to the pool of diagnostics.
/// for file level ignores this will clear all the rules from the store.
///
/// This method furthermore issues more contextual warnings like disabling a rule after
/// the entire file has been disabled.
pub fn apply_top_level_directives(
    directives: &[Directive],
    store: &mut CstRuleStore,
    diagnostics: &mut Vec<Diagnostic>,
    file_id: usize,
) {
    let mut ignored = Vec::new();
    let mut cleared = None;

    for directive in directives {
        for command in &directive.commands {
            if command.top_level() {
                match command {
                    Command::IgnoreFile => {
                        store.rules.clear();
                        cleared = Some(directive.comment.token.text_range());
                    }
                    Command::IgnoreRulesFile(rules) => {
                        ignored.push(directive.comment.token.text_range());
                        store.rules.retain(|rule| {
                            !rules.iter().any(|allowed| allowed.name() == rule.name())
                        });
                    }
                    _ => unreachable!(),
                }
            }
        }
    }

    if let Some(range) = cleared {
        for ignored_range in ignored {
            let warn = Diagnostic::warning(
                file_id,
                "linter",
                "ignoring redundant rule ignore directive",
            )
            .secondary(range, "this directive ignores all rules")
            .primary(ignored_range, "this directive is ignored");

            diagnostics.push(warn);
        }
    }
}

pub fn apply_node_directives(
    directives: &[Directive],
    node: &SyntaxNode,
    store: &CstRuleStore,
) -> Option<CstRuleStore> {
    let comment = node.first_token().and_then(|t| t.comment())?;
    let directive = directives.iter().find(|dir| dir.comment == comment)?;
    let mut store = store.clone();

    for command in &directive.commands {
        match command {
            Command::IgnoreNode(_) => {
                store.rules.clear();
            }
            Command::IgnoreRules(rules, _) => {
                store
                    .rules
                    .retain(|rule| !rules.iter().any(|allowed| allowed.name() == rule.name()));
            }
            _ => {}
        }
    }
    Some(store)
}

pub fn skip_node(directives: &[Directive], node: &SyntaxNode, rule: &dyn CstRule) -> bool {
    if let Some(comment) = node.first_token().and_then(|t| t.comment()) {
        if let Some(directive) = directives.iter().find(|dir| dir.comment == comment) {
            for command in &directive.commands {
                match command {
                    Command::IgnoreNode(_) => {
                        return true;
                    }
                    Command::IgnoreRules(rules, _) => {
                        if rules.iter().any(|allowed| allowed.name() == rule.name()) {
                            return true;
                        }
                    }
                    _ => {}
                }
            }
        }
    }
    false
}

rule_tests! {
    crate::groups::errors::NoEmpty::default(),
    err: {
        "{}",
        "
        // rslint-ignore no-empty
        {}

        {}
        "
    },
    ok: {
        "
        // rslint-ignore no-empty

        {}
        ",
        "
        // rslint-ignore no-empty
        {}
        ",
        "
        // rslint-ignore 
        {}
        "
    }
}