diff --git a/change/@fluentui-eslint-plugin-2d20c89f-0a2b-4e89-a47c-e6314bd42a2e.json b/change/@fluentui-eslint-plugin-2d20c89f-0a2b-4e89-a47c-e6314bd42a2e.json new file mode 100644 index 0000000000000..c29cea839fea1 --- /dev/null +++ b/change/@fluentui-eslint-plugin-2d20c89f-0a2b-4e89-a47c-e6314bd42a2e.json @@ -0,0 +1,7 @@ +{ + "type": "none", + "comment": "chore: add internal lint rule, no change to public behavior/api.", + "packageName": "@fluentui/eslint-plugin", + "email": "yuanboxue@microsoft.com", + "dependentChangeType": "none" +} diff --git a/change/@fluentui-react-accordion-74970004-bc83-461d-a43c-3758c0c429b5.json b/change/@fluentui-react-accordion-74970004-bc83-461d-a43c-3758c0c429b5.json new file mode 100644 index 0000000000000..79a53aeabc11d --- /dev/null +++ b/change/@fluentui-react-accordion-74970004-bc83-461d-a43c-3758c0c429b5.json @@ -0,0 +1,7 @@ +{ + "type": "none", + "comment": "chore: disable consistent-callback-type lint rule for existing callbacks", + "packageName": "@fluentui/react-accordion", + "email": "yuanboxue@microsoft.com", + "dependentChangeType": "none" +} diff --git a/change/@fluentui-react-card-26618c50-93dc-4116-8d0a-90c4f1566875.json b/change/@fluentui-react-card-26618c50-93dc-4116-8d0a-90c4f1566875.json new file mode 100644 index 0000000000000..ee120f34268fa --- /dev/null +++ b/change/@fluentui-react-card-26618c50-93dc-4116-8d0a-90c4f1566875.json @@ -0,0 +1,7 @@ +{ + "type": "none", + "comment": "chore: disable consistent-callback-type lint rule for existing callbacks", + "packageName": "@fluentui/react-card", + "email": "yuanboxue@microsoft.com", + "dependentChangeType": "none" +} diff --git a/change/@fluentui-react-checkbox-61435c59-fe06-4b4a-a1b1-f0a318fd8c87.json b/change/@fluentui-react-checkbox-61435c59-fe06-4b4a-a1b1-f0a318fd8c87.json new file mode 100644 index 0000000000000..23ea3d6cd4d2a --- /dev/null +++ b/change/@fluentui-react-checkbox-61435c59-fe06-4b4a-a1b1-f0a318fd8c87.json @@ -0,0 +1,7 @@ +{ + "type": "none", + "comment": "chore: disable consistent-callback-type lint rule for existing callbacks", + "packageName": "@fluentui/react-checkbox", + "email": "yuanboxue@microsoft.com", + "dependentChangeType": "none" +} diff --git a/change/@fluentui-react-combobox-536f8d48-97f5-47a6-8328-e9359bc62c1e.json b/change/@fluentui-react-combobox-536f8d48-97f5-47a6-8328-e9359bc62c1e.json new file mode 100644 index 0000000000000..5b0668181a82c --- /dev/null +++ b/change/@fluentui-react-combobox-536f8d48-97f5-47a6-8328-e9359bc62c1e.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "chore: disable consistent-callback-type lint rule for existing callbacks", + "packageName": "@fluentui/react-combobox", + "email": "yuanboxue@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@fluentui-react-datepicker-compat-583ce0d7-9840-431b-8056-91f5991ef562.json b/change/@fluentui-react-datepicker-compat-583ce0d7-9840-431b-8056-91f5991ef562.json new file mode 100644 index 0000000000000..f2fd314ee15bf --- /dev/null +++ b/change/@fluentui-react-datepicker-compat-583ce0d7-9840-431b-8056-91f5991ef562.json @@ -0,0 +1,7 @@ +{ + "type": "none", + "comment": "chore: disable consistent-callback-type lint rule for existing callbacks", + "packageName": "@fluentui/react-datepicker-compat", + "email": "yuanboxue@microsoft.com", + "dependentChangeType": "none" +} diff --git a/change/@fluentui-react-dialog-285527f9-bb4d-41d5-b6f4-1b57ab4f8ced.json b/change/@fluentui-react-dialog-285527f9-bb4d-41d5-b6f4-1b57ab4f8ced.json new file mode 100644 index 0000000000000..f0e539703c2cd --- /dev/null +++ b/change/@fluentui-react-dialog-285527f9-bb4d-41d5-b6f4-1b57ab4f8ced.json @@ -0,0 +1,7 @@ +{ + "type": "none", + "comment": "chore: disable consistent-callback-type lint rule for existing callbacks", + "packageName": "@fluentui/react-dialog", + "email": "yuanboxue@microsoft.com", + "dependentChangeType": "none" +} diff --git a/change/@fluentui-react-input-b337e912-5bde-4162-9a26-201fcc5347ee.json b/change/@fluentui-react-input-b337e912-5bde-4162-9a26-201fcc5347ee.json new file mode 100644 index 0000000000000..a8c02a501adc8 --- /dev/null +++ b/change/@fluentui-react-input-b337e912-5bde-4162-9a26-201fcc5347ee.json @@ -0,0 +1,7 @@ +{ + "type": "none", + "comment": "chore: disable consistent-callback-type lint rule for existing callbacks", + "packageName": "@fluentui/react-input", + "email": "yuanboxue@microsoft.com", + "dependentChangeType": "none" +} diff --git a/change/@fluentui-react-menu-ba500681-bcdd-4437-aaa3-002bf617b009.json b/change/@fluentui-react-menu-ba500681-bcdd-4437-aaa3-002bf617b009.json new file mode 100644 index 0000000000000..6ac81d313aaf6 --- /dev/null +++ b/change/@fluentui-react-menu-ba500681-bcdd-4437-aaa3-002bf617b009.json @@ -0,0 +1,7 @@ +{ + "type": "none", + "comment": "chore: disable consistent-callback-type lint rule for existing callbacks", + "packageName": "@fluentui/react-menu", + "email": "yuanboxue@microsoft.com", + "dependentChangeType": "none" +} diff --git a/change/@fluentui-react-migration-v0-v9-2af1c3a5-5f09-4ead-80b6-c7370457d6c9.json b/change/@fluentui-react-migration-v0-v9-2af1c3a5-5f09-4ead-80b6-c7370457d6c9.json new file mode 100644 index 0000000000000..043296131a673 --- /dev/null +++ b/change/@fluentui-react-migration-v0-v9-2af1c3a5-5f09-4ead-80b6-c7370457d6c9.json @@ -0,0 +1,7 @@ +{ + "type": "none", + "comment": "chore: disable consistent-callback-type lint rule for existing callbacks", + "packageName": "@fluentui/react-migration-v0-v9", + "email": "yuanboxue@microsoft.com", + "dependentChangeType": "none" +} diff --git a/change/@fluentui-react-popover-6de680db-0afe-4131-a96c-9ef10d14c997.json b/change/@fluentui-react-popover-6de680db-0afe-4131-a96c-9ef10d14c997.json new file mode 100644 index 0000000000000..7c0a38cda3519 --- /dev/null +++ b/change/@fluentui-react-popover-6de680db-0afe-4131-a96c-9ef10d14c997.json @@ -0,0 +1,7 @@ +{ + "type": "none", + "comment": "chore: disable consistent-callback-type lint rule for existing callbacks", + "packageName": "@fluentui/react-popover", + "email": "yuanboxue@microsoft.com", + "dependentChangeType": "none" +} diff --git a/change/@fluentui-react-radio-6a06b070-1df9-4416-92fd-8f4de71b1759.json b/change/@fluentui-react-radio-6a06b070-1df9-4416-92fd-8f4de71b1759.json new file mode 100644 index 0000000000000..6f8d256c388fa --- /dev/null +++ b/change/@fluentui-react-radio-6a06b070-1df9-4416-92fd-8f4de71b1759.json @@ -0,0 +1,7 @@ +{ + "type": "none", + "comment": "chore: disable consistent-callback-type lint rule for existing callbacks", + "packageName": "@fluentui/react-radio", + "email": "yuanboxue@microsoft.com", + "dependentChangeType": "none" +} diff --git a/change/@fluentui-react-search-preview-c4304060-394d-4f53-80d2-d34d7f781013.json b/change/@fluentui-react-search-preview-c4304060-394d-4f53-80d2-d34d7f781013.json new file mode 100644 index 0000000000000..c199678730daa --- /dev/null +++ b/change/@fluentui-react-search-preview-c4304060-394d-4f53-80d2-d34d7f781013.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "chore: disable consistent-callback-type lint rule for existing callbacks", + "packageName": "@fluentui/react-search-preview", + "email": "yuanboxue@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@fluentui-react-select-6c0bfff0-b7e4-46a6-a4d3-ca79eada2d17.json b/change/@fluentui-react-select-6c0bfff0-b7e4-46a6-a4d3-ca79eada2d17.json new file mode 100644 index 0000000000000..21501db53b8df --- /dev/null +++ b/change/@fluentui-react-select-6c0bfff0-b7e4-46a6-a4d3-ca79eada2d17.json @@ -0,0 +1,7 @@ +{ + "type": "none", + "comment": "chore: disable consistent-callback-type lint rule for existing callbacks", + "packageName": "@fluentui/react-select", + "email": "yuanboxue@microsoft.com", + "dependentChangeType": "none" +} diff --git a/change/@fluentui-react-slider-a79b527f-6529-4b9e-92ef-fca325b162db.json b/change/@fluentui-react-slider-a79b527f-6529-4b9e-92ef-fca325b162db.json new file mode 100644 index 0000000000000..476c65572b0b4 --- /dev/null +++ b/change/@fluentui-react-slider-a79b527f-6529-4b9e-92ef-fca325b162db.json @@ -0,0 +1,7 @@ +{ + "type": "none", + "comment": "chore: disable consistent-callback-type lint rule for existing callbacks", + "packageName": "@fluentui/react-slider", + "email": "yuanboxue@microsoft.com", + "dependentChangeType": "none" +} diff --git a/change/@fluentui-react-spinbutton-2becfac8-c8ca-4ecb-b961-dfad3e44f878.json b/change/@fluentui-react-spinbutton-2becfac8-c8ca-4ecb-b961-dfad3e44f878.json new file mode 100644 index 0000000000000..951a9ccd8aeb5 --- /dev/null +++ b/change/@fluentui-react-spinbutton-2becfac8-c8ca-4ecb-b961-dfad3e44f878.json @@ -0,0 +1,7 @@ +{ + "type": "none", + "comment": "chore: disable consistent-callback-type lint rule for existing callbacks", + "packageName": "@fluentui/react-spinbutton", + "email": "yuanboxue@microsoft.com", + "dependentChangeType": "none" +} diff --git a/change/@fluentui-react-switch-a4f6ce47-4fdb-421d-8229-ec4e30dd9d6b.json b/change/@fluentui-react-switch-a4f6ce47-4fdb-421d-8229-ec4e30dd9d6b.json new file mode 100644 index 0000000000000..db121065b1b8c --- /dev/null +++ b/change/@fluentui-react-switch-a4f6ce47-4fdb-421d-8229-ec4e30dd9d6b.json @@ -0,0 +1,7 @@ +{ + "type": "none", + "comment": "chore: disable consistent-callback-type lint rule for existing callbacks", + "packageName": "@fluentui/react-switch", + "email": "yuanboxue@microsoft.com", + "dependentChangeType": "none" +} diff --git a/change/@fluentui-react-table-ea92aa04-54e5-4f63-936e-e37fe9a4f2f7.json b/change/@fluentui-react-table-ea92aa04-54e5-4f63-936e-e37fe9a4f2f7.json new file mode 100644 index 0000000000000..57d41a639a2f4 --- /dev/null +++ b/change/@fluentui-react-table-ea92aa04-54e5-4f63-936e-e37fe9a4f2f7.json @@ -0,0 +1,7 @@ +{ + "type": "none", + "comment": "chore: disable consistent-callback-type lint rule for existing callbacks", + "packageName": "@fluentui/react-table", + "email": "yuanboxue@microsoft.com", + "dependentChangeType": "none" +} diff --git a/change/@fluentui-react-tabs-e928b3f8-e533-4e3c-960d-b62cd1b6661b.json b/change/@fluentui-react-tabs-e928b3f8-e533-4e3c-960d-b62cd1b6661b.json new file mode 100644 index 0000000000000..5ca3caf290066 --- /dev/null +++ b/change/@fluentui-react-tabs-e928b3f8-e533-4e3c-960d-b62cd1b6661b.json @@ -0,0 +1,7 @@ +{ + "type": "none", + "comment": "chore: disable consistent-callback-type lint rule for existing callbacks", + "packageName": "@fluentui/react-tabs", + "email": "yuanboxue@microsoft.com", + "dependentChangeType": "none" +} diff --git a/change/@fluentui-react-tags-b38fc107-b805-4f7b-bc94-18852cee65c8.json b/change/@fluentui-react-tags-b38fc107-b805-4f7b-bc94-18852cee65c8.json new file mode 100644 index 0000000000000..f3c5f889551c0 --- /dev/null +++ b/change/@fluentui-react-tags-b38fc107-b805-4f7b-bc94-18852cee65c8.json @@ -0,0 +1,7 @@ +{ + "type": "none", + "comment": "chore: disable consistent-callback-type lint rule for existing callbacks", + "packageName": "@fluentui/react-tags", + "email": "yuanboxue@microsoft.com", + "dependentChangeType": "none" +} diff --git a/change/@fluentui-react-teaching-popover-preview-d2a6cc93-7e92-4bce-bbf4-44aa4fc67376.json b/change/@fluentui-react-teaching-popover-preview-d2a6cc93-7e92-4bce-bbf4-44aa4fc67376.json new file mode 100644 index 0000000000000..288587a1818bd --- /dev/null +++ b/change/@fluentui-react-teaching-popover-preview-d2a6cc93-7e92-4bce-bbf4-44aa4fc67376.json @@ -0,0 +1,7 @@ +{ + "type": "none", + "comment": "chore: disable consistent-callback-type lint rule for existing callbacks", + "packageName": "@fluentui/react-teaching-popover-preview", + "email": "yuanboxue@microsoft.com", + "dependentChangeType": "none" +} diff --git a/change/@fluentui-react-textarea-a9dcf4be-9061-4e14-aeac-2299e8f2a02e.json b/change/@fluentui-react-textarea-a9dcf4be-9061-4e14-aeac-2299e8f2a02e.json new file mode 100644 index 0000000000000..0eb0380f66ed1 --- /dev/null +++ b/change/@fluentui-react-textarea-a9dcf4be-9061-4e14-aeac-2299e8f2a02e.json @@ -0,0 +1,7 @@ +{ + "type": "none", + "comment": "chore: disable consistent-callback-type lint rule for existing callbacks", + "packageName": "@fluentui/react-textarea", + "email": "yuanboxue@microsoft.com", + "dependentChangeType": "none" +} diff --git a/change/@fluentui-react-timepicker-compat-459ec9f4-1fb6-43f4-ae67-ab03757ed9c1.json b/change/@fluentui-react-timepicker-compat-459ec9f4-1fb6-43f4-ae67-ab03757ed9c1.json new file mode 100644 index 0000000000000..f9cad44ad5e90 --- /dev/null +++ b/change/@fluentui-react-timepicker-compat-459ec9f4-1fb6-43f4-ae67-ab03757ed9c1.json @@ -0,0 +1,7 @@ +{ + "type": "none", + "comment": "chore: disable consistent-callback-type lint rule for existing callbacks", + "packageName": "@fluentui/react-timepicker-compat", + "email": "yuanboxue@microsoft.com", + "dependentChangeType": "none" +} diff --git a/change/@fluentui-react-toast-32f33ebb-12ce-4009-9088-06208e6bc563.json b/change/@fluentui-react-toast-32f33ebb-12ce-4009-9088-06208e6bc563.json new file mode 100644 index 0000000000000..c0313f7526f0f --- /dev/null +++ b/change/@fluentui-react-toast-32f33ebb-12ce-4009-9088-06208e6bc563.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "chore: disable consistent-callback-type lint rule for existing callbacks", + "packageName": "@fluentui/react-toast", + "email": "yuanboxue@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@fluentui-react-toolbar-8707f19b-076b-41da-ad95-246744dc3cce.json b/change/@fluentui-react-toolbar-8707f19b-076b-41da-ad95-246744dc3cce.json new file mode 100644 index 0000000000000..93db9a91b7f43 --- /dev/null +++ b/change/@fluentui-react-toolbar-8707f19b-076b-41da-ad95-246744dc3cce.json @@ -0,0 +1,7 @@ +{ + "type": "none", + "comment": "chore: disable consistent-callback-type lint rule for existing callbacks", + "packageName": "@fluentui/react-toolbar", + "email": "yuanboxue@microsoft.com", + "dependentChangeType": "none" +} diff --git a/change/@fluentui-react-tooltip-486e2f8e-d4ef-49e6-865a-128e020396f8.json b/change/@fluentui-react-tooltip-486e2f8e-d4ef-49e6-865a-128e020396f8.json new file mode 100644 index 0000000000000..aaa3f8ac03bae --- /dev/null +++ b/change/@fluentui-react-tooltip-486e2f8e-d4ef-49e6-865a-128e020396f8.json @@ -0,0 +1,7 @@ +{ + "type": "none", + "comment": "chore: disable consistent-callback-type lint rule for existing callbacks", + "packageName": "@fluentui/react-tooltip", + "email": "yuanboxue@microsoft.com", + "dependentChangeType": "none" +} diff --git a/change/@fluentui-react-tree-6f3593c4-9155-4a86-8cdf-653b8992e6a1.json b/change/@fluentui-react-tree-6f3593c4-9155-4a86-8cdf-653b8992e6a1.json new file mode 100644 index 0000000000000..c0333263c203d --- /dev/null +++ b/change/@fluentui-react-tree-6f3593c4-9155-4a86-8cdf-653b8992e6a1.json @@ -0,0 +1,7 @@ +{ + "type": "none", + "comment": "chore: disable consistent-callback-type lint rule for existing callbacks", + "packageName": "@fluentui/react-tree", + "email": "yuanboxue@microsoft.com", + "dependentChangeType": "none" +} diff --git a/change/@fluentui-react-virtualizer-7344c6de-f2e4-4795-bd41-06f3bba0406c.json b/change/@fluentui-react-virtualizer-7344c6de-f2e4-4795-bd41-06f3bba0406c.json new file mode 100644 index 0000000000000..859f9baa44fcf --- /dev/null +++ b/change/@fluentui-react-virtualizer-7344c6de-f2e4-4795-bd41-06f3bba0406c.json @@ -0,0 +1,7 @@ +{ + "type": "none", + "comment": "chore: disable consistent-callback-type lint rule for existing callbacks", + "packageName": "@fluentui/react-virtualizer", + "email": "yuanboxue@microsoft.com", + "dependentChangeType": "none" +} diff --git a/packages/eslint-plugin/src/configs/react.js b/packages/eslint-plugin/src/configs/react.js index 6f6f305ebfe43..f56209c52454d 100644 --- a/packages/eslint-plugin/src/configs/react.js +++ b/packages/eslint-plugin/src/configs/react.js @@ -2,6 +2,7 @@ const path = require('path'); const configHelpers = require('../utils/configHelpers'); +const { __internal } = require('../internal'); /** @type {import("eslint").Linter.RulesRecord} */ const typeAwareRules = { @@ -58,5 +59,6 @@ module.exports = { 'react/jsx-no-bind': 'off', }, }, - ], + __internal.overrides.react, + ].filter(Boolean), }; diff --git a/packages/eslint-plugin/src/internal.js b/packages/eslint-plugin/src/internal.js index 7a120416be6c3..08b8f11c27ba7 100644 --- a/packages/eslint-plugin/src/internal.js +++ b/packages/eslint-plugin/src/internal.js @@ -21,6 +21,17 @@ const __internal = { * `@nx/eslint-plugin` is necessary in order to register custom lint rules that live within tools/eslint-rules */ plugins: shouldRegister ? ['@nx'] : [], + // extend this object with your rule overrides + overrides: { + react: shouldRegister + ? { + files: ['**/src/**/*.{ts,tsx}'], + rules: { + '@nx/workspace-consistent-callback-type': 'error', + }, + } + : null, + }, }; exports.__internal = __internal; diff --git a/packages/react-components/react-accordion/src/components/Accordion/Accordion.types.ts b/packages/react-components/react-accordion/src/components/Accordion/Accordion.types.ts index 554016e527ba7..0cbadd9f6fef4 100644 --- a/packages/react-components/react-accordion/src/components/Accordion/Accordion.types.ts +++ b/packages/react-components/react-accordion/src/components/Accordion/Accordion.types.ts @@ -49,6 +49,7 @@ export type AccordionProps = ComponentProps; /** diff --git a/packages/react-components/react-card/src/components/Card/Card.types.ts b/packages/react-components/react-card/src/components/Card/Card.types.ts index 18ee8b4eae446..f2d45c9b82734 100644 --- a/packages/react-components/react-card/src/components/Card/Card.types.ts +++ b/packages/react-components/react-card/src/components/Card/Card.types.ts @@ -124,6 +124,7 @@ export type CardProps = ComponentProps & { /** * Callback to be called when the selected state value changes. */ + // eslint-disable-next-line @nx/workspace-consistent-callback-type -- can't change type of existing callback onSelectionChange?: (event: CardOnSelectionChangeEvent, data: CardOnSelectData) => void; }; diff --git a/packages/react-components/react-checkbox/src/components/Checkbox/Checkbox.types.ts b/packages/react-components/react-checkbox/src/components/Checkbox/Checkbox.types.ts index acb4001a98624..963ef95141293 100644 --- a/packages/react-components/react-checkbox/src/components/Checkbox/Checkbox.types.ts +++ b/packages/react-components/react-checkbox/src/components/Checkbox/Checkbox.types.ts @@ -64,6 +64,7 @@ export type CheckboxProps = Omit< /** * Callback to be called when the checked state value changes. */ + // eslint-disable-next-line @nx/workspace-consistent-callback-type -- can't change type of existing callback onChange?: (ev: React.ChangeEvent, data: CheckboxOnChangeData) => void; /** diff --git a/packages/react-components/react-combobox/src/utils/ComboboxBase.types.ts b/packages/react-components/react-combobox/src/utils/ComboboxBase.types.ts index f5ba8d3863910..1ea84c8b2218a 100644 --- a/packages/react-components/react-combobox/src/utils/ComboboxBase.types.ts +++ b/packages/react-components/react-combobox/src/utils/ComboboxBase.types.ts @@ -41,6 +41,7 @@ export type ComboboxBaseProps = SelectionProps & /** * Callback when the open/closed state of the dropdown changes */ + // eslint-disable-next-line @nx/workspace-consistent-callback-type -- can't change type of existing callback onOpenChange?: (e: ComboboxBaseOpenEvents, data: ComboboxBaseOpenChangeData) => void; /** diff --git a/packages/react-components/react-combobox/src/utils/Selection.types.ts b/packages/react-components/react-combobox/src/utils/Selection.types.ts index 6e8c6150c0094..1cbc8affdcef6 100644 --- a/packages/react-components/react-combobox/src/utils/Selection.types.ts +++ b/packages/react-components/react-combobox/src/utils/Selection.types.ts @@ -17,6 +17,7 @@ export type SelectionProps = { multiselect?: boolean; /* Callback when an option is selected */ + // eslint-disable-next-line @nx/workspace-consistent-callback-type -- can't change type of existing callback onOptionSelect?: (event: SelectionEvents, data: OptionOnSelectData) => void; /** diff --git a/packages/react-components/react-datepicker-compat/src/components/DatePicker/DatePicker.types.ts b/packages/react-components/react-datepicker-compat/src/components/DatePicker/DatePicker.types.ts index bffab2aa1118c..2b6b11b75ee20 100644 --- a/packages/react-components/react-datepicker-compat/src/components/DatePicker/DatePicker.types.ts +++ b/packages/react-components/react-datepicker-compat/src/components/DatePicker/DatePicker.types.ts @@ -16,6 +16,7 @@ export type DatePickerProps = Omit>, 'de /** * Callback issued when a date is selected */ + // eslint-disable-next-line @nx/workspace-consistent-callback-type -- can't change type of existing callback onSelectDate?: (date: Date | null | undefined) => void; /** @@ -84,11 +85,13 @@ export type DatePickerProps = Omit>, 'de /** * Callback to run when the DatePicker's open state changes */ + // eslint-disable-next-line @nx/workspace-consistent-callback-type -- can't change type of existing callback onOpenChange?: (open: boolean) => void; /** * Callback to run after the DatePicker's input has been validated */ + // eslint-disable-next-line @nx/workspace-consistent-callback-type -- can't change type of existing callback onValidationResult?: (data: DatePickerValidationResultData) => void; /** diff --git a/packages/react-components/react-dialog/src/components/Dialog/Dialog.types.ts b/packages/react-components/react-dialog/src/components/Dialog/Dialog.types.ts index e91b0e3d1ba22..85b44d87616ca 100644 --- a/packages/react-components/react-dialog/src/components/Dialog/Dialog.types.ts +++ b/packages/react-components/react-dialog/src/components/Dialog/Dialog.types.ts @@ -81,6 +81,7 @@ export type DialogProps = ComponentProps> & { * @param data - A data object with relevant information, * such as open value and type of interaction that created the event */ + // eslint-disable-next-line @nx/workspace-consistent-callback-type -- can't change type of existing callback onOpenChange?: DialogOpenChangeEventHandler; /** * Can contain two children including {@link DialogTrigger} and {@link DialogSurface}. diff --git a/packages/react-components/react-input/src/components/Input/Input.types.ts b/packages/react-components/react-input/src/components/Input/Input.types.ts index 79a8a8500f4ae..2afe13a355f8c 100644 --- a/packages/react-components/react-input/src/components/Input/Input.types.ts +++ b/packages/react-components/react-input/src/components/Input/Input.types.ts @@ -77,6 +77,7 @@ export type InputProps = Omit< /** * Called when the user changes the input's value. */ + // eslint-disable-next-line @nx/workspace-consistent-callback-type -- can't change type of existing callback onChange?: (ev: React.ChangeEvent, data: InputOnChangeData) => void; /** diff --git a/packages/react-components/react-menu/src/components/Menu/Menu.types.ts b/packages/react-components/react-menu/src/components/Menu/Menu.types.ts index dcadc40dd74f8..56135c67b5097 100644 --- a/packages/react-components/react-menu/src/components/Menu/Menu.types.ts +++ b/packages/react-components/react-menu/src/components/Menu/Menu.types.ts @@ -40,6 +40,7 @@ export type MenuProps = ComponentProps & * Call back when the component requests to change value * The `open` value is used as a hint when directly controlling the component */ + // eslint-disable-next-line @nx/workspace-consistent-callback-type -- can't change type of existing callback onOpenChange?: (e: MenuOpenEvent, data: MenuOpenChangeData) => void; /** diff --git a/packages/react-components/react-menu/src/components/MenuList/MenuList.types.ts b/packages/react-components/react-menu/src/components/MenuList/MenuList.types.ts index 7e469e730f611..3aaa37a127550 100644 --- a/packages/react-components/react-menu/src/components/MenuList/MenuList.types.ts +++ b/packages/react-components/react-menu/src/components/MenuList/MenuList.types.ts @@ -43,6 +43,7 @@ export type MenuListProps = ComponentProps & { * @param event - React's original SyntheticEvent * @param data - A data object with relevant information */ + // eslint-disable-next-line @nx/workspace-consistent-callback-type -- can't change type of existing callback onCheckedValueChange?: (e: MenuCheckedValueChangeEvent, data: MenuCheckedValueChangeData) => void; }; diff --git a/packages/react-components/react-menu/src/components/MenuTrigger/MenuTrigger.types.ts b/packages/react-components/react-menu/src/components/MenuTrigger/MenuTrigger.types.ts index c8a78b839648e..177dfdb985984 100644 --- a/packages/react-components/react-menu/src/components/MenuTrigger/MenuTrigger.types.ts +++ b/packages/react-components/react-menu/src/components/MenuTrigger/MenuTrigger.types.ts @@ -20,10 +20,12 @@ export type MenuTriggerChildProps; + /* eslint-disable @nx/workspace-consistent-callback-type -- can't change type of existing callback */ onMouseEnter: React.MouseEventHandler; onMouseLeave: React.MouseEventHandler; onMouseMove: React.MouseEventHandler; onContextMenu: React.MouseEventHandler; + /* eslint-enable @nx/workspace-consistent-callback-type */ } >; diff --git a/packages/react-components/react-migration-v0-v9/src/components/List/List/List.types.ts b/packages/react-components/react-migration-v0-v9/src/components/List/List/List.types.ts index b0fd34fc871e2..dea80cc588af8 100644 --- a/packages/react-components/react-migration-v0-v9/src/components/List/List/List.types.ts +++ b/packages/react-components/react-migration-v0-v9/src/components/List/List/List.types.ts @@ -53,6 +53,7 @@ export type ListProps = ComponentProps & { /** * Callback for selection change events, used for both controlled and uncontrolled (as notification) */ + // eslint-disable-next-line @nx/workspace-consistent-callback-type -- can't change type of existing callback onSelectionChange?: (event: React.SyntheticEvent, data: { selectedItems: SelectionItemId[] }) => void; /** diff --git a/packages/react-components/react-popover/src/components/Popover/Popover.types.ts b/packages/react-components/react-popover/src/components/Popover/Popover.types.ts index 65b08a76a2dfe..b9001833f8bc2 100644 --- a/packages/react-components/react-popover/src/components/Popover/Popover.types.ts +++ b/packages/react-components/react-popover/src/components/Popover/Popover.types.ts @@ -64,6 +64,7 @@ export type PopoverProps = Pick & { * Call back when the component requests to change value * The `open` value is used as a hint when directly controlling the component */ + // eslint-disable-next-line @nx/workspace-consistent-callback-type -- can't change type of existing callback onOpenChange?: (e: OpenPopoverEvents, data: OnOpenChangeData) => void; /** diff --git a/packages/react-components/react-popover/src/components/PopoverTrigger/PopoverTrigger.types.ts b/packages/react-components/react-popover/src/components/PopoverTrigger/PopoverTrigger.types.ts index 918cf0687646c..8f41c82e703ff 100644 --- a/packages/react-components/react-popover/src/components/PopoverTrigger/PopoverTrigger.types.ts +++ b/packages/react-components/react-popover/src/components/PopoverTrigger/PopoverTrigger.types.ts @@ -28,8 +28,10 @@ export type PopoverTriggerChildProps; + /* eslint-disable @nx/workspace-consistent-callback-type -- can't change type of existing callback */ onMouseEnter: React.MouseEventHandler; onMouseLeave: React.MouseEventHandler; onContextMenu: React.MouseEventHandler; + /* eslint-enable @nx/workspace-consistent-callback-type */ } >; diff --git a/packages/react-components/react-radio/src/components/Radio/Radio.types.ts b/packages/react-components/react-radio/src/components/Radio/Radio.types.ts index 38df20cddb08e..e7d50d0a34f59 100644 --- a/packages/react-components/react-radio/src/components/Radio/Radio.types.ts +++ b/packages/react-components/react-radio/src/components/Radio/Radio.types.ts @@ -60,6 +60,7 @@ export type RadioProps = Omit, 'input'>, 'onC * **Note:** `onChange` is NOT called when this Radio is deselected. * Use RadioGroup's `onChange` event to determine when the selection in the group changes. */ + // eslint-disable-next-line @nx/workspace-consistent-callback-type -- can't change type of existing callback onChange?: (ev: React.ChangeEvent, data: RadioOnChangeData) => void; }; diff --git a/packages/react-components/react-radio/src/components/RadioGroup/RadioGroup.types.ts b/packages/react-components/react-radio/src/components/RadioGroup/RadioGroup.types.ts index 02cf82774dde7..946054e898339 100644 --- a/packages/react-components/react-radio/src/components/RadioGroup/RadioGroup.types.ts +++ b/packages/react-components/react-radio/src/components/RadioGroup/RadioGroup.types.ts @@ -33,6 +33,7 @@ export type RadioGroupProps = Omit>, 'on /** * Callback when the selected Radio item changes. */ + // eslint-disable-next-line @nx/workspace-consistent-callback-type -- can't change type of existing callback onChange?: (ev: React.FormEvent, data: RadioGroupOnChangeData) => void; /** diff --git a/packages/react-components/react-search-preview/src/components/SearchBox/SearchBox.types.ts b/packages/react-components/react-search-preview/src/components/SearchBox/SearchBox.types.ts index b92ff4959bfce..11aa98d2fdf0a 100644 --- a/packages/react-components/react-search-preview/src/components/SearchBox/SearchBox.types.ts +++ b/packages/react-components/react-search-preview/src/components/SearchBox/SearchBox.types.ts @@ -22,6 +22,7 @@ export type SearchBoxProps = Omit< * When the dismiss button is clicked, this will be called with an event of type React.MouseEvent * and an empty string as the `value` property of the data parameter */ + // eslint-disable-next-line @nx/workspace-consistent-callback-type -- can't change type of existing callback onChange?: (event: SearchBoxChangeEvent, data: InputOnChangeData) => void; }; diff --git a/packages/react-components/react-select/src/components/Select/Select.types.ts b/packages/react-components/react-select/src/components/Select/Select.types.ts index 56f39e1e50075..bab585152aa71 100644 --- a/packages/react-components/react-select/src/components/Select/Select.types.ts +++ b/packages/react-components/react-select/src/components/Select/Select.types.ts @@ -27,6 +27,7 @@ export type SelectProps = Omit, 'select'>, ' /** * Called when the user changes the select element's value by selecting an option. */ + // eslint-disable-next-line @nx/workspace-consistent-callback-type -- can't change type of existing callback onChange?: (ev: React.ChangeEvent, data: SelectOnChangeData) => void; /** diff --git a/packages/react-components/react-slider/src/components/Slider/Slider.types.ts b/packages/react-components/react-slider/src/components/Slider/Slider.types.ts index 078192342a05a..e09c915d7d6d9 100644 --- a/packages/react-components/react-slider/src/components/Slider/Slider.types.ts +++ b/packages/react-components/react-slider/src/components/Slider/Slider.types.ts @@ -94,6 +94,7 @@ export type SliderProps = Omit< /** * Triggers a callback when the value has been changed. This will be called on every individual step. */ + // eslint-disable-next-line @nx/workspace-consistent-callback-type -- can't change type of existing callback onChange?: (ev: React.ChangeEvent, data: SliderOnChangeData) => void; }; diff --git a/packages/react-components/react-spinbutton/src/components/SpinButton/SpinButton.types.ts b/packages/react-components/react-spinbutton/src/components/SpinButton/SpinButton.types.ts index 0f806769f26d3..7f48f7835dba8 100644 --- a/packages/react-components/react-spinbutton/src/components/SpinButton/SpinButton.types.ts +++ b/packages/react-components/react-spinbutton/src/components/SpinButton/SpinButton.types.ts @@ -80,6 +80,7 @@ export type SpinButtonProps = Omit< * - User *commits* edits to the input text by focusing away (blurring) or pressing enter. * Note that this is NOT called for every key press while the user is editing. */ + // eslint-disable-next-line @nx/workspace-consistent-callback-type -- can't change type of existing callback onChange?: (event: SpinButtonChangeEvent, data: SpinButtonOnChangeData) => void; /** diff --git a/packages/react-components/react-switch/src/components/Switch/Switch.types.ts b/packages/react-components/react-switch/src/components/Switch/Switch.types.ts index 14eaba29dd9b3..4145db0996aed 100644 --- a/packages/react-components/react-switch/src/components/Switch/Switch.types.ts +++ b/packages/react-components/react-switch/src/components/Switch/Switch.types.ts @@ -68,6 +68,7 @@ export type SwitchProps = Omit< /** * Callback to be called when the checked state value changes. */ + // eslint-disable-next-line @nx/workspace-consistent-callback-type -- can't change type of existing callback onChange?: (ev: React.ChangeEvent, data: SwitchOnChangeData) => void; }; diff --git a/packages/react-components/react-table/src/components/DataGrid/DataGrid.types.ts b/packages/react-components/react-table/src/components/DataGrid/DataGrid.types.ts index d68391be117d3..99c45c70ee164 100644 --- a/packages/react-components/react-table/src/components/DataGrid/DataGrid.types.ts +++ b/packages/react-components/react-table/src/components/DataGrid/DataGrid.types.ts @@ -64,8 +64,11 @@ export type DataGridProps = TableProps & Pick, 'focusMode' | 'subtleSelection' | 'selectionAppearance' | 'resizableColumns'> & Pick & Pick & { + /* eslint-disable @nx/workspace-consistent-callback-type -- can't change type of existing callback */ onSortChange?: (e: React.MouseEvent, sortState: SortState) => void; onSelectionChange?: (e: React.MouseEvent | React.KeyboardEvent, data: OnSelectionChangeData) => void; + /* eslint-enable @nx/workspace-consistent-callback-type */ + /** * Enables row selection and sets the selection mode * @default false @@ -78,6 +81,7 @@ export type DataGridProps = TableProps & /** * A callback triggered when a column is resized. */ + // eslint-disable-next-line @nx/workspace-consistent-callback-type -- can't change type of existing callback onColumnResize?: ( e: KeyboardEvent | TouchEvent | MouseEvent | undefined, data: { columnId: TableColumnId; width: number }, diff --git a/packages/react-components/react-tabs/src/components/TabList/TabList.types.ts b/packages/react-components/react-tabs/src/components/TabList/TabList.types.ts index a184ef4f67c2b..16f1feaed473c 100644 --- a/packages/react-components/react-tabs/src/components/TabList/TabList.types.ts +++ b/packages/react-components/react-tabs/src/components/TabList/TabList.types.ts @@ -71,6 +71,7 @@ export type TabListProps = ComponentProps & { /** * Raised when a tab is selected. */ + // eslint-disable-next-line @nx/workspace-consistent-callback-type -- can't change type of existing callback onTabSelect?: SelectTabEventHandler; /** diff --git a/packages/react-components/react-tags/src/components/TagGroup/TagGroup.types.ts b/packages/react-components/react-tags/src/components/TagGroup/TagGroup.types.ts index 35816a321eb50..de65024c14044 100644 --- a/packages/react-components/react-tags/src/components/TagGroup/TagGroup.types.ts +++ b/packages/react-components/react-tags/src/components/TagGroup/TagGroup.types.ts @@ -17,6 +17,7 @@ export type TagGroupProps = ComponentProps & { /** * Callback for when a tag is dismissed */ + // eslint-disable-next-line @nx/workspace-consistent-callback-type -- can't change type of existing callback onDismiss?: TagDismissHandler; size?: TagSize; diff --git a/packages/react-components/react-teaching-popover-preview/src/components/TeachingPopover/TeachingPopover.types.ts b/packages/react-components/react-teaching-popover-preview/src/components/TeachingPopover/TeachingPopover.types.ts index 619e3e624a5e6..5d9f3bab8f7ee 100644 --- a/packages/react-components/react-teaching-popover-preview/src/components/TeachingPopover/TeachingPopover.types.ts +++ b/packages/react-components/react-teaching-popover-preview/src/components/TeachingPopover/TeachingPopover.types.ts @@ -14,6 +14,8 @@ export type TeachingPopoverProps = Omit & { * Enables user to dictate current page number via props */ currentPage?: number; + + /* eslint-disable @nx/workspace-consistent-callback-type -- can't change type of existing callback */ /** * Callback to notify a page change (can be used to update 'currentPage' externally). */ @@ -25,6 +27,8 @@ export type TeachingPopoverProps = Omit & { * Callback to notify when the final button step of a carousel has been activated. */ onFinish?: (event: React.MouseEvent) => void; + /* eslint-enable @nx/workspace-consistent-callback-type */ + /** * The appearance property (extended from popover, but removed 'inverted'). */ diff --git a/packages/react-components/react-textarea/src/components/Textarea/Textarea.types.ts b/packages/react-components/react-textarea/src/components/Textarea/Textarea.types.ts index c9b28eaab9442..47520929a4304 100644 --- a/packages/react-components/react-textarea/src/components/Textarea/Textarea.types.ts +++ b/packages/react-components/react-textarea/src/components/Textarea/Textarea.types.ts @@ -40,6 +40,7 @@ export type TextareaProps = Omit< /** * Callback for when the user changes the value. */ + // eslint-disable-next-line @nx/workspace-consistent-callback-type -- can't change type of existing callback onChange?: (ev: React.ChangeEvent, data: TextareaOnChangeData) => void; /** diff --git a/packages/react-components/react-timepicker-compat/src/components/TimePicker/TimePicker.types.ts b/packages/react-components/react-timepicker-compat/src/components/TimePicker/TimePicker.types.ts index 684feac8813fb..86828fdaaf5ab 100644 --- a/packages/react-components/react-timepicker-compat/src/components/TimePicker/TimePicker.types.ts +++ b/packages/react-components/react-timepicker-compat/src/components/TimePicker/TimePicker.types.ts @@ -145,6 +145,7 @@ export type TimePickerProps = Omit, 'input /** * Callback for when a time selection is made. */ + // eslint-disable-next-line @nx/workspace-consistent-callback-type -- can't change type of existing callback onTimeChange?: (event: TimeSelectionEvents, data: TimeSelectionData) => void; /** diff --git a/packages/react-components/react-toast/src/components/Timer/Timer.tsx b/packages/react-components/react-toast/src/components/Timer/Timer.tsx index ed29a8e4df5c5..4e7583da1f25a 100644 --- a/packages/react-components/react-toast/src/components/Timer/Timer.tsx +++ b/packages/react-components/react-toast/src/components/Timer/Timer.tsx @@ -4,6 +4,7 @@ import { useBaseAnimationStyles } from './useTimerStyles.styles'; export type TimerProps = { running: boolean; timeout: number; + // eslint-disable-next-line @nx/workspace-consistent-callback-type -- can't change type of existing callback onTimeout: () => void; as?: 'span'; }; diff --git a/packages/react-components/react-toolbar/src/components/Toolbar/Toolbar.types.ts b/packages/react-components/react-toolbar/src/components/Toolbar/Toolbar.types.ts index 95a84823d3fb2..6fa2b148d294f 100644 --- a/packages/react-components/react-toolbar/src/components/Toolbar/Toolbar.types.ts +++ b/packages/react-components/react-toolbar/src/components/Toolbar/Toolbar.types.ts @@ -47,6 +47,7 @@ export type ToolbarProps = ComponentProps & { * @param event - React's original SyntheticEvent * @param data - A data object with relevant information */ + // eslint-disable-next-line @nx/workspace-consistent-callback-type -- can't change type of existing callback onCheckedValueChange?: (e: ToolbarCheckedValueChangeEvent, data: ToolbarCheckedValueChangeData) => void; }; diff --git a/packages/react-components/react-tooltip/src/components/Tooltip/Tooltip.types.ts b/packages/react-components/react-tooltip/src/components/Tooltip/Tooltip.types.ts index c09590c3e92b7..e4b4e7218f27e 100644 --- a/packages/react-components/react-tooltip/src/components/Tooltip/Tooltip.types.ts +++ b/packages/react-components/react-tooltip/src/components/Tooltip/Tooltip.types.ts @@ -63,6 +63,7 @@ export type TooltipProps = ComponentProps & * **Note**: for backwards compatibility, `event` will be undefined if this was triggered by a keyboard event on * the document element. Use `data.documentKeyboardEvent` if the keyboard event object is needed. */ + // eslint-disable-next-line @nx/workspace-consistent-callback-type -- can't change type of existing callback onVisibleChange?: ( event: React.PointerEvent | React.FocusEvent | undefined, data: OnVisibleChangeData, diff --git a/packages/react-components/react-tree/src/components/TreeItem/TreeItem.types.ts b/packages/react-components/react-tree/src/components/TreeItem/TreeItem.types.ts index 38a53044062ec..35f01743e3ac0 100644 --- a/packages/react-components/react-tree/src/components/TreeItem/TreeItem.types.ts +++ b/packages/react-components/react-tree/src/components/TreeItem/TreeItem.types.ts @@ -54,6 +54,7 @@ export type TreeItemProps = ComponentProps> & { * NOTE: controlling the open state of a tree item will not affect the open state of its children */ open?: boolean; + // eslint-disable-next-line @nx/workspace-consistent-callback-type -- can't change type of existing callback onOpenChange?: (e: TreeItemOpenChangeEvent, data: TreeItemOpenChangeData) => void; /** * This property is inferred through context on a nested tree, and required for a flat tree. diff --git a/packages/react-components/react-virtualizer/src/components/Virtualizer/Virtualizer.types.ts b/packages/react-components/react-virtualizer/src/components/Virtualizer/Virtualizer.types.ts index ae78857259718..f2664f6d6d9f6 100644 --- a/packages/react-components/react-virtualizer/src/components/Virtualizer/Virtualizer.types.ts +++ b/packages/react-components/react-virtualizer/src/components/Virtualizer/Virtualizer.types.ts @@ -159,6 +159,7 @@ export type VirtualizerConfigProps = { /** * Callback for notifying when a flagged index has been rendered */ + // eslint-disable-next-line @nx/workspace-consistent-callback-type -- can't change type of existing callback onRenderedFlaggedIndex?: (index: number) => void; /* diff --git a/tools/eslint-rules/index.ts b/tools/eslint-rules/index.ts index c5d9c0f7e0ad7..e3e602fdf24ca 100644 --- a/tools/eslint-rules/index.ts +++ b/tools/eslint-rules/index.ts @@ -1,3 +1,7 @@ +import { + RULE_NAME as consistentCallbackTypeName, + rule as consistentCallbackType, +} from './rules/consistent-callback-type'; /** * Import your custom workspace rules at the top of this file. * @@ -23,5 +27,5 @@ module.exports = { * [myCustomRuleName]: myCustomRule * } */ - rules: {}, + rules: { [consistentCallbackTypeName]: consistentCallbackType }, }; diff --git a/tools/eslint-rules/rules/consistent-callback-type.spec.ts b/tools/eslint-rules/rules/consistent-callback-type.spec.ts new file mode 100644 index 0000000000000..28e5930dafb78 --- /dev/null +++ b/tools/eslint-rules/rules/consistent-callback-type.spec.ts @@ -0,0 +1,75 @@ +import { TSESLint } from '@typescript-eslint/experimental-utils'; +import { rule, RULE_NAME } from './consistent-callback-type'; +const ruleTester = new TSESLint.RuleTester({ + parser: require.resolve('@typescript-eslint/parser'), +}); +ruleTester.run(RULE_NAME, rule, { + valid: [ + // Valid when prop is TSTypeAliasDeclaration and the callback uses EventHandler + { + code: ` + import { EventHandler, EventData } from '@fluentui/react-utilities'; + export type OnSomeEventData = EventData<'focus', React.FocusEvent> & { + open: boolean, + }; + export type SomeProps = { + onSomeEvent?: EventHandler, + }; + `, + filename: 'src/components/SomeComponent/SomeComponent.type.ts', + }, + // Valid when prop is TSIntersectionType and the callback uses EventHandler + { + code: ` + import { EventHandler, EventData } from '@fluentui/react-utilities'; + export type OnSomeEventData = EventData<'focus', React.FocusEvent> & { + open: boolean, + }; + export type SomeProps = SomeType & { + onSomeEvent?: EventHandler, + }; + `, + filename: 'src/components/SomeComponent/SomeComponent.type.ts', + }, + // Valid when prop has a function that is not callback, and not using EventHandler type + { + code: ` + type SomeArgBag = { one: number }; + export type SomeProps = { + someFunction?: (args: SomeArgBag) => void; + someFunction2?: (args: { onSomething: string }) => void; // test nested TSTypeLiteral node that starts with 'on' + }; + `, + filename: 'src/components/SomeComponent/SomeComponent.type.ts', + }, + ], + invalid: [ + // Invalid when callback in props is not using EventHandler + { + code: ` + export type SomeProps = { + onChange?: (ev: React.ChangeEvent, data: {}) => void; + }; + `, + errors: [{ messageId: 'invalidType' }], + }, + // Invalid when callback in props is not using EventHandler, and the props is TSIntersectionType + { + code: ` + export type SomeProps = SomeType & { + onChange?: (ev: React.ChangeEvent, data: {}) => void; + }; + `, + errors: [{ messageId: 'invalidType' }], + }, + // Invalid when callback in props is not using EventHandler, and the prop is two intersected TypeLiteral + { + code: ` + export type SomeProps = SomeType & { onClick?: (ev: React.ClickEvent) => void } & { + onChange?: (ev: React.ChangeEvent, data: {}) => void, + }; + `, + errors: [{ messageId: 'invalidType' }, { messageId: 'invalidType' }], + }, + ], +}); diff --git a/tools/eslint-rules/rules/consistent-callback-type.ts b/tools/eslint-rules/rules/consistent-callback-type.ts new file mode 100644 index 0000000000000..a2dcaba073a4a --- /dev/null +++ b/tools/eslint-rules/rules/consistent-callback-type.ts @@ -0,0 +1,67 @@ +import { ESLintUtils, AST_NODE_TYPES, TSESTree } from '@typescript-eslint/experimental-utils'; + +// NOTE: The rule will be available in ESLint configs as "@nx/workspace-consistent-callback-type" +export const RULE_NAME = 'consistent-callback-type'; + +export const rule = ESLintUtils.RuleCreator(() => __filename)({ + name: RULE_NAME, + meta: { + type: 'problem', + docs: { + description: 'Enforce callback props to be typed with `EventHandler`', + category: 'Best Practices', + recommended: 'error', + }, + messages: { + invalidType: 'callback props should be typed with @fluentui/react-utilities#EventHandler', + }, + schema: [], + }, + defaultOptions: [], + create(context) { + let isTypeLiteralDirectChild = false; // captures `*.Props` TSTypeAliasDeclaration node and tracks direct child TSTypeLiteral node within + + return { + 'TSTypeAliasDeclaration[id.name=/.*Props$/] TSTypeLiteral': () => { + isTypeLiteralDirectChild = true; + }, + + 'TSTypeAliasDeclaration[id.name=/.*Props$/] TSTypeLiteral:exit': (node: TSESTree.TSTypeLiteral) => { + if (isTypeLiteralDirectChild) { + node.members.forEach(member => { + if ( + member.type === AST_NODE_TYPES.TSPropertySignature && + member.key.type === AST_NODE_TYPES.Identifier && + /^on[A-Z]/.test(member.key.name) + ) { + const typeAnnotation = member.typeAnnotation?.typeAnnotation; + // Check if typeAnnotation is of type EventHandler + if ( + !( + typeAnnotation && + typeAnnotation.type === AST_NODE_TYPES.TSTypeReference && + typeAnnotation.typeName.type === AST_NODE_TYPES.Identifier && + typeAnnotation.typeName.name === 'EventHandler' && + typeAnnotation.typeParameters + ) + ) { + context.report({ + node: member, + messageId: 'invalidType', + }); + } + } + }); + } + }, + + 'TSTypeAliasDeclaration[id.name=/.*Props$/] TSTypeLiteral TSTypeLiteral': () => { + isTypeLiteralDirectChild = false; + }, + + 'TSTypeAliasDeclaration[id.name=/.*Props$/] TSTypeLiteral TSTypeLiteral:exit': () => { + isTypeLiteralDirectChild = true; + }, + }; + }, +});