Skip to content

Commit 62be62f

Browse files
authored
feat: ignore diagnostic in file (#313)
1 parent 78a4e03 commit 62be62f

File tree

3 files changed

+140
-55
lines changed

3 files changed

+140
-55
lines changed

README.md

+10
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,16 @@ function foo(): any {
166166
}
167167
```
168168

169+
You can also ignore certain diagnostics in the whole file
170+
171+
```ts
172+
// deno-lint-ignore-file no-explicit-any no-empty
173+
174+
function foo(): any {
175+
// ...
176+
}
177+
```
178+
169179
### Diagnostics
170180

171181
To ignore certain diagnostic `// deno-lint-ignore <codes...>` directive should be placed

src/lib.rs

+54
Original file line numberDiff line numberDiff line change
@@ -117,4 +117,58 @@ mod lint_tests {
117117

118118
assert_eq!(diagnostics.len(), 0);
119119
}
120+
121+
#[test]
122+
fn file_directive_with_code() {
123+
let diagnostics = lint(
124+
r#"
125+
// deno-lint-ignore-file no-explicit-any
126+
127+
function bar(p: any) {
128+
// pass
129+
}
130+
"#,
131+
false,
132+
false,
133+
);
134+
135+
assert_eq!(diagnostics.len(), 0);
136+
}
137+
138+
#[test]
139+
fn file_directive_with_code_unused() {
140+
let diagnostics = lint(
141+
r#"
142+
// deno-lint-ignore-file no-explicit-any no-empty
143+
144+
function bar(p: any) {
145+
// pass
146+
}
147+
"#,
148+
false,
149+
true,
150+
);
151+
152+
assert_eq!(diagnostics.len(), 1);
153+
assert_diagnostic(&diagnostics[0], "ban-unused-ignore", 2, 1);
154+
}
155+
156+
#[test]
157+
fn file_directive_with_code_higher_precedence() {
158+
let diagnostics = lint(
159+
r#"
160+
// deno-lint-ignore-file no-explicit-any
161+
162+
// deno-lint-ignore no-explicit-any
163+
function bar(p: any) {
164+
// pass
165+
}
166+
"#,
167+
false,
168+
true,
169+
);
170+
171+
assert_eq!(diagnostics.len(), 1);
172+
assert_diagnostic(&diagnostics[0], "ban-unused-ignore", 4, 1);
173+
}
120174
}

src/linter.rs

+76-55
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ pub struct IgnoreDirective {
7575
pub span: Span,
7676
pub codes: Vec<String>,
7777
pub used_codes: HashMap<String, bool>,
78+
pub is_global: bool,
7879
}
7980

8081
impl IgnoreDirective {
@@ -84,7 +85,10 @@ impl IgnoreDirective {
8485
&mut self,
8586
diagnostic: &LintDiagnostic,
8687
) -> bool {
87-
if self.position.line != diagnostic.range.start.line - 1 {
88+
// `is_global` means that diagnostic is ignored in whole file.
89+
if self.is_global {
90+
// pass
91+
} else if self.position.line != diagnostic.range.start.line - 1 {
8892
return false;
8993
}
9094

@@ -234,28 +238,6 @@ impl Linter {
234238
Ok(diagnostics)
235239
}
236240

237-
fn has_ignore_file_directive(
238-
&self,
239-
comments: &SingleThreadedComments,
240-
module: &swc_ecmascript::ast::Module,
241-
) -> bool {
242-
comments.with_leading(module.span.lo(), |module_leading_comments| {
243-
for comment in module_leading_comments.iter() {
244-
if comment.kind == CommentKind::Line {
245-
let text = comment.text.trim();
246-
if self
247-
.ignore_file_directives
248-
.iter()
249-
.any(|directive| directive == text)
250-
{
251-
return true;
252-
}
253-
}
254-
}
255-
false
256-
})
257-
}
258-
259241
fn filter_diagnostics(
260242
&self,
261243
context: Arc<Context>,
@@ -322,10 +304,34 @@ impl Linter {
322304
module: swc_ecmascript::ast::Module,
323305
comments: SingleThreadedComments,
324306
) -> Vec<LintDiagnostic> {
325-
if self.has_ignore_file_directive(&comments, &module) {
326-
return vec![];
327-
}
328307
let start = Instant::now();
308+
let file_ignore_directive = comments.with_leading(module.span.lo(), |c| {
309+
let directives = c
310+
.iter()
311+
.filter_map(|comment| {
312+
parse_ignore_comment(
313+
&self.ignore_file_directives,
314+
&*self.ast_parser.source_map,
315+
comment,
316+
true,
317+
)
318+
})
319+
.collect::<Vec<IgnoreDirective>>();
320+
321+
if directives.is_empty() {
322+
None
323+
} else {
324+
Some(directives[0].clone())
325+
}
326+
});
327+
328+
// If there's a file ignore directive that has no codes specified we must ignore
329+
// whole file and skip linting it.
330+
if let Some(ignore_directive) = &file_ignore_directive {
331+
if ignore_directive.codes.is_empty() {
332+
return vec![];
333+
}
334+
}
329335

330336
let (leading, trailing) = comments.take_all();
331337
let leading_coms = Rc::try_unwrap(leading)
@@ -337,13 +343,17 @@ impl Linter {
337343
.into_inner();
338344
let trailing = trailing_coms.into_iter().collect();
339345

340-
let ignore_directives = parse_ignore_directives(
346+
let mut ignore_directives = parse_ignore_directives(
341347
&self.ignore_diagnostic_directives,
342348
&self.ast_parser.source_map,
343349
&leading,
344350
&trailing,
345351
);
346352

353+
if let Some(ignore_directive) = file_ignore_directive {
354+
ignore_directives.insert(0, ignore_directive);
355+
}
356+
347357
let scope = Arc::new(analyze(&module));
348358
let control_flow = Arc::new(ControlFlow::analyze(&module));
349359

@@ -380,19 +390,25 @@ fn parse_ignore_directives(
380390

381391
leading_comments.values().for_each(|comments| {
382392
for comment in comments {
383-
if let Some(ignore) =
384-
parse_ignore_comment(&ignore_diagnostic_directives, source_map, comment)
385-
{
393+
if let Some(ignore) = parse_ignore_comment(
394+
&ignore_diagnostic_directives,
395+
source_map,
396+
comment,
397+
false,
398+
) {
386399
ignore_directives.push(ignore);
387400
}
388401
}
389402
});
390403

391404
trailing_comments.values().for_each(|comments| {
392405
for comment in comments {
393-
if let Some(ignore) =
394-
parse_ignore_comment(&ignore_diagnostic_directives, source_map, comment)
395-
{
406+
if let Some(ignore) = parse_ignore_comment(
407+
&ignore_diagnostic_directives,
408+
source_map,
409+
comment,
410+
false,
411+
) {
396412
ignore_directives.push(ignore);
397413
}
398414
}
@@ -407,6 +423,7 @@ fn parse_ignore_comment(
407423
ignore_diagnostic_directives: &[String],
408424
source_map: &SourceMap,
409425
comment: &Comment,
426+
is_global: bool,
410427
) -> Option<IgnoreDirective> {
411428
if comment.kind != CommentKind::Line {
412429
return None;
@@ -415,28 +432,32 @@ fn parse_ignore_comment(
415432
let comment_text = comment.text.trim();
416433

417434
for ignore_dir in ignore_diagnostic_directives {
418-
if comment_text.starts_with(ignore_dir) {
419-
let comment_text = comment_text.strip_prefix(ignore_dir).unwrap();
420-
let comment_text = IGNORE_COMMENT_CODE_RE.replace_all(comment_text, ",");
421-
let codes = comment_text
422-
.split(',')
423-
.filter(|code| !code.is_empty())
424-
.map(|code| String::from(code.trim()))
425-
.collect::<Vec<String>>();
426-
427-
let location = source_map.lookup_char_pos(comment.span.lo());
428-
429-
let mut used_codes = HashMap::new();
430-
codes.iter().for_each(|code| {
431-
used_codes.insert(code.to_string(), false);
432-
});
433-
434-
return Some(IgnoreDirective {
435-
position: location.into(),
436-
span: comment.span,
437-
codes,
438-
used_codes,
439-
});
435+
if let Some(prefix) = comment_text.split_whitespace().next() {
436+
if prefix == ignore_dir {
437+
let comment_text = comment_text.strip_prefix(ignore_dir).unwrap();
438+
let comment_text =
439+
IGNORE_COMMENT_CODE_RE.replace_all(comment_text, ",");
440+
let codes = comment_text
441+
.split(',')
442+
.filter(|code| !code.is_empty())
443+
.map(|code| String::from(code.trim()))
444+
.collect::<Vec<String>>();
445+
446+
let location = source_map.lookup_char_pos(comment.span.lo());
447+
448+
let mut used_codes = HashMap::new();
449+
codes.iter().for_each(|code| {
450+
used_codes.insert(code.to_string(), false);
451+
});
452+
453+
return Some(IgnoreDirective {
454+
position: location.into(),
455+
span: comment.span,
456+
codes,
457+
used_codes,
458+
is_global,
459+
});
460+
}
440461
}
441462
}
442463

0 commit comments

Comments
 (0)