Tslint: no-implicit-dependencies: Support path mapping

Created on 20 Oct 2017  ·  18Comments  ·  Source: palantir/tslint

Bug Report

  • __TSLint version__: 5.8.0
  • __TypeScript version__: 2.7.0-dev.20171020
  • __Running TSLint via__: CLI

TypeScript code being linted

src/a.ts

import { x } from "foo";

types/foo.d.ts

export const x = 0;

tsconfig.json

{
    "compilerOptions": {
        "paths": {
            "*": "types/*"
        }
    }
}

with tslint.json configuration:

{
    "rules": {
        "no-implicit-dependencies": true
    }
}

Actual behavior

ERROR: /home/andy/sample/tslint/src/a.ts[1, 19]: Module 'foo' is not listed as dependency in package.json

Expected behavior

No error. I think if an import isn't resolved to something in node_modules this rule should ignore it.

Most helpful comment

This rule does not work when we use alias for our own source code (not to import from npm packages). It is very useful to use absolute path instead of relative path.
In our tsconfig.json, for an angular application, we have just to add :

"compilerOptions": {
  ...
  "baseUrl": "./src",
  "paths": {
    "~/env": ["environments/environment"],
    "~/*": ["app/*"]
  }
}

Then we can do imports like :

import {FooService} from '~/core';
import {Environment} from '~/env';

May be we should reopen this issue to solve this case (there is no need for node_modules, just the tsconfig.json file).
I appreciate this rule, so it will be unfortunate to disable it.

All 18 comments

I think if an import isn't resolved to something in node_modules this rule should ignore it.

The rule doesn't try to resolve the modules. That would mean you need to have all dependencies installed, which is not really possible with peerDependencies and optionalDependencies. With the current implementation you can lint a fresh clone without installing anything. I guess that's what most code quality tools do.

As I understand path mappings, they only exist at compile time. At runtime you still need the module installed for node / webpack / whatever to correctly pick it up.
That means path mappings are only relevant if you have type only imports that are elided during compilation. In this case the rule is probably not the right choice for you.

In our case there is generally no package.json at all, so I guess we should just disable this rule then. Thanks!

In my case, I'm "importing" separate typescript projects I'm working on simultaneously using path mapping:

"compilerOptions": {
    ...
    "paths": {
        "tsbase": ["../tsBaseProject/src"],
        "tslibrary": ["../tsProjectLibrary/src"]
    }
}

so that I can use them in the project as if they were modules.
Is there a way to whitelist them?

@marcoqu Path mappings are only relevant at compile time. At runtime these modules need to exist in node_modules. I suggest adding them as either as dependencies or peerDependencies to your package.json

When I compile the main source that "imports" the secondary projects, everything is compiled into a single bundle as if they were actual folders in the project. I do not need to have them in the node_modules folder.
To be clear, in the secondary project folders I have actual .ts files, not only the type declarations.

+1 for adding a whitelist like in no-submodule-imports

We also have the case that we use case that we define a path alias '~' to the base dir in order to avoid relative imports. This alias is later resolved by webpack, fuse-box, etc. Starting with 5.8, tslint spits out lots of spurious errors because of this...

What he said ^^

After upgrading I now have hundreds of these errors for the same reasons outlined above. Kind of a pointless rule.

This rule does not work when we use alias for our own source code (not to import from npm packages). It is very useful to use absolute path instead of relative path.
In our tsconfig.json, for an angular application, we have just to add :

"compilerOptions": {
  ...
  "baseUrl": "./src",
  "paths": {
    "~/env": ["environments/environment"],
    "~/*": ["app/*"]
  }
}

Then we can do imports like :

import {FooService} from '~/core';
import {Environment} from '~/env';

May be we should reopen this issue to solve this case (there is no need for node_modules, just the tsconfig.json file).
I appreciate this rule, so it will be unfortunate to disable it.

@andy-ms please reconsider supporting paths (we use it extensively with nx workspaces). This is a really useful rule, but for now I am forced to disable it.

I tried looking through the source code and fix would need to go somewhere along these lines. I've also checked how Typescript is handling that and I tracked it down to this function. It's definitely no easy feat to duplicate that logic. I am not sure if that function can be reused, there is a bunch of arguments I am not certain of.

I'd very much like to see this getting fixed as well. Path mapping is a highly valued feature. I also tried using linked modules as a workaround, but those aren't supported either.

I did figure out a workaround that solves the issue somewhat for me, but it's not certain it will for everyone, or that it's maintainable at all. Anyway, the solution goes as follows:

Add a fake package to optionalDependencies with the name of the path map from tsconfig.json, and install dependencies using npm install --no-optional. This unfortunately doesn't work with yarn --ignore-optional - it fails trying to fetch the package still.

So, with paths in tsconfig.json looking something like this:

    "paths": {
        "~/*": ["src/*"],
        "some-path/*": ["whatever/*"]
    }

And optional in package.json like this:

    "optionalDependencies": {
        "~": "tslint-hack",
        "some-path": "tslint-hack"
    },

It should be possible to get production and development dependencies to install using npm install --no-optional. This obviously assumes you don't need any optional dependencies installed. Also worth mentioning I didn't get it working with @ as the package name.

If you choose to use this hack can it probably be smart to add a .npmrc file to the project root, with optional=false configured, so you can go back to running npm install without the --no-optional flag.

Another solution that _should_ work is to actually create a package with the desired name and publishing it to a private registry, using verdaccio or similar. I belive it to be possible to configure private registries per module using .npmrc or .yarnrc, and as such should be better in terms of maintainability. None of this is tested though.

Hope this can help out a little for those that want to use this tslint rule and keep their module resolutions in place. But it's no substitute for a proper fix..

Also having this issue, resulting in sonar flagging this incorrectly as a code smell.

I think this is a valid request since tslint is all about typescript and paths are a valid (and important) typescript setting.

What if I have the package.json in a different directory than that of tslint.json?

- web
    - package.json
    - ClientApp
        - tslint.json

I'm working on a similar setup and I'm getting errors in almost all the files due to this rule. Any solution for this?

The way I've found for getting around this issue is using the following configuration option.

"no-implicit-dependencies": [true, ["src", "app", "~"]]

It whitelists the paths provided. Obviously, this means duplication but it's a quick fix if you're looking for one.

For those of us that use @ symbols as prefixes for custom paths I've raised a PR to fix a small bug with the current implementation #4192

"no-implicit-dependencies": [true, ["@src", "@app", "~"]]

@ifiokjr I was using @ as my src alias, so my imports looked like @/components
Couldn't set @ as ignored path since it tried to import @/components as a whole module instead of resolving @ first.

I changed my alias to ~ and used the line above in my tslint, solving the problem

Was this page helpful?
0 / 5 - 0 ratings