eslint-import-resolver-typescript icon indicating copy to clipboard operation
eslint-import-resolver-typescript copied to clipboard

`import/extensions`, `import/no-unresolved` don't highlight import of nonexistent .js

Open jwbth opened this issue 1 year ago • 6 comments

(Original issue is posted at https://github.com/import-js/eslint-plugin-import/issues/3015.)

Take this simple setup:

eslint.config.js

import eslint from '@eslint/js';
import tseslint from 'typescript-eslint';
import * as pluginImport from 'eslint-plugin-import';

export default [
  eslint.configs.recommended,
  ...tseslint.configs.recommended,
  {
    files: ['**/*.ts'],
    plugins: {
      'import': pluginImport,
    },
    settings: {
      'import/resolver': {
        node: true,
        typescript: true,
      },
    },
    rules: {
      'import/extensions': 'error',
      'import/no-unresolved': 'error',
    },
  },
];

index.ts

import { num } from './exported.js';

exported.ts

export const num = 3;

In index.ts, ./exported.js points to a nonexistent file. Neither import/extensions, nor import/no-unresolved highlight it though.

I narrowed down the problem to typescript: true – as soon as I comment that line, I see both errors:

Unexpected use of file extension "js" for "./exported.js" eslint(import/extensions) Unable to resolve path to module './exported.js'. eslint(import/no-unresolved)

I assume this is the problem with eslint-import-resolver-typescript. I tried messing with it in some ways, but to no avail.

I should also note that the highlight will appear also if:

  • I change the base name to one that has no corresponding .ts filename:

    Unexpected use of file extension "js" for "./exported_nonexistentBase.js" eslint(import/extensions) Unable to resolve path to module './exported_nonexistentBase.js'. eslint(import/no-unresolved)

  • I change the extension to any other:

    Unable to resolve path to module './exported.jsx'. eslint(import/no-unresolved)

  • I rename exported.ts to exported.js so that the import exists

    Unexpected use of file extension "js" for "./exported.js". eslint(import/extensions)

So, this seems to be a pretty unique case with a nonexistent .js silently mapping to an existent .ts with the same basename.

jwbth avatar Jun 16 '24 14:06 jwbth

I'm experiencing this same issue. Unfortunately, I think this behavior may be technically correct, by present state of the longstanding conflict between typescript and node about module resolution.

https://github.com/microsoft/TypeScript/issues/13422 https://github.com/typescript-eslint/typescript-eslint/issues/3288

turbocrime avatar Jun 22 '24 06:06 turbocrime

@turbocrime Well, it brings real harm, because Webpack can't resolve it and can't build. (On the other hand, there is strange behavior in VSCode that adds this .js to auto-added imports when the first import has .js or something, see stackoverflow.)

I suppose the point of ESLint is to prevent this type of situations and be literal in terms of what it is asked to consider an error. And please see the list "the highlight will appear also if" at the end of my post – it demonstrates that, even if this kind of resolving is correct for some reason, it is not consistent.

jwbth avatar Jun 22 '24 12:06 jwbth

part of the situation is you're using three independent resolvers

  1. typescript's moduleResolution as configured in your tsconfig
  2. this resolver which attempts to respect tsconfig, but is used by the linter with extension lint rules that contradict tsconfig
  3. webpack's independent module resolution which also needs configuration

you could configure your lint rules to permit .js extensions to refer to .ts imports. i understand this is awkward, but typescript devs seem to encourage this.

if you are just bundling this code with webpack, you may be interested in tsconfig's "bundler" moduleResolution which is more permissive with file extensions. then configure this resolver's search paths to match your webpack config, so they both import a .ts extension for a bare name. you'll need to pass a config object instead of true for settings.import/resolver.typescript

if you are using or publishing bare tsc output anywhere, you probably don't want to use bundler resolution.

turbocrime avatar Jun 22 '24 15:06 turbocrime

Thanks for the detailed response. At this stage I just configured VS Code to prevent it from inserting .js when not asked; that solved it for me.

jwbth avatar Jun 22 '24 16:06 jwbth

Sorry if this is a tangent, but what's the benefit of using import/no-unresolved in Typescript projects? Typescript already handle unresolved imports with it's own warnings.

AndreaPontrandolfo avatar Aug 20 '24 13:08 AndreaPontrandolfo

@AndreaPontrandolfo TypeScript warnings for .js (and only .js) are absent for me just as ESLint warnings. For explicitness, this is my tsconfig.json for this particular test:

{
  "compilerOptions": {
    "outDir": "./built",
    "target": "es2022",
    "moduleResolution": "Bundler"
  }
}

And as I said,

it brings real harm, because Webpack can't resolve it and can't build.

jwbth avatar Aug 21 '24 05:08 jwbth

Duplicate of https://github.com/import-js/eslint-plugin-import/issues/2111

JounQin avatar Mar 18 '25 03:03 JounQin