rslint_core/directives/
mod.rs1pub(self) mod lexer;
16
17mod commands;
18mod parser;
19
20pub use self::commands::*;
21pub use self::parser::*;
22
23use crate::{rule_tests, CstRule, CstRuleStore, Diagnostic, SyntaxNode};
24use rslint_lexer::SyntaxKind;
25use rslint_parser::{util::*, SmolStr, TextRange, TextSize};
26
27#[derive(Debug, Clone)]
30pub enum ComponentKind {
31 Rule(Box<dyn CstRule>),
36 CommandName(SmolStr),
38 Number(u64),
42 Literal(&'static str),
46 Repetition(Vec<Component>),
48}
49
50impl ComponentKind {
51 pub fn documentation(&self) -> Option<&'static str> {
53 match self {
54 ComponentKind::Rule(rule) => Some(rule.docs()),
55 ComponentKind::CommandName(name) => match name.as_ref() {
56 "ignore" => Some(
57 "`ignore` will ignore all rules, or any given rules in some range or node.",
58 ),
59 _ => None,
60 },
61 _ => None,
62 }
63 }
64}
65
66impl ComponentKind {
67 pub fn rule(&self) -> Option<Box<dyn CstRule>> {
68 match self {
69 ComponentKind::Rule(rule) => Some(rule.clone()),
70 _ => None,
71 }
72 }
73
74 pub fn command_name(&self) -> Option<&str> {
75 match self {
76 ComponentKind::CommandName(name) => Some(name.as_str()),
77 _ => None,
78 }
79 }
80
81 pub fn literal(&self) -> Option<&str> {
82 match self {
83 ComponentKind::Literal(val) => Some(*val),
84 _ => None,
85 }
86 }
87
88 pub fn number(&self) -> Option<u64> {
89 match self {
90 ComponentKind::Number(val) => Some(*val),
91 _ => None,
92 }
93 }
94
95 pub fn repetition(&self) -> Option<&[Component]> {
96 match self {
97 ComponentKind::Repetition(components) => Some(components.as_slice()),
98 _ => None,
99 }
100 }
101}
102
103#[derive(Debug, Clone)]
106pub struct Component {
107 pub kind: ComponentKind,
108 pub range: TextRange,
109}
110
111#[derive(Debug, Clone)]
132pub enum Instruction {
133 RuleName,
134 Number,
135
136 CommandName(&'static str),
137 Literal(&'static str),
138 Optional(Vec<Instruction>),
139 Repetition(Box<Instruction>, SyntaxKind),
140 Either(Box<Instruction>, Box<Instruction>),
141}
142
143#[derive(Debug, Clone)]
145pub struct Directive {
146 pub line: usize,
148 pub comment: Comment,
149 pub components: Vec<Component>,
150 pub command: Option<Command>,
153}
154
155impl Directive {
156 pub fn component_at(&self, idx: TextSize) -> Option<&Component> {
158 self.components
159 .iter()
160 .find(|c| c.range.contains(idx))
161 .and_then(|component| {
162 if let ComponentKind::Repetition(components) = &component.kind {
163 components.iter().find(|c| c.range.contains(idx))
164 } else {
165 Some(component)
166 }
167 })
168 }
169}
170
171pub fn apply_top_level_directives(
177 directives: &[Directive],
178 store: &mut CstRuleStore,
179 diagnostics: &mut Vec<DirectiveError>,
180 file_id: usize,
181) {
182 let mut ignored = vec![];
185 let mut cleared = None;
186
187 for directive in directives {
188 match &directive.command {
189 Some(Command::IgnoreFile) => {
190 store.rules.clear();
191 cleared = Some(directive.comment.token.text_range());
192 }
193 Some(Command::IgnoreFileRules(rules)) => {
194 ignored.push(directive.comment.token.text_range());
195 store
196 .rules
197 .retain(|rule| !rules.iter().any(|allowed| allowed.name() == rule.name()));
198 }
199 _ => {}
200 }
201 }
202
203 if let Some(range) = cleared {
204 for ignored_range in ignored {
205 let warn = Diagnostic::warning(
206 file_id,
207 "linter",
208 "ignoring redundant rule ignore directive",
209 )
210 .secondary(range, "this directive ignores all rules")
211 .primary(ignored_range, "this directive is ignored")
212 .unnecessary();
213
214 diagnostics.push(DirectiveError::new(warn, DirectiveErrorKind::Other));
215 }
216 }
217}
218
219pub fn apply_node_directives(
220 directives: &[Directive],
221 node: &SyntaxNode,
222 store: &CstRuleStore,
223) -> Option<CstRuleStore> {
224 let comment = node.first_token().and_then(|t| t.comment())?;
225 let directive = directives.iter().find(|dir| dir.comment == comment)?;
226 let mut store = store.clone();
227
228 match &directive.command {
229 Some(Command::IgnoreNode(_)) => {
230 store.rules.clear();
231 }
232 Some(Command::IgnoreNodeRules(_, rules)) => {
233 store
234 .rules
235 .retain(|rule| !rules.iter().any(|allowed| allowed.name() == rule.name()));
236 }
237 _ => {}
238 }
239 Some(store)
240}
241
242pub fn skip_node(directives: &[Directive], node: &SyntaxNode, rule: &dyn CstRule) -> bool {
243 if let Some(comment) = node.first_token().and_then(|t| t.comment()) {
244 if let Some(directive) = directives.iter().find(|dir| dir.comment == comment) {
245 match &directive.command {
246 Some(Command::IgnoreNode(_)) => {
247 return true;
248 }
249 Some(Command::IgnoreNodeRules(_, rules)) => {
250 if rules.iter().any(|allowed| allowed.name() == rule.name()) {
251 return true;
252 }
253 }
254 _ => {}
255 }
256 }
257 }
258 false
259}
260
261rule_tests! {
262 crate::groups::errors::NoEmpty::default(),
263 err: {
264 "{}",
265 "
266 // rslint-ignore no-empty
267 {}
268
269 {}
270 "
271 },
272 ok: {
273 "
274 // rslint-ignore no-empty
275
276 {}
277 ",
278 "
279 // rslint-ignore no-empty
280 {}
281 ",
282 "
283 // rslint-ignore
284 {}
285 "
286 }
287}