Skip to content

Commit bce6154

Browse files
committed
feat(linter/prefer-includes): add rule (#15774)
1 parent cac06e2 commit bce6154

File tree

8 files changed

+127
-5
lines changed

8 files changed

+127
-5
lines changed

apps/oxlint/fixtures/tsgolint/.oxlintrc.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
"typescript/no-unsafe-unary-minus": "error",
3131
"typescript/non-nullable-type-assertion-style": "error",
3232
"typescript/only-throw-error": "error",
33+
"typescript/prefer-includes": "error",
3334
"typescript/prefer-promise-reject-errors": "error",
3435
"typescript/prefer-reduce-type-parameter": "error",
3536
"typescript/prefer-return-this-type": "error",
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
const text = 'hello';
2+
if (text.indexOf('h') !== -1) {
3+
}
4+
5+
const items = [1, 2];
6+
if (items.indexOf(1) !== -1) {
7+
}
8+
9+
if (/test/.test(text)) {
10+
}

apps/oxlint/src/snapshots/fixtures__tsgolint_--type-aware [email protected]

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ arguments: --type-aware --silent
66
working directory: fixtures/tsgolint
77
----------
88

9-
Found 0 warnings and 54 errors.
10-
Finished in <variable>ms on 43 files using 1 threads.
9+
Found 0 warnings and 57 errors.
10+
Finished in <variable>ms on 44 files using 1 threads.
1111
----------
1212
CLI result: LintFoundErrors
1313
----------

apps/oxlint/src/snapshots/fixtures__tsgolint_--type-aware -c [email protected]

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ working directory: fixtures/tsgolint
3838
help: Remove the debugger statement
3939
4040
Found 2 warnings and 2 errors.
41-
Finished in <variable>ms on 43 files with 1 rules using 1 threads.
41+
Finished in <variable>ms on 44 files with 1 rules using 1 threads.
4242
----------
4343
CLI result: LintFoundErrors
4444
----------

apps/oxlint/src/snapshots/[email protected]

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,30 @@ working directory: fixtures/tsgolint
246246
: ^^^^^^^
247247
`----
248248
249+
x typescript-eslint(prefer-includes): Use 'includes()' method instead.
250+
,-[prefer-includes.ts:2:5]
251+
1 | const text = 'hello';
252+
2 | if (text.indexOf('h') !== -1) {
253+
: ^^^^^^^^^^^^^^^^^^^^^^^^
254+
3 | }
255+
`----
256+
257+
x typescript-eslint(prefer-includes): Use 'includes()' method instead.
258+
,-[prefer-includes.ts:6:5]
259+
5 | const items = [1, 2];
260+
6 | if (items.indexOf(1) !== -1) {
261+
: ^^^^^^^^^^^^^^^^^^^^^^^
262+
7 | }
263+
`----
264+
265+
x typescript-eslint(prefer-includes): Use `String#includes()` method with a string instead.
266+
,-[prefer-includes.ts:9:5]
267+
8 |
268+
9 | if (/test/.test(text)) {
269+
: ^^^^^^^^^^^^^^^^^
270+
10 | }
271+
`----
272+
249273
x typescript-eslint(prefer-promise-reject-errors): Expected the Promise rejection reason to be an Error.
250274
,-[prefer-promise-reject-errors.ts:1:1]
251275
1 | Promise.reject('error');
@@ -396,8 +420,8 @@ working directory: fixtures/tsgolint
396420
: ^^^^^^^^
397421
`----
398422
399-
Found 0 warnings and 54 errors.
400-
Finished in <variable>ms on 43 files using 1 threads.
423+
Found 0 warnings and 57 errors.
424+
Finished in <variable>ms on 44 files using 1 threads.
401425
----------
402426
CLI result: LintFoundErrors
403427
----------

crates/oxc_linter/src/generated/rule_runner_impls.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2890,6 +2890,11 @@ impl RuleRunner for crate::rules::typescript::prefer_function_type::PreferFuncti
28902890
const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Run;
28912891
}
28922892

2893+
impl RuleRunner for crate::rules::typescript::prefer_includes::PreferIncludes {
2894+
const NODE_TYPES: Option<&AstTypesBitset> = None;
2895+
const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Unknown;
2896+
}
2897+
28932898
impl RuleRunner for crate::rules::typescript::prefer_literal_enum_member::PreferLiteralEnumMember {
28942899
const NODE_TYPES: Option<&AstTypesBitset> =
28952900
Some(&AstTypesBitset::from_types(&[AstType::TSEnumMember]));

crates/oxc_linter/src/rules.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,7 @@ pub(crate) mod typescript {
266266
pub mod prefer_enum_initializers;
267267
pub mod prefer_for_of;
268268
pub mod prefer_function_type;
269+
pub mod prefer_includes;
269270
pub mod prefer_literal_enum_member;
270271
pub mod prefer_namespace_keyword;
271272
pub mod prefer_promise_reject_errors;
@@ -1130,6 +1131,7 @@ oxc_macros::declare_all_lint_rules! {
11301131
typescript::prefer_enum_initializers,
11311132
typescript::prefer_for_of,
11321133
typescript::prefer_function_type,
1134+
typescript::prefer_includes,
11331135
typescript::prefer_literal_enum_member,
11341136
typescript::prefer_namespace_keyword,
11351137
typescript::prefer_promise_reject_errors,
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
use oxc_macros::declare_oxc_lint;
2+
3+
use crate::rule::Rule;
4+
5+
#[derive(Debug, Default, Clone)]
6+
pub struct PreferIncludes;
7+
8+
declare_oxc_lint!(
9+
/// ### What it does
10+
///
11+
/// Enforce using `.includes()` instead of `.indexOf() !== -1` or `/regex/.test()`.
12+
///
13+
/// ### Why is this bad?
14+
///
15+
/// `.includes()` is more readable and expressive than checking `.indexOf() !== -1`.
16+
/// It clearly communicates the intent to check for the presence of a value.
17+
/// Additionally, for simple string searches, `.includes()` is often preferred over
18+
/// regex `.test()` for better performance and clarity.
19+
///
20+
/// ### Examples
21+
///
22+
/// Examples of **incorrect** code for this rule:
23+
/// ```ts
24+
/// // Using indexOf
25+
/// const str = 'hello world';
26+
/// if (str.indexOf('world') !== -1) {
27+
/// console.log('found');
28+
/// }
29+
///
30+
/// if (str.indexOf('world') != -1) {
31+
/// console.log('found');
32+
/// }
33+
///
34+
/// if (str.indexOf('world') > -1) {
35+
/// console.log('found');
36+
/// }
37+
///
38+
/// // Using regex test for simple strings
39+
/// if (/world/.test(str)) {
40+
/// console.log('found');
41+
/// }
42+
///
43+
/// // Arrays
44+
/// const arr = [1, 2, 3];
45+
/// if (arr.indexOf(2) !== -1) {
46+
/// console.log('found');
47+
/// }
48+
/// ```
49+
///
50+
/// Examples of **correct** code for this rule:
51+
/// ```ts
52+
/// // Using includes for strings
53+
/// const str = 'hello world';
54+
/// if (str.includes('world')) {
55+
/// console.log('found');
56+
/// }
57+
///
58+
/// // Using includes for arrays
59+
/// const arr = [1, 2, 3];
60+
/// if (arr.includes(2)) {
61+
/// console.log('found');
62+
/// }
63+
///
64+
/// // Complex regex patterns are allowed
65+
/// if (/wo+rld/.test(str)) {
66+
/// console.log('found');
67+
/// }
68+
///
69+
/// // Regex with flags
70+
/// if (/world/i.test(str)) {
71+
/// console.log('found');
72+
/// }
73+
/// ```
74+
PreferIncludes(tsgolint),
75+
typescript,
76+
pedantic,
77+
pending,
78+
);
79+
80+
impl Rule for PreferIncludes {}

0 commit comments

Comments
 (0)