diff --git a/lint-staged.config.mjs b/lint-staged.config.mjs new file mode 100644 index 0000000..b40edbe --- /dev/null +++ b/lint-staged.config.mjs @@ -0,0 +1,5 @@ +const scriptExtensionsGlob = '?(m|c){j,t}s?x'; + +export default { + '**/*': () => 'prettier . --write --loglevel=warn', +}; diff --git a/package.json b/package.json index 6d5db00..16c1992 100644 --- a/package.json +++ b/package.json @@ -10,8 +10,8 @@ "eslint": "npm run eslint --workspaces --if-present", "eslint:fix": "npm run eslint:fix --workspaces --if-present", "build": "npm run build --workspaces --if-present", - "format": "prettier --write \"./**/*.{ts,js,tsx,jsx,json}\"", - "prepare": "husky install" + "format": "prettier . --write --loglevel=warn", + "postinstall": "husky install" }, "devDependencies": { "@mediamonks/eslint-config": "*", @@ -36,8 +36,5 @@ "parserOptions": { "ecmaVersion": "latest" } - }, - "lint-staged": { - "*.{js,jsx,ts,tsx,md,json}": "prettier --write" } } diff --git a/packages/eslint-plugin-react/README.md b/packages/eslint-plugin-react/README.md index 79bb34e..6e1fdec 100755 --- a/packages/eslint-plugin-react/README.md +++ b/packages/eslint-plugin-react/README.md @@ -38,8 +38,8 @@ Or use the recommended config 💡 Manually fixable by [editor suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions). -| Name | Description | 💼 | 💡 | -| :------------------------------------------------------------------------------------------- | :---------------------------------------------------------- | :-- | :-- | -| [throttle-use-resize-observer-callback](docs/rules/throttle-use-resize-observer-callback.md) | Callback function in useResizeObserver should be throttled. | ✅ | 💡 | +| Name | Description | 💼 | 💡 | +| :------------------------------------------------------------- | :------------------------------------ | :-- | :-- | +| [throttle-hook-callback](docs/rules/throttle-hook-callback.md) | Callback in hook should be throttled. | ✅ | 💡 | diff --git a/packages/eslint-plugin-react/docs/rules/throttle-use-resize-observer-callback.md b/packages/eslint-plugin-react/docs/rules/throttle-hook-callback.md similarity index 75% rename from packages/eslint-plugin-react/docs/rules/throttle-use-resize-observer-callback.md rename to packages/eslint-plugin-react/docs/rules/throttle-hook-callback.md index e5b0c77..38559ab 100644 --- a/packages/eslint-plugin-react/docs/rules/throttle-use-resize-observer-callback.md +++ b/packages/eslint-plugin-react/docs/rules/throttle-hook-callback.md @@ -1,4 +1,4 @@ -# Callback function in useResizeObserver should be throttled (`@mediamonks/react/throttle-use-resize-observer-callback`) +# Callback in hook should be throttled (`@mediamonks/react/throttle-hook-callback`) 💼 This rule is enabled in the ✅ `recommended` config. @@ -9,8 +9,8 @@ ## Rule details -This rule enforces that the callback function in useResizeObserver should be wrapped with a -throttling function. By default, the rule supports the following throttle function names: +This rule enforces that the callback in a hook should be wrapped with a throttle function. By +default, the rule allows the following throttle functions: - `useRafCallback` - `useDebounceCallback` @@ -43,6 +43,8 @@ useResizeObserver( This rule has an optional object configuration: +- `hookNames`: An array of strings specifying custom hook names. Default hook names will be used if + this option is not provided. - `throttleFunctionNames`: An array of strings specifying custom throttle function names. Default throttle function names will be used if this option is not provided. @@ -56,6 +58,7 @@ This rule takes one optional object argument of type object: "@mediamonks/react/use-resize-observer-throttle-callback": [ "error", { + "hookNames": ["useCustomHook"], "throttleFunctionNames": ["customThrottleFunction"] } ] diff --git a/packages/eslint-plugin-react/index.cts b/packages/eslint-plugin-react/index.cts index de17ec2..93ed4ff 100644 --- a/packages/eslint-plugin-react/index.cts +++ b/packages/eslint-plugin-react/index.cts @@ -1,11 +1,9 @@ import { type ESLint } from 'eslint'; -import useThrottleFunction from './rules/throttleUseResizeObserverCallback'; +import * as throttleCallback from './rules/throttleHookCallback'; export = { rules: { - /* eslint-disable @typescript-eslint/naming-convention */ - 'throttle-use-resize-observer-callback': useThrottleFunction, - /* eslint-enable */ + [throttleCallback.name]: throttleCallback.default, }, configs: { recommended: { @@ -15,9 +13,7 @@ export = { }, plugins: ['@mediamonks/react'], rules: { - /* eslint-disable @typescript-eslint/naming-convention */ - '@mediamonks/react/throttle-use-resize-observer-callback': 'error', - /* eslint-enable */ + [`@mediamonks/react/${throttleCallback.name}`]: 'error', }, }, }, diff --git a/packages/eslint-plugin-react/lib/getRuleDocumentationPath.ts b/packages/eslint-plugin-react/lib/getRuleDocumentationPath.ts new file mode 100644 index 0000000..b812fbd --- /dev/null +++ b/packages/eslint-plugin-react/lib/getRuleDocumentationPath.ts @@ -0,0 +1,5 @@ +import { repository } from '../package.json'; + +export function getRuleDocumentationPath(ruleName: string): string { + return `${repository.url.slice(0, -4)}/${repository.directory}/docs/rules/${ruleName}.md`; +} diff --git a/packages/eslint-plugin-react/package.json b/packages/eslint-plugin-react/package.json index 5f23311..37d370e 100644 --- a/packages/eslint-plugin-react/package.json +++ b/packages/eslint-plugin-react/package.json @@ -12,7 +12,8 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/mediamonks/eslint-config.git" + "url": "https://github.com/mediamonks/eslint-config.git", + "directory": "packages/eslint-plugin-react" }, "description": "Sharable eslint config based on Media.Monks Frontend Coding Standards", "keywords": [ diff --git a/packages/eslint-plugin-react/rules/throttleHookCallback.test.ts b/packages/eslint-plugin-react/rules/throttleHookCallback.test.ts new file mode 100644 index 0000000..71f0c9d --- /dev/null +++ b/packages/eslint-plugin-react/rules/throttleHookCallback.test.ts @@ -0,0 +1,45 @@ +import { RuleTester } from 'eslint'; +import * as throttleHookCallback from './throttleHookCallback'; + +const ruleTester = new RuleTester({ + parserOptions: { ecmaVersion: 6 }, +}); + +ruleTester.run(`@mediamonks/react/${throttleHookCallback.name}`, throttleHookCallback.default, { + valid: [ + { + code: 'useResizeObserver({}, useRafCallback(() => {}));', + }, + { + code: 'useResizeObserver({}, useDebounceCallback(() => {}, 200));', + }, + { + code: 'useResizeObserver({}, useThrottleCallback(() => {}, 200));', + }, + ], + + invalid: [ + { + code: 'useResizeObserver({}, function() {});', + errors: [{ message: 'Callback in hook should be throttled.' }], + }, + { + code: 'useResizeObserver({}, () => {});', + errors: [{ message: 'Callback in hook should be throttled.' }], + }, + { + code: ` + function myFunction() {} + useResizeObserver({}, myFunction); + `, + errors: [{ message: 'Callback in hook should be throttled.' }], + }, + { + code: ` + const myFunction = () => {}; + useResizeObserver({}, myFunction); + `, + errors: [{ message: 'Callback in hook should be throttled.' }], + }, + ], +}); diff --git a/packages/eslint-plugin-react/rules/throttleUseResizeObserverCallback.ts b/packages/eslint-plugin-react/rules/throttleHookCallback.ts similarity index 71% rename from packages/eslint-plugin-react/rules/throttleUseResizeObserverCallback.ts rename to packages/eslint-plugin-react/rules/throttleHookCallback.ts index 1fb8d4d..58388ab 100644 --- a/packages/eslint-plugin-react/rules/throttleUseResizeObserverCallback.ts +++ b/packages/eslint-plugin-react/rules/throttleHookCallback.ts @@ -1,12 +1,17 @@ import { type Rule } from 'eslint'; +import { getRuleDocumentationPath } from '../lib/getRuleDocumentationPath'; + +export const name = 'throttle-hook-callback'; const messages = { - missingThrottleFunction: 'Callback function in useResizeObserver should be throttled.', + missingThrottleFunction: 'Callback in hook should be throttled.', wrapWithUseRafCallback: 'Wrap callback with useRafCallback', wrapWithUseDebounceCallback: 'Wrap callback with useDebounceCallback', wrapWithUseThrottleCallback: 'Wrap callback with useThrottleCallback', }; +const defaultHookNames = ['useResizeObserver', 'useMutationObserver']; + const defaultThrottleFunctionNames = [ 'useRafCallback', 'useDebounceCallback', @@ -20,9 +25,9 @@ export default { meta: { messages, docs: { - description: 'Callback function in useResizeObserver should be throttled.', + description: 'Callback in hook should be throttled.', recommended: true, - url: 'https://example.com/', + url: getRuleDocumentationPath(name), }, schema: [ { @@ -45,7 +50,12 @@ export default { return { // eslint-disable-next-line @typescript-eslint/naming-convention CallExpression(node): void { - if ('name' in node.callee && node.callee.name !== 'useResizeObserver') { + const { + throttleFunctionNames = defaultThrottleFunctionNames, + hookNames = defaultHookNames, + } = context.options.at(0) ?? {}; + + if ('name' in node.callee && !hookNames.includes(node.callee.name)) { return; } @@ -55,17 +65,13 @@ export default { return; } - // Check if CallExpression is one of the throttle functions - if (callbackNode.type === 'CallExpression') { - const { throttleFunctionNames = defaultThrottleFunctionNames } = - context.options.at(0) ?? {}; - - if ( - 'name' in callbackNode.callee && - throttleFunctionNames.includes(callbackNode.callee.name) - ) { - return; - } + if ( + callbackNode.type === 'CallExpression' && + 'name' in callbackNode.callee && + // Check if CallExpression is one of the throttle functions + throttleFunctionNames.includes(callbackNode.callee.name) + ) { + return; } context.report({ diff --git a/packages/eslint-plugin-react/rules/throttleUseResizeObserverCallback.test.ts b/packages/eslint-plugin-react/rules/throttleUseResizeObserverCallback.test.ts deleted file mode 100644 index e9ffd71..0000000 --- a/packages/eslint-plugin-react/rules/throttleUseResizeObserverCallback.test.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { RuleTester } from 'eslint'; -import throttleUseResizeObserverCallback from './throttleUseResizeObserverCallback'; - -const ruleTester = new RuleTester({ - parserOptions: { ecmaVersion: 6 }, -}); - -ruleTester.run( - '@mediamonks/react/throttle-use-resize-observer-callback', - throttleUseResizeObserverCallback, - { - valid: [ - { - code: 'useResizeObserver({}, useRafCallback(() => {}));', - }, - { - code: 'useResizeObserver({}, useDebounceCallback(() => {}, 200));', - }, - { - code: 'useResizeObserver({}, useThrottleCallback(() => {}, 200));', - }, - ], - - invalid: [ - { - code: 'useResizeObserver({}, function() {});', - errors: [{ message: 'Callback function in useResizeObserver should be throttled.' }], - }, - { - code: 'useResizeObserver({}, () => {});', - errors: [{ message: 'Callback function in useResizeObserver should be throttled.' }], - }, - { - code: ` - function myFunction() {} - useResizeObserver({}, myFunction); - `, - errors: [{ message: 'Callback function in useResizeObserver should be throttled.' }], - }, - { - code: ` - const myFunction = () => {}; - useResizeObserver({}, myFunction); - `, - errors: [{ message: 'Callback function in useResizeObserver should be throttled.' }], - }, - ], - }, -); diff --git a/packages/eslint-plugin-react/tsconfig.json b/packages/eslint-plugin-react/tsconfig.json index c0d25f8..4f9ec5d 100644 --- a/packages/eslint-plugin-react/tsconfig.json +++ b/packages/eslint-plugin-react/tsconfig.json @@ -7,7 +7,8 @@ "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "strict": true, - "skipLibCheck": true + "skipLibCheck": true, + "resolveJsonModule": true }, "exclude": ["**/*.test.ts"] } diff --git a/packages/test-eslint-config-react/testJs.js b/packages/test-eslint-config-react/testJs.js index 0777ee7..639b4b0 100644 --- a/packages/test-eslint-config-react/testJs.js +++ b/packages/test-eslint-config-react/testJs.js @@ -38,7 +38,7 @@ function MyComponent() { useResizeObserver( {}, - // eslint-disable-next-line @mediamonks/react/throttle-use-resize-observer-callback + // eslint-disable-next-line @mediamonks/react/throttle-hook-callback noop, ); diff --git a/packages/test-eslint-config-typescript-react/TestTs.ts b/packages/test-eslint-config-typescript-react/TestTs.ts index 6796a4e..f4a8397 100644 --- a/packages/test-eslint-config-typescript-react/TestTs.ts +++ b/packages/test-eslint-config-typescript-react/TestTs.ts @@ -48,7 +48,7 @@ function _MyComponent(): null { useResizeObserver( useRef(document.createElement('div')), - // eslint-disable-next-line @mediamonks/react/throttle-use-resize-observer-callback + // eslint-disable-next-line @mediamonks/react/throttle-hook-callback noop, );