Skip to main content

deno_lint/
rules.rs

1// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
2
3use crate::context::Context;
4use crate::tags;
5use crate::tags::Tags;
6use crate::Program;
7use crate::ProgramRef;
8use std::cmp::Ordering;
9use std::collections::HashSet;
10
11pub mod adjacent_overload_signatures;
12pub mod ban_ts_comment;
13pub mod ban_types;
14pub mod ban_unknown_rule_code;
15pub mod ban_untagged_ignore;
16pub mod ban_untagged_todo;
17pub mod ban_unused_ignore;
18pub mod camelcase;
19pub mod constructor_super;
20pub mod default_param_last;
21pub mod eqeqeq;
22pub mod explicit_function_return_type;
23pub mod explicit_module_boundary_types;
24pub mod for_direction;
25pub mod fresh_handler_export;
26pub mod fresh_server_event_handlers;
27pub mod getter_return;
28pub mod guard_for_in;
29pub mod jsx_boolean_value;
30pub mod jsx_button_has_type;
31pub mod jsx_curly_braces;
32pub mod jsx_key;
33pub mod jsx_no_children_prop;
34pub mod jsx_no_comment_text_nodes;
35pub mod jsx_no_duplicate_props;
36pub mod jsx_no_unescaped_entities;
37pub mod jsx_no_useless_fragment;
38pub mod jsx_props_no_spread_multi;
39pub mod jsx_void_dom_elements_no_children;
40pub mod no_array_constructor;
41pub mod no_async_promise_executor;
42pub mod no_await_in_loop;
43pub mod no_await_in_sync_fn;
44pub mod no_boolean_literal_for_arguments;
45pub mod no_case_declarations;
46pub mod no_class_assign;
47pub mod no_compare_neg_zero;
48pub mod no_cond_assign;
49pub mod no_console;
50pub mod no_const_assign;
51pub mod no_constant_condition;
52pub mod no_control_regex;
53pub mod no_debugger;
54pub mod no_delete_var;
55pub mod no_deprecated_deno_api;
56pub mod no_dupe_args;
57pub mod no_dupe_class_members;
58pub mod no_dupe_else_if;
59pub mod no_dupe_keys;
60pub mod no_duplicate_case;
61pub mod no_empty;
62pub mod no_empty_character_class;
63pub mod no_empty_enum;
64pub mod no_empty_interface;
65pub mod no_empty_pattern;
66pub mod no_eval;
67pub mod no_ex_assign;
68pub mod no_explicit_any;
69pub mod no_external_imports;
70pub mod no_extra_boolean_cast;
71pub mod no_extra_non_null_assertion;
72pub mod no_fallthrough;
73pub mod no_func_assign;
74pub mod no_global_assign;
75pub mod no_implicit_declare_namespace_export;
76pub mod no_import_assertions;
77pub mod no_import_assign;
78pub mod no_import_prefix;
79pub mod no_inferrable_types;
80pub mod no_inner_declarations;
81pub mod no_invalid_regexp;
82pub mod no_invalid_triple_slash_reference;
83pub mod no_irregular_whitespace;
84pub mod no_misused_new;
85pub mod no_namespace;
86pub mod no_new_symbol;
87pub mod no_node_globals;
88pub mod no_non_null_asserted_optional_chain;
89pub mod no_non_null_assertion;
90pub mod no_obj_calls;
91pub mod no_octal;
92pub mod no_process_global;
93pub mod no_prototype_builtins;
94pub mod no_redeclare;
95pub mod no_regex_spaces;
96pub mod no_self_assign;
97pub mod no_self_compare;
98pub mod no_setter_return;
99pub mod no_shadow_restricted_names;
100pub mod no_sparse_arrays;
101pub mod no_sync_fn_in_async_fn;
102pub mod no_this_alias;
103pub mod no_this_before_super;
104pub mod no_throw_literal;
105pub mod no_top_level_await;
106pub mod no_undef;
107pub mod no_unreachable;
108pub mod no_unsafe_finally;
109pub mod no_unsafe_negation;
110pub mod no_unused_labels;
111pub mod no_unused_vars;
112pub mod no_unversioned_import;
113pub mod no_useless_rename;
114pub mod no_var;
115pub mod no_window;
116pub mod no_window_prefix;
117pub mod no_with;
118pub mod prefer_as_const;
119pub mod prefer_ascii;
120pub mod prefer_const;
121pub mod prefer_namespace_keyword;
122pub mod prefer_primordials;
123pub mod react_no_danger;
124pub mod react_no_danger_with_children;
125pub mod react_rules_of_hooks;
126pub mod require_await;
127pub mod require_yield;
128pub mod single_var_declarator;
129pub mod triple_slash_reference;
130pub mod use_isnan;
131pub mod valid_typeof;
132pub mod verbatim_module_syntax;
133
134pub trait LintRule: std::fmt::Debug + Send + Sync {
135  /// Executes lint using `dprint-swc-ecma-ast-view`.
136  /// Falls back to the `lint_program` method if not implemented.
137  fn lint_program_with_ast_view<'view>(
138    &self,
139    context: &mut Context<'view>,
140    program: Program<'view>,
141  );
142
143  /// Returns the unique code that identifies the rule
144  fn code(&self) -> &'static str;
145
146  /// Returns the tags this rule belongs to, e.g. `recommended`
147  fn tags(&self) -> Tags {
148    &[]
149  }
150
151  /// The lower the return value is, the earlier this rule will be run.
152  ///
153  /// By default it is 0. Some rules might want to defer being run to the end
154  /// and they might override this value.
155  fn priority(&self) -> u32 {
156    0
157  }
158}
159
160/// TODO(@magurotuna): remove this after all rules get to use ast_view
161pub fn program_ref(program: Program) -> ProgramRef {
162  match program {
163    Program::Module(m) => ProgramRef::Module(m.inner),
164    Program::Script(s) => ProgramRef::Script(s.inner),
165  }
166}
167
168pub fn get_all_rules() -> Vec<Box<dyn LintRule>> {
169  get_all_rules_raw()
170}
171
172/// Filters the lint rules to only the recommended rules.
173pub fn recommended_rules(
174  all_rules: Vec<Box<dyn LintRule>>,
175) -> Vec<Box<dyn LintRule>> {
176  all_rules
177    .into_iter()
178    .filter(|r| r.tags().contains(&tags::RECOMMENDED))
179    .collect()
180}
181
182/// Returns a list of rules after filtering.
183///
184/// Following rules are applied (in the described order):
185///
186/// - if `maybe_tags` is `None` then all defined rules are returned, otherwise
187///   only rules matching at least one tag will be returned; if provided list
188///   is empty then all rules will be excluded by default
189///
190/// - if `maybe_exclude` is `Some`, all rules with matching codes will
191///   be filtered out
192///
193/// - if `maybe_include` is `Some`, rules with matching codes will be added
194///   to the return list
195///
196/// Before returning the list will sorted alphabetically.
197pub fn filtered_rules(
198  all_rules: Vec<Box<dyn LintRule>>,
199  maybe_tags: Option<Vec<String>>,
200  maybe_exclude: Option<Vec<String>>,
201  maybe_include: Option<Vec<String>>,
202) -> Vec<Box<dyn LintRule>> {
203  let tags_set =
204    maybe_tags.map(|tags| tags.into_iter().collect::<HashSet<_>>());
205
206  let mut rules = all_rules
207    .into_iter()
208    .filter(|rule| {
209      let mut passes = if let Some(tags_set) = &tags_set {
210        rule
211          .tags()
212          .iter()
213          .any(|t| tags_set.contains(&t.to_string()))
214      } else {
215        true
216      };
217
218      if let Some(includes) = &maybe_include {
219        if includes.contains(&rule.code().to_owned()) {
220          passes |= true;
221        }
222      }
223
224      if let Some(excludes) = &maybe_exclude {
225        if excludes.contains(&rule.code().to_owned()) {
226          passes &= false;
227        }
228      }
229
230      passes
231    })
232    .collect::<Vec<_>>();
233
234  rules.sort_by_key(|r| r.code());
235
236  rules
237}
238
239/// Sort lint rules by priority and alphabetically.
240pub(crate) fn sort_rules_by_priority(rules: &mut [Box<dyn LintRule>]) {
241  rules.sort_by(|rule1, rule2| {
242    let priority_cmp = rule1.priority().cmp(&rule2.priority());
243
244    if priority_cmp == Ordering::Equal {
245      return rule1.code().cmp(rule2.code());
246    }
247
248    priority_cmp
249  });
250}
251
252fn get_all_rules_raw() -> Vec<Box<dyn LintRule>> {
253  vec![
254    Box::new(adjacent_overload_signatures::AdjacentOverloadSignatures),
255    Box::new(ban_ts_comment::BanTsComment),
256    Box::new(ban_types::BanTypes),
257    Box::new(ban_unknown_rule_code::BanUnknownRuleCode),
258    Box::new(ban_untagged_ignore::BanUntaggedIgnore),
259    Box::new(ban_untagged_todo::BanUntaggedTodo),
260    Box::new(ban_unused_ignore::BanUnusedIgnore),
261    Box::new(camelcase::Camelcase),
262    Box::new(constructor_super::ConstructorSuper),
263    Box::new(default_param_last::DefaultParamLast),
264    Box::new(eqeqeq::Eqeqeq),
265    Box::new(explicit_function_return_type::ExplicitFunctionReturnType),
266    Box::new(explicit_module_boundary_types::ExplicitModuleBoundaryTypes),
267    Box::new(for_direction::ForDirection),
268    Box::new(fresh_handler_export::FreshHandlerExport),
269    Box::new(fresh_server_event_handlers::FreshServerEventHandlers),
270    Box::new(getter_return::GetterReturn),
271    Box::new(guard_for_in::GuardForIn),
272    Box::new(jsx_boolean_value::JSXBooleanValue),
273    Box::new(jsx_button_has_type::JSXButtonHasType),
274    Box::new(jsx_curly_braces::JSXCurlyBraces),
275    Box::new(jsx_key::JSXKey),
276    Box::new(jsx_no_children_prop::JSXNoChildrenProp),
277    Box::new(jsx_no_comment_text_nodes::JSXNoCommentTextNodes),
278    Box::new(jsx_no_duplicate_props::JSXNoDuplicateProps),
279    Box::new(jsx_no_unescaped_entities::JSXNoUnescapedEntities),
280    Box::new(jsx_no_useless_fragment::JSXNoUselessFragment),
281    Box::new(jsx_props_no_spread_multi::JSXPropsNoSpreadMulti),
282    Box::new(jsx_void_dom_elements_no_children::JSXVoidDomElementsNoChildren),
283    Box::new(no_array_constructor::NoArrayConstructor),
284    Box::new(no_async_promise_executor::NoAsyncPromiseExecutor),
285    Box::new(no_await_in_loop::NoAwaitInLoop),
286    Box::new(no_await_in_sync_fn::NoAwaitInSyncFn),
287    Box::new(no_boolean_literal_for_arguments::NoBooleanLiteralForArguments),
288    Box::new(no_case_declarations::NoCaseDeclarations),
289    Box::new(no_class_assign::NoClassAssign),
290    Box::new(no_compare_neg_zero::NoCompareNegZero),
291    Box::new(no_cond_assign::NoCondAssign),
292    Box::new(no_console::NoConsole),
293    Box::new(no_const_assign::NoConstAssign),
294    Box::new(no_constant_condition::NoConstantCondition),
295    Box::new(no_control_regex::NoControlRegex),
296    Box::new(no_debugger::NoDebugger),
297    Box::new(no_delete_var::NoDeleteVar),
298    Box::new(no_deprecated_deno_api::NoDeprecatedDenoApi),
299    Box::new(no_dupe_args::NoDupeArgs),
300    Box::new(no_dupe_class_members::NoDupeClassMembers),
301    Box::new(no_dupe_else_if::NoDupeElseIf),
302    Box::new(no_dupe_keys::NoDupeKeys),
303    Box::new(no_duplicate_case::NoDuplicateCase),
304    Box::new(no_empty::NoEmpty),
305    Box::new(no_empty_character_class::NoEmptyCharacterClass),
306    Box::new(no_empty_enum::NoEmptyEnum),
307    Box::new(no_empty_interface::NoEmptyInterface),
308    Box::new(no_empty_pattern::NoEmptyPattern),
309    Box::new(no_eval::NoEval),
310    Box::new(no_ex_assign::NoExAssign),
311    Box::new(no_explicit_any::NoExplicitAny),
312    Box::new(no_external_imports::NoExternalImport),
313    Box::new(no_extra_boolean_cast::NoExtraBooleanCast),
314    Box::new(no_extra_non_null_assertion::NoExtraNonNullAssertion),
315    Box::new(no_fallthrough::NoFallthrough),
316    Box::new(no_func_assign::NoFuncAssign),
317    Box::new(no_global_assign::NoGlobalAssign),
318    Box::new(
319      no_implicit_declare_namespace_export::NoImplicitDeclareNamespaceExport,
320    ),
321    Box::new(no_import_assertions::NoImportAssertions),
322    Box::new(no_import_assign::NoImportAssign),
323    Box::new(no_import_prefix::NoImportPrefix),
324    Box::new(no_inferrable_types::NoInferrableTypes),
325    Box::new(no_inner_declarations::NoInnerDeclarations),
326    Box::new(no_invalid_regexp::NoInvalidRegexp),
327    Box::new(no_invalid_triple_slash_reference::NoInvalidTripleSlashReference),
328    Box::new(no_irregular_whitespace::NoIrregularWhitespace),
329    Box::new(no_misused_new::NoMisusedNew),
330    Box::new(no_namespace::NoNamespace),
331    Box::new(no_new_symbol::NoNewSymbol),
332    Box::new(no_node_globals::NoNodeGlobals),
333    Box::new(
334      no_non_null_asserted_optional_chain::NoNonNullAssertedOptionalChain,
335    ),
336    Box::new(no_non_null_assertion::NoNonNullAssertion),
337    Box::new(no_obj_calls::NoObjCalls),
338    Box::new(no_octal::NoOctal),
339    Box::new(no_process_global::NoProcessGlobal),
340    Box::new(no_prototype_builtins::NoPrototypeBuiltins),
341    Box::new(no_redeclare::NoRedeclare),
342    Box::new(no_regex_spaces::NoRegexSpaces),
343    Box::new(no_self_assign::NoSelfAssign),
344    Box::new(no_self_compare::NoSelfCompare),
345    Box::new(no_setter_return::NoSetterReturn),
346    Box::new(no_shadow_restricted_names::NoShadowRestrictedNames),
347    Box::new(no_sparse_arrays::NoSparseArrays),
348    Box::new(no_sync_fn_in_async_fn::NoSyncFnInAsyncFn),
349    Box::new(no_this_alias::NoThisAlias),
350    Box::new(no_this_before_super::NoThisBeforeSuper),
351    Box::new(no_throw_literal::NoThrowLiteral),
352    Box::new(no_top_level_await::NoTopLevelAwait),
353    Box::new(no_undef::NoUndef),
354    Box::new(no_unreachable::NoUnreachable),
355    Box::new(no_unsafe_finally::NoUnsafeFinally),
356    Box::new(no_unsafe_negation::NoUnsafeNegation),
357    Box::new(no_unused_labels::NoUnusedLabels),
358    Box::new(no_unused_vars::NoUnusedVars),
359    Box::new(no_unversioned_import::NoUnversionedImport),
360    Box::new(no_useless_rename::NoUselessRename),
361    Box::new(no_var::NoVar),
362    Box::new(no_window::NoWindow),
363    Box::new(no_window_prefix::NoWindowPrefix),
364    Box::new(no_with::NoWith),
365    Box::new(prefer_as_const::PreferAsConst),
366    Box::new(prefer_ascii::PreferAscii),
367    Box::new(prefer_const::PreferConst),
368    Box::new(prefer_namespace_keyword::PreferNamespaceKeyword),
369    Box::new(prefer_primordials::PreferPrimordials),
370    Box::new(react_no_danger::ReactNoDanger),
371    Box::new(react_no_danger_with_children::ReactNoDangerWithChildren),
372    Box::new(react_rules_of_hooks::ReactRulesOfHooks),
373    Box::new(require_await::RequireAwait),
374    Box::new(require_yield::RequireYield),
375    Box::new(single_var_declarator::SingleVarDeclarator),
376    Box::new(triple_slash_reference::TripleSlashReference),
377    Box::new(use_isnan::UseIsNaN),
378    Box::new(valid_typeof::ValidTypeof),
379    Box::new(verbatim_module_syntax::VerbatimModuleSyntax),
380  ]
381}
382
383#[cfg(test)]
384mod tests {
385  use std::sync::Arc;
386
387  use crate::tags;
388
389  use super::*;
390
391  #[test]
392  fn recommended_rules_sorted_alphabetically() {
393    let mut sorted_recommended_rules = recommended_rules(get_all_rules());
394    sorted_recommended_rules.sort_by_key(|r| r.code());
395
396    for (sorted, unsorted) in sorted_recommended_rules
397      .iter()
398      .zip(recommended_rules(get_all_rules()).iter())
399    {
400      assert_eq!(sorted.code(), unsorted.code());
401    }
402  }
403
404  #[test]
405  fn all_rules_sorted_alphabetically() {
406    let mut all_rules = get_all_rules_raw();
407    all_rules.sort_by_key(|r| r.code());
408    for (sorted, unsorted) in all_rules.iter().zip(get_all_rules_raw()) {
409      assert_eq!(sorted.code(), unsorted.code());
410    }
411  }
412
413  #[test]
414  fn test_get_filtered_rules() {
415    // Should return recommended rules when given `recommended` tag.
416    let rules = filtered_rules(
417      get_all_rules(),
418      Some(vec!["recommended".to_string()]),
419      None,
420      None,
421    );
422    for (r, rr) in rules.iter().zip(recommended_rules(get_all_rules()).iter()) {
423      assert_eq!(r.code(), rr.code());
424    }
425
426    // Should allow to add more rules to recommended rules.
427    let rules = filtered_rules(
428      get_all_rules(),
429      Some(vec!["recommended".to_string()]),
430      None,
431      Some(vec!["ban-untagged-todo".to_string()]),
432    );
433    assert_eq!(rules.len(), recommended_rules(get_all_rules()).len() + 1);
434
435    // Recommended should allow to exclude some recommended rules and include more on top.
436    let rules = filtered_rules(
437      get_all_rules(),
438      Some(vec!["recommended".to_string()]),
439      Some(vec!["ban-ts-comment".to_string()]),
440      Some(vec!["ban-untagged-todo".to_string()]),
441    );
442    assert_eq!(rules.len(), recommended_rules(get_all_rules()).len());
443
444    // Should skip all rules if given empty tags vec.
445    let rules = filtered_rules(get_all_rules(), Some(vec![]), None, None);
446    assert!(rules.is_empty());
447
448    // Should still allow to include rules when passed empty tags vec.
449    let rules = filtered_rules(
450      get_all_rules(),
451      Some(vec![]),
452      None,
453      Some(vec!["ban-untagged-todo".to_string()]),
454    );
455    assert_eq!(rules.len(), 1);
456
457    // Excluded rules should have priority over included rules.
458    let rules = filtered_rules(
459      get_all_rules(),
460      Some(vec![]),
461      Some(vec!["ban-untagged-todo".to_string()]),
462      Some(vec!["ban-untagged-todo".to_string()]),
463    );
464    assert_eq!(rules.len(), 0);
465
466    // Should still allow to include other rules, when other duplicates are excluded.
467    let rules = filtered_rules(
468      get_all_rules(),
469      Some(vec![]),
470      Some(vec![
471        "ban-untagged-todo".to_string(),
472        "ban-ts-comment".to_string(),
473      ]),
474      Some(vec![
475        "ban-untagged-todo".to_string(),
476        "ban-ts-comment".to_string(),
477        "no-const-assign".to_string(),
478        "no-throw-literal".to_string(),
479      ]),
480    );
481    assert_eq!(rules.len(), 2);
482    assert_eq!(rules[0].code(), "no-const-assign");
483    assert_eq!(rules[1].code(), "no-throw-literal");
484  }
485
486  #[test]
487  fn ensure_lint_rules_are_sharable_across_threads() {
488    use std::thread::spawn;
489
490    let rules = Arc::new(recommended_rules(get_all_rules()));
491    let handles = (0..2)
492      .map(|_| {
493        let rules = Arc::clone(&rules);
494        spawn(move || {
495          for rule in rules.iter() {
496            assert!(rule.tags().contains(&tags::RECOMMENDED));
497          }
498        })
499      })
500      .collect::<Vec<_>>();
501
502    for handle in handles {
503      handle.join().unwrap();
504    }
505  }
506
507  #[test]
508  fn sort_by_priority() {
509    let mut rules: Vec<Box<dyn LintRule>> = vec![
510      Box::new(ban_unknown_rule_code::BanUnknownRuleCode),
511      Box::new(ban_unused_ignore::BanUnusedIgnore),
512      Box::new(no_redeclare::NoRedeclare),
513      Box::new(eqeqeq::Eqeqeq),
514    ];
515
516    sort_rules_by_priority(&mut rules);
517
518    assert_eq!(rules[0].code(), "eqeqeq");
519    assert_eq!(rules[1].code(), "no-redeclare");
520    assert_eq!(rules[2].code(), "ban-unknown-rule-code");
521    assert_eq!(rules[3].code(), "ban-unused-ignore");
522  }
523}