Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

import/no-restricted-paths doesn't support path aliases #317

Open
gyenabubakar opened this issue Oct 25, 2024 · 6 comments
Open

import/no-restricted-paths doesn't support path aliases #317

gyenabubakar opened this issue Oct 25, 2024 · 6 comments

Comments

@gyenabubakar
Copy link

gyenabubakar commented Oct 25, 2024

I have three path aliases setup in my vite.config.ts and tsconfig.json:

  • ~ => src/
  • ui => src/components/*
  • utils => src/lib/index.ts

Files in src/components/* and src/lib/* must not import stuff from ~ and ~/**. I've tried using eslint-plugin-import and eslint-import-resolver-typescript but the former expects the real path, not the aliases and that's not the behaviour I want.

Here's my config (v9):

import prettier from 'eslint-config-prettier';
import js from '@eslint/js';
import svelte from 'eslint-plugin-svelte';
import globals from 'globals';
import ts from 'typescript-eslint';
import importPlugin from 'eslint-plugin-import';

export default ts.config(
  js.configs.recommended,
  ...ts.configs.recommended,
  ...svelte.configs['flat/recommended'],
  prettier,
  ...svelte.configs['flat/prettier'],
  importPlugin.flatConfigs.recommended,
  {
    languageOptions: {
      globals: {
        ...globals.browser,
        ...globals.node,
      },
    },
  },
  {
    files: ['**/*.svelte'],

    languageOptions: {
      parserOptions: {
        parser: ts.parser,
      },
    },
  },
  {
    ignores: ['build/', '.svelte-kit/', 'dist/', 'storybook-static/'],
  },
  {
    rules: {
      '@typescript-eslint/consistent-type-imports': 'error',
      // ignore unused vars starting with _
      '@typescript-eslint/no-unused-vars': [
        'warn', // or "error"
        {
          argsIgnorePattern: '^_',
          varsIgnorePattern: '^_',
          caughtErrorsIgnorePattern: '^_',
        },
      ],
    },
  },
  {
    settings: {
      'import/resolver': {
        typescript: {
          project: '.', // this loads <rootDir>/tsconfig.json to eslint
        },
      },
    },
  },
  {
    rules: {
      'import/no-restricted-paths': [
        'error',
        {
          zones: [
            {
              target: ['./src/lib', './src/components'],
              from: ['./src/lib', './src/components'],
              except: ['utils', 'ui'],
              message: "Import via the alias 'utils' or 'ui' instead.",
            },
          ],
        },
      ],
    },
  },
);
@ljharb
Copy link
Member

ljharb commented Oct 25, 2024

eslint-import-resolver-typescript is indeed the way to do this, if it's in your tsconfig - cc @JounQin to debug

@mcshaman
Copy link

mcshaman commented Nov 29, 2024

How? As I mentioned here there is no documentation for how to implement in and ESLint flat config (which seems to be what the OP is using).

@xpz24
Copy link

xpz24 commented Dec 1, 2024

tdlr: The plugin respects the path aliases in path option of tsconfig.

This shows how to reference the resolver so we can pass options to it, I tried both, using import to import the entire resolver module and also by just referencing it via by using its name without importing it as shown in the docs:

Import method:

import * as tsResolver from 'eslint-import-resolver-typescript'
import eslintPluginImportX from 'eslint-plugin-import-x'

const importXTypeScript = eslintPluginImportX.flatConfigs.typescript
importXTypeScript.settings['import-x/resolver'] = {
  name: 'tsResolver',
  options: {
    alias: [
      { name: '/', path: path.resolve(import.meta.dirname, 'public') },
      { name: '@components', path: path.resolve(import.meta.dirname, 'src/ui') },
    ],
  },
  resolver: tsResolver,
}

Name method:

import eslintPluginImportX from 'eslint-plugin-import-x'

const importXTypeScript = eslintPluginImportX.flatConfigs.typescript
importXTypeScript.settings['import-x/resolver'].typescript = {
  alias: [
    { name: '/', path: path.resolve(import.meta.dirname, 'public') },
    { name: '@components', path: path.resolve(import.meta.dirname, 'src/ui') },
  ],
}

Since enhanced-resolve options are supported I tried passing the alias options, no errors but it did not work. Then I saw that it respects the path option in tsconfig.json
tsconfig path alias:

{
"baseUrl": "./" /* Specify the base directory to resolve non-relative module names. */,
 "paths": {
      "/*": ["./public/*"],
      "@component/*": ["./src/ui/*"]
    } /* Specify a set of entries that re-map imports to additional lookup locations. */,
}

Now all the imports and stuff work as expected.

@mcshaman
Copy link

mcshaman commented Dec 3, 2024

@xpz24 thank you this was very helpful. I have realised that to get eslint-import-resolver-typescript to help resolve aliases, as defined in my tsconfig.json I just need to have it installed as a dependency, i.e. I don't need to import it or reference it in the config, eslint-plugin-import-x just finds it (some how). This seems very weird and I can totally see somebody uninstalling the dependency at some stage because they can't find a reference to it in the code base... But oh well.

One of the key issues you helped me solve was baseUrl in the tsconfig. I was getting an error from ESLint referencing the baseUrl being empty but it didn't mention that it was coming from tsconfig... I thought it was a missing setting in the ESLint config.

Here is basically what I ended up with:

// eslint.config.mjs
import globals from "globals";
import pluginImportX from "eslint-plugin-import-x";

/** @type {import('eslint').Linter.Config[]} */
const config = [
	{ languageOptions: { globals: { ...globals.browser, ...globals.node } } },
	pluginImportX.flatConfigs.recommended,
	pluginImportX.flatConfigs.typescript,
	// ... other configs and rules
];

export default config;
// tsconfig.json 
{
	"compilerOptions": {
		// ... other settings
		"baseUrl": ".",
		"paths": {
			"@/fixtures/*": ["fixtures/*"],
			"@/jest/*": ["config/jest/*"],
			"@/*": ["src/*"]
		}
	},
}

@melroy89
Copy link

melroy89 commented Dec 3, 2024

This is what I have thus far:

import eslint from '@eslint/js';
import tseslint from 'typescript-eslint';
import eslintPrettier from 'eslint-config-prettier';
import pluginJest from 'eslint-plugin-jest';

export default tseslint.config(
    {
        ignores: ['dist', 'bin', 'jest.config.cjs'],
    },
    eslint.configs.recommended, 
    ...tseslint.configs.strict,
    ...tseslint.configs.stylistic,
    eslintPrettier,
    {
        files: ['test/**/*'],
        plugins: { jest: pluginJest },
        languageOptions: {
            globals: pluginJest.environments.globals.globals
        },
        ...pluginJest.configs['flat/recommended'],
        rules: {
            'jest/no-standalone-expect': [
                'error',
                {
                    'additionalTestBlockFunctions': ['beforeAll', 'beforeEach', 'afterEach', 'afterAll']
                }
            ]
        }
    },
);

No idea how to add eslint-import-resolver-typescript . Also getting to this file took me about 2 days and some try and error to be honest.

@xpz24
Copy link

xpz24 commented Dec 7, 2024

@mcshaman

I don't need to import it or reference it in the config, eslint-plugin-import-x just finds it (some how).

That is super weird indeed, since I need to enable it explicitly in eslint.config.mjs for it work. I have installed it as a dev dependency.

baseUrl

This feature was designed for use in conjunction with AMD module loaders in the browser and is not recommended in any other context. As of TypeScript 4.1, baseUrl is no longer required to be set when using paths.

But if I do not use baseUrl, VSCode path suggestions do not work with aliases.

These aliases are working perfectly now:

"baseUrl": ".",
"paths": {
  "/*": ["public/*"],
  "@utils/*": ["src/scripts/utils/*"],
  "@root/*": ["./*"]
},
"rootDir": ".",

@melroy89
Import the resolver plugin and add the configs below tseslint configs, I have attached my eslint config for reference.

/* eslint-disable import-x/no-named-as-default-member */
import eslint from '@eslint/js'
import eslintConfigPrettier from 'eslint-config-prettier'
import eslintPluginImportX from 'eslint-plugin-import-x'
import eslintPluginUnicorn from 'eslint-plugin-unicorn'
import globals from 'globals'
// import nodePlugin from 'eslint-plugin-n' // for node applications
import tseslint from 'typescript-eslint'

// typeScript (Ensure eslint applies only to typescript files)
const strictTypeChecked = tseslint.configs.strictTypeChecked.map((ruleSet) =>
  ruleSet.files === undefined
    ? { ...ruleSet, files: ['**/*.ts', '**/*.tsx', '**/*.mts', '**/*.cts'] }
    : ruleSet,
)
const stylisticTypeChecked = tseslint.configs.stylisticTypeChecked.map((ruleSet) =>
  ruleSet.files === undefined
    ? { ...ruleSet, files: ['**/*.ts', '**/*.tsx', '**/*.mts', '**/*.cts'] }
    : ruleSet,
)

// importX plugin
const importXRecommended = eslintPluginImportX.flatConfigs.recommended
const importXTypeScript = eslintPluginImportX.flatConfigs.typescript
importXRecommended.languageOptions.ecmaVersion = 'latest' // Probably not necessary 

const baseConfig = tseslint.config(
  eslint.configs.recommended,
  ...strictTypeChecked,
  ...stylisticTypeChecked,
  importXRecommended,
  importXTypeScript,
  {
    languageOptions: {
      globals: {
        ...globals.builtin,
        // ...globals.nodeBuiltin,
        ...globals.browser,
        // ...globals.node,
      },
      ecmaVersion: 'latest',
      sourceType: 'module',
      parserOptions: {
        projectService: true,
        tsconfigRootDir: import.meta.dirname,
      },
    },
  },
  // nodePlugin.configs['flat/recommended'],
  eslintPluginUnicorn.configs['flat/recommended'],
  eslintConfigPrettier,
)

// const eslintMjsRules = {
//   files: ['eslint.config.mjs'],
//   rules: {
//     'n/no-unpublished-import': [
//       'error',
//       {
//         allowModules: [
//           '@eslint/js',
//           'eslint-config-prettier',
//           'typescript-eslint',
//           'eslint-plugin-n',
//           'tailwindcss',
//         ],
//       },
//     ],
//   },
// }

export default [...baseConfig]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

5 participants