diff --git a/apps/website/content/docs/rules/overview.mdx b/apps/website/content/docs/rules/overview.mdx index 54f1073c1..f2b535d9f 100644 --- a/apps/website/content/docs/rules/overview.mdx +++ b/apps/website/content/docs/rules/overview.mdx @@ -135,7 +135,7 @@ full: true | [`component-name`](naming-convention-component-name) | 0️⃣ | `🔍` `⚙️` | Enforces naming conventions for components. | | [`filename`](naming-convention-filename) | 0️⃣ | `🔍` `⚙️` | Enforces naming convention for JSX files. | | [`filename-extension`](naming-convention-filename-extension) | 0️⃣ | `🔍` `⚙️` | Enforces consistent use of the JSX file extension. | -| [`use-state`](naming-convention-use-state) | 0️⃣ | `🔍` | Enforces destructuring and symmetric naming of `useState` hook value and setter variables. | +| [`use-state`](naming-convention-use-state) | 0️⃣ | `🔍` | Enforces destructuring and symmetric naming of `useState` hook value and setter. | ## Debug Rules diff --git a/packages/plugins/eslint-plugin-react-naming-convention/src/rules/component-name.spec.ts b/packages/plugins/eslint-plugin-react-naming-convention/src/rules/component-name.spec.ts index cedf902c5..1b166a67c 100644 --- a/packages/plugins/eslint-plugin-react-naming-convention/src/rules/component-name.spec.ts +++ b/packages/plugins/eslint-plugin-react-naming-convention/src/rules/component-name.spec.ts @@ -5,21 +5,21 @@ ruleTester.run(RULE_NAME, rule, { invalid: [ { code: /* tsx */ ``, - errors: [{ messageId: "componentName" }], + errors: [{ messageId: "usePascalCase" }], }, { code: /* tsx */ ``, - errors: [{ messageId: "componentName" }], + errors: [{ messageId: "useConstantCase" }], options: [{ rule: "CONSTANT_CASE" }], }, { code: /* tsx */ ``, - errors: [{ messageId: "componentName" }], + errors: [{ messageId: "useConstantCase" }], options: ["CONSTANT_CASE"], }, { code: /* tsx */ ``, - errors: [{ messageId: "componentName" }], + errors: [{ messageId: "usePascalCase" }], options: [{ allowAllCaps: false, rule: "PascalCase" }], }, { @@ -28,7 +28,7 @@ ruleTester.run(RULE_NAME, rule, { return
foo
} `, - errors: [{ messageId: "componentName" }], + errors: [{ messageId: "useConstantCase" }], options: [{ rule: "CONSTANT_CASE" }], }, { @@ -39,7 +39,7 @@ ruleTester.run(RULE_NAME, rule, { ) } `, - errors: [{ messageId: "componentName" }], + errors: [{ messageId: "useConstantCase" }], options: [{ allowLeadingUnderscore: false, rule: "CONSTANT_CASE" }], }, ], diff --git a/packages/plugins/eslint-plugin-react-naming-convention/src/rules/component-name.ts b/packages/plugins/eslint-plugin-react-naming-convention/src/rules/component-name.ts index af1fd4127..c914b8cfe 100644 --- a/packages/plugins/eslint-plugin-react-naming-convention/src/rules/component-name.ts +++ b/packages/plugins/eslint-plugin-react-naming-convention/src/rules/component-name.ts @@ -1,12 +1,10 @@ import * as AST from "@eslint-react/ast"; import { useComponentCollector, useComponentCollectorLegacy } from "@eslint-react/core"; -import type { _ } from "@eslint-react/eff"; -import { constFalse } from "@eslint-react/eff"; +import { _ } from "@eslint-react/eff"; import * as JSX from "@eslint-react/jsx"; import type { RuleFeature } from "@eslint-react/shared"; import { RE_CONSTANT_CASE, RE_PASCAL_CASE } from "@eslint-react/shared"; import type { JSONSchema4 } from "@typescript-eslint/utils/json-schema"; -import type { CamelCase } from "string-ts"; import { match } from "ts-pattern"; import { createRule } from "../utils"; @@ -18,7 +16,9 @@ export const RULE_FEATURES = [ "CFG", ] as const satisfies RuleFeature[]; -export type MessageID = CamelCase; +export type MessageID = + | "usePascalCase" + | "useConstantCase"; type Case = "CONSTANT_CASE" | "PascalCase"; @@ -88,32 +88,43 @@ function normalizeOptions(options: Options) { } as const; } -function validate(name: string | _, options: ReturnType) { - if (name == null) return false; - if (options.excepts.some((regex) => regex.test(name))) { - return true; +function getViolationMessage(name: string | _, options: ReturnType): MessageID | _ { + if (name == null) return _; + const { + allowAllCaps = false, + allowLeadingUnderscore = false, + allowNamespace = false, + excepts, + rule, + } = options; + if (excepts.some((regex) => regex.test(name))) { + return _; } let normalized = name .normalize("NFKD") .replace(/[\u0300-\u036F]/g, ""); normalized = normalized.split(".").at(-1) ?? normalized; - const { allowLeadingUnderscore = false, allowNamespace = false } = options; if (allowNamespace) { normalized = normalized.replace(":", ""); } if (allowLeadingUnderscore) { normalized = normalized.replace(/^_/, ""); } - return match(options.rule) - .with("CONSTANT_CASE", () => RE_CONSTANT_CASE.test(normalized)) + return match(rule) + .with("CONSTANT_CASE", () => + RE_CONSTANT_CASE.test(normalized) + ? _ + : "useConstantCase") .with("PascalCase", () => { // Allow all caps if the string is shorter than 4 characters. e.g. UI, CSS, SVG, etc. if (normalized.length > 3 && /^[A-Z]+$/u.test(normalized)) { - return options.allowAllCaps ?? false; + return allowAllCaps + ? _ + : "usePascalCase"; } - return RE_PASCAL_CASE.test(normalized); + return RE_PASCAL_CASE.test(normalized) ? _ : "usePascalCase"; }) - .otherwise(constFalse); + .otherwise(() => _); } export default createRule({ @@ -124,7 +135,8 @@ export default createRule({ description: "enforce component naming convention to 'PascalCase' or 'CONSTANT_CASE'", }, messages: { - componentName: "A component name must be in {{case}}.", + useConstantCase: "Component name '{{name}}' must be in CONSTANT_CASE.", + usePascalCase: "Component name '{{name}}' must be in PascalCase.", }, schema, }, @@ -143,14 +155,13 @@ export default createRule({ if (/^[a-z]/u.test(name)) { return; } - if (validate(name, options)) { - return; - } + const violation = getViolationMessage(name, options); + if (violation == null) return; context.report({ - messageId: "componentName", + messageId: violation, node, data: { - case: options.rule, + name, }, }); }, @@ -160,29 +171,30 @@ export default createRule({ for (const { node: component } of functionComponents.values()) { const id = AST.getFunctionIdentifier(component); if (id?.name == null) continue; - if (validate(id.name, options)) { - continue; - } + const name = id.name; + const violation = getViolationMessage(name, options); + if (violation == null) continue; context.report({ - messageId: "componentName", + messageId: violation, node: id, data: { - case: options.rule, + name, }, }); } for (const { node: component } of classComponents.values()) { const id = AST.getClassIdentifier(component); if (id?.name == null) continue; - if (!validate(id.name, options)) { - context.report({ - messageId: "componentName", - node: id, - data: { - case: options.rule, - }, - }); - } + const name = id.name; + const violation = getViolationMessage(name, options); + if (violation == null) continue; + context.report({ + messageId: violation, + node: id, + data: { + case: options.rule, + }, + }); } }, }; diff --git a/packages/plugins/eslint-plugin-react-naming-convention/src/rules/filename-extension.spec.ts b/packages/plugins/eslint-plugin-react-naming-convention/src/rules/filename-extension.spec.ts index ea2411d86..b15a18c49 100644 --- a/packages/plugins/eslint-plugin-react-naming-convention/src/rules/filename-extension.spec.ts +++ b/packages/plugins/eslint-plugin-react-naming-convention/src/rules/filename-extension.spec.ts @@ -11,7 +11,7 @@ ruleTester.run(RULE_NAME, rule, { code: withoutJSX, errors: [ { - messageId: "filenameExtensionUnexpected", + messageId: "useJsxFileExtensionAsNeeded", }, ], filename: "react.tsx", @@ -21,7 +21,7 @@ ruleTester.run(RULE_NAME, rule, { code: withoutJSX, errors: [ { - messageId: "filenameExtensionUnexpected", + messageId: "useJsxFileExtensionAsNeeded", }, ], filename: "react.tsx", @@ -31,7 +31,7 @@ ruleTester.run(RULE_NAME, rule, { code: withJSXElement, errors: [ { - messageId: "filenameExtensionInvalid", + messageId: "useJsxFileExtension", }, ], filename: "react.tsx", diff --git a/packages/plugins/eslint-plugin-react-naming-convention/src/rules/filename-extension.ts b/packages/plugins/eslint-plugin-react-naming-convention/src/rules/filename-extension.ts index 38c73637d..1a6eff735 100644 --- a/packages/plugins/eslint-plugin-react-naming-convention/src/rules/filename-extension.ts +++ b/packages/plugins/eslint-plugin-react-naming-convention/src/rules/filename-extension.ts @@ -12,8 +12,8 @@ export const RULE_FEATURES = [ ] as const satisfies RuleFeature[]; export type MessageID = - | "filenameExtensionInvalid" - | "filenameExtensionUnexpected"; + | "useJsxFileExtension" + | "useJsxFileExtensionAsNeeded"; type Allow = "always" | "as-needed"; @@ -74,8 +74,8 @@ export default createRule({ description: "enforce naming convention for JSX file extensions", }, messages: { - filenameExtensionInvalid: "The JSX file extension is required.", - filenameExtensionUnexpected: "Use JSX file extension as needed.", + useJsxFileExtension: "Use {{extensions}} file extension for JSX files.", + useJsxFileExtensionAsNeeded: "Do not use {{extensions}} file extension for files without JSX.", }, schema, }, @@ -86,6 +86,7 @@ export default createRule({ const extensions = isObject(options) && "extensions" in options ? options.extensions : defaultOptions[0].extensions; + const extensionsString = extensions.map((ext) => `'${ext}'`).join(", "); const filename = context.filename; let hasJSXNode = false; @@ -102,8 +103,11 @@ export default createRule({ const isJSXExt = extensions.includes(fileNameExt); if (hasJSXNode && !isJSXExt) { context.report({ - messageId: "filenameExtensionInvalid", + messageId: "useJsxFileExtension", node, + data: { + extensions: extensionsString, + }, }); return; } @@ -119,8 +123,11 @@ export default createRule({ && allow === "as-needed" ) { context.report({ - messageId: "filenameExtensionUnexpected", + messageId: "useJsxFileExtensionAsNeeded", node, + data: { + extensions: extensionsString, + }, }); } }, diff --git a/packages/plugins/eslint-plugin-react-naming-convention/src/rules/use-state.md b/packages/plugins/eslint-plugin-react-naming-convention/src/rules/use-state.md index bc523fc6e..13377c730 100644 --- a/packages/plugins/eslint-plugin-react-naming-convention/src/rules/use-state.md +++ b/packages/plugins/eslint-plugin-react-naming-convention/src/rules/use-state.md @@ -20,7 +20,7 @@ react-naming-convention/use-state ## What it does -Enforces destructuring and symmetric naming of `useState` hook value and setter variables +Enforces destructuring and symmetric naming of `useState` hook value and setter ## Examples diff --git a/packages/plugins/eslint-plugin-react-naming-convention/src/rules/use-state.spec.ts b/packages/plugins/eslint-plugin-react-naming-convention/src/rules/use-state.spec.ts index f50f91b85..255b89443 100644 --- a/packages/plugins/eslint-plugin-react-naming-convention/src/rules/use-state.spec.ts +++ b/packages/plugins/eslint-plugin-react-naming-convention/src/rules/use-state.spec.ts @@ -12,7 +12,7 @@ ruleTester.run(RULE_NAME, rule, { } `, errors: [{ - messageId: "useState", + messageId: "unexpected", data: { setterName: "setState", stateName: "state", @@ -28,7 +28,7 @@ ruleTester.run(RULE_NAME, rule, { } `, errors: [{ - messageId: "useState", + messageId: "unexpected", data: { setterName: "setState", stateName: "state", @@ -51,7 +51,7 @@ ruleTester.run(RULE_NAME, rule, { } `, errors: [{ - messageId: "useState", + messageId: "unexpected", data: { setterName: "setState", stateName: "state", @@ -69,7 +69,7 @@ ruleTester.run(RULE_NAME, rule, { } `, errors: [{ - messageId: "useState", + messageId: "unexpected", data: { setterName: "setState", stateName: "state", @@ -87,7 +87,7 @@ ruleTester.run(RULE_NAME, rule, { } `, errors: [{ - messageId: "useState", + messageId: "unexpected", data: { setterName: "setState", stateName: "state", @@ -105,7 +105,7 @@ ruleTester.run(RULE_NAME, rule, { } `, errors: [{ - messageId: "useState", + messageId: "unexpected", data: { setterName: "setState", stateName: "state", diff --git a/packages/plugins/eslint-plugin-react-naming-convention/src/rules/use-state.ts b/packages/plugins/eslint-plugin-react-naming-convention/src/rules/use-state.ts index c92b080cd..4480a45fe 100644 --- a/packages/plugins/eslint-plugin-react-naming-convention/src/rules/use-state.ts +++ b/packages/plugins/eslint-plugin-react-naming-convention/src/rules/use-state.ts @@ -7,7 +7,6 @@ import { import type { RuleFeature } from "@eslint-react/shared"; import { getSettingsFromContext } from "@eslint-react/shared"; import { AST_NODE_TYPES as T } from "@typescript-eslint/types"; -import type { CamelCase } from "string-ts"; import { capitalize } from "string-ts"; import { createRule } from "../utils"; @@ -18,7 +17,7 @@ export const RULE_FEATURES = [ "CHK", ] as const satisfies RuleFeature[]; -export type MessageID = CamelCase; +export type MessageID = "unexpected"; function isSetterNameLoose(name: string) { // eslint-disable-next-line @typescript-eslint/no-misused-spread @@ -32,11 +31,11 @@ export default createRule<[], MessageID>({ meta: { type: "problem", docs: { - description: "enforce destructuring and symmetric naming of 'useState' hook value and setter variables", + description: "enforce destructuring and symmetric naming of 'useState' hook value and setter", [Symbol.for("rule_features")]: RULE_FEATURES, }, messages: { - useState: "An useState call is not destructured into value + setter pair.", + unexpected: "An useState call is not destructured into value + setter pair.", }, schema: [], }, @@ -73,14 +72,14 @@ export default createRule<[], MessageID>({ const { id } = hookCall.parent; switch (id.type) { case T.Identifier: { - context.report({ messageId: "useState", node: id }); + context.report({ messageId: "unexpected", node: id }); break; } case T.ArrayPattern: { const [state, setState] = id.elements; if (state?.type === T.ObjectPattern && setState?.type === T.Identifier) { if (!isSetterNameLoose(setState.name)) { - context.report({ messageId: "useState", node: id }); + context.report({ messageId: "unexpected", node: id }); } break; } @@ -92,7 +91,7 @@ export default createRule<[], MessageID>({ if (setStateName === expectedSetterName) { return; } - context.report({ messageId: "useState", node: id }); + context.report({ messageId: "unexpected", node: id }); } } } diff --git a/packages/plugins/eslint-plugin-react-web-api/src/rules/no-leaked-event-listener.spec.ts b/packages/plugins/eslint-plugin-react-web-api/src/rules/no-leaked-event-listener.spec.ts index 94199189c..6b9083796 100644 --- a/packages/plugins/eslint-plugin-react-web-api/src/rules/no-leaked-event-listener.spec.ts +++ b/packages/plugins/eslint-plugin-react-web-api/src/rules/no-leaked-event-listener.spec.ts @@ -13,7 +13,7 @@ ruleTester.run(RULE_NAME, rule, { `, errors: [ { - messageId: "noLeakedEventListenerInLifecycle", + messageId: "expectedRemoveEventListenerInUnmount", }, ], }, @@ -27,7 +27,7 @@ ruleTester.run(RULE_NAME, rule, { `, errors: [ { - messageId: "noLeakedEventListenerInEffect", + messageId: "expectedRemoveEventListenerInCleanup", }, ], }, @@ -46,7 +46,7 @@ ruleTester.run(RULE_NAME, rule, { `, errors: [ { - messageId: "noLeakedEventListenerInEffect", + messageId: "expectedRemoveEventListenerInCleanup", }, ], }, @@ -63,15 +63,15 @@ ruleTester.run(RULE_NAME, rule, { `, errors: [ { - messageId: "noLeakedEventListenerInEffect", + messageId: "expectedRemoveEventListenerInCleanup", data: { effectMethodKind: "useEffect", eventMethodKind: "addEventListener" }, }, { - messageId: "noLeakedEventListenerOfInlineFunction", + messageId: "unexpectedInlineFunction", data: { eventMethodKind: "addEventListener" }, }, { - messageId: "noLeakedEventListenerOfInlineFunction", + messageId: "unexpectedInlineFunction", data: { eventMethodKind: "removeEventListener" }, }, ], @@ -89,15 +89,15 @@ ruleTester.run(RULE_NAME, rule, { `, errors: [ { - messageId: "noLeakedEventListenerInEffect", + messageId: "expectedRemoveEventListenerInCleanup", data: { effectMethodKind: "useEffect", eventMethodKind: "addEventListener" }, }, { - messageId: "noLeakedEventListenerOfInlineFunction", + messageId: "unexpectedInlineFunction", data: { eventMethodKind: "addEventListener" }, }, { - messageId: "noLeakedEventListenerOfInlineFunction", + messageId: "unexpectedInlineFunction", data: { eventMethodKind: "removeEventListener" }, }, ], @@ -113,7 +113,7 @@ ruleTester.run(RULE_NAME, rule, { `, errors: [ { - messageId: "noLeakedEventListenerInEffect", + messageId: "expectedRemoveEventListenerInCleanup", }, ], }, @@ -131,15 +131,15 @@ ruleTester.run(RULE_NAME, rule, { `, errors: [ { - messageId: "noLeakedEventListenerInEffect", + messageId: "expectedRemoveEventListenerInCleanup", data: { effectMethodKind: "useEffect", eventMethodKind: "addEventListener" }, }, { - messageId: "noLeakedEventListenerOfInlineFunction", + messageId: "unexpectedInlineFunction", data: { eventMethodKind: "addEventListener" }, }, { - messageId: "noLeakedEventListenerOfInlineFunction", + messageId: "unexpectedInlineFunction", data: { eventMethodKind: "removeEventListener" }, }, ], @@ -158,7 +158,7 @@ ruleTester.run(RULE_NAME, rule, { `, errors: [ { - messageId: "noLeakedEventListenerInEffect", + messageId: "expectedRemoveEventListenerInCleanup", }, ], }, @@ -176,7 +176,7 @@ ruleTester.run(RULE_NAME, rule, { `, errors: [ { - messageId: "noLeakedEventListenerInEffect", + messageId: "expectedRemoveEventListenerInCleanup", }, ], }, @@ -194,7 +194,7 @@ ruleTester.run(RULE_NAME, rule, { `, errors: [ { - messageId: "noLeakedEventListenerInEffect", + messageId: "expectedRemoveEventListenerInCleanup", }, ], }, @@ -212,7 +212,7 @@ ruleTester.run(RULE_NAME, rule, { `, errors: [ { - messageId: "noLeakedEventListenerInEffect", + messageId: "expectedRemoveEventListenerInCleanup", }, ], }, @@ -230,7 +230,7 @@ ruleTester.run(RULE_NAME, rule, { `, errors: [ { - messageId: "noLeakedEventListenerInEffect", + messageId: "expectedRemoveEventListenerInCleanup", }, ], }, @@ -248,7 +248,7 @@ ruleTester.run(RULE_NAME, rule, { `, errors: [ { - messageId: "noLeakedEventListenerInEffect", + messageId: "expectedRemoveEventListenerInCleanup", }, ], }, @@ -266,7 +266,7 @@ ruleTester.run(RULE_NAME, rule, { `, errors: [ { - messageId: "noLeakedEventListenerInEffect", + messageId: "expectedRemoveEventListenerInCleanup", }, ], }, @@ -284,7 +284,7 @@ ruleTester.run(RULE_NAME, rule, { `, errors: [ { - messageId: "noLeakedEventListenerInEffect", + messageId: "expectedRemoveEventListenerInCleanup", }, ], }, @@ -302,7 +302,7 @@ ruleTester.run(RULE_NAME, rule, { `, errors: [ { - messageId: "noLeakedEventListenerInEffect", + messageId: "expectedRemoveEventListenerInCleanup", }, ], }, @@ -321,7 +321,7 @@ ruleTester.run(RULE_NAME, rule, { `, errors: [ { - messageId: "noLeakedEventListenerInEffect", + messageId: "expectedRemoveEventListenerInCleanup", }, ], }, @@ -341,7 +341,7 @@ ruleTester.run(RULE_NAME, rule, { `, errors: [ { - messageId: "noLeakedEventListenerInEffect", + messageId: "expectedRemoveEventListenerInCleanup", }, ], }, @@ -360,7 +360,7 @@ ruleTester.run(RULE_NAME, rule, { `, errors: [ { - messageId: "noLeakedEventListenerInEffect", + messageId: "expectedRemoveEventListenerInCleanup", }, ], }, @@ -379,7 +379,7 @@ ruleTester.run(RULE_NAME, rule, { `, errors: [ { - messageId: "noLeakedEventListenerInEffect", + messageId: "expectedRemoveEventListenerInCleanup", }, ], }, @@ -399,10 +399,10 @@ ruleTester.run(RULE_NAME, rule, { `, errors: [ { - messageId: "noLeakedEventListenerInEffect", + messageId: "expectedRemoveEventListenerInCleanup", }, { - messageId: "noLeakedEventListenerInEffect", + messageId: "expectedRemoveEventListenerInCleanup", }, ], }, @@ -423,7 +423,7 @@ ruleTester.run(RULE_NAME, rule, { `, errors: [ { - messageId: "noLeakedEventListenerInEffect", + messageId: "expectedRemoveEventListenerInCleanup", }, ], }, @@ -453,7 +453,7 @@ ruleTester.run(RULE_NAME, rule, { `, errors: [ { - messageId: "noLeakedEventListenerInEffect", + messageId: "expectedRemoveEventListenerInCleanup", }, ], }, @@ -484,7 +484,7 @@ ruleTester.run(RULE_NAME, rule, { `, errors: [ { - messageId: "noLeakedEventListenerInEffect", + messageId: "expectedRemoveEventListenerInCleanup", }, ], }, @@ -515,7 +515,7 @@ ruleTester.run(RULE_NAME, rule, { `, errors: [ { - messageId: "noLeakedEventListenerInEffect", + messageId: "expectedRemoveEventListenerInCleanup", }, ], }, @@ -545,7 +545,7 @@ ruleTester.run(RULE_NAME, rule, { `, errors: [ { - messageId: "noLeakedEventListenerInEffect", + messageId: "expectedRemoveEventListenerInCleanup", }, ], }, @@ -576,7 +576,7 @@ ruleTester.run(RULE_NAME, rule, { `, errors: [ { - messageId: "noLeakedEventListenerInEffect", + messageId: "expectedRemoveEventListenerInCleanup", }, ], }, @@ -607,7 +607,7 @@ ruleTester.run(RULE_NAME, rule, { `, errors: [ { - messageId: "noLeakedEventListenerInEffect", + messageId: "expectedRemoveEventListenerInCleanup", }, ], }, @@ -638,7 +638,7 @@ ruleTester.run(RULE_NAME, rule, { `, errors: [ { - messageId: "noLeakedEventListenerInEffect", + messageId: "expectedRemoveEventListenerInCleanup", }, ], }, diff --git a/packages/plugins/eslint-plugin-react-web-api/src/rules/no-leaked-event-listener.ts b/packages/plugins/eslint-plugin-react-web-api/src/rules/no-leaked-event-listener.ts index af001aac9..e17eaf101 100644 --- a/packages/plugins/eslint-plugin-react-web-api/src/rules/no-leaked-event-listener.ts +++ b/packages/plugins/eslint-plugin-react-web-api/src/rules/no-leaked-event-listener.ts @@ -21,9 +21,9 @@ export const RULE_FEATURES = [ ] as const satisfies RuleFeature[]; export type MessageID = - | "noLeakedEventListenerInEffect" - | "noLeakedEventListenerInLifecycle" - | "noLeakedEventListenerOfInlineFunction"; + | "expectedRemoveEventListenerInCleanup" + | "expectedRemoveEventListenerInUnmount" + | "unexpectedInlineFunction"; // #endregion @@ -153,11 +153,11 @@ export default createRule<[], MessageID>({ [Symbol.for("rule_features")]: RULE_FEATURES, }, messages: { - noLeakedEventListenerInEffect: + expectedRemoveEventListenerInCleanup: "An 'addEventListener' in '{{effectMethodKind}}' should have a corresponding 'removeEventListener' in its cleanup function.", - noLeakedEventListenerInLifecycle: + expectedRemoveEventListenerInUnmount: "An 'addEventListener' in 'componentDidMount' should have a corresponding 'removeEventListener' in 'componentWillUnmount' method.", - noLeakedEventListenerOfInlineFunction: "A/an '{{eventMethodKind}}' should not have an inline listener function.", + unexpectedInlineFunction: "A/an '{{eventMethodKind}}' should not have an inline listener function.", }, schema: [], }, @@ -211,7 +211,7 @@ export default createRule<[], MessageID>({ return; } context.report({ - messageId: "noLeakedEventListenerOfInlineFunction", + messageId: "unexpectedInlineFunction", node: listener, data: { eventMethodKind: callKind }, }); @@ -291,7 +291,7 @@ export default createRule<[], MessageID>({ case "setup": case "cleanup": context.report({ - messageId: "noLeakedEventListenerInEffect", + messageId: "expectedRemoveEventListenerInCleanup", node: aEntry.node, data: { effectMethodKind: "useEffect", @@ -301,7 +301,7 @@ export default createRule<[], MessageID>({ case "mount": case "unmount": context.report({ - messageId: "noLeakedEventListenerInLifecycle", + messageId: "expectedRemoveEventListenerInUnmount", node: aEntry.node, }); continue; diff --git a/packages/plugins/eslint-plugin-react-web-api/src/rules/no-leaked-interval.spec.ts b/packages/plugins/eslint-plugin-react-web-api/src/rules/no-leaked-interval.spec.ts index d7cbbe06d..08d63e273 100644 --- a/packages/plugins/eslint-plugin-react-web-api/src/rules/no-leaked-interval.spec.ts +++ b/packages/plugins/eslint-plugin-react-web-api/src/rules/no-leaked-interval.spec.ts @@ -13,7 +13,7 @@ ruleTester.run(RULE_NAME, rule, { `, errors: [ { - messageId: "noLeakedIntervalNoIntervalId", + messageId: "expectedIntervalId", }, ], }, @@ -27,7 +27,7 @@ ruleTester.run(RULE_NAME, rule, { `, errors: [ { - messageId: "noLeakedIntervalNoIntervalId", + messageId: "expectedIntervalId", }, ], }, @@ -41,7 +41,7 @@ ruleTester.run(RULE_NAME, rule, { `, errors: [ { - messageId: "noLeakedIntervalNoIntervalId", + messageId: "expectedIntervalId", }, ], }, @@ -55,7 +55,7 @@ ruleTester.run(RULE_NAME, rule, { `, errors: [ { - messageId: "noLeakedIntervalInEffect", + messageId: "expectedClearIntervalInCleanup", }, ], }, @@ -69,7 +69,7 @@ ruleTester.run(RULE_NAME, rule, { `, errors: [ { - messageId: "noLeakedIntervalInEffect", + messageId: "expectedClearIntervalInCleanup", }, ], }, diff --git a/packages/plugins/eslint-plugin-react-web-api/src/rules/no-leaked-interval.ts b/packages/plugins/eslint-plugin-react-web-api/src/rules/no-leaked-interval.ts index ea966217f..6619e1061 100644 --- a/packages/plugins/eslint-plugin-react-web-api/src/rules/no-leaked-interval.ts +++ b/packages/plugins/eslint-plugin-react-web-api/src/rules/no-leaked-interval.ts @@ -19,9 +19,9 @@ export const RULE_FEATURES = [ ] as const satisfies RuleFeature[]; export type MessageID = - | "noLeakedIntervalInEffect" - | "noLeakedIntervalInLifecycle" - | "noLeakedIntervalNoIntervalId"; + | "expectedClearIntervalInCleanup" + | "expectedClearIntervalInUnmount" + | "expectedIntervalId"; // #endregion @@ -64,11 +64,11 @@ export default createRule<[], MessageID>({ [Symbol.for("rule_features")]: RULE_FEATURES, }, messages: { - noLeakedIntervalInEffect: + expectedClearIntervalInCleanup: "A 'setInterval' created in '{{ kind }}' must be cleared with 'clearInterval' in the cleanup function.", - noLeakedIntervalInLifecycle: + expectedClearIntervalInUnmount: "A 'setInterval' created in '{{ kind }}' must be cleared with 'clearInterval' in the 'componentWillUnmount' method.", - noLeakedIntervalNoIntervalId: "A 'setInterval' must be assigned to a variable for proper cleanup.", + expectedIntervalId: "A 'setInterval' must be assigned to a variable for proper cleanup.", }, schema: [], }, @@ -104,7 +104,7 @@ export default createRule<[], MessageID>({ const intervalIdNode = VAR.getVariableDeclaratorId(node); if (intervalIdNode == null) { context.report({ - messageId: "noLeakedIntervalNoIntervalId", + messageId: "expectedIntervalId", node, }); break; @@ -150,7 +150,7 @@ export default createRule<[], MessageID>({ case "setup": case "cleanup": context.report({ - messageId: "noLeakedIntervalInEffect", + messageId: "expectedClearIntervalInCleanup", node: sEntry.node, data: { kind: "useEffect", @@ -160,7 +160,7 @@ export default createRule<[], MessageID>({ case "mount": case "unmount": context.report({ - messageId: "noLeakedIntervalInLifecycle", + messageId: "expectedClearIntervalInUnmount", node: sEntry.node, data: { kind: "componentDidMount", diff --git a/packages/plugins/eslint-plugin-react-web-api/src/rules/no-leaked-resize-observer.spec.ts b/packages/plugins/eslint-plugin-react-web-api/src/rules/no-leaked-resize-observer.spec.ts index 551fc4ba3..b9e287196 100644 --- a/packages/plugins/eslint-plugin-react-web-api/src/rules/no-leaked-resize-observer.spec.ts +++ b/packages/plugins/eslint-plugin-react-web-api/src/rules/no-leaked-resize-observer.spec.ts @@ -18,7 +18,7 @@ ruleTester.run(RULE_NAME, rule, { `, errors: [ { - messageId: "noLeakedResizeObserver", + messageId: "expectedDisconnectOrUnobserveInCleanup", }, ], }, @@ -40,7 +40,7 @@ ruleTester.run(RULE_NAME, rule, { `, errors: [ { - messageId: "noLeakedResizeObserver", + messageId: "expectedDisconnectOrUnobserveInCleanup", }, ], }, @@ -58,7 +58,7 @@ ruleTester.run(RULE_NAME, rule, { `, errors: [ { - messageId: "noLeakedResizeObserverNoFloatingInstance", + messageId: "unexpectedFloatingInstance", }, ], }, @@ -81,7 +81,7 @@ ruleTester.run(RULE_NAME, rule, { `, errors: [ { - messageId: "noLeakedResizeObserver", + messageId: "expectedDisconnectOrUnobserveInCleanup", }, ], }, @@ -107,7 +107,7 @@ ruleTester.run(RULE_NAME, rule, { `, errors: [ { - messageId: "noLeakedResizeObserverInControlFlow", + messageId: "expectedDisconnectInControlFlow", }, ], }, @@ -133,7 +133,7 @@ ruleTester.run(RULE_NAME, rule, { `, errors: [ { - messageId: "noLeakedResizeObserverInControlFlow", + messageId: "expectedDisconnectInControlFlow", }, ], }, @@ -153,7 +153,7 @@ ruleTester.run(RULE_NAME, rule, { `, errors: [ { - messageId: "noLeakedResizeObserverNoFloatingInstance", + messageId: "unexpectedFloatingInstance", }, ], }, diff --git a/packages/plugins/eslint-plugin-react-web-api/src/rules/no-leaked-resize-observer.ts b/packages/plugins/eslint-plugin-react-web-api/src/rules/no-leaked-resize-observer.ts index 16a3a45e7..712d7e3b5 100644 --- a/packages/plugins/eslint-plugin-react-web-api/src/rules/no-leaked-resize-observer.ts +++ b/packages/plugins/eslint-plugin-react-web-api/src/rules/no-leaked-resize-observer.ts @@ -20,9 +20,9 @@ export const RULE_FEATURES = [ ] as const satisfies RuleFeature[]; export type MessageID = - | "noLeakedResizeObserver" - | "noLeakedResizeObserverInControlFlow" - | "noLeakedResizeObserverNoFloatingInstance"; + | "expectedDisconnectInControlFlow" + | "expectedDisconnectOrUnobserveInCleanup" + | "unexpectedFloatingInstance"; // #endregion @@ -92,11 +92,11 @@ export default createRule<[], MessageID>({ [Symbol.for("rule_features")]: RULE_FEATURES, }, messages: { - noLeakedResizeObserver: - "A 'ResizeObserver' instance created in 'useEffect' must be disconnected in the cleanup function.", - noLeakedResizeObserverInControlFlow: + expectedDisconnectInControlFlow: "Dynamically added 'ResizeObserver.observe' should be cleared all at once using 'ResizeObserver.disconnect' in the cleanup function.", - noLeakedResizeObserverNoFloatingInstance: + expectedDisconnectOrUnobserveInCleanup: + "A 'ResizeObserver' instance created in 'useEffect' must be disconnected in the cleanup function.", + unexpectedFloatingInstance: "A 'ResizeObserver' instance created in component or custom Hook must be assigned to a variable for proper cleanup.", }, schema: [], @@ -188,7 +188,7 @@ export default createRule<[], MessageID>({ const id = getInstanceID(node); if (id == null) { context.report({ - messageId: "noLeakedResizeObserverNoFloatingInstance", + messageId: "unexpectedFloatingInstance", node, }); return; @@ -212,14 +212,14 @@ export default createRule<[], MessageID>({ const hasDynamicallyAdded = oentries .some((e) => !isPhaseNode(AST.findParentNode(e.node, or(isDynamic, isPhaseNode)))); if (hasDynamicallyAdded) { - context.report({ messageId: "noLeakedResizeObserverInControlFlow", node }); + context.report({ messageId: "expectedDisconnectInControlFlow", node }); continue; } for (const oEntry of oentries) { if (uentries.some((uEntry) => isInstanceIDEqual(uEntry.element, oEntry.element, context))) { continue; } - context.report({ messageId: "noLeakedResizeObserver", node: oEntry.node }); + context.report({ messageId: "expectedDisconnectOrUnobserveInCleanup", node: oEntry.node }); } } }, diff --git a/packages/plugins/eslint-plugin-react-web-api/src/rules/no-leaked-timeout.spec.ts b/packages/plugins/eslint-plugin-react-web-api/src/rules/no-leaked-timeout.spec.ts index 2f03113be..e0b92ef98 100644 --- a/packages/plugins/eslint-plugin-react-web-api/src/rules/no-leaked-timeout.spec.ts +++ b/packages/plugins/eslint-plugin-react-web-api/src/rules/no-leaked-timeout.spec.ts @@ -13,7 +13,7 @@ ruleTester.run(RULE_NAME, rule, { `, errors: [ { - messageId: "noLeakedTimeoutNoTimeoutId", + messageId: "expectedTimeoutId", }, ], }, @@ -27,7 +27,7 @@ ruleTester.run(RULE_NAME, rule, { `, errors: [ { - messageId: "noLeakedTimeoutNoTimeoutId", + messageId: "expectedTimeoutId", }, ], }, @@ -41,7 +41,7 @@ ruleTester.run(RULE_NAME, rule, { `, errors: [ { - messageId: "noLeakedTimeoutNoTimeoutId", + messageId: "expectedTimeoutId", }, ], }, @@ -55,7 +55,7 @@ ruleTester.run(RULE_NAME, rule, { `, errors: [ { - messageId: "noLeakedTimeoutInEffect", + messageId: "expectedClearTimeoutInCleanup", }, ], }, @@ -69,7 +69,7 @@ ruleTester.run(RULE_NAME, rule, { `, errors: [ { - messageId: "noLeakedTimeoutInEffect", + messageId: "expectedClearTimeoutInCleanup", }, ], }, diff --git a/packages/plugins/eslint-plugin-react-web-api/src/rules/no-leaked-timeout.ts b/packages/plugins/eslint-plugin-react-web-api/src/rules/no-leaked-timeout.ts index 89df7c287..11967f58f 100644 --- a/packages/plugins/eslint-plugin-react-web-api/src/rules/no-leaked-timeout.ts +++ b/packages/plugins/eslint-plugin-react-web-api/src/rules/no-leaked-timeout.ts @@ -19,9 +19,9 @@ export const RULE_FEATURES = [ ] as const satisfies RuleFeature[]; export type MessageID = - | "noLeakedTimeoutInEffect" - | "noLeakedTimeoutInLifecycle" - | "noLeakedTimeoutNoTimeoutId"; + | "expectedClearTimeoutInCleanup" + | "expectedClearTimeoutInUnmount" + | "expectedTimeoutId"; // #endregion @@ -63,11 +63,11 @@ export default createRule<[], MessageID>({ [Symbol.for("rule_features")]: RULE_FEATURES, }, messages: { - noLeakedTimeoutInEffect: + expectedClearTimeoutInCleanup: "A 'setTimeout' created in '{{ kind }}' must be cleared with 'clearTimeout' in the cleanup function.", - noLeakedTimeoutInLifecycle: + expectedClearTimeoutInUnmount: "A 'setTimeout' created in '{{ kind }}' must be cleared with 'clearTimeout' in the 'componentWillUnmount' method.", - noLeakedTimeoutNoTimeoutId: "A 'setTimeout' must be assigned to a variable for proper cleanup.", + expectedTimeoutId: "A 'setTimeout' must be assigned to a variable for proper cleanup.", }, schema: [], }, @@ -100,7 +100,7 @@ export default createRule<[], MessageID>({ const timeoutIdNode = VAR.getVariableDeclaratorId(node); if (timeoutIdNode == null) { context.report({ - messageId: "noLeakedTimeoutNoTimeoutId", + messageId: "expectedTimeoutId", node, }); break; @@ -139,7 +139,7 @@ export default createRule<[], MessageID>({ case "setup": case "cleanup": context.report({ - messageId: "noLeakedTimeoutInEffect", + messageId: "expectedClearTimeoutInCleanup", node: sEntry.node, data: { kind: "useEffect", @@ -149,7 +149,7 @@ export default createRule<[], MessageID>({ case "mount": case "unmount": context.report({ - messageId: "noLeakedTimeoutInLifecycle", + messageId: "expectedClearTimeoutInUnmount", node: sEntry.node, data: { kind: "componentDidMount",