Skip to content

Commit 4c17bc6

Browse files
authored
feat(linter): eslint/no-constructor-return (#3321)
1 parent 0cdb45a commit 4c17bc6

File tree

3 files changed

+127
-0
lines changed

3 files changed

+127
-0
lines changed

crates/oxc_linter/src/rules.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ mod eslint {
4848
pub mod no_const_assign;
4949
pub mod no_constant_binary_expression;
5050
pub mod no_constant_condition;
51+
pub mod no_constructor_return;
5152
pub mod no_continue;
5253
pub mod no_control_regex;
5354
pub mod no_debugger;
@@ -493,6 +494,7 @@ oxc_macros::declare_all_lint_rules! {
493494
eslint::no_new_native_nonconstructor,
494495
eslint::no_restricted_globals,
495496
eslint::prefer_exponentiation_operator,
497+
eslint::no_constructor_return,
496498
typescript::adjacent_overload_signatures,
497499
typescript::array_type,
498500
typescript::ban_ts_comment,
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
use oxc_ast::{
2+
ast::{MethodDefinition, MethodDefinitionKind},
3+
AstKind,
4+
};
5+
use oxc_diagnostics::OxcDiagnostic;
6+
use oxc_macros::declare_oxc_lint;
7+
use oxc_semantic::AstNodeId;
8+
use oxc_span::Span;
9+
10+
use crate::{context::LintContext, rule::Rule, AstNode};
11+
12+
fn no_constructor_return_diagnostic(span: Span) -> OxcDiagnostic {
13+
OxcDiagnostic::warn(
14+
"eslint(no-constructor-return): Unexpected return statement in constructor.",
15+
)
16+
.with_labels([span.into()])
17+
}
18+
19+
#[derive(Debug, Default, Clone)]
20+
pub struct NoConstructorReturn;
21+
22+
declare_oxc_lint!(
23+
/// ### What it does
24+
///
25+
/// Disallow returning value from constructor
26+
///
27+
/// ### Why is this bad?
28+
///
29+
/// In JavaScript, returning a value in the constructor of a class may be a mistake.
30+
/// Forbidding this pattern prevents mistakes resulting from unfamiliarity with the language or a copy-paste error.
31+
///
32+
/// ### Example
33+
/// Bad:
34+
/// ```rust
35+
/// class C {
36+
/// constructor() { return 42; }
37+
/// }
38+
/// ```
39+
///
40+
/// Good:
41+
/// ```rust
42+
/// class C {
43+
/// constructor() { this.value = 42; }
44+
/// }
45+
/// ```
46+
NoConstructorReturn,
47+
correctness
48+
);
49+
50+
impl Rule for NoConstructorReturn {
51+
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
52+
let AstKind::ReturnStatement(ret) = node.kind() else { return };
53+
if ret.argument.is_none() {
54+
return;
55+
}
56+
57+
if is_definitely_in_constructor(ctx, node.id()) {
58+
ctx.diagnostic(no_constructor_return_diagnostic(ret.span));
59+
}
60+
}
61+
}
62+
63+
fn is_constructor(node: &AstNode<'_>) -> bool {
64+
matches!(
65+
node.kind(),
66+
AstKind::MethodDefinition(MethodDefinition { kind: MethodDefinitionKind::Constructor, .. })
67+
)
68+
}
69+
70+
fn is_definitely_in_constructor(ctx: &LintContext, node_id: AstNodeId) -> bool {
71+
ctx.nodes()
72+
.ancestors(node_id)
73+
.map(|id| ctx.nodes().get_node(id))
74+
.skip_while(|node| !node.kind().is_function_like())
75+
.nth(1)
76+
.is_some_and(is_constructor)
77+
}
78+
79+
#[test]
80+
fn test() {
81+
use crate::tester::Tester;
82+
83+
let pass = vec![
84+
"function fn() { return }",
85+
"function fn(kumiko) { if (kumiko) { return kumiko } }",
86+
"const fn = function () { return }",
87+
"const fn = function () { if (kumiko) { return kumiko } }",
88+
"const fn = () => { return }",
89+
"const fn = () => { if (kumiko) { return kumiko } }",
90+
"return 'Kumiko Oumae'",
91+
"class C { }",
92+
"class C { constructor() {} }",
93+
"class C { constructor() { let v } }",
94+
"class C { method() { return '' } }",
95+
"class C { get value() { return '' } }",
96+
"class C { constructor(a) { if (!a) { return } else { a() } } }",
97+
"class C { constructor() { function fn() { return true } } }",
98+
"class C { constructor() { this.fn = function () { return true } } }",
99+
"class C { constructor() { this.fn = () => { return true } } }",
100+
"class C { constructor() { return } }",
101+
"class C { constructor() { { return } } }",
102+
];
103+
104+
let fail = vec![
105+
"class C { constructor() { return '' } }",
106+
"class C { constructor(a) { if (!a) { return '' } else { a() } } }",
107+
];
108+
109+
Tester::new(NoConstructorReturn::NAME, pass, fail).test_and_snapshot();
110+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
source: crates/oxc_linter/src/tester.rs
3+
expression: no_constructor_return
4+
---
5+
eslint(no-constructor-return): Unexpected return statement in constructor.
6+
╭─[no_constructor_return.tsx:1:27]
7+
1class C { constructor() { return '' } }
8+
· ─────────
9+
╰────
10+
11+
eslint(no-constructor-return): Unexpected return statement in constructor.
12+
╭─[no_constructor_return.tsx:1:38]
13+
1class C { constructor(a) { if (!a) { return '' } else { a() } } }
14+
· ─────────
15+
╰────

0 commit comments

Comments
 (0)