eslint-plugin-import icon indicating copy to clipboard operation
eslint-plugin-import copied to clipboard

import plugin breaking in nx based mono-repo

Open rishavpandey43 opened this issue 1 year ago • 11 comments

I have been encountering a weird issue.

I have a react+typescript based project it was working fine.

Now, I migrated everything to nx-based mono-repo, and I'm getting the following error -

   Maximum call stack size exceeded

   Occurred while linting /Users/rishavpandey/Professional Work/Skuad/skuad-fe/apps/pay-platform/src/App.tsx:2
   Rule: "import/namespace

These are imports in App.tsx -

import { useEffect } from 'react';
import { datadogRum } from '@datadog/browser-rum';

import HeaderSidebarWrapper from '@pay/old/components/HeaderSidebarWrapper';
import useGetConfig from '@pay/old/hooks/useGetConfig/useGetConfig';
import {
  Accessors,
  ConfigType,
} from '@pay/old/queries/graphql/types/coreTypes';
import useSetMixPanelUserId from '@pay/old/hooks/useSetMixPanelUserId';
import useTrackPageViews from '@pay/old/hooks/useTrackPageViews';
import { ContractContextProvider } from '@pay/old/contextData/contractCreationContext';
import { setGaUserSession } from '@pay/old/hooks/useGoogleAnalytics';
import useClarity from '@pay/old/hooks/useClarity';
import { useMainContext } from '@pay/contexts/MainContextProvider';
import { useContextState } from '@pay/old/contextData/mainAppContext';

import MainRouter from './router';

This is my current eslintrc config the internal app-

apps/pay-platform/.eslintrc.js -

const dotenv = require('dotenv');
dotenv.config();

module.exports = {
  root: true,
  parserOptions: {
    project: 'apps/pay-platform/tsconfig.*?.json',
  },
  plugins: ['react', 'import', 'jsx-a11y', 'graphql'],
  extends: [
    'plugin:react/recommended',
    'plugin:react-hooks/recommended',
    // 'plugin:import/recommended',
    // 'plugin:import/typescript',
    'plugin:jsx-a11y/recommended',
    '../../.eslintrc.js',
  ],
  settings: {
    react: {
      version: 'detect',
    },
    // 'import/resolver': {
    //   typescript: {
    //     alwaysTryTypes: true,
    //     project: './tsconfig.*?.json',
    //   },
    //   node: {
    //     extensions: [',.js', ',.jsx', ',.ts', ',.tsx'],
    //   },
    // },
  },
  rules: {
    // // *** Import Rules ***
    // 'import/no-named-as-default-member': 'error', // * Stricter than default
    // 'import/no-named-as-default': 'error', // * Stricter than default
    // 'import/no-duplicates': 'error', // * Stricter than default
    'import/order': [
      'error',
      {
        groups: [
          'builtin',
          'external',
          'internal',
          'parent',
          'sibling',
          'index',
        ],
        pathGroups: [
          {
            pattern: '@pay/**',
            group: 'internal',
            position: 'before',
          },
        ],
        'newlines-between': 'always',
      },
    ],
  },
  overrides: [],
};

As soon as I comment on anything related to the import plugin, Eslint will break.

The same config was working fine in an earlier standalone project, but now it's failing.

This is root level eslint config -

const tsNamingConvention = require('./ts-naming-convention');

module.exports = {
  root: true,
  parser: '@typescript-eslint/parser',
  parserOptions: {
    ecmaVersion: 2020,
    sourceType: 'module',
    project: ['./tsconfig.base.json'],
    tsconfigRootDir: '.',
    lib: ['dom', 'esnext'],
    ecmaFeatures: {
      jsx: true,
    },
  },
  plugins: ['@nx', 'prettier', '@typescript-eslint'],
  extends: [
    'eslint:recommended',
    'plugin:@nx/typescript',
    'plugin:@nx/javascript',
    'plugin:@typescript-eslint/recommended',
    'plugin:prettier/recommended',
    'prettier', // Ensure "prettier" is last
  ],
  env: {
    browser: true,
    node: true,
    es6: true,
  },
  globals: {
    document: true,
    window: true,
    location: true,
    fetch: true,
  },
  ignorePatterns: [
    'node_modules/',
    'dist/',
    '**/*.spec.ts',
    '**/*.generated.tsx',
    '**/*.generated.ts',
    '**/*.coreTypes.ts',
    '**/*.cmsTypes.ts',
  ],
  rules: {
    // *** ESLint Recommended Rules with Stricter Settings ***
    eqeqeq: 'error', // Enforce strict equality.
    'array-callback-return': 'error', // Ensure a return statement in array callbacks.
    'no-sequences': 'error', // Disallow comma operator.
    'no-useless-concat': 'error', // Disallow unnecessary concatenation.
    'no-redeclare': 'error', // Disallow variable redeclaration.
    'no-lone-blocks': 'error', // Disallow unnecessary nested blocks.
    'no-extra-boolean-cast': 'error', // Disallow unnecessary boolean casts.
    'no-unexpected-multiline': 'error', // Disallow confusing multiline expressions.
    'no-var': 'error', // Require let or const instead of var.
    'prefer-spread': 'error', // Suggest using spread syntax instead of .apply().
    'prefer-rest-params': 'error', // Suggest using rest parameters instead of arguments.
    'no-console': ['error', { allow: ['warn', 'error'] }], // Disallow console logs.
    'max-lines': [
      'warn',
      { max: 500, skipComments: true, skipBlankLines: true },
    ],

    // *** General JavaScript Rules ***
    'no-template-curly-in-string': 'error', // Disallow template literal placeholder syntax in regular strings.
    'no-restricted-globals': 'error', // Disallow specified global variables.

    // *** Naming Convention Rules ***
    '@typescript-eslint/naming-convention': tsNamingConvention,
  },
  overrides: [
    {
      files: ['*.ts', '*.tsx', '*.js', '*.jsx'],
      rules: {
        '@nx/enforce-module-boundaries': [
          'error',
          {
            enforceBuildableLibDependency: true,
            allow: [],
            depConstraints: [
              {
                sourceTag: '*',
                onlyDependOnLibsWithTags: ['*'],
              },
            ],
          },
        ],
      },
    },
  ],
};

rishavpandey43 avatar Jun 15 '24 15:06 rishavpandey43

You do need those import/resolver settings.

ljharb avatar Jun 15 '24 16:06 ljharb

Still, there is the same issue. Btw in my standalone app, I needed settings for import/resolver so that it identifies absolute import path definitions like @pay.

In the standalone app, everything is perfect, even --fix is working for import/order but here its breaking.

rishavpandey43 avatar Jun 15 '24 16:06 rishavpandey43

What version of eslint and this plugin are you using?

It would be most helpful if you could create a minimal repro repo - I realize this is a tough ask.

ljharb avatar Jun 15 '24 16:06 ljharb

    "@typescript-eslint/eslint-plugin": "^6.2.1",
    "@typescript-eslint/parser": "^6.2.1",
    "eslint": "^8.56.0",
    "eslint-config-prettier": "^9.1.0",
    "eslint-import-resolver-typescript": "^3.6.1",
    "eslint-plugin-graphql": "^4.0.0",
    "eslint-plugin-import": "^2.29.1",
    "eslint-plugin-jsx-a11y": "^6.8.0",
    "eslint-plugin-prettier": "^5.0.0",
    "eslint-plugin-react": "^7.34.1",
    "eslint-plugin-react-hooks": "^4.6.2",
    "eslint-plugin-redux": "^0.1.0",
    "eslint-plugin-redux-saga": "^1.3.2",
    "eslint-plugin-cypress": "^2.13.4",
    "eslint-plugin-unused-imports": "^2.0.0",

I tried adding off for a few rules 'import/namespace': 'off', 'import/default': 'off', but then it's breaking for another one.

only import/order is working.

Do I need to make extra changes since this is mono-repo?

I found these tips https://nx.dev/recipes/tips-n-tricks/eslint on nx, on how to use tsconfig.json in eslint which is not commonly done in a standalone app, but even after this import plugin is breaking.

Also, I remembered, import/resolver is necessary when creating path aliasing, Otherwise you will get error of Unable to resolve path to module '@pay/old/hooks/useGetConfig/useGetConfig'.eslint[import/no-unresolved](https://github.com/import-js/eslint-plugin-import/blob/v2.29.1/docs/rules/no-unresolved.md)

Let me try creating a test app, I'll have to create several dummy files/folders with code to ensure we solve the problem correctly.

rishavpandey43 avatar Jun 15 '24 16:06 rishavpandey43

Hi @ljharb

I tried creating a demo app for this but encountered a new issue of 1:24 error Unable to resolve path to module '@demo-org/ui-lib' import/no-unresolved.

Unable to re-create the above main issue. I'll try and update soon.

I have successfully set the setting of import/resolver as said in https://stackoverflow.com/a/55280867/7888165

This is a demo app - https://github.com/rishavpandey43/eslint-import-issue-check

https://github.com/rishavpandey43/eslint-import-issue-check

rishavpandey43 avatar Jun 18 '24 16:06 rishavpandey43

Hey @ljharb

I guess I found something.

This time inside my apps/pay-platform/.eslintrc.js I removed extending the global eslint config and put all the rules in a single file itself.

It works now.

This is the current value of apps/pay-platform/.eslintrc.js-

const dotenv = require('dotenv');
dotenv.config();

module.exports = {
  extends: [
    'eslint:recommended',
    'plugin:react/recommended',
    'plugin:react-hooks/recommended',
    'plugin:@typescript-eslint/recommended',
    'plugin:import/recommended',
    'plugin:import/typescript',
    'plugin:jsx-a11y/recommended',
    'plugin:prettier/recommended',
    'prettier',
  ],
  parser: '@typescript-eslint/parser',
  parserOptions: {
    ecmaFeatures: {
      jsx: true,
    },
    ecmaVersion: 12,
    sourceType: 'module',
    project: 'apps/pay-platform/tsconfig.*?.json',
  },
  plugins: [
    'prettier',
    'react',
    'import',
    'jsx-a11y',
    '@typescript-eslint',
    'graphql',
  ],
  root: true,
  ignorePatterns: [
    '**/*.generated.tsx',
    '**/*.generated.ts',
    '**/*.coreTypes.ts',
    '**/*.cmsTypes.ts',
  ],
  // These rules are either not present in the plugins or have stricter settings than the default ones.
  rules: {
    // *** ESLint Recommended Rules with Stricter Settings ***
    eqeqeq: 'error',
    'array-callback-return': 'error',
    'no-sequences': 'error',
    'no-useless-concat': 'error',
    'no-redeclare': 'error',
    'no-lone-blocks': 'error',
    'no-extra-boolean-cast': 'error',
    'no-unexpected-multiline': 'error',
    'no-var': 'error',
    'prefer-spread': 'error',
    'prefer-rest-params': 'error',
    'no-console': ['error', { allow: ['warn', 'error'] }],
    'max-lines': [
      'off',
      { max: 500, skipComments: true, skipBlankLines: true },
    ],

    // *** General JavaScript Rules ***
    'no-template-curly-in-string': 'error',
    'no-restricted-globals': 'error',

    // *** React and JSX Rules ***
    'react/react-in-jsx-scope': 'off',
    'react/jsx-uses-react': 'off',
    'react/prop-types': 'off',
    'react/jsx-uses-vars': 'error',
    'react/jsx-filename-extension': ['error', { extensions: ['.jsx', '.tsx'] }],
    'react/display-name': 'off',
    'react-hooks/exhaustive-deps': 'off',
    'react/jsx-no-useless-fragment': 'error',

    // *** TypeScript Rules ***
    '@typescript-eslint/ban-ts-comment': 'off',
    '@typescript-eslint/no-explicit-any': 'off',
    '@typescript-eslint/consistent-type-definitions': ['off', 'interface'],
    '@typescript-eslint/no-duplicate-enum-values': 'error',
    '@typescript-eslint/await-thenable': 'error',
    '@typescript-eslint/no-unused-vars': [
      'error',
      {
        argsIgnorePattern: '^_',
      },
    ],

    // *** Import Rules ***
    'import/no-named-as-default-member': 'error',
    'import/no-named-as-default': 'error',
    'import/no-duplicates': 'error',
    'import/order': [
      'error',
      {
        groups: [
          'builtin',
          'external',
          'internal',
          'parent',
          'sibling',
          'index',
        ],
        pathGroups: [
          {
            pattern: '@pay/**',
            group: 'internal',
            position: 'before',
          },
        ],
        'newlines-between': 'always',
      },
    ],
  },
  overrides: [
    {
      files: ['*.graphql'],
      parser: '@graphql-eslint/eslint-plugin',
      plugins: ['@graphql-eslint'],
      rules: {
        '@graphql-eslint/known-type-names': 'error',
        'graphql/template-strings': 'error',
        '@graphql-eslint/unique-operation-name': 'error',
        '@graphql-eslint/no-unreachable-types': 'error',
      },
      parserOptions: {
        schema: [
          process.env.VITE_API_GRAPHQL_BASE_URL,
          process.env.VITE_API_CMS_GRAPHQL_BASE_URL,
        ],
      },
    },
  ],
  env: {
    browser: true,
    node: true,
  },
  settings: {
    react: {
      version: 'detect',
    },
    'import/resolver': {
      typescript: {
        alwaysTryTypes: true,
        project: 'apps/pay-platform/tsconfig.*?.json',
      },
      node: {
        extensions: ['.js', '.jsx', '.ts', '.tsx'],
      },
    },
  },
};

Earlier I tried to maintain one global .eslintrc.js file which will hold generic rules applicable to all my internal apps and packages, and then project-specific rules in respective projects. But this is failing with eslint-plugin-import plugin other rules were working fine.

But, now when I merged everything in a single internal app-level eslint config, things worked out.

Also, I tried creating an Nx-based monorepo but was unable to do. Encountering a lot of version mismatches, and even after that, the dummy project will not have that many files where imports are checked.

rishavpandey43 avatar Jun 20 '24 03:06 rishavpandey43

@rishavpandey43 does that mean you're using flat config, in eslint 8?

ljharb avatar Jun 25 '24 06:06 ljharb

It would be great to have a sample configuration that works with Nx workspaces. I have tried various settings and always end up with the same error:

Error while loading rule 'import/no-unused-modules': All files matched by '/x/y/z' are ignored.

LayZeeDK avatar Sep 02 '24 19:09 LayZeeDK

Hi @ljharb

I have fixed everything on mono-repo.

I'm using V8 of eslint only, but there were a lot of changes that needed to be made.

https://nx.dev/recipes/tips-n-tricks/eslint

This article helped me a lot when dealing with import issues, as in monorepo we need to have an absolute path configured in parserOption, else nothing will work.

Also, sharing the eslint config also differs in monorepo, as for my current architecture, I have created a base config at the top level, and sharing between multiple apps/packages. And if I need extra rules, I have added those to the respective app/package level eslint config.

I'm planning to write a blog on this, as everything was very tricky and conceptual. I will share the detailed version with you all guys soon.

rishavpandey43 avatar Sep 03 '24 04:09 rishavpandey43

It would be great to have a sample configuration that works with Nx workspaces. I have tried various settings and always end up with the same error:

Error while loading rule 'import/no-unused-modules': All files matched by '/x/y/z' are ignored.

@LayZeeDK Bro, it seems in this issue. You might need to check the tsconfig file that is being used in eslint parser.

Seeing the error, it seems that eslint is trying to lint some files, but those are not included in tsconfig.

rishavpandey43 avatar Sep 03 '24 04:09 rishavpandey43

It's the same in a freshly generated Nx workspace, so I can't figure out how it would be configured improperly.

LayZeeDK avatar Sep 03 '24 04:09 LayZeeDK