How to deal with the TSLint error "A project without tags cannot depend on any libraries" in an Nx monorepo

How to deal with the TSLint error "A project without tags cannot depend on any libraries" in an Nx monorepo
Photo by Sarah Kilian / Unsplash

Have you ever faced the following lint error in your Nx monorepo:

A project without tags cannot depend on any libraries

while your project clearly has tags defined in nx.json?

Then this article is just for you!

In just a few minutes you will understand what causes the error and how you can resolve it.

Scenario

For the sake of demonstration, let's assume that we have an Nx monorepo with an nx.json file that contains the following projects:

"projects": {
  "shared-feature-users": {
    "tags": ["scope:shared", "type:feature"]
  },
  "shared-data-access-users": {
    "tags": ["scope:shared", "type:data-access"]
  }
}

and that a component in the shared-feature-users library imports
a service from the shared-data-access-users library.

If you are not familiar with Nx scopes and types, you can learn all about them here.

By default, the nx-enforce-module-boundaries TSLint rule in nx.json comes with the following boundaries:

"nx-enforce-module-boundaries": [
  true,
  {
    "allow": [],
    "depConstraints": [
      {
        "sourceTag": "*",
        "onlyDependOnLibsWithTags": ["*"]
      }
    }
  ]
]

In essence, this allows every application and library to import any other library.

If you are not familiar with the nx-enforce-module-boundaries TSLint rule, you can learn more about it here.

When you run:

$ ng lint

no boundary violations are reported because of the wildcard rule that allows all libraries to import items from any other libraries.

Great! That's expected behaviour!

Now let's replace the wildcard rule with a new rule that only allows util libraries to import from other util libraries:

"nx-enforce-module-boundaries": [
  true,
  {
    "allow": [],
    "depConstraints": [
      {
        "sourceTag": "type:util",
        "onlyDependOnLibsWithTags": ["type:util"]
      }
    }
  ]
]

If you now run:

$ ng lint

then the following error is reported:

libs/shared/feature-users/src/lib/some.component.ts
 - A project without tags cannot depend on any libraries

But the shared-feature-users project clearly does have tags defined in nx.json!

So why does TSLint throw this error?

Here is what happens behind the scenes:

  1. the TSLint rule makes an inventory of all sourceTag tags listed in depConstraints
  2. next, the project's tags are matched against the sourceTag tags from depConstraints
  3. if the linter reaches the end of the depConstraints and none of the sourceTag tags matched against any of the project's tags, the “A project without tags cannot depend on any libraries” error is thrown

So the error is not thrown because the project does not have any tags in nx.json. Instead, the error is thrown because none of the project’s tags matched a sourceTag tag in depConstraints.

So how do we resolve this issue?

A brute solution

A brute way to solve the issue is to add a wildcard rule at the end of the of depConstraints in nx-enforce-module-boundaries:

"nx-enforce-module-boundaries": [
  true,
  {
    "allow": [],
    "depConstraints": [
      {
        "sourceTag": "type:util",
        "onlyDependOnLibsWithTags": ["type:util"]
      },
      {
        "sourceTag": "*",
        "onlyDependOnLibsWithTags": ["*"]
      }
    }
  ]
]

This ensures that every application or library that does not match any of the rules above, is allowed access to any other library.

The danger, however, is that you introduce a wildcard that accidentally also matches other imports and thus allows more than you intended.

In addition, the rules are processed from top to bottom, so it is imperative that the rule is added at the end.

Adding the rule at the top:

"nx-enforce-module-boundaries": [
  true,
  {
    "allow": [],
    "depConstraints": [
      {
        "sourceTag": "*",
        "onlyDependOnLibsWithTags": ["*"]
      },
      {
        "sourceTag": "type:util",
        "onlyDependOnLibsWithTags": ["type:util"]
      }
    }
  ]
]

would make the wildcard match any library immediately and prevent the second rule from having any effect at all.

An elegant solution

A more elegant way to solve the issue is to add a rule that matches at least one of your project tags:

"nx-enforce-module-boundaries": [
  true,
  {
    "allow": [],
    "depConstraints": [
      {
        "sourceTag": "type:util",
        "onlyDependOnLibsWithTags": ["type:util"]
      },
      {
        "sourceTag": "scope:shared",
        "onlyDependOnLibsWithTags": ["scope:shared"]
      }

    }
  ]
]

Here, we added a rule with "sourceTag": "scope:shared".

Because the TSLint rule now matches the scope:shared tag when linting the shared-feature-users project, it is happy because it found a match before the end of the depConstraints rules was reached.

As a result, the “A project without tags cannot depend on any libraries” error is no longer thrown.

The benefit of the elegant way is two-fold:

  1. the rule is explicit in your intention
  2. the rule does not introduce a wildcard that accidentally matches unintended imports

So if you ever face the “A project without tags cannot depend on any libraries” error, you now know how to resolve it gracefully.

Have a great one and happy linting!