diff --git a/docs/rules/no-classes.md b/docs/rules/no-classes.md index 66c02a38c..50cceebbd 100644 --- a/docs/rules/no-classes.md +++ b/docs/rules/no-classes.md @@ -53,43 +53,29 @@ const dogA = { console.log(`${dogA.name} is ${getAgeInDogYears(dogA.age)} in dog years.`); ``` -### React Examples +## Options -Thanks to libraries like [recompose](https://github.com/acdlite/recompose) and Redux's -[React Container components](http://redux.js.org/docs/basics/UsageWithReact.html), there's not much reason to build -Components using `React.createClass` or ES6 classes anymore. The `no-this-expressions` rule makes this explicit. +This rule accepts an options object of the following type: -```js -const Message = React.createClass({ - render() { - return
{this.props.message}
; // <- no this allowed - }, -}); +```ts +type Options = { + ignoreIdentifierPattern?: string[] | string; + ignoreCodePattern?: string[] | string; +}; ``` -Instead of creating classes, you should use React 0.14's -[Stateless Functional -Components](https://medium.com/@joshblack/stateless-components-in-react-0-14-f9798f8b992d#.t5z2fdit6) -and save yourself some keystrokes: +### Default Options -```js -const Message = ({ message }) =>
{message}
; +```ts +const defaults = {}; ``` -What about lifecycle methods like `shouldComponentUpdate`? -We can use the [recompose](https://github.com/acdlite/recompose) library to apply these optimizations to your -Stateless Functional Components. The [recompose](https://github.com/acdlite/recompose) library relies on the fact that -your Redux state is immutable to efficiently implement `shouldComponentUpdate` for you. +### `ignoreIdentifierPattern` -```js -import { onlyUpdateForKeys, pure } from "recompose"; - -const Message = ({ message }) =>
{message}
; +This option takes a RegExp string or an array of RegExp strings. +It allows for the ability to ignore violations based on the class's name. -// Optimized version of same component, using shallow comparison of props -// Same effect as React's PureRenderMixin -const OptimizedMessage = pure(Message); +### `ignoreCodePattern` -// Even more optimized: only updates if specific prop keys have changed -const HyperOptimizedMessage = onlyUpdateForKeys(["message"], Message); -``` +This option takes a RegExp string or an array of RegExp strings. +It allows for the ability to ignore violations based on the code itself. diff --git a/src/rules/no-classes.ts b/src/rules/no-classes.ts index cc2c52f7e..7f04c9788 100644 --- a/src/rules/no-classes.ts +++ b/src/rules/no-classes.ts @@ -1,6 +1,14 @@ import { type JSONSchema4 } from "@typescript-eslint/utils/json-schema"; import { type RuleContext } from "@typescript-eslint/utils/ts-eslint"; +import { deepmerge } from "deepmerge-ts"; +import { + type IgnoreCodePatternOption, + type IgnoreIdentifierPatternOption, + ignoreCodePatternOptionSchema, + ignoreIdentifierPatternOptionSchema, + shouldIgnorePattern, +} from "#/options"; import { ruleNameScope } from "#/utils/misc"; import { type ESClass } from "#/utils/node-types"; import { @@ -23,12 +31,21 @@ export const fullName: `${typeof ruleNameScope}/${typeof name}` = `${ruleNameSco /** * The options this rule can take. */ -type Options = [{}]; +type Options = [IgnoreIdentifierPatternOption & IgnoreCodePatternOption]; /** * The schema for the rule options. */ -const schema: JSONSchema4[] = []; +const schema: JSONSchema4[] = [ + { + type: "object", + properties: deepmerge( + ignoreIdentifierPatternOptionSchema, + ignoreCodePatternOptionSchema, + ), + additionalProperties: false, + }, +]; /** * The default options for the rule. @@ -64,8 +81,26 @@ const meta: NamedCreateRuleCustomMeta = { function checkClass( node: ESClass, context: Readonly>, + options: Readonly, ): RuleResult { - // All class nodes violate this rule. + const [optionsObject] = options; + const { ignoreIdentifierPattern, ignoreCodePattern } = optionsObject; + + if ( + shouldIgnorePattern( + node, + context, + ignoreIdentifierPattern, + undefined, + ignoreCodePattern, + ) + ) { + return { + context, + descriptors: [], + }; + } + return { context, descriptors: [{ node, messageId: "generic" }] }; } diff --git a/tests/rules/__snapshots__/no-classes.test.ts.snap b/tests/rules/__snapshots__/no-classes.test.ts.snap index b4d296a36..cc30e10ef 100644 --- a/tests/rules/__snapshots__/no-classes.test.ts.snap +++ b/tests/rules/__snapshots__/no-classes.test.ts.snap @@ -1,5 +1,37 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html +exports[`no-classes > javascript - es latest > ignoreCodePattern > should report classes with non-matching identifiers 1`] = ` +[ + { + "column": 1, + "endColumn": 13, + "endLine": 1, + "line": 1, + "message": "Unexpected class, use functions not classes.", + "messageId": "generic", + "nodeType": "ClassDeclaration", + "ruleId": "no-classes", + "severity": 2, + }, +] +`; + +exports[`no-classes > javascript - es latest > options > ignoreIdentifierPattern > should report classes with non-matching identifiers 1`] = ` +[ + { + "column": 1, + "endColumn": 13, + "endLine": 1, + "line": 1, + "message": "Unexpected class, use functions not classes.", + "messageId": "generic", + "nodeType": "ClassDeclaration", + "ruleId": "no-classes", + "severity": 2, + }, +] +`; + exports[`no-classes > javascript - es latest > reports class declarations 1`] = ` [ { diff --git a/tests/rules/no-classes.test.ts b/tests/rules/no-classes.test.ts index 05346b6c0..b78e8951f 100644 --- a/tests/rules/no-classes.test.ts +++ b/tests/rules/no-classes.test.ts @@ -1,3 +1,4 @@ +import dedent from "dedent"; import { createRuleTester } from "eslint-vitest-rule-tester"; import { describe, expect, it } from "vitest"; @@ -30,5 +31,51 @@ describe(name, () => { }); expect(invalidResult2.messages).toMatchSnapshot(); }); + + describe("options", () => { + describe("ignoreIdentifierPattern", () => { + it("should not report classes with matching identifiers", () => { + valid({ + code: dedent` + class Foo {} + `, + options: [{ ignoreIdentifierPattern: "^Foo$" }], + }); + }); + + it("should report classes with non-matching identifiers", () => { + const invalidResult = invalid({ + code: dedent` + class Bar {} + `, + options: [{ ignoreIdentifierPattern: "^Foo$" }], + errors: ["generic"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + }); + }); + + describe("ignoreCodePattern", () => { + it("should not report classes with matching identifiers", () => { + valid({ + code: dedent` + class Foo {} + `, + options: [{ ignoreCodePattern: "class Foo" }], + }); + }); + + it("should report classes with non-matching identifiers", () => { + const invalidResult = invalid({ + code: dedent` + class Bar {} + `, + options: [{ ignoreCodePattern: "class Foo" }], + errors: ["generic"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + }); }); });