Skip to content
Snippets Groups Projects
.eslintrc.js 19.5 KiB
Newer Older
  • Learn to ignore specific revisions
  • const path = require('path');
    
    const rulesDirPlugin = require('eslint-plugin-rulesdir');
    rulesDirPlugin.RULES_DIR = path.join(
      __dirname,
      'packages',
      'eslint-plugin-actual',
      'lib',
      'rules',
    );
    
    
    const ruleFCMsg =
      'Type the props argument and let TS infer or use ComponentType for a component prop';
    
    
    const restrictedImportPatterns = [
      {
        group: ['*.api', '*.web', '*.electron'],
        message: 'Don’t directly reference imports from other platforms',
      },
    
      {
        group: ['uuid'],
        importNames: ['*'],
        message: "Use `import { v4 as uuidv4 } from 'uuid'` instead",
      },
    
    const restrictedImportColors = [
      {
        group: ['**/style', '**/colors'],
        importNames: ['colors'],
        message: 'Please use themes instead of colors',
      },
    ];
    
    
    module.exports = {
    
      root: true,
      env: {
        browser: true,
        commonjs: true,
        es6: true,
        jest: true,
        node: true,
      },
      plugins: [
        'prettier',
        'import',
        'rulesdir',
        '@typescript-eslint',
        'jsx-a11y',
        'react-hooks',
      ],
    
      extends: [
        'plugin:react/recommended',
    
        'plugin:@typescript-eslint/recommended',
    
        'plugin:import/typescript',
    
      parser: '@typescript-eslint/parser',
      parserOptions: { project: [path.join(__dirname, './tsconfig.json')] },
    
      reportUnusedDisableDirectives: true,
    
      globals: {
        globalThis: false,
    
        // http://eslint.org/docs/rules/
        'array-callback-return': 'warn',
        'default-case': ['warn', { commentPattern: '^no default$' }],
        'dot-location': ['warn', 'property'],
        eqeqeq: ['warn', 'smart'],
        'new-parens': 'warn',
        'no-array-constructor': 'warn',
        'no-caller': 'warn',
        'no-cond-assign': ['warn', 'except-parens'],
        'no-const-assign': 'warn',
        'no-control-regex': 'warn',
        'no-delete-var': 'warn',
        'no-dupe-args': 'warn',
        'no-dupe-class-members': 'warn',
        'no-dupe-keys': 'warn',
        'no-duplicate-case': 'warn',
        'no-empty-character-class': 'warn',
        'no-empty-pattern': 'warn',
        'no-eval': 'warn',
        'no-ex-assign': 'warn',
        'no-extend-native': 'warn',
        'no-extra-bind': 'warn',
        'no-extra-label': 'warn',
        'no-fallthrough': 'warn',
        'no-func-assign': 'warn',
        'no-implied-eval': 'warn',
        'no-invalid-regexp': 'warn',
        'no-iterator': 'warn',
        'no-label-var': 'warn',
        'no-labels': ['warn', { allowLoop: true, allowSwitch: false }],
        'no-lone-blocks': 'warn',
        'no-mixed-operators': [
          'warn',
          {
            groups: [
              ['&', '|', '^', '~', '<<', '>>', '>>>'],
              ['==', '!=', '===', '!==', '>', '>=', '<', '<='],
              ['&&', '||'],
              ['in', 'instanceof'],
            ],
            allowSamePrecedence: false,
          },
        ],
        'no-multi-str': 'warn',
        'no-global-assign': 'warn',
        'no-unsafe-negation': 'warn',
        'no-new-func': 'warn',
        'no-new-object': 'warn',
        'no-new-symbol': 'warn',
        'no-new-wrappers': 'warn',
        'no-obj-calls': 'warn',
        'no-octal': 'warn',
        'no-octal-escape': 'warn',
        'no-redeclare': 'warn',
        'no-regex-spaces': 'warn',
        'no-script-url': 'warn',
        'no-self-assign': 'warn',
        'no-self-compare': 'warn',
        'no-sequences': 'warn',
        'no-shadow-restricted-names': 'warn',
        'no-sparse-arrays': 'warn',
        'no-template-curly-in-string': 'warn',
        'no-this-before-super': 'warn',
        'no-throw-literal': 'warn',
        'no-undef': 'error',
        'no-unreachable': 'warn',
        'no-unused-expressions': [
          'error',
          {
            allowShortCircuit: true,
            allowTernary: true,
            allowTaggedTemplates: true,
          },
        ],
        'no-unused-labels': 'warn',
        'no-use-before-define': [
          'warn',
          {
            functions: false,
            classes: false,
            variables: false,
          },
        ],
        'no-useless-computed-key': 'warn',
        'no-useless-concat': 'warn',
        'no-useless-constructor': 'warn',
        'no-useless-escape': 'warn',
        'no-useless-rename': [
          'warn',
          {
            ignoreDestructuring: false,
            ignoreImport: false,
            ignoreExport: false,
          },
        ],
        'no-with': 'warn',
        'no-whitespace-before-property': 'warn',
        'react-hooks/exhaustive-deps': 'warn',
        'require-yield': 'warn',
        'rest-spread-spacing': ['warn', 'never'],
        strict: ['warn', 'never'],
        'unicode-bom': ['warn', 'never'],
        'use-isnan': 'warn',
        'valid-typeof': 'warn',
        'no-restricted-properties': [
          'error',
          {
            object: 'require',
            property: 'ensure',
            message:
              'Please use import() instead. More info: https://facebook.github.io/create-react-app/docs/code-splitting',
          },
          {
            object: 'System',
            property: 'import',
            message:
              'Please use import() instead. More info: https://facebook.github.io/create-react-app/docs/code-splitting',
          },
        ],
        'getter-return': 'warn',
    
        // https://github.com/benmosher/eslint-plugin-import/tree/master/docs/rules
        'import/first': 'error',
        'import/no-amd': 'error',
        'import/no-anonymous-default-export': 'warn',
        'import/no-webpack-loader-syntax': 'error',
    
        // https://github.com/yannickcr/eslint-plugin-react/tree/master/docs/rules
        'react/forbid-foreign-prop-types': ['warn', { allowInPropTypes: true }],
        'react/jsx-no-comment-textnodes': 'warn',
        'react/jsx-no-duplicate-props': 'warn',
        'react/jsx-no-target-blank': 'warn',
        'react/jsx-no-undef': 'error',
        'react/jsx-pascal-case': [
          'warn',
          {
            allowAllCaps: true,
            ignore: [],
          },
        ],
        'react/no-danger-with-children': 'warn',
        // Disabled because of undesirable warnings
        // See https://github.com/facebook/create-react-app/issues/5204 for
        // blockers until its re-enabled
        // 'react/no-deprecated': 'warn',
        'react/no-direct-mutation-state': 'warn',
        'react/no-is-mounted': 'warn',
        'react/no-typos': 'error',
        'react/require-render-return': 'error',
        'react/style-prop-object': 'warn',
    
        // https://github.com/evcohen/eslint-plugin-jsx-a11y/tree/master/docs/rules
        'jsx-a11y/alt-text': 'warn',
        'jsx-a11y/anchor-has-content': 'warn',
        'jsx-a11y/anchor-is-valid': [
          'warn',
          {
            aspects: ['noHref', 'invalidHref'],
          },
        ],
        'jsx-a11y/aria-activedescendant-has-tabindex': 'warn',
        'jsx-a11y/aria-props': 'warn',
        'jsx-a11y/aria-proptypes': 'warn',
        'jsx-a11y/aria-role': ['warn', { ignoreNonDOM: true }],
        'jsx-a11y/aria-unsupported-elements': 'warn',
        'jsx-a11y/heading-has-content': 'warn',
        'jsx-a11y/iframe-has-title': 'warn',
        'jsx-a11y/img-redundant-alt': 'warn',
        'jsx-a11y/no-access-key': 'warn',
        'jsx-a11y/no-distracting-elements': 'warn',
        'jsx-a11y/no-redundant-roles': 'warn',
        'jsx-a11y/role-has-required-aria-props': 'warn',
        'jsx-a11y/role-supports-aria-props': 'warn',
        'jsx-a11y/scope': 'warn',
    
        // https://github.com/facebook/react/tree/main/packages/eslint-plugin-react-hooks
        'react-hooks/rules-of-hooks': 'error',
    
    
    
        // Note: base rule explicitly disabled in favor of the TS one
    
        'no-unused-vars': 'off',
    
        '@typescript-eslint/no-unused-vars': [
    
            ignoreRestSiblings: true,
    
        curly: ['warn', 'multi-line', 'consistent'],
    
        'no-restricted-globals': ['warn'].concat(
    
          require('confusing-browser-globals').filter(g => g !== 'self'),
        ),
    
    
        'react/jsx-filename-extension': [
          'warn',
          { extensions: ['.jsx', '.tsx'], allow: 'as-needed' },
        ],
    
        'react/jsx-no-useless-fragment': 'warn',
        'react/self-closing-comp': 'warn',
    
        'react/no-unstable-nested-components': [
          'warn',
          { allowAsProps: true, customValidators: ['formatter'] },
        ],
    
        'rulesdir/typography': 'warn',
        'rulesdir/prefer-if-statement': 'warn',
    
        // https://github.com/eslint/eslint/issues/16954
        // https://github.com/eslint/eslint/issues/16953
    
        'no-loop-func': 'off',
    
        // Do don't need this as we're using TypeScript
        'react/prop-types': 'off',
    
    
        'react/react-in-jsx-scope': 'off',
    
        'react/jsx-curly-brace-presence': 'warn',
    
        'object-shorthand': ['warn', 'properties'],
    
        'import/extensions': [
    
          'never',
          {
            json: 'always',
          },
        ],
    
        'import/no-useless-path-segments': 'warn',
        'import/no-duplicates': ['warn', { 'prefer-inline': true }],
        'import/no-unused-modules': ['warn', { unusedExports: true }],
    
            alphabetize: {
              caseInsensitive: true,
    
            groups: [
              'builtin', // Built-in types are first
              'external',
    
            ],
            'newlines-between': 'always',
            pathGroups: [
              // Enforce that React (and react-related packages) is the first import
              { group: 'builtin', pattern: 'react?(-*)', position: 'before' },
              // Separate imports from Actual from "real" external imports
              {
                group: 'external',
                pattern: 'loot-{core,design}/**/*',
    
            pathGroupsExcludedImportTypes: ['react'],
          },
        ],
    
        'no-restricted-syntax': [
    
          {
            // forbid React.* as they are legacy https://twitter.com/dan_abramov/status/1308739731551858689
            selector:
              ":matches(MemberExpression[object.name='React'], TSQualifiedName[left.name='React'])",
            message:
              'Using default React import is discouraged, please use named exports directly instead.',
          },
    
    Neil's avatar
    Neil committed
            // forbid <a> in favor of <Link>
    
            selector: 'JSXOpeningElement[name.name="a"]',
    
    Neil's avatar
    Neil committed
            message: 'Using <a> is discouraged, please use <Link> instead.',
    
        'no-restricted-imports': [
    
          { patterns: [...restrictedImportPatterns, ...restrictedImportColors] },
        ],
    
        '@typescript-eslint/ban-ts-comment': [
          'error',
          { 'ts-ignore': 'allow-with-description' },
        ],
    
    
        // Rules disable during TS migration
        '@typescript-eslint/no-var-requires': 'off',
    
        'prefer-spread': 'off',
    
        '@typescript-eslint/no-empty-function': 'off',
    
        '@typescript-eslint/no-require-imports': 'off',
    
        'import/no-default-export': 'warn',
    
        {
          files: ['**/*.ts?(x)'],
          parser: '@typescript-eslint/parser',
          parserOptions: {
            ecmaVersion: 2018,
            sourceType: 'module',
            ecmaFeatures: {
              jsx: true,
            },
    
            // typescript-eslint specific options
            warnOnUnsupportedTypeScriptVersion: true,
          },
          plugins: ['@typescript-eslint'],
          // If adding a typescript-eslint version of an existing ESLint rule,
          // make sure to disable the ESLint rule here.
          rules: {
            // TypeScript's `noFallthroughCasesInSwitch` option is more robust (#6906)
            'default-case': 'off',
            // 'tsc' already handles this (https://github.com/typescript-eslint/typescript-eslint/issues/291)
            'no-dupe-class-members': 'off',
            // 'tsc' already handles this (https://github.com/typescript-eslint/typescript-eslint/issues/477)
            'no-undef': 'off',
    
            // Add TypeScript specific rules (and turn off ESLint equivalents)
            '@typescript-eslint/consistent-type-assertions': 'warn',
            'no-array-constructor': 'off',
            '@typescript-eslint/no-array-constructor': 'warn',
            'no-redeclare': 'off',
            '@typescript-eslint/no-redeclare': 'warn',
            'no-use-before-define': 'off',
            '@typescript-eslint/no-use-before-define': [
              'warn',
              {
                functions: false,
                classes: false,
                variables: false,
                typedefs: false,
              },
            ],
            'no-unused-expressions': 'off',
            '@typescript-eslint/no-unused-expressions': [
              'error',
              {
                allowShortCircuit: true,
                allowTernary: true,
                allowTaggedTemplates: true,
              },
            ],
            'no-useless-constructor': 'off',
            '@typescript-eslint/no-useless-constructor': 'warn',
          },
        },
    
          files: ['.eslintrc.js', './**/.eslintrc.js'],
    
          parserOptions: { project: null },
    
          rules: {
            '@typescript-eslint/consistent-type-exports': 'off',
          },
    
        },
        {
          files: [
            './packages/desktop-client/**/*.{ts,tsx}',
            './packages/loot-core/src/client/**/*.{ts,tsx}',
          ],
          rules: {
            // enforce type over interface
    
            '@typescript-eslint/consistent-type-definitions': ['warn', 'type'],
    
            // enforce import type
            '@typescript-eslint/consistent-type-imports': [
    
              { prefer: 'type-imports', fixStyle: 'inline-type-imports' },
            ],
    
            '@typescript-eslint/no-restricted-types': [
    
              {
                types: {
                  // forbid FC as superflous
                  FunctionComponent: { message: ruleFCMsg },
                  FC: { message: ruleFCMsg },
                },
              },
            ],
          },
        },
    
        {
          files: ['./packages/desktop-client/**/*'],
          excludedFiles: [
            './packages/desktop-client/src/hooks/useNavigate.{ts,tsx}',
          ],
          rules: {
            'no-restricted-imports': [
              'warn',
              {
                patterns: [
                  {
                    group: ['react-router-dom'],
                    importNames: ['useNavigate'],
                    message: 'Please use Actual’s useNavigate() hook instead.',
                  },
                ],
              },
            ],
          },
        },
    
        {
          files: ['./packages/loot-core/src/**/*'],
          rules: {
            'no-restricted-imports': [
    
                  ...restrictedImportPatterns,
    
                  {
                    group: ['loot-core/**'],
                    message:
                      'Please use relative imports in loot-core instead of importing from `loot-core/*`',
                  },
                ],
              },
            ],
          },
        },
    
            'packages/loot-core/src/types/**/*',
            'packages/loot-core/src/client/state-types/**/*',
            '**/icons/**/*',
            '**/{mocks,__mocks__}/**/*',
    
            // can't correctly resolve usages
            '**/*.{testing,electron,browser,web,api}.ts',
          ],
    
          rules: { 'import/no-unused-modules': 'off' },
    
    Neil's avatar
    Neil committed
            './packages/desktop-client/src/style/index.*',
            './packages/desktop-client/src/style/palette.*',
    
          ],
          rules: {
            'no-restricted-imports': ['off', { patterns: restrictedImportColors }],
          },
        },
    
            './packages/api/migrations/*',
            './packages/loot-core/migrations/*',
    
            'import/no-default-export': 'off',
    
        {
          // TODO: fix the issues in these files
          files: [
            './packages/desktop-client/src/components/accounts/Account.jsx',
            './packages/desktop-client/src/components/accounts/MobileAccount.jsx',
            './packages/desktop-client/src/components/accounts/MobileAccounts.jsx',
            './packages/desktop-client/src/components/App.tsx',
            './packages/desktop-client/src/components/budget/BudgetCategories.jsx',
            './packages/desktop-client/src/components/budget/BudgetSummaries.tsx',
            './packages/desktop-client/src/components/budget/DynamicBudgetTable.tsx',
            './packages/desktop-client/src/components/budget/index.tsx',
            './packages/desktop-client/src/components/budget/MobileBudget.tsx',
    
            './packages/desktop-client/src/components/budget/envelope/HoldMenu.tsx',
            './packages/desktop-client/src/components/budget/envelope/TransferMenu.tsx',
    
            './packages/desktop-client/src/components/common/Menu.tsx',
            './packages/desktop-client/src/components/FinancesApp.tsx',
            './packages/desktop-client/src/components/GlobalKeys.ts',
            './packages/desktop-client/src/components/LoggedInUser.tsx',
            './packages/desktop-client/src/components/manager/ManagementApp.jsx',
            './packages/desktop-client/src/components/manager/subscribe/common.tsx',
            './packages/desktop-client/src/components/ManageRules.tsx',
            './packages/desktop-client/src/components/mobile/MobileAmountInput.jsx',
            './packages/desktop-client/src/components/mobile/MobileNavTabs.tsx',
            './packages/desktop-client/src/components/Modals.tsx',
            './packages/desktop-client/src/components/modals/EditRule.jsx',
            './packages/desktop-client/src/components/modals/ImportTransactions.jsx',
            './packages/desktop-client/src/components/modals/MergeUnusedPayees.jsx',
            './packages/desktop-client/src/components/Notifications.tsx',
            './packages/desktop-client/src/components/payees/ManagePayees.jsx',
            './packages/desktop-client/src/components/payees/ManagePayeesWithData.jsx',
            './packages/desktop-client/src/components/payees/PayeeTable.tsx',
            './packages/desktop-client/src/components/reports/graphs/tableGraph/ReportTable.tsx',
            './packages/desktop-client/src/components/reports/graphs/tableGraph/ReportTableTotals.tsx',
            './packages/desktop-client/src/components/reports/reports/CashFlowCard.jsx',
            './packages/desktop-client/src/components/reports/reports/CustomReport.jsx',
            './packages/desktop-client/src/components/reports/reports/NetWorthCard.jsx',
            './packages/desktop-client/src/components/reports/SaveReportName.tsx',
            './packages/desktop-client/src/components/reports/useReport.ts',
            './packages/desktop-client/src/components/schedules/ScheduleDetails.jsx',
            './packages/desktop-client/src/components/schedules/SchedulesTable.tsx',
            './packages/desktop-client/src/components/select/DateSelect.tsx',
            './packages/desktop-client/src/components/sidebar/Tools.tsx',
            './packages/desktop-client/src/components/sort.tsx',
            './packages/desktop-client/src/components/spreadsheet/useSheetValue.ts',
            './packages/desktop-client/src/components/table.tsx',
            './packages/desktop-client/src/components/Titlebar.tsx',
            './packages/desktop-client/src/components/transactions/MobileTransaction.jsx',
            './packages/desktop-client/src/components/transactions/SelectedTransactions.jsx',
            './packages/desktop-client/src/components/transactions/SimpleTransactionsTable.jsx',
            './packages/desktop-client/src/components/transactions/TransactionList.jsx',
            './packages/desktop-client/src/components/transactions/TransactionsTable.jsx',
            './packages/desktop-client/src/components/transactions/TransactionsTable.test.jsx',
            './packages/desktop-client/src/hooks/useAccounts.ts',
            './packages/desktop-client/src/hooks/useCategories.ts',
            './packages/desktop-client/src/hooks/usePayees.ts',
            './packages/desktop-client/src/hooks/useProperFocus.tsx',
            './packages/desktop-client/src/hooks/useSelected.tsx',
            './packages/loot-core/src/client/query-hooks.tsx',
          ],
          rules: {
            'react-hooks/exhaustive-deps': 'off',
          },
        },
    
        {
          files: [
            '.eslintrc.js',
            '*.test.js',
            '*.test.ts',
            '*.test.jsx',
            '*.test.tsx',
          ],
          rules: {
            'rulesdir/typography': 'off',
          },
        },
    
        'import/resolver': {
          typescript: {
            alwaysTryTypes: true,
          },