Skip to content

Commit 17c3d6d

Browse files
committed
fix(linter): Improve docs, diagnostic message, and implementation of typescript/consistent-indexed-object-style rule. (#15750)
Diagnostic messages are grammatically correct and also tell the user what to actually do now. And the implementation of the rule has been refactored a bit to ensure the enum is used for the config options rather than a boolean, which was confusing (especially in the generated docs). Docs now look like this: ### What it does Choose between requiring either `Record` type or indexed signature types. These two types are equivalent, this rule enforces consistency in picking one style over the other: ```ts type Foo = Record<string, unknown>; type Foo = { [key: string]: unknown; } ``` ### Why is this bad? Inconsistent style for indexed object types can harm readability in a project. ### Examples Examples of **incorrect** code for this rule with `consistent-indexed-object-style: ["error", "record"]` (default): ```ts interface Foo { [key: string]: unknown; } type Foo = { [key: string]: unknown; }; ``` Examples of **correct** code for this rule with `consistent-indexed-object-style: ["error", "record"]` (default): ```ts type Foo = Record<string, unknown>; ``` Examples of **incorrect** code for this rule with `consistent-indexed-object-style: ["error", "index-signature"]`: ```ts type Foo = Record<string, unknown>; ``` Examples of **correct** code for this rule with `consistent-indexed-object-style: ["error", "index-signature"]`: ```ts interface Foo { [key: string]: unknown; } type Foo = { [key: string]: unknown; }; ``` ## Configuration This rule accepts a configuration object with the following properties: ### preferredStyle type: `"record" | "index-signature"` default: `"record"` When set to `record`, enforces the use of a `Record` for indexed object types, e.g. `Record<string, unknown>`. When set to `index-signature`, enforces the use of indexed signature types, e.g. `{ [key: string]: unknown }`.
1 parent 3ab750a commit 17c3d6d

File tree

2 files changed

+94
-84
lines changed

2 files changed

+94
-84
lines changed

crates/oxc_linter/src/rules/typescript/consistent_indexed_object_style.rs

Lines changed: 60 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -6,34 +6,48 @@ use oxc_diagnostics::OxcDiagnostic;
66
use oxc_macros::declare_oxc_lint;
77
use oxc_span::Span;
88
use schemars::JsonSchema;
9+
use serde::{Deserialize, Serialize};
910

1011
use crate::{
1112
AstNode,
1213
context::{ContextHost, LintContext},
1314
rule::Rule,
1415
};
1516

16-
fn consistent_indexed_object_style_diagnostic(a: &str, b: &str, span: Span) -> OxcDiagnostic {
17-
OxcDiagnostic::warn(format!("A {a} is preferred over an {b}."))
18-
.with_help(format!("A {a} is preferred over an {b}."))
19-
.with_label(span)
17+
fn consistent_indexed_object_style_diagnostic(
18+
preferred: ConsistentIndexedObjectStyleConfig,
19+
span: Span,
20+
) -> OxcDiagnostic {
21+
let (warning_message, help_message) = match preferred {
22+
ConsistentIndexedObjectStyleConfig::Record => (
23+
"A record is preferred over an index signature.",
24+
"Use a record type like `Record<string, unknown>` instead of an index signature.",
25+
),
26+
ConsistentIndexedObjectStyleConfig::IndexSignature => (
27+
"An index signature is preferred over a record.",
28+
"Use an index signature like `{ [key: string]: unknown }` instead of a record type.",
29+
),
30+
};
31+
32+
OxcDiagnostic::warn(warning_message).with_help(help_message).with_label(span)
2033
}
2134

2235
#[derive(Debug, Clone, JsonSchema)]
2336
#[serde(rename_all = "camelCase", default)]
2437
pub struct ConsistentIndexedObjectStyle {
25-
/// When set to `true`, enforces the use of `Record` type for indexed object types.
26-
/// When set to `false`, enforces the use of indexed signature types.
27-
is_record_mode: bool,
38+
/// When set to `record`, enforces the use of a `Record` for indexed object types, e.g. `Record<string, unknown>`.
39+
/// When set to `index-signature`, enforces the use of indexed signature types, e.g. `{ [key: string]: unknown }`.
40+
preferred_style: ConsistentIndexedObjectStyleConfig,
2841
}
2942

3043
impl Default for ConsistentIndexedObjectStyle {
3144
fn default() -> Self {
32-
Self { is_record_mode: true }
45+
Self { preferred_style: ConsistentIndexedObjectStyleConfig::Record }
3346
}
3447
}
3548

36-
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq)]
49+
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Deserialize, Serialize, JsonSchema)]
50+
#[serde(rename_all = "kebab-case")]
3751
enum ConsistentIndexedObjectStyleConfig {
3852
#[default]
3953
Record,
@@ -45,47 +59,54 @@ declare_oxc_lint!(
4559
///
4660
/// Choose between requiring either `Record` type or indexed signature types.
4761
///
62+
/// These two types are equivalent, this rule enforces consistency in picking one style over the other:
63+
///
64+
/// ```ts
65+
/// type Foo = Record<string, unknown>;
66+
///
67+
/// type Foo = {
68+
/// [key: string]: unknown;
69+
/// }
70+
/// ```
71+
///
4872
/// ### Why is this bad?
4973
///
5074
/// Inconsistent style for indexed object types can harm readability in a project.
5175
///
5276
/// ### Examples
5377
///
54-
/// Examples of **incorrect** code for this rule with the default "record":
55-
/// ```ts
56-
/// /*eslint consistent-indexed-object-style: ["error", "record"]*/
78+
/// Examples of **incorrect** code for this rule with
79+
/// `consistent-indexed-object-style: ["error", "record"]` (default):
5780
///
81+
/// ```ts
5882
/// interface Foo {
59-
/// [key: string]: unknown;
83+
/// [key: string]: unknown;
6084
/// }
6185
/// type Foo = {
62-
/// [key: string]: unknown;
86+
/// [key: string]: unknown;
6387
/// };
6488
/// ```
6589
///
66-
/// Examples of **correct** code for this rule:
90+
/// Examples of **correct** code for this rule with
91+
/// `consistent-indexed-object-style: ["error", "record"]` (default):
6792
/// ```ts
68-
/// /*eslint consistent-indexed-object-style: ["error", "record"]*/
69-
///
7093
/// type Foo = Record<string, unknown>;
7194
/// ```
7295
///
73-
/// Examples of **incorrect** code for this rule with "index-signature":
96+
/// Examples of **incorrect** code for this rule with
97+
/// `consistent-indexed-object-style: ["error", "index-signature"]`:
7498
/// ```ts
75-
/// /*eslint consistent-indexed-object-style: ["error", "index-signature"]*/
76-
///
7799
/// type Foo = Record<string, unknown>;
78100
/// ```
79101
///
80-
/// Examples of **correct** code for this rule:
102+
/// Examples of **correct** code for this rule with
103+
/// `consistent-indexed-object-style: ["error", "index-signature"]`:
81104
/// ```ts
82-
/// /*eslint consistent-indexed-object-style: ["error", "index-signature"]*/
83-
///
84105
/// interface Foo {
85-
/// [key: string]: unknown;
106+
/// [key: string]: unknown;
86107
/// }
87108
/// type Foo = {
88-
/// [key: string]: unknown;
109+
/// [key: string]: unknown;
89110
/// };
90111
/// ```
91112
ConsistentIndexedObjectStyle,
@@ -104,11 +125,13 @@ impl Rule for ConsistentIndexedObjectStyle {
104125
_ => ConsistentIndexedObjectStyleConfig::IndexSignature,
105126
},
106127
);
107-
Self { is_record_mode: config == ConsistentIndexedObjectStyleConfig::Record }
128+
Self { preferred_style: config }
108129
}
109130

110131
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
111-
if self.is_record_mode {
132+
let preferred_style = self.preferred_style;
133+
134+
if self.preferred_style == ConsistentIndexedObjectStyleConfig::Record {
112135
match node.kind() {
113136
AstKind::TSInterfaceDeclaration(inf) => {
114137
if inf.body.body.len() > 1 {
@@ -126,16 +149,14 @@ impl Rule for ConsistentIndexedObjectStyle {
126149
TSTypeName::IdentifierReference(ide) => {
127150
if ide.name != inf.id.name {
128151
ctx.diagnostic(consistent_indexed_object_style_diagnostic(
129-
"record",
130-
"index signature",
152+
preferred_style,
131153
sig.span,
132154
));
133155
}
134156
}
135157
TSTypeName::QualifiedName(_) | TSTypeName::ThisExpression(_) => {
136158
ctx.diagnostic(consistent_indexed_object_style_diagnostic(
137-
"record",
138-
"index signature",
159+
preferred_style,
139160
sig.span,
140161
));
141162
}
@@ -153,8 +174,7 @@ impl Rule for ConsistentIndexedObjectStyle {
153174

154175
if dec.id.name != ide.name {
155176
ctx.diagnostic(consistent_indexed_object_style_diagnostic(
156-
"record",
157-
"index signature",
177+
preferred_style,
158178
sig.span,
159179
));
160180
}
@@ -163,8 +183,7 @@ impl Rule for ConsistentIndexedObjectStyle {
163183
}
164184
_ => {
165185
ctx.diagnostic(consistent_indexed_object_style_diagnostic(
166-
"record",
167-
"index signature",
186+
preferred_style,
168187
sig.span,
169188
));
170189
}
@@ -190,16 +209,14 @@ impl Rule for ConsistentIndexedObjectStyle {
190209

191210
if ide.name != dec.id.name {
192211
ctx.diagnostic(consistent_indexed_object_style_diagnostic(
193-
"record",
194-
"index signature",
212+
preferred_style,
195213
sig.span,
196214
));
197215
}
198216
}
199217
TSTypeName::QualifiedName(_) | TSTypeName::ThisExpression(_) => {
200218
ctx.diagnostic(consistent_indexed_object_style_diagnostic(
201-
"record",
202-
"index signature",
219+
preferred_style,
203220
sig.span,
204221
));
205222
}
@@ -217,8 +234,7 @@ impl Rule for ConsistentIndexedObjectStyle {
217234

218235
if dec.id.name != ide.name {
219236
ctx.diagnostic(consistent_indexed_object_style_diagnostic(
220-
"record",
221-
"index signature",
237+
preferred_style,
222238
sig.span,
223239
));
224240
}
@@ -227,8 +243,7 @@ impl Rule for ConsistentIndexedObjectStyle {
227243
}
228244
_ => {
229245
ctx.diagnostic(consistent_indexed_object_style_diagnostic(
230-
"record",
231-
"index signature",
246+
preferred_style,
232247
sig.span,
233248
));
234249
}
@@ -252,11 +267,7 @@ impl Rule for ConsistentIndexedObjectStyle {
252267
&tref.type_arguments.as_ref().and_then(|params| params.params.first())
253268
{
254269
ctx.diagnostic_with_fix(
255-
consistent_indexed_object_style_diagnostic(
256-
"index signature",
257-
"record",
258-
tref.span,
259-
),
270+
consistent_indexed_object_style_diagnostic(preferred_style, tref.span),
260271
|fixer| {
261272
let key = fixer.source_range(first.span);
262273
let params_span = Span::new(first.span.end + 1, tref.span.end - 1);
@@ -267,8 +278,7 @@ impl Rule for ConsistentIndexedObjectStyle {
267278
);
268279
} else {
269280
ctx.diagnostic(consistent_indexed_object_style_diagnostic(
270-
"index signature",
271-
"record",
281+
preferred_style,
272282
tref.span,
273283
));
274284
}

0 commit comments

Comments
 (0)