Skip to content

Commit 5125f89

Browse files
committed
feat(linter/unicorn): support no-null checkArguments option (#23098)
- Support `checkArguments` in `unicorn/no-null`, defaulting to upstream behavior of checking direct call and constructor arguments. - Allow direct `null` call/constructor arguments when `checkArguments: false`, while still reporting nested `null` values. - Port the missing upstream Unicorn cases and update generated config artifacts. Fixes #23048
1 parent b8b9797 commit 5125f89

5 files changed

Lines changed: 126 additions & 1 deletion

File tree

apps/oxlint/src-js/package/config.generated.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4782,6 +4782,10 @@ export interface NoArraySort {
47824782
allowExpressionStatement?: boolean;
47834783
}
47844784
export interface NoNull {
4785+
/**
4786+
* When set to `true`, disallow the use of `null` as a direct function call or constructor argument.
4787+
*/
4788+
checkArguments?: boolean;
47854789
/**
47864790
* When set to `true`, the rule will also check strict equality/inequality comparisons (`===` and `!==`) against `null`.
47874791
*/

crates/oxc_linter/src/rules/unicorn/no_null.rs

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,19 @@ fn no_null_diagnostic(null: Span) -> OxcDiagnostic {
2525
OxcDiagnostic::warn("Do not use `null` literals").with_label(null)
2626
}
2727

28-
#[derive(Debug, Default, Clone, JsonSchema, Deserialize)]
28+
#[derive(Debug, Clone, JsonSchema, Deserialize)]
2929
#[serde(rename_all = "camelCase", default, deny_unknown_fields)]
3030
pub struct NoNull {
3131
/// When set to `true`, the rule will also check strict equality/inequality comparisons (`===` and `!==`) against `null`.
3232
check_strict_equality: bool,
33+
/// When set to `true`, disallow the use of `null` as a direct function call or constructor argument.
34+
check_arguments: bool,
35+
}
36+
37+
impl Default for NoNull {
38+
fn default() -> Self {
39+
Self { check_strict_equality: false, check_arguments: true }
40+
}
3341
}
3442

3543
declare_oxc_lint!(
@@ -75,6 +83,13 @@ fn match_null_arg(call_expr: &CallExpression, index: usize, span: Span) -> bool
7583
}
7684
}
7785

86+
fn match_direct_null_arg(arguments: &[Argument], span: Span) -> bool {
87+
arguments
88+
.iter()
89+
.filter_map(Argument::as_expression)
90+
.any(|expr| matches!(expr.get_inner_expression(), Expression::NullLiteral(null_lit) if span.contains_inclusive(null_lit.span)))
91+
}
92+
7893
impl NoNull {
7994
fn diagnose_binary_expression(
8095
&self,
@@ -198,6 +213,18 @@ impl Rule for NoNull {
198213
{
199214
// no violation
200215
}
216+
(AstKind::CallExpression(call_expr), _)
217+
if !self.check_arguments
218+
&& match_direct_null_arg(&call_expr.arguments, null_literal.span) =>
219+
{
220+
// no violation
221+
}
222+
(AstKind::NewExpression(new_expr), _)
223+
if !self.check_arguments
224+
&& match_direct_null_arg(&new_expr.arguments, null_literal.span) =>
225+
{
226+
// no violation
227+
}
201228
(AstKind::BinaryExpression(binary_expr), _) => {
202229
self.diagnose_binary_expression(ctx, null_literal, binary_expr);
203230
}
@@ -264,6 +291,17 @@ fn test() {
264291
"checkStrictEquality": option,
265292
}])
266293
}
294+
fn check_arguments(option: bool) -> serde_json::Value {
295+
serde_json::json!([{
296+
"checkArguments": option,
297+
}])
298+
}
299+
fn check_arguments_and_strict_equality() -> serde_json::Value {
300+
serde_json::json!([{
301+
"checkArguments": false,
302+
"checkStrictEquality": true,
303+
}])
304+
}
267305

268306
let pass = vec![
269307
("let foo", None),
@@ -290,6 +328,14 @@ fn test() {
290328
("if (foo !== null) {}", Some(check_strict_equality(false))),
291329
("if (null !== foo) {}", Some(check_strict_equality(false))),
292330
("if (foo === null || foo === undefined) {}", None),
331+
("foo(null)", Some(check_arguments(false))),
332+
("foo(bar, null)", Some(check_arguments(false))),
333+
("drawingManager.setMap(null)", Some(check_arguments(false))),
334+
("markers[index].setMap(null)", Some(check_arguments(false))),
335+
("object?.method?.(null)", Some(check_arguments(false))),
336+
("foo?.(null)", Some(check_arguments(false))),
337+
("new HttpResponse(null)", Some(check_arguments(false))),
338+
("new HttpResponse(body, null)", Some(check_arguments(false))),
293339
];
294340

295341
let fail = vec![
@@ -312,6 +358,18 @@ fn test() {
312358
("var foo = null;", None),
313359
("var foo = 1, bar = null, baz = 2;", None),
314360
("const foo = null;", None),
361+
("const foo = null;", Some(check_arguments(false))),
362+
(
363+
"function foo() {
364+
return null;
365+
}",
366+
Some(check_arguments(false)),
367+
),
368+
("if (foo === null) {}", Some(check_arguments_and_strict_equality())),
369+
("foo([null])", Some(check_arguments(false))),
370+
("foo(bar ?? null)", Some(check_arguments(false))),
371+
("foo(...[null])", Some(check_arguments(false))),
372+
("new HttpResponse([null])", Some(check_arguments(false))),
315373
// `checkStrictEquality`
316374
("if (foo === null) {}", Some(check_strict_equality(true))),
317375
("if (null === foo) {}", Some(check_strict_equality(true))),

crates/oxc_linter/src/snapshots/unicorn_no_null.snap

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,57 @@ source: crates/oxc_linter/src/tester.rs
8989
╰────
9090
help: Replace `null` with `undefined`.
9191

92+
unicorn(no-null): Do not use `null` literals
93+
╭─[no_null.tsx:1:13]
94+
1const foo = null;
95+
· ────
96+
╰────
97+
help: Replace `null` with `undefined`.
98+
99+
unicorn(no-null): Do not use `null` literals
100+
╭─[no_null.tsx:2:20]
101+
1function foo() {
102+
2return null;
103+
· ────
104+
3 │ }
105+
╰────
106+
help: Delete this code.
107+
108+
unicorn(no-null): Do not use `null` literals
109+
╭─[no_null.tsx:1:13]
110+
1if (foo === null) {}
111+
· ────
112+
╰────
113+
help: Replace `null` with `undefined`.
114+
115+
unicorn(no-null): Do not use `null` literals
116+
╭─[no_null.tsx:1:6]
117+
1foo([null])
118+
· ────
119+
╰────
120+
help: Replace `null` with `undefined`.
121+
122+
unicorn(no-null): Do not use `null` literals
123+
╭─[no_null.tsx:1:12]
124+
1foo(bar ?? null)
125+
· ────
126+
╰────
127+
help: Replace `null` with `undefined`.
128+
129+
unicorn(no-null): Do not use `null` literals
130+
╭─[no_null.tsx:1:9]
131+
1foo(...[null])
132+
· ────
133+
╰────
134+
help: Replace `null` with `undefined`.
135+
136+
unicorn(no-null): Do not use `null` literals
137+
╭─[no_null.tsx:1:19]
138+
1new HttpResponse([null])
139+
· ────
140+
╰────
141+
help: Replace `null` with `undefined`.
142+
92143
unicorn(no-null): Do not use `null` literals
93144
╭─[no_null.tsx:1:13]
94145
1if (foo === null) {}

npm/oxlint/configuration_schema.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10913,6 +10913,12 @@
1091310913
"NoNull": {
1091410914
"type": "object",
1091510915
"properties": {
10916+
"checkArguments": {
10917+
"description": "When set to `true`, disallow the use of `null` as a direct function call or constructor argument.",
10918+
"default": true,
10919+
"type": "boolean",
10920+
"markdownDescription": "When set to `true`, disallow the use of `null` as a direct function call or constructor argument."
10921+
},
1091610922
"checkStrictEquality": {
1091710923
"description": "When set to `true`, the rule will also check strict equality/inequality comparisons (`===` and `!==`) against `null`.",
1091810924
"default": false,

tasks/website_linter/src/snapshots/schema_json.snap

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10917,6 +10917,12 @@ expression: json
1091710917
"NoNull": {
1091810918
"type": "object",
1091910919
"properties": {
10920+
"checkArguments": {
10921+
"description": "When set to `true`, disallow the use of `null` as a direct function call or constructor argument.",
10922+
"default": true,
10923+
"type": "boolean",
10924+
"markdownDescription": "When set to `true`, disallow the use of `null` as a direct function call or constructor argument."
10925+
},
1092010926
"checkStrictEquality": {
1092110927
"description": "When set to `true`, the rule will also check strict equality/inequality comparisons (`===` and `!==`) against `null`.",
1092210928
"default": false,

0 commit comments

Comments
 (0)