Skip to content

Commit 1f8968a

Browse files
authoredJul 17, 2024··
feat(linter): Add eslint-plugin-promise rules: avoid-new, no-new-statics, params-names (#4293)
This introduces the `eslint-plugin-promise` plugin and implements three relatively simple rules. Split off from #4252
1 parent fc0b17d commit 1f8968a

File tree

15 files changed

+512
-1
lines changed

15 files changed

+512
-1
lines changed
 

‎apps/oxlint/src/command/lint.rs

+4
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,10 @@ pub struct EnablePlugins {
223223
/// Enable the React performance plugin and detect rendering performance problems
224224
#[bpaf(switch, hide_usage)]
225225
pub react_perf_plugin: bool,
226+
227+
/// Enable the promise plugin and detect promise usage problems
228+
#[bpaf(switch, hide_usage)]
229+
pub promise_plugin: bool,
226230
}
227231

228232
#[cfg(test)]

‎apps/oxlint/src/lint/mod.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,8 @@ impl Runner for LintRunner {
104104
.with_vitest_plugin(enable_plugins.vitest_plugin)
105105
.with_jsx_a11y_plugin(enable_plugins.jsx_a11y_plugin)
106106
.with_nextjs_plugin(enable_plugins.nextjs_plugin)
107-
.with_react_perf_plugin(enable_plugins.react_perf_plugin);
107+
.with_react_perf_plugin(enable_plugins.react_perf_plugin)
108+
.with_promise_plugin(enable_plugins.promise_plugin);
108109

109110
let linter = match Linter::from_options(lint_options) {
110111
Ok(lint_service) => lint_service,

‎crates/oxc_linter/src/options.rs

+9
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ pub struct LintOptions {
2929
pub jsx_a11y_plugin: bool,
3030
pub nextjs_plugin: bool,
3131
pub react_perf_plugin: bool,
32+
pub promise_plugin: bool,
3233
}
3334

3435
impl Default for LintOptions {
@@ -48,6 +49,7 @@ impl Default for LintOptions {
4849
jsx_a11y_plugin: false,
4950
nextjs_plugin: false,
5051
react_perf_plugin: false,
52+
promise_plugin: false,
5153
}
5254
}
5355
}
@@ -138,6 +140,12 @@ impl LintOptions {
138140
self.react_perf_plugin = yes;
139141
self
140142
}
143+
144+
#[must_use]
145+
pub fn with_promise_plugin(mut self, yes: bool) -> Self {
146+
self.promise_plugin = yes;
147+
self
148+
}
141149
}
142150

143151
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
@@ -342,6 +350,7 @@ impl LintOptions {
342350
"react_perf" => self.react_perf_plugin,
343351
"oxc" => self.oxc_plugin,
344352
"eslint" | "tree_shaking" => true,
353+
"promise" => self.promise_plugin,
345354
name => panic!("Unhandled plugin: {name}"),
346355
})
347356
.cloned()

‎crates/oxc_linter/src/rules.rs

+9
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,12 @@ mod tree_shaking {
432432
pub mod no_side_effects_in_initialization;
433433
}
434434

435+
mod promise {
436+
pub mod avoid_new;
437+
pub mod no_new_statics;
438+
pub mod param_names;
439+
}
440+
435441
oxc_macros::declare_all_lint_rules! {
436442
eslint::array_callback_return,
437443
eslint::constructor_super,
@@ -822,4 +828,7 @@ oxc_macros::declare_all_lint_rules! {
822828
jsdoc::require_returns_type,
823829
jsdoc::require_yields,
824830
tree_shaking::no_side_effects_in_initialization,
831+
promise::avoid_new,
832+
promise::no_new_statics,
833+
promise::param_names,
825834
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
use oxc_ast::{ast::Expression, AstKind};
2+
use oxc_diagnostics::OxcDiagnostic;
3+
use oxc_macros::declare_oxc_lint;
4+
use oxc_span::Span;
5+
6+
use crate::{context::LintContext, rule::Rule, AstNode};
7+
8+
fn avoid_new_promise_diagnostic(span0: Span) -> OxcDiagnostic {
9+
OxcDiagnostic::warn("eslint-plugin-promise(avoid-new): Avoid creating new promises")
10+
.with_label(span0)
11+
}
12+
13+
#[derive(Debug, Default, Clone)]
14+
pub struct AvoidNew;
15+
16+
declare_oxc_lint!(
17+
/// ### What it does
18+
///
19+
/// Disallow creating new promises outside of utility libs.
20+
///
21+
/// ### Why is this bad?
22+
///
23+
/// If you dislike the new promise style promises.
24+
///
25+
/// ### Example
26+
/// ```javascript
27+
/// new Promise((resolve, reject) => { ... });
28+
/// ```
29+
AvoidNew,
30+
restriction,
31+
);
32+
33+
impl Rule for AvoidNew {
34+
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
35+
let AstKind::NewExpression(expr) = node.kind() else {
36+
return;
37+
};
38+
39+
let Expression::Identifier(ident) = &expr.callee else {
40+
return;
41+
};
42+
43+
if ident.name == "Promise" && ctx.semantic().is_reference_to_global_variable(ident) {
44+
ctx.diagnostic(avoid_new_promise_diagnostic(expr.span));
45+
}
46+
}
47+
}
48+
49+
#[test]
50+
fn test() {
51+
use crate::tester::Tester;
52+
53+
let pass = vec![
54+
"Promise.resolve()",
55+
"Promise.reject()",
56+
"Promise.all()",
57+
"new Horse()",
58+
"new PromiseLikeThing()",
59+
"new Promise.resolve()",
60+
];
61+
62+
let fail = vec![
63+
"var x = new Promise(function (x, y) {})",
64+
"new Promise()",
65+
"Thing(new Promise(() => {}))",
66+
];
67+
68+
Tester::new(AvoidNew::NAME, pass, fail).test_and_snapshot();
69+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
use oxc_ast::{ast::Expression, AstKind};
2+
use oxc_diagnostics::OxcDiagnostic;
3+
use oxc_macros::declare_oxc_lint;
4+
use oxc_span::Span;
5+
6+
use crate::{context::LintContext, rule::Rule, AstNode};
7+
8+
fn static_promise_diagnostic(x0: &str, span0: Span) -> OxcDiagnostic {
9+
OxcDiagnostic::warn(format!(
10+
"eslint-plugin-promise(no-new-statics): Disallow calling `new` on a `Promise.{x0}`"
11+
))
12+
.with_label(span0)
13+
}
14+
15+
#[derive(Debug, Default, Clone)]
16+
pub struct NoNewStatics;
17+
18+
declare_oxc_lint!(
19+
/// ### What it does
20+
///
21+
/// Disallow calling new on a Promise static method.
22+
///
23+
/// ### Why is this bad?
24+
///
25+
/// Calling a Promise static method with new is invalid, resulting in a TypeError at runtime.
26+
///
27+
/// ### Example
28+
/// ```javascript
29+
/// new Promise.resolve(value);
30+
/// ```
31+
NoNewStatics,
32+
correctness
33+
);
34+
35+
impl Rule for NoNewStatics {
36+
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
37+
let AstKind::NewExpression(new_expr) = node.kind() else {
38+
return;
39+
};
40+
41+
let Some(member_expr) = &new_expr.callee.get_member_expr() else {
42+
return;
43+
};
44+
45+
let Expression::Identifier(ident) = &member_expr.object() else {
46+
return;
47+
};
48+
49+
if ident.name != "Promise" || !ctx.semantic().is_reference_to_global_variable(ident) {
50+
return;
51+
}
52+
53+
let Some(prop_name) = member_expr.static_property_name() else {
54+
return;
55+
};
56+
57+
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
58+
if matches!(
59+
prop_name,
60+
"resolve" | "reject" | "all" | "allSettled" | "race" | "any" | "withResolvers"
61+
) {
62+
ctx.diagnostic_with_fix(
63+
static_promise_diagnostic(
64+
prop_name,
65+
Span::new(new_expr.span.start, ident.span.start - 1),
66+
),
67+
|fixer| fixer.delete_range(Span::new(new_expr.span.start, ident.span.start)),
68+
);
69+
}
70+
}
71+
}
72+
73+
#[test]
74+
fn test() {
75+
use crate::tester::Tester;
76+
77+
let pass = vec![
78+
"Promise.resolve()",
79+
"Promise.reject()",
80+
"Promise.all()",
81+
"Promise.race()",
82+
"new Promise(function (resolve, reject) {})",
83+
"new SomeClass()",
84+
"SomeClass.resolve()",
85+
"new SomeClass.resolve()",
86+
];
87+
88+
let fail = vec![
89+
"new Promise.resolve()",
90+
"new Promise.reject()",
91+
"new Promise.all()",
92+
"new Promise.allSettled()",
93+
"new Promise.any()",
94+
"new Promise.race()",
95+
"function foo() {
96+
var a = getA()
97+
return new Promise.resolve(a)
98+
}",
99+
];
100+
101+
let fix = vec![
102+
("new Promise.resolve()", "Promise.resolve()", None),
103+
("new Promise.reject()", "Promise.reject()", None),
104+
("new Promise.all()", "Promise.all()", None),
105+
("new Promise.allSettled()", "Promise.allSettled()", None),
106+
("new Promise.any()", "Promise.any()", None),
107+
("new Promise.race()", "Promise.race()", None),
108+
];
109+
Tester::new(NoNewStatics::NAME, pass, fail).expect_fix(fix).test_and_snapshot();
110+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
use oxc_ast::{
2+
ast::{BindingPatternKind, Expression, FormalParameter, FormalParameters},
3+
AstKind,
4+
};
5+
use oxc_diagnostics::OxcDiagnostic;
6+
use oxc_macros::declare_oxc_lint;
7+
use oxc_span::Span;
8+
use regex::Regex;
9+
10+
use crate::{context::LintContext, rule::Rule, AstNode};
11+
12+
fn param_names_diagnostic(span0: Span, x0: &str) -> OxcDiagnostic {
13+
OxcDiagnostic::warn(format!("eslint-plugin-promise(param-names): Promise constructor parameters must be named to match `{x0}`")).with_label(span0)
14+
}
15+
16+
#[derive(Debug, Default, Clone)]
17+
pub struct ParamNames(Box<ParamNamesConfig>);
18+
19+
#[derive(Debug, Default, Clone)]
20+
pub struct ParamNamesConfig {
21+
resolve_pattern: Option<Regex>,
22+
reject_pattern: Option<Regex>,
23+
}
24+
25+
impl std::ops::Deref for ParamNames {
26+
type Target = ParamNamesConfig;
27+
28+
fn deref(&self) -> &Self::Target {
29+
&self.0
30+
}
31+
}
32+
33+
enum ParamType {
34+
Resolve,
35+
Reject,
36+
}
37+
38+
declare_oxc_lint!(
39+
/// ### What it does
40+
///
41+
/// Enforce standard parameter names for Promise constructors.
42+
///
43+
/// ### Why is this bad?
44+
///
45+
/// Ensures that new Promise() is instantiated with the parameter names resolve, reject to
46+
/// avoid confusion with order such as reject, resolve. The Promise constructor uses the
47+
/// RevealingConstructor pattern. Using the same parameter names as the language specification
48+
/// makes code more uniform and easier to understand.
49+
///
50+
/// ### Example
51+
/// ```javascript
52+
/// new Promise(function (reject, resolve) { ... }) // incorrect order
53+
/// new Promise(function (ok, fail) { ... }) // non-standard parameter names
54+
/// ```
55+
ParamNames,
56+
style,
57+
);
58+
59+
impl Rule for ParamNames {
60+
fn from_configuration(value: serde_json::Value) -> Self {
61+
let mut cfg = ParamNamesConfig::default();
62+
63+
if let Some(config) = value.get(0) {
64+
if let Some(val) = config.get("resolvePattern").and_then(serde_json::Value::as_str) {
65+
cfg.resolve_pattern = Regex::new(val).ok();
66+
}
67+
if let Some(val) = config.get("rejectPattern").and_then(serde_json::Value::as_str) {
68+
cfg.reject_pattern = Regex::new(val).ok();
69+
}
70+
}
71+
72+
Self(Box::new(cfg))
73+
}
74+
75+
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
76+
let AstKind::NewExpression(new_expr) = node.kind() else {
77+
return;
78+
};
79+
80+
if !new_expr.callee.is_specific_id("Promise") || new_expr.arguments.len() != 1 {
81+
return;
82+
}
83+
84+
for argument in &new_expr.arguments {
85+
let Some(arg_expr) = argument.as_expression() else {
86+
continue;
87+
};
88+
match arg_expr {
89+
Expression::ArrowFunctionExpression(arrow_expr) => {
90+
self.check_parameter_names(&arrow_expr.params, ctx);
91+
}
92+
Expression::FunctionExpression(func_expr) => {
93+
self.check_parameter_names(&func_expr.params, ctx);
94+
}
95+
_ => continue,
96+
}
97+
}
98+
}
99+
}
100+
101+
impl ParamNames {
102+
fn check_parameter_names(&self, params: &FormalParameters, ctx: &LintContext) {
103+
if params.items.is_empty() {
104+
return;
105+
}
106+
107+
self.check_parameter(&params.items[0], &ParamType::Resolve, ctx);
108+
109+
if params.items.len() > 1 {
110+
self.check_parameter(&params.items[1], &ParamType::Reject, ctx);
111+
}
112+
}
113+
114+
fn check_parameter(&self, param: &FormalParameter, param_type: &ParamType, ctx: &LintContext) {
115+
let BindingPatternKind::BindingIdentifier(param_ident) = &param.pattern.kind else {
116+
return;
117+
};
118+
119+
let param_pattern = if matches!(param_type, ParamType::Reject) {
120+
&self.reject_pattern
121+
} else {
122+
&self.resolve_pattern
123+
};
124+
125+
match param_pattern {
126+
Some(pattern) => {
127+
if !pattern.is_match(param_ident.name.as_str()) {
128+
ctx.diagnostic(param_names_diagnostic(param_ident.span, pattern.as_str()));
129+
}
130+
}
131+
None => {
132+
if matches!(param_type, ParamType::Resolve)
133+
&& !matches!(param_ident.name.as_str(), "_resolve" | "resolve")
134+
{
135+
ctx.diagnostic(param_names_diagnostic(param_ident.span, "^_?resolve$"));
136+
} else if matches!(param_type, ParamType::Reject)
137+
&& !matches!(param_ident.name.as_str(), "_reject" | "reject")
138+
{
139+
ctx.diagnostic(param_names_diagnostic(param_ident.span, "^_?reject$"));
140+
}
141+
}
142+
}
143+
}
144+
}
145+
146+
#[test]
147+
fn test() {
148+
use crate::tester::Tester;
149+
150+
let pass = vec![
151+
("new Promise(function(resolve, reject) {})", None),
152+
("new Promise(function(resolve, _reject) {})", None),
153+
("new Promise(function(_resolve, reject) {})", None),
154+
("new Promise(function(_resolve, _reject) {})", None),
155+
("new Promise(function(resolve) {})", None),
156+
("new Promise(function(_resolve) {})", None),
157+
("new Promise(resolve => {})", None),
158+
("new Promise((resolve, reject) => {})", None),
159+
("new Promise(() => {})", None),
160+
("new NonPromise()", None),
161+
(
162+
"new Promise((yes, no) => {})",
163+
Some(serde_json::json!([{ "resolvePattern": "^yes$", "rejectPattern": "^no$" }])),
164+
),
165+
];
166+
167+
let fail = vec![
168+
("new Promise(function(reject, resolve) {})", None),
169+
("new Promise(function(resolve, rej) {})", None),
170+
("new Promise(yes => {})", None),
171+
("new Promise((yes, no) => {})", None),
172+
(
173+
"new Promise(function(resolve, reject) { config(); })",
174+
Some(serde_json::json!([{ "resolvePattern": "^yes$", "rejectPattern": "^no$" }])),
175+
),
176+
];
177+
178+
Tester::new(ParamNames::NAME, pass, fail).test_and_snapshot();
179+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
source: crates/oxc_linter/src/tester.rs
3+
---
4+
eslint-plugin-promise(avoid-new): Avoid creating new promises
5+
╭─[avoid_new.tsx:1:9]
6+
1var x = new Promise(function (x, y) {})
7+
· ───────────────────────────────
8+
╰────
9+
10+
eslint-plugin-promise(avoid-new): Avoid creating new promises
11+
╭─[avoid_new.tsx:1:1]
12+
1new Promise()
13+
· ─────────────
14+
╰────
15+
16+
eslint-plugin-promise(avoid-new): Avoid creating new promises
17+
╭─[avoid_new.tsx:1:7]
18+
1Thing(new Promise(() => {}))
19+
· ─────────────────────
20+
╰────
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
---
2+
source: crates/oxc_linter/src/tester.rs
3+
---
4+
eslint-plugin-promise(no-new-statics): Disallow calling `new` on a `Promise.resolve`
5+
╭─[no_new_statics.tsx:1:1]
6+
1new Promise.resolve()
7+
· ───
8+
╰────
9+
10+
eslint-plugin-promise(no-new-statics): Disallow calling `new` on a `Promise.reject`
11+
╭─[no_new_statics.tsx:1:1]
12+
1new Promise.reject()
13+
· ───
14+
╰────
15+
16+
eslint-plugin-promise(no-new-statics): Disallow calling `new` on a `Promise.all`
17+
╭─[no_new_statics.tsx:1:1]
18+
1new Promise.all()
19+
· ───
20+
╰────
21+
22+
eslint-plugin-promise(no-new-statics): Disallow calling `new` on a `Promise.allSettled`
23+
╭─[no_new_statics.tsx:1:1]
24+
1new Promise.allSettled()
25+
· ───
26+
╰────
27+
28+
eslint-plugin-promise(no-new-statics): Disallow calling `new` on a `Promise.any`
29+
╭─[no_new_statics.tsx:1:1]
30+
1new Promise.any()
31+
· ───
32+
╰────
33+
34+
eslint-plugin-promise(no-new-statics): Disallow calling `new` on a `Promise.race`
35+
╭─[no_new_statics.tsx:1:1]
36+
1new Promise.race()
37+
· ───
38+
╰────
39+
40+
eslint-plugin-promise(no-new-statics): Disallow calling `new` on a `Promise.resolve`
41+
╭─[no_new_statics.tsx:3:13]
42+
2var a = getA()
43+
3return new Promise.resolve(a)
44+
· ───
45+
4 │ }
46+
╰────
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
---
2+
source: crates/oxc_linter/src/tester.rs
3+
---
4+
eslint-plugin-promise(param-names): Promise constructor parameters must be named to match `^_?resolve$`
5+
╭─[param_names.tsx:1:22]
6+
1new Promise(function(reject, resolve) {})
7+
· ──────
8+
╰────
9+
10+
eslint-plugin-promise(param-names): Promise constructor parameters must be named to match `^_?reject$`
11+
╭─[param_names.tsx:1:30]
12+
1new Promise(function(reject, resolve) {})
13+
· ───────
14+
╰────
15+
16+
eslint-plugin-promise(param-names): Promise constructor parameters must be named to match `^_?reject$`
17+
╭─[param_names.tsx:1:31]
18+
1new Promise(function(resolve, rej) {})
19+
· ───
20+
╰────
21+
22+
eslint-plugin-promise(param-names): Promise constructor parameters must be named to match `^_?resolve$`
23+
╭─[param_names.tsx:1:13]
24+
1new Promise(yes => {})
25+
· ───
26+
╰────
27+
28+
eslint-plugin-promise(param-names): Promise constructor parameters must be named to match `^_?resolve$`
29+
╭─[param_names.tsx:1:14]
30+
1new Promise((yes, no) => {})
31+
· ───
32+
╰────
33+
34+
eslint-plugin-promise(param-names): Promise constructor parameters must be named to match `^_?reject$`
35+
╭─[param_names.tsx:1:19]
36+
1new Promise((yes, no) => {})
37+
· ──
38+
╰────
39+
40+
eslint-plugin-promise(param-names): Promise constructor parameters must be named to match `^yes$`
41+
╭─[param_names.tsx:1:22]
42+
1new Promise(function(resolve, reject) { config(); })
43+
· ───────
44+
╰────
45+
46+
eslint-plugin-promise(param-names): Promise constructor parameters must be named to match `^no$`
47+
╭─[param_names.tsx:1:31]
48+
1new Promise(function(resolve, reject) { config(); })
49+
· ──────
50+
╰────

‎justfile

+3
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,9 @@ new-react-perf-rule name:
145145
new-n-rule name:
146146
cargo run -p rulegen {{name}} n
147147

148+
new-promise-rule name:
149+
cargo run -p rulegen {{name}} promise
150+
148151
clone-submodule dir url sha:
149152
git clone --depth=1 {{url}} {{dir}} || true
150153
cd {{dir}} && git fetch origin {{sha}} && git reset --hard {{sha}}

‎tasks/rulegen/src/main.rs

+7
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ const NODE_TEST_PATH: &str =
5555
const TREE_SHAKING_PATH: &str =
5656
"https://raw.githubusercontent.com/lukastaegert/eslint-plugin-tree-shaking/master/src/rules";
5757

58+
const PROMISE_TEST_PATH: &str =
59+
"https://raw.githubusercontent.com/eslint-community/eslint-plugin-promise/main/__tests__";
60+
5861
struct TestCase {
5962
source_text: String,
6063
code: Option<String>,
@@ -570,6 +573,7 @@ pub enum RuleKind {
570573
JSDoc,
571574
Node,
572575
TreeShaking,
576+
Promise,
573577
}
574578

575579
impl RuleKind {
@@ -586,6 +590,7 @@ impl RuleKind {
586590
"jsdoc" => Self::JSDoc,
587591
"n" => Self::Node,
588592
"tree-shaking" => Self::TreeShaking,
593+
"promise" => Self::Promise,
589594
_ => Self::ESLint,
590595
}
591596
}
@@ -606,6 +611,7 @@ impl Display for RuleKind {
606611
Self::JSDoc => write!(f, "eslint-plugin-jsdoc"),
607612
Self::Node => write!(f, "eslint-plugin-n"),
608613
Self::TreeShaking => write!(f, "eslint-plugin-tree-shaking"),
614+
Self::Promise => write!(f, "eslint-plugin-promise"),
609615
}
610616
}
611617
}
@@ -632,6 +638,7 @@ fn main() {
632638
RuleKind::JSDoc => format!("{JSDOC_TEST_PATH}/{camel_rule_name}.js"),
633639
RuleKind::Node => format!("{NODE_TEST_PATH}/{kebab_rule_name}.js"),
634640
RuleKind::TreeShaking => format!("{TREE_SHAKING_PATH}/{kebab_rule_name}.test.ts"),
641+
RuleKind::Promise => format!("{PROMISE_TEST_PATH}/{kebab_rule_name}.js"),
635642
RuleKind::Oxc => String::new(),
636643
};
637644

‎tasks/rulegen/src/template.rs

+1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ impl<'a> Template<'a> {
4242
RuleKind::JSDoc => Path::new("crates/oxc_linter/src/rules/jsdoc"),
4343
RuleKind::Node => Path::new("crates/oxc_linter/src/rules/node"),
4444
RuleKind::TreeShaking => Path::new("crates/oxc_linter/src/rules/tree_shaking"),
45+
RuleKind::Promise => Path::new("crates/oxc_linter/src/rules/promise"),
4546
};
4647

4748
std::fs::create_dir_all(path)?;

‎tasks/website/src/linter/snapshots/cli.snap

+2
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ Arguments:
6666
Enable the Next.js plugin and detect Next.js problems
6767
- **` --react-perf-plugin`** &mdash;
6868
Enable the React performance plugin and detect rendering performance problems
69+
- **` --promise-plugin`** &mdash;
70+
Enable the promise plugin and detect promise usage problems
6971

7072

7173

‎tasks/website/src/linter/snapshots/cli_terminal.snap

+1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ Enable Plugins
4040
--nextjs-plugin Enable the Next.js plugin and detect Next.js problems
4141
--react-perf-plugin Enable the React performance plugin and detect rendering performance
4242
problems
43+
--promise-plugin Enable the promise plugin and detect promise usage problems
4344

4445
Fix Problems
4546
--fix Fix as many issues as possible. Only unfixed issues are reported in

0 commit comments

Comments
 (0)
Please sign in to comment.