Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions docs/src/rules/accessor-pairs.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ This rule always checks object literals and property descriptors. By default, it
* `setWithoutGet` set to `true` will warn for setters without getters (Default `true`).
* `getWithoutSet` set to `true` will warn for getters without setters (Default `false`).
* `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.
* `enforceForTSTypes`: set to `true` additionally applies this rule to TypeScript type definitions (Default `false`).

### setWithoutGet

Expand Down Expand Up @@ -273,6 +274,71 @@ const Quux = class {

:::

### enforceForTSTypes

When `enforceForTSTypes` is set to `true`:

* `"getWithoutSet": true` will also warn for getters without setters in TypeScript types.
* `"setWithoutGet": true` will also warn for setters without getters in TypeScript types.

Examples of **incorrect** code for `{ "getWithoutSet": true, "enforceForTSTypes": true }`:

:::incorrect

```ts
/*eslint accessor-pairs: ["error", { "getWithoutSet": true, "enforceForTSTypes": true }]*/

interface I {
get a(): string
}

type T = {
get a(): number
}
```

:::

Examples of **incorrect** code for `{ "setWithoutGet": true, "enforceForTSTypes": true }`:

:::incorrect

```ts
/*eslint accessor-pairs: ["error", { "setWithoutGet": true, "enforceForTSTypes": true }]*/

interface I {
set a(value: unknown): void
}

type T = {
set a(value: unknown): void
}
```

:::

When `enforceForTSTypes` is set to `false`, this rule ignores TypeScript types.

Examples of **correct** code for `{ "getWithoutSet": true, "setWithoutGet": true, "enforceForTSTypes": false }`:

:::correct

```ts
/*eslint accessor-pairs: ["error", {
"getWithoutSet": true, "setWithoutGet": true, "enforceForTSTypes": false
}]*/

interface I {
get a(): string
}

type T = {
set a(value: unknown): void
}
```

:::

## Known Limitations

Due to the limits of static analysis, this rule does not account for possible side effects and in certain cases
Expand Down
50 changes: 48 additions & 2 deletions docs/src/rules/grouped-accessor-pairs.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,12 +152,15 @@ const Bar = class {

## Options

This rule has a string option:

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

The optional object option allows opting-in to check additional object-likes:
* `enforceForTSTypes`: also check TypeScript types (interfaces and type literals)

### getBeforeSet

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

:::
### enforceForTSTypes

Examples of **incorrect** code for this rule with `["anyOrder", { enforceForTSTypes: true }]`:

::: incorrect

```ts
/*eslint grouped-accessor-pairs: ["error", "anyOrder", { enforceForTSTypes: true }] */

interface I {
get a(): string,
between: true,
set a(value: string): void
}

type T = {
get a(): string,
between: true,
set a(value: string): void
};
```

:::

Examples of **correct** code for this rule with with `["anyOrder", { enforceForTSTypes: true }]`:

::: correct

```ts
/*eslint grouped-accessor-pairs: ["error", "anyOrder", { enforceForTSTypes: true }] */

interface I {
get a(): string,
set a(value: string): void,
}

type T = {
set a(value: string): void,
get a(): string,
};
```

:::

## Known Limitations
Expand Down
35 changes: 35 additions & 0 deletions lib/rules/accessor-pairs.js
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ module.exports = {

defaultOptions: [
{
enforceForTSTypes: false,
enforceForClassMembers: true,
getWithoutSet: false,
setWithoutGet: true,
Expand All @@ -174,6 +175,9 @@ module.exports = {
enforceForClassMembers: {
type: "boolean",
},
enforceForTSTypes: {
type: "boolean",
},
},
additionalProperties: false,
},
Expand All @@ -190,6 +194,8 @@ module.exports = {
"Setter is not present for {{ name }}.",
missingGetterInClass: "Getter is not present for class {{ name }}.",
missingSetterInClass: "Setter is not present for class {{ name }}.",
missingGetterInType: "Getter is not present for type {{ name }}.",
missingSetterInType: "Setter is not present for type {{ name }}.",
},
},
create(context) {
Expand All @@ -198,6 +204,7 @@ module.exports = {
getWithoutSet: checkGetWithoutSet,
setWithoutGet: checkSetWithoutGet,
enforceForClassMembers,
enforceForTSTypes,
},
] = context.options;
const sourceCode = context.sourceCode;
Expand Down Expand Up @@ -228,6 +235,15 @@ module.exports = {
name: astUtils.getFunctionNameWithKind(node.value),
},
});
} else if (node.type === "TSMethodSignature") {
context.report({
node,
messageId: `${messageKind}InType`,
loc: astUtils.getFunctionHeadLoc(node, sourceCode),
data: {
name: astUtils.getFunctionNameWithKind(node),
},
});
} else {
context.report({
node,
Expand Down Expand Up @@ -371,13 +387,32 @@ module.exports = {
checkList(methodDefinitions.filter(m => !m.static));
}

/**
* Checks the given type.
* @param {ASTNode} node `TSTypeLiteral` or `TSInterfaceBody` node to check.
* @returns {void}
* @private
*/
function checkType(node) {
const members =
node.type === "TSTypeLiteral" ? node.members : node.body;
const methodDefinitions = members.filter(
m => m.type === "TSMethodSignature",
);

checkList(methodDefinitions);
}

const listeners = {};

if (checkSetWithoutGet || checkGetWithoutSet) {
listeners.ObjectExpression = checkObjectExpression;
if (enforceForClassMembers) {
listeners.ClassBody = checkClassBody;
}
if (enforceForTSTypes) {
listeners["TSTypeLiteral, TSInterfaceBody"] = checkType;
}
}

return listeners;
Expand Down
42 changes: 36 additions & 6 deletions lib/rules/grouped-accessor-pairs.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ function isAccessorKind(node) {
return node.kind === "get" || node.kind === "set";
}

const DEFAULT_ORDER = "anyOrder";

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
Expand All @@ -96,7 +98,7 @@ module.exports = {
meta: {
type: "suggestion",

defaultOptions: ["anyOrder"],
defaultOptions: [DEFAULT_ORDER],

docs: {
description:
Expand All @@ -106,8 +108,15 @@ module.exports = {
},

schema: [
{ enum: ["anyOrder", "getBeforeSet", "setBeforeGet"] },
{
enum: ["anyOrder", "getBeforeSet", "setBeforeGet"],
type: "object",
properties: {
enforceForTSTypes: {
type: "boolean",
},
},
additionalProperties: false,
},
],

Expand All @@ -120,7 +129,9 @@ module.exports = {
},

create(context) {
const [order] = context.options;
const order = context.options[0] ?? DEFAULT_ORDER;
const enforceForTSTypes =
context.options[1]?.enforceForTSTypes ?? false;
const sourceCode = context.sourceCode;

/**
Expand All @@ -135,13 +146,22 @@ module.exports = {
context.report({
node: latterNode,
messageId,
loc: astUtils.getFunctionHeadLoc(latterNode.value, sourceCode),
loc: astUtils.getFunctionHeadLoc(
latterNode.type !== "TSMethodSignature"
? latterNode.value
: latterNode,
sourceCode,
),
data: {
formerName: astUtils.getFunctionNameWithKind(
formerNode.value,
formerNode.type !== "TSMethodSignature"
? formerNode.value
: formerNode,
),
latterName: astUtils.getFunctionNameWithKind(
latterNode.value,
latterNode.type !== "TSMethodSignature"
? latterNode.value
: latterNode,
),
},
});
Expand Down Expand Up @@ -232,6 +252,16 @@ module.exports = {
n => n.type === "MethodDefinition" && n.static,
);
},
"TSTypeLiteral, TSInterfaceBody"(node) {
if (enforceForTSTypes) {
checkList(
node.type === "TSTypeLiteral"
? node.members
: node.body,
n => n.type === "TSMethodSignature",
);
}
},
};
},
};
22 changes: 19 additions & 3 deletions lib/rules/utils/ast-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,8 @@ function getStaticPropertyName(node) {
case "Property":
case "PropertyDefinition":
case "MethodDefinition":
case "TSPropertySignature":
case "TSMethodSignature":
prop = node.key;
break;

Expand Down Expand Up @@ -1944,13 +1946,15 @@ module.exports = {

if (
parent.type === "MethodDefinition" ||
parent.type === "PropertyDefinition"
parent.type === "PropertyDefinition" ||
node.type === "TSPropertySignature" ||
node.type === "TSMethodSignature"
) {
// The proposal uses `static` word consistently before visibility words: https://github.com/tc39/proposal-static-class-features
if (parent.static) {
tokens.push("static");
}
if (!parent.computed && parent.key.type === "PrivateIdentifier") {
if (!parent.computed && parent.key?.type === "PrivateIdentifier") {
tokens.push("private");
}
}
Expand All @@ -1972,6 +1976,14 @@ module.exports = {
} else {
tokens.push("method");
}
} else if (node.type === "TSMethodSignature") {
if (node.kind === "get") {
tokens.push("getter");
} else if (node.kind === "set") {
tokens.push("setter");
} else {
tokens.push("method");
}
} else if (parent.type === "PropertyDefinition") {
tokens.push("method");
} else {
Expand All @@ -1997,6 +2009,8 @@ module.exports = {
tokens.push(`'${node.id.name}'`);
}
}
} else if (node.type === "TSMethodSignature") {
tokens.push(`'${getStaticPropertyName(node)}'`);
} else if (node.id) {
tokens.push(`'${node.id.name}'`);
}
Expand Down Expand Up @@ -2109,7 +2123,9 @@ module.exports = {
if (
parent.type === "Property" ||
parent.type === "MethodDefinition" ||
parent.type === "PropertyDefinition"
parent.type === "PropertyDefinition" ||
parent.type === "TSPropertySignature" ||
parent.type === "TSMethodSignature"
) {
start = parent.loc.start;
end = getOpeningParenOfParams(node, sourceCode).loc.start;
Expand Down
Loading
Loading