Skip to content

Commit 18aede1

Browse files
committedAug 5, 2024··
feat(no-classes): add options ignoreIdentifierPattern and ignoreCodePattern (#863)
fix #851
1 parent 39beb25 commit 18aede1

File tree

4 files changed

+133
-33
lines changed

4 files changed

+133
-33
lines changed
 

‎docs/rules/no-classes.md

+16-30
Original file line numberDiff line numberDiff line change
@@ -53,43 +53,29 @@ const dogA = {
5353
console.log(`${dogA.name} is ${getAgeInDogYears(dogA.age)} in dog years.`);
5454
```
5555

56-
### React Examples
56+
## Options
5757

58-
Thanks to libraries like [recompose](https://github.com/acdlite/recompose) and Redux's
59-
[React Container components](http://redux.js.org/docs/basics/UsageWithReact.html), there's not much reason to build
60-
Components using `React.createClass` or ES6 classes anymore. The `no-this-expressions` rule makes this explicit.
58+
This rule accepts an options object of the following type:
6159

62-
```js
63-
const Message = React.createClass({
64-
render() {
65-
return <div>{this.props.message}</div>; // <- no this allowed
66-
},
67-
});
60+
```ts
61+
type Options = {
62+
ignoreIdentifierPattern?: string[] | string;
63+
ignoreCodePattern?: string[] | string;
64+
};
6865
```
6966

70-
Instead of creating classes, you should use React 0.14's
71-
[Stateless Functional
72-
Components](https://medium.com/@joshblack/stateless-components-in-react-0-14-f9798f8b992d#.t5z2fdit6)
73-
and save yourself some keystrokes:
67+
### Default Options
7468

75-
```js
76-
const Message = ({ message }) => <div>{message}</div>;
69+
```ts
70+
const defaults = {};
7771
```
7872

79-
What about lifecycle methods like `shouldComponentUpdate`?
80-
We can use the [recompose](https://github.com/acdlite/recompose) library to apply these optimizations to your
81-
Stateless Functional Components. The [recompose](https://github.com/acdlite/recompose) library relies on the fact that
82-
your Redux state is immutable to efficiently implement `shouldComponentUpdate` for you.
73+
### `ignoreIdentifierPattern`
8374

84-
```js
85-
import { onlyUpdateForKeys, pure } from "recompose";
86-
87-
const Message = ({ message }) => <div>{message}</div>;
75+
This option takes a RegExp string or an array of RegExp strings.
76+
It allows for the ability to ignore violations based on the class's name.
8877

89-
// Optimized version of same component, using shallow comparison of props
90-
// Same effect as React's PureRenderMixin
91-
const OptimizedMessage = pure(Message);
78+
### `ignoreCodePattern`
9279

93-
// Even more optimized: only updates if specific prop keys have changed
94-
const HyperOptimizedMessage = onlyUpdateForKeys(["message"], Message);
95-
```
80+
This option takes a RegExp string or an array of RegExp strings.
81+
It allows for the ability to ignore violations based on the code itself.

‎src/rules/no-classes.ts

+38-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
import type { JSONSchema4 } from "@typescript-eslint/utils/json-schema";
22
import type { RuleContext } from "@typescript-eslint/utils/ts-eslint";
3+
import { deepmerge } from "deepmerge-ts";
34

5+
import {
6+
type IgnoreCodePatternOption,
7+
type IgnoreIdentifierPatternOption,
8+
ignoreCodePatternOptionSchema,
9+
ignoreIdentifierPatternOptionSchema,
10+
shouldIgnorePattern,
11+
} from "#/options";
412
import { ruleNameScope } from "#/utils/misc";
513
import type { ESClass } from "#/utils/node-types";
614
import {
@@ -23,12 +31,21 @@ export const fullName: `${typeof ruleNameScope}/${typeof name}` = `${ruleNameSco
2331
/**
2432
* The options this rule can take.
2533
*/
26-
type Options = [{}];
34+
type Options = [IgnoreIdentifierPatternOption & IgnoreCodePatternOption];
2735

2836
/**
2937
* The schema for the rule options.
3038
*/
31-
const schema: JSONSchema4[] = [];
39+
const schema: JSONSchema4[] = [
40+
{
41+
type: "object",
42+
properties: deepmerge(
43+
ignoreIdentifierPatternOptionSchema,
44+
ignoreCodePatternOptionSchema,
45+
),
46+
additionalProperties: false,
47+
},
48+
];
3249

3350
/**
3451
* The default options for the rule.
@@ -64,8 +81,26 @@ const meta: NamedCreateRuleCustomMeta<keyof typeof errorMessages> = {
6481
function checkClass(
6582
node: ESClass,
6683
context: Readonly<RuleContext<keyof typeof errorMessages, Options>>,
84+
options: Readonly<Options>,
6785
): RuleResult<keyof typeof errorMessages, Options> {
68-
// All class nodes violate this rule.
86+
const [optionsObject] = options;
87+
const { ignoreIdentifierPattern, ignoreCodePattern } = optionsObject;
88+
89+
if (
90+
shouldIgnorePattern(
91+
node,
92+
context,
93+
ignoreIdentifierPattern,
94+
undefined,
95+
ignoreCodePattern,
96+
)
97+
) {
98+
return {
99+
context,
100+
descriptors: [],
101+
};
102+
}
103+
69104
return { context, descriptors: [{ node, messageId: "generic" }] };
70105
}
71106

‎tests/rules/__snapshots__/no-classes.test.ts.snap

+32
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,37 @@
11
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
22

3+
exports[`no-classes > javascript - es latest > ignoreCodePattern > should report classes with non-matching identifiers 1`] = `
4+
[
5+
{
6+
"column": 1,
7+
"endColumn": 13,
8+
"endLine": 1,
9+
"line": 1,
10+
"message": "Unexpected class, use functions not classes.",
11+
"messageId": "generic",
12+
"nodeType": "ClassDeclaration",
13+
"ruleId": "no-classes",
14+
"severity": 2,
15+
},
16+
]
17+
`;
18+
19+
exports[`no-classes > javascript - es latest > options > ignoreIdentifierPattern > should report classes with non-matching identifiers 1`] = `
20+
[
21+
{
22+
"column": 1,
23+
"endColumn": 13,
24+
"endLine": 1,
25+
"line": 1,
26+
"message": "Unexpected class, use functions not classes.",
27+
"messageId": "generic",
28+
"nodeType": "ClassDeclaration",
29+
"ruleId": "no-classes",
30+
"severity": 2,
31+
},
32+
]
33+
`;
34+
335
exports[`no-classes > javascript - es latest > reports class declarations 1`] = `
436
[
537
{

‎tests/rules/no-classes.test.ts

+47
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import dedent from "dedent";
12
import { createRuleTester } from "eslint-vitest-rule-tester";
23
import { describe, expect, it } from "vitest";
34

@@ -30,5 +31,51 @@ describe(name, () => {
3031
});
3132
expect(invalidResult2.messages).toMatchSnapshot();
3233
});
34+
35+
describe("options", () => {
36+
describe("ignoreIdentifierPattern", () => {
37+
it("should not report classes with matching identifiers", () => {
38+
valid({
39+
code: dedent`
40+
class Foo {}
41+
`,
42+
options: [{ ignoreIdentifierPattern: "^Foo$" }],
43+
});
44+
});
45+
46+
it("should report classes with non-matching identifiers", () => {
47+
const invalidResult = invalid({
48+
code: dedent`
49+
class Bar {}
50+
`,
51+
options: [{ ignoreIdentifierPattern: "^Foo$" }],
52+
errors: ["generic"],
53+
});
54+
expect(invalidResult.messages).toMatchSnapshot();
55+
});
56+
});
57+
});
58+
59+
describe("ignoreCodePattern", () => {
60+
it("should not report classes with matching identifiers", () => {
61+
valid({
62+
code: dedent`
63+
class Foo {}
64+
`,
65+
options: [{ ignoreCodePattern: "class Foo" }],
66+
});
67+
});
68+
69+
it("should report classes with non-matching identifiers", () => {
70+
const invalidResult = invalid({
71+
code: dedent`
72+
class Bar {}
73+
`,
74+
options: [{ ignoreCodePattern: "class Foo" }],
75+
errors: ["generic"],
76+
});
77+
expect(invalidResult.messages).toMatchSnapshot();
78+
});
79+
});
3380
});
3481
});

0 commit comments

Comments
 (0)
Please sign in to comment.