diff --git a/desktop/.eslintrc.js b/desktop/.eslintrc.js index eb9cee073..e8399ab01 100644 --- a/desktop/.eslintrc.js +++ b/desktop/.eslintrc.js @@ -145,6 +145,7 @@ module.exports = { 'flipper/no-electron-remote-imports': [1], 'flipper/no-console-error-without-context': [2], 'flipper/no-ts-file-extension': 1, + 'flipper/no-i-prefix-interfaces': 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.ts b/desktop/eslint-plugin-flipper/src/index.ts index 20db3646b..e81c444b3 100644 --- a/desktop/eslint-plugin-flipper/src/index.ts +++ b/desktop/eslint-plugin-flipper/src/index.ts @@ -19,6 +19,9 @@ import noConsoleErrorWithoutContext, { import noTsFileExtension, { RULE_NAME as noTsFileExtensionRuleName, } from './rules/noTsFileExtension'; +import noIPrefixInterfaces, { + RULE_NAME as noIPrefixInterfacesRuleName, +} from './rules/noIPrefixInterfaces'; module.exports = { rules: { @@ -26,5 +29,6 @@ module.exports = { [noElectronRemoteImportsRuleName]: noElectronRemoteImports, [noConsoleErrorWithoutContextRuleName]: noConsoleErrorWithoutContext, [noTsFileExtensionRuleName]: noTsFileExtension, + [noIPrefixInterfacesRuleName]: noIPrefixInterfaces, }, }; diff --git a/desktop/eslint-plugin-flipper/src/rules/__tests__/noIPrefixInterfaces.node.ts b/desktop/eslint-plugin-flipper/src/rules/__tests__/noIPrefixInterfaces.node.ts new file mode 100644 index 000000000..ff3233c36 --- /dev/null +++ b/desktop/eslint-plugin-flipper/src/rules/__tests__/noIPrefixInterfaces.node.ts @@ -0,0 +1,39 @@ +/** + * 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 {TSESLint} from '@typescript-eslint/experimental-utils'; +import rule, {RULE_NAME} from '../noIPrefixInterfaces'; + +const tester = new TSESLint.RuleTester({ + parser: require.resolve('@typescript-eslint/parser'), + parserOptions: { + sourceType: 'module', + ecmaVersion: 2020, + }, +}); + +tester.run(RULE_NAME, rule, { + valid: [ + { + code: `interface Icon {name: string}`, + filename: __filename, + }, + { + code: `interface IOSDevice {version: string}`, + filename: __filename, + }, + ], + invalid: [ + { + code: `interface IDevice {version: string;}`, + filename: __filename, + errors: [{messageId: 'noTsInterfacePrefixI'}], + }, + ], +}); diff --git a/desktop/eslint-plugin-flipper/src/rules/noIPrefixInterfaces.ts b/desktop/eslint-plugin-flipper/src/rules/noIPrefixInterfaces.ts new file mode 100644 index 000000000..935fbf3b8 --- /dev/null +++ b/desktop/eslint-plugin-flipper/src/rules/noIPrefixInterfaces.ts @@ -0,0 +1,54 @@ +/** + * 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 = 'noTsInterfacePrefixI'; +export const RULE_NAME = 'no-i-prefix-interfaces'; + +export default createESLintRule({ + name: RULE_NAME, + meta: { + type: 'problem', + docs: { + description: 'Avoid prefixing TS interfaces with "I"', + recommended: 'error', + }, + schema: [], + messages: { + noTsInterfacePrefixI: 'Do not prefix interfaces with "I"', + }, + }, + defaultOptions: [], + create(context) { + return { + TSInterfaceDeclaration(node) { + const [l1, l2, l3] = node.id.name; + if ( + l1 === 'I' && + isLetter(l2) && + l2 === l2.toUpperCase() && + isLetter(l3) && + l3 === l3.toLowerCase() + ) { + context.report({ + node: node.id, + messageId: 'noTsInterfacePrefixI', + }); + } + }, + }; + }, +}); + +function isLetter(x: string | undefined): boolean { + return typeof x === 'string' && /^[a-z]$/i.test(x); +} diff --git a/docs/extending/dev-setup.mdx b/docs/extending/dev-setup.mdx index 55f9dc18d..7f3a27ed6 100644 --- a/docs/extending/dev-setup.mdx +++ b/docs/extending/dev-setup.mdx @@ -159,7 +159,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 -* Don’t prefix interfaces with `I` * Install 3rd party type definitions as dev dependency (e.g. `yarn add @types/lodash --dev`) ## Submitting a diff / PR