Skip to content

Commit 0e957a7

Browse files
authored
feat: support typescript types in accessor rules (#19882)
1 parent 2f73a23 commit 0e957a7

File tree

8 files changed

+487
-12
lines changed

8 files changed

+487
-12
lines changed

docs/src/rules/accessor-pairs.md

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ This rule always checks object literals and property descriptors. By default, it
5151
* `setWithoutGet` set to `true` will warn for setters without getters (Default `true`).
5252
* `getWithoutSet` set to `true` will warn for getters without setters (Default `false`).
5353
* `enforceForClassMembers` set to `true` additionally applies this rule to class getters/setters (Default `true`). Set `enforceForClassMembers` to `false` if you want this rule to ignore class declarations and class expressions.
54+
* `enforceForTSTypes`: set to `true` additionally applies this rule to TypeScript type definitions (Default `false`).
5455

5556
### setWithoutGet
5657

@@ -273,6 +274,71 @@ const Quux = class {
273274

274275
:::
275276

277+
### enforceForTSTypes
278+
279+
When `enforceForTSTypes` is set to `true`:
280+
281+
* `"getWithoutSet": true` will also warn for getters without setters in TypeScript types.
282+
* `"setWithoutGet": true` will also warn for setters without getters in TypeScript types.
283+
284+
Examples of **incorrect** code for `{ "getWithoutSet": true, "enforceForTSTypes": true }`:
285+
286+
:::incorrect
287+
288+
```ts
289+
/*eslint accessor-pairs: ["error", { "getWithoutSet": true, "enforceForTSTypes": true }]*/
290+
291+
interface I {
292+
get a(): string
293+
}
294+
295+
type T = {
296+
get a(): number
297+
}
298+
```
299+
300+
:::
301+
302+
Examples of **incorrect** code for `{ "setWithoutGet": true, "enforceForTSTypes": true }`:
303+
304+
:::incorrect
305+
306+
```ts
307+
/*eslint accessor-pairs: ["error", { "setWithoutGet": true, "enforceForTSTypes": true }]*/
308+
309+
interface I {
310+
set a(value: unknown): void
311+
}
312+
313+
type T = {
314+
set a(value: unknown): void
315+
}
316+
```
317+
318+
:::
319+
320+
When `enforceForTSTypes` is set to `false`, this rule ignores TypeScript types.
321+
322+
Examples of **correct** code for `{ "getWithoutSet": true, "setWithoutGet": true, "enforceForTSTypes": false }`:
323+
324+
:::correct
325+
326+
```ts
327+
/*eslint accessor-pairs: ["error", {
328+
"getWithoutSet": true, "setWithoutGet": true, "enforceForTSTypes": false
329+
}]*/
330+
331+
interface I {
332+
get a(): string
333+
}
334+
335+
type T = {
336+
set a(value: unknown): void
337+
}
338+
```
339+
340+
:::
341+
276342
## Known Limitations
277343
278344
Due to the limits of static analysis, this rule does not account for possible side effects and in certain cases

docs/src/rules/grouped-accessor-pairs.md

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,12 +152,15 @@ const Bar = class {
152152

153153
## Options
154154

155-
This rule has a string option:
156-
155+
This rule has a primary string and an optional secondary object option.
156+
The string option specifies the order:
157157
* `"anyOrder"` (default) does not enforce order.
158158
* `"getBeforeSet"` if a property has both getter and setter, requires the getter to be defined before the setter.
159159
* `"setBeforeGet"` if a property has both getter and setter, requires the setter to be defined before the getter.
160160

161+
The optional object option allows opting-in to check additional object-likes:
162+
* `enforceForTSTypes`: also check TypeScript types (interfaces and type literals)
163+
161164
### getBeforeSet
162165

163166
Examples of **incorrect** code for this rule with the `"getBeforeSet"` option:
@@ -308,6 +311,49 @@ const Bar = class {
308311
}
309312
```
310313

314+
:::
315+
### enforceForTSTypes
316+
317+
Examples of **incorrect** code for this rule with `["anyOrder", { enforceForTSTypes: true }]`:
318+
319+
::: incorrect
320+
321+
```ts
322+
/*eslint grouped-accessor-pairs: ["error", "anyOrder", { enforceForTSTypes: true }] */
323+
324+
interface I {
325+
get a(): string,
326+
between: true,
327+
set a(value: string): void
328+
}
329+
330+
type T = {
331+
get a(): string,
332+
between: true,
333+
set a(value: string): void
334+
};
335+
```
336+
337+
:::
338+
339+
Examples of **correct** code for this rule with with `["anyOrder", { enforceForTSTypes: true }]`:
340+
341+
::: correct
342+
343+
```ts
344+
/*eslint grouped-accessor-pairs: ["error", "anyOrder", { enforceForTSTypes: true }] */
345+
346+
interface I {
347+
get a(): string,
348+
set a(value: string): void,
349+
}
350+
351+
type T = {
352+
set a(value: string): void,
353+
get a(): string,
354+
};
355+
```
356+
311357
:::
312358

313359
## Known Limitations

lib/rules/accessor-pairs.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ module.exports = {
148148

149149
defaultOptions: [
150150
{
151+
enforceForTSTypes: false,
151152
enforceForClassMembers: true,
152153
getWithoutSet: false,
153154
setWithoutGet: true,
@@ -174,6 +175,9 @@ module.exports = {
174175
enforceForClassMembers: {
175176
type: "boolean",
176177
},
178+
enforceForTSTypes: {
179+
type: "boolean",
180+
},
177181
},
178182
additionalProperties: false,
179183
},
@@ -190,6 +194,8 @@ module.exports = {
190194
"Setter is not present for {{ name }}.",
191195
missingGetterInClass: "Getter is not present for class {{ name }}.",
192196
missingSetterInClass: "Setter is not present for class {{ name }}.",
197+
missingGetterInType: "Getter is not present for type {{ name }}.",
198+
missingSetterInType: "Setter is not present for type {{ name }}.",
193199
},
194200
},
195201
create(context) {
@@ -198,6 +204,7 @@ module.exports = {
198204
getWithoutSet: checkGetWithoutSet,
199205
setWithoutGet: checkSetWithoutGet,
200206
enforceForClassMembers,
207+
enforceForTSTypes,
201208
},
202209
] = context.options;
203210
const sourceCode = context.sourceCode;
@@ -228,6 +235,15 @@ module.exports = {
228235
name: astUtils.getFunctionNameWithKind(node.value),
229236
},
230237
});
238+
} else if (node.type === "TSMethodSignature") {
239+
context.report({
240+
node,
241+
messageId: `${messageKind}InType`,
242+
loc: astUtils.getFunctionHeadLoc(node, sourceCode),
243+
data: {
244+
name: astUtils.getFunctionNameWithKind(node),
245+
},
246+
});
231247
} else {
232248
context.report({
233249
node,
@@ -371,13 +387,32 @@ module.exports = {
371387
checkList(methodDefinitions.filter(m => !m.static));
372388
}
373389

390+
/**
391+
* Checks the given type.
392+
* @param {ASTNode} node `TSTypeLiteral` or `TSInterfaceBody` node to check.
393+
* @returns {void}
394+
* @private
395+
*/
396+
function checkType(node) {
397+
const members =
398+
node.type === "TSTypeLiteral" ? node.members : node.body;
399+
const methodDefinitions = members.filter(
400+
m => m.type === "TSMethodSignature",
401+
);
402+
403+
checkList(methodDefinitions);
404+
}
405+
374406
const listeners = {};
375407

376408
if (checkSetWithoutGet || checkGetWithoutSet) {
377409
listeners.ObjectExpression = checkObjectExpression;
378410
if (enforceForClassMembers) {
379411
listeners.ClassBody = checkClassBody;
380412
}
413+
if (enforceForTSTypes) {
414+
listeners["TSTypeLiteral, TSInterfaceBody"] = checkType;
415+
}
381416
}
382417

383418
return listeners;

lib/rules/grouped-accessor-pairs.js

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ function isAccessorKind(node) {
8787
return node.kind === "get" || node.kind === "set";
8888
}
8989

90+
const DEFAULT_ORDER = "anyOrder";
91+
9092
//------------------------------------------------------------------------------
9193
// Rule Definition
9294
//------------------------------------------------------------------------------
@@ -96,7 +98,7 @@ module.exports = {
9698
meta: {
9799
type: "suggestion",
98100

99-
defaultOptions: ["anyOrder"],
101+
defaultOptions: [DEFAULT_ORDER],
100102

101103
docs: {
102104
description:
@@ -106,8 +108,15 @@ module.exports = {
106108
},
107109

108110
schema: [
111+
{ enum: ["anyOrder", "getBeforeSet", "setBeforeGet"] },
109112
{
110-
enum: ["anyOrder", "getBeforeSet", "setBeforeGet"],
113+
type: "object",
114+
properties: {
115+
enforceForTSTypes: {
116+
type: "boolean",
117+
},
118+
},
119+
additionalProperties: false,
111120
},
112121
],
113122

@@ -120,7 +129,9 @@ module.exports = {
120129
},
121130

122131
create(context) {
123-
const [order] = context.options;
132+
const order = context.options[0] ?? DEFAULT_ORDER;
133+
const enforceForTSTypes =
134+
context.options[1]?.enforceForTSTypes ?? false;
124135
const sourceCode = context.sourceCode;
125136

126137
/**
@@ -135,13 +146,22 @@ module.exports = {
135146
context.report({
136147
node: latterNode,
137148
messageId,
138-
loc: astUtils.getFunctionHeadLoc(latterNode.value, sourceCode),
149+
loc: astUtils.getFunctionHeadLoc(
150+
latterNode.type !== "TSMethodSignature"
151+
? latterNode.value
152+
: latterNode,
153+
sourceCode,
154+
),
139155
data: {
140156
formerName: astUtils.getFunctionNameWithKind(
141-
formerNode.value,
157+
formerNode.type !== "TSMethodSignature"
158+
? formerNode.value
159+
: formerNode,
142160
),
143161
latterName: astUtils.getFunctionNameWithKind(
144-
latterNode.value,
162+
latterNode.type !== "TSMethodSignature"
163+
? latterNode.value
164+
: latterNode,
145165
),
146166
},
147167
});
@@ -232,6 +252,16 @@ module.exports = {
232252
n => n.type === "MethodDefinition" && n.static,
233253
);
234254
},
255+
"TSTypeLiteral, TSInterfaceBody"(node) {
256+
if (enforceForTSTypes) {
257+
checkList(
258+
node.type === "TSTypeLiteral"
259+
? node.members
260+
: node.body,
261+
n => n.type === "TSMethodSignature",
262+
);
263+
}
264+
},
235265
};
236266
},
237267
};

lib/rules/utils/ast-utils.js

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,8 @@ function getStaticPropertyName(node) {
298298
case "Property":
299299
case "PropertyDefinition":
300300
case "MethodDefinition":
301+
case "TSPropertySignature":
302+
case "TSMethodSignature":
301303
prop = node.key;
302304
break;
303305

@@ -1989,13 +1991,15 @@ module.exports = {
19891991

19901992
if (
19911993
parent.type === "MethodDefinition" ||
1992-
parent.type === "PropertyDefinition"
1994+
parent.type === "PropertyDefinition" ||
1995+
node.type === "TSPropertySignature" ||
1996+
node.type === "TSMethodSignature"
19931997
) {
19941998
// The proposal uses `static` word consistently before visibility words: https://github.com/tc39/proposal-static-class-features
19951999
if (parent.static) {
19962000
tokens.push("static");
19972001
}
1998-
if (!parent.computed && parent.key.type === "PrivateIdentifier") {
2002+
if (!parent.computed && parent.key?.type === "PrivateIdentifier") {
19992003
tokens.push("private");
20002004
}
20012005
}
@@ -2017,6 +2021,14 @@ module.exports = {
20172021
} else {
20182022
tokens.push("method");
20192023
}
2024+
} else if (node.type === "TSMethodSignature") {
2025+
if (node.kind === "get") {
2026+
tokens.push("getter");
2027+
} else if (node.kind === "set") {
2028+
tokens.push("setter");
2029+
} else {
2030+
tokens.push("method");
2031+
}
20202032
} else if (parent.type === "PropertyDefinition") {
20212033
tokens.push("method");
20222034
} else {
@@ -2042,6 +2054,8 @@ module.exports = {
20422054
tokens.push(`'${node.id.name}'`);
20432055
}
20442056
}
2057+
} else if (node.type === "TSMethodSignature") {
2058+
tokens.push(`'${getStaticPropertyName(node)}'`);
20452059
} else if (node.id) {
20462060
tokens.push(`'${node.id.name}'`);
20472061
}
@@ -2154,7 +2168,9 @@ module.exports = {
21542168
if (
21552169
parent.type === "Property" ||
21562170
parent.type === "MethodDefinition" ||
2157-
parent.type === "PropertyDefinition"
2171+
parent.type === "PropertyDefinition" ||
2172+
parent.type === "TSPropertySignature" ||
2173+
parent.type === "TSMethodSignature"
21582174
) {
21592175
start = parent.loc.start;
21602176
end = getOpeningParenOfParams(node, sourceCode).loc.start;

0 commit comments

Comments
 (0)