Skip to content

Commit

Permalink
feat(no-expression-statements): add option to ignore self returning f…
Browse files Browse the repository at this point in the history
…unctions

fix #611
  • Loading branch information
RebeccaStevens committed Jul 21, 2023
1 parent a7094fc commit ca04a34
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 11 deletions.
8 changes: 8 additions & 0 deletions docs/rules/no-expression-statements.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ This rule accepts an options object of the following type:
type Options = {
ignorePattern?: string[] | string;
ignoreVoid?: boolean;
ignoreSelfReturning?: boolean;
};
```

Expand All @@ -68,13 +69,20 @@ type Options = {
```ts
const defaults = {
ignoreVoid: false,
ignoreSelfReturning: false,
};
```

### `ignoreVoid`

When enabled, expression of type void are not flagged as violations. This options requires TypeScript in order to work.

### `ignoreSelfReturning`

Like `ignoreVoid` but instead does not flag function calls that always only return `this`.

Limitation: The function declaration must explicitly use `return this`; equivalents (such as assign this to a variable first, that is then returned) won't be considered valid.

### `ignorePattern`

This option takes a RegExp string or an array of RegExp strings.
Expand Down
67 changes: 56 additions & 11 deletions src/rules/no-expression-statements.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import {
} from "@typescript-eslint/utils/json-schema";
import { type RuleContext } from "@typescript-eslint/utils/ts-eslint";
import { deepmerge } from "deepmerge-ts";
import { isThisKeyword } from "ts-api-utils";

import tsApiUtils from "#eslint-plugin-functional/conditional-imports/ts-api-utils";
import typescript from "#eslint-plugin-functional/conditional-imports/typescript";
import { type IgnorePatternOption } from "#eslint-plugin-functional/options";
import {
shouldIgnorePattern,
Expand All @@ -21,7 +23,10 @@ import {
createRule,
getTypeOfNode,
} from "#eslint-plugin-functional/utils/rule";
import { isYieldExpression } from "#eslint-plugin-functional/utils/type-guards";
import {
isCallExpression,
isYieldExpression,
} from "#eslint-plugin-functional/utils/type-guards";

/**
* The name of this rule.
Expand All @@ -34,6 +39,7 @@ export const name = "no-expression-statements" as const;
type Options = [
IgnorePatternOption & {
ignoreVoid: boolean;
ignoreSelfReturning: boolean;
},
];

Expand All @@ -47,6 +53,9 @@ const schema: JSONSchema4[] = [
ignoreVoid: {
type: "boolean",
},
ignoreSelfReturning: {
type: "boolean",
},
} satisfies JSONSchema4ObjectSchema["properties"]),
additionalProperties: false,
},
Expand All @@ -58,6 +67,7 @@ const schema: JSONSchema4[] = [
const defaultOptions: Options = [
{
ignoreVoid: false,
ignoreSelfReturning: false,
},
];

Expand Down Expand Up @@ -107,18 +117,53 @@ function checkExpressionStatement(
};
}

const { ignoreVoid } = optionsObject;
const { ignoreVoid, ignoreSelfReturning } = optionsObject;

if (ignoreVoid) {
const type = getTypeOfNode(node.expression, context);
if (
(ignoreVoid || ignoreSelfReturning) &&
isCallExpression(node.expression)
) {
const returnType = getTypeOfNode(node.expression.callee, context);
if (returnType === null) {
return {
context,
descriptors: [{ node, messageId: "generic" }],
};
}

return {
context,
descriptors:
type !== null && tsApiUtils?.isIntrinsicVoidType(type) === true
? []
: [{ node, messageId: "generic" }],
};
if (ignoreVoid && tsApiUtils?.isIntrinsicVoidType(returnType) === true) {
return {
context,
descriptors: [],
};
}

const declaration = returnType.getSymbol()?.valueDeclaration;
if (
typescript !== undefined &&
declaration !== undefined &&
typescript.isFunctionLike(declaration) &&
"body" in declaration &&
declaration.body !== undefined &&
typescript.isBlock(declaration.body)
) {
const returnStatements = declaration.body.statements.filter(
typescript.isReturnStatement,
);

if (
returnStatements.every(
(statement) =>
statement.expression !== undefined &&
isThisKeyword(statement.expression),
)
) {
return {
context,
descriptors: [],
};
}
}
}

return {
Expand Down
23 changes: 23 additions & 0 deletions tests/rules/no-expression-statement/ts/valid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,29 @@ const tests: Array<ValidTestCaseSet<OptionsOf<typeof rule>>> = [
`,
optionsSet: [[{ ignoreVoid: true }]],
},
// Allowed ignoring self returning expressions.
{
code: dedent`
function foo() { return this; }
foo();
`,
optionsSet: [[{ ignoreSelfReturning: true }]],
},
{
code: dedent`
const foo = { bar() { return this; }};
foo.bar();
`,
optionsSet: [[{ ignoreSelfReturning: true }]],
},
{
code: dedent`
class Foo { bar() { return this; }};
const foo = new Foo();
foo.bar();
`,
optionsSet: [[{ ignoreSelfReturning: true }]],
},
];

export default tests;

0 comments on commit ca04a34

Please sign in to comment.