diff --git a/desktop/.eslintrc.js b/desktop/.eslintrc.js index 070d2a313..81e61e099 100644 --- a/desktop/.eslintrc.js +++ b/desktop/.eslintrc.js @@ -146,6 +146,7 @@ module.exports = { 'flipper/no-console-error-without-context': [2], 'flipper/no-ts-file-extension': 2, 'flipper/no-i-prefix-interfaces': 2, + 'flipper/no-interface-props-or-state': 2, 'communist-spelling/communist-spelling': [1, {allow: ['cancelled']}], // promise rules, see https://github.com/xjamundx/eslint-plugin-promise for details on each of them diff --git a/desktop/eslint-plugin-flipper/src/index.tsx b/desktop/eslint-plugin-flipper/src/index.tsx index e81c444b3..148f967ad 100644 --- a/desktop/eslint-plugin-flipper/src/index.tsx +++ b/desktop/eslint-plugin-flipper/src/index.tsx @@ -22,6 +22,9 @@ import noTsFileExtension, { import noIPrefixInterfaces, { RULE_NAME as noIPrefixInterfacesRuleName, } from './rules/noIPrefixInterfaces'; +import noInterfaceProps, { + RULE_NAME as noInterfacePropsRuleName, +} from './rules/noInterfacePropsOrState'; module.exports = { rules: { @@ -30,5 +33,6 @@ module.exports = { [noConsoleErrorWithoutContextRuleName]: noConsoleErrorWithoutContext, [noTsFileExtensionRuleName]: noTsFileExtension, [noIPrefixInterfacesRuleName]: noIPrefixInterfaces, + [noInterfacePropsRuleName]: noInterfaceProps, }, }; diff --git a/desktop/eslint-plugin-flipper/src/rules/noInterfacePropsOrState.tsx b/desktop/eslint-plugin-flipper/src/rules/noInterfacePropsOrState.tsx new file mode 100644 index 000000000..7a8cd7aeb --- /dev/null +++ b/desktop/eslint-plugin-flipper/src/rules/noInterfacePropsOrState.tsx @@ -0,0 +1,48 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + */ + +import {createESLintRule} from '../utils/createEslintRule'; + +type Options = []; + +export type MessageIds = 'noInterfacePropsOrState'; +export const RULE_NAME = 'no-interface-props-or-state'; + +export default createESLintRule({ + name: RULE_NAME, + meta: { + type: 'problem', + docs: { + description: 'Use type aliases for component props instead of interfaces', + recommended: 'error', + }, + schema: [], + messages: { + noInterfacePropsOrState: + 'Use type aliases for component props and state instead of interfaces', + }, + }, + defaultOptions: [], + create(context) { + return { + TSInterfaceDeclaration(node) { + if ( + !(node.id.name.endsWith('Props') || node.id.name.endsWith('State')) + ) { + return; + } + + context.report({ + node: node.id, + messageId: 'noInterfacePropsOrState', + }); + }, + }; + }, +}); diff --git a/desktop/flipper-plugin/src/ui/DataList.tsx b/desktop/flipper-plugin/src/ui/DataList.tsx index 3f30d9669..33f4998e9 100644 --- a/desktop/flipper-plugin/src/ui/DataList.tsx +++ b/desktop/flipper-plugin/src/ui/DataList.tsx @@ -213,12 +213,12 @@ export const DataList: (( enableArrow: true, }; -interface DataListItemProps { +type DataListItemProps = { // TODO: add icon support title?: string | React.ReactElement; description?: string | React.ReactElement; enableArrow?: boolean; -} +}; const ArrowWrapper = styled.div({ flex: 0, diff --git a/desktop/flipper-plugin/src/ui/data-table/DataTable.tsx b/desktop/flipper-plugin/src/ui/data-table/DataTable.tsx index 6bdbe9ee4..89cb8fa12 100644 --- a/desktop/flipper-plugin/src/ui/data-table/DataTable.tsx +++ b/desktop/flipper-plugin/src/ui/data-table/DataTable.tsx @@ -53,7 +53,7 @@ import {debounce} from 'lodash'; import {useInUnitTest} from '../../utils/useInUnitTest'; import {createDataSource} from '../../state/createDataSource'; -interface DataTableBaseProps { +type DataTableBaseProps = { columns: DataTableColumn[]; enableSearchbar?: boolean; enableAutoScroll?: boolean; @@ -73,7 +73,7 @@ interface DataTableBaseProps { onRenderEmpty?: | null | ((dataSource?: DataSource) => React.ReactElement); -} +}; export type ItemRenderer = ( item: T, diff --git a/desktop/flipper-ui-core/src/__tests__/PluginContainer.node.tsx b/desktop/flipper-ui-core/src/__tests__/PluginContainer.node.tsx index c56cdaf79..3c733e614 100644 --- a/desktop/flipper-ui-core/src/__tests__/PluginContainer.node.tsx +++ b/desktop/flipper-ui-core/src/__tests__/PluginContainer.node.tsx @@ -29,9 +29,9 @@ import {updateSettings} from '../reducers/settings'; import {switchPlugin} from '../reducers/pluginManager'; import {awaitPluginCommandQueueEmpty} from '../dispatcher/pluginManager'; -interface PersistedState { +type PersistedState = { count: 1; -} +}; class TestPlugin extends FlipperPlugin { static id = 'TestPlugin'; diff --git a/desktop/flipper-ui-core/src/__tests__/createMockFlipperWithPlugin.node.tsx b/desktop/flipper-ui-core/src/__tests__/createMockFlipperWithPlugin.node.tsx index 5398da4b2..9818bc1d5 100644 --- a/desktop/flipper-ui-core/src/__tests__/createMockFlipperWithPlugin.node.tsx +++ b/desktop/flipper-ui-core/src/__tests__/createMockFlipperWithPlugin.node.tsx @@ -12,9 +12,9 @@ import {FlipperPlugin} from '../plugin'; import {TestIdler} from '../utils/Idler'; import {getAllClients} from '../reducers/connections'; -interface PersistedState { +type PersistedState = { count: 1; -} +}; class TestPlugin extends FlipperPlugin { static id = 'TestPlugin'; diff --git a/docs/extending/dev-setup.mdx b/docs/extending/dev-setup.mdx index 7f3a27ed6..15d554404 100644 --- a/docs/extending/dev-setup.mdx +++ b/docs/extending/dev-setup.mdx @@ -158,7 +158,6 @@ To start Flipper against a specific OnDemand instance, set FB_ONDEMAND flag, e.g ## Guidelines for writing TypeScript -* Prefer `type` for React props and state over interfaces * Install 3rd party type definitions as dev dependency (e.g. `yarn add @types/lodash --dev`) ## Submitting a diff / PR