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

Question: Is it possible to have different kinds of circular dependencies? #585

Closed
PetFeld-ed opened this issue Apr 20, 2022 · 2 comments · Fixed by #589
Closed

Question: Is it possible to have different kinds of circular dependencies? #585

PetFeld-ed opened this issue Apr 20, 2022 · 2 comments · Fixed by #589

Comments

@PetFeld-ed
Copy link

Summary

I would like to find out if (how) it is possible to handle different kinds of circular dependencies in different ways. E.g. one type of circular dependency should only issue a warning whereas another type should be an error.

Context

My project is divided in different modules. The folder structure looks like this:

src
|----- moduleA
             |----- controllers
             |----- database
             |----- logic
|----- moduleB
             |----- controllers
             |----- database
             |----- logic
|----- moduleC
              | [...]
|----- moduleD
              | [...]

Now if there is a circular dependency inside a single module this should be just a warning.
I can do this by using the default no-circular rule and set its severity to warn:

    {
      name: 'no-circular',
      severity: 'warn',
      comment:
        'This dependency is part of a circular relationship. You might want to revise ' +
        'your solution (i.e. use dependency inversion, make sure the modules have a single responsibility) ',
      from: {},
      to: {
        circular: true,
      },
    },

Now I would like to add a rule that if there is a circular dependency between two modules (e.g. moduleA depends on moduleC and moduleC depends on moduleA) this should be an error instead.
I tried by writing a custom rule for this by using group matching as described in the documentation (https://github.com/sverweij/dependency-cruiser/blob/HEAD/doc/rules-reference.md#group-matching---an-example-matching-peer-folders):

    {
      name: 'no-circular-dependency-of-modules',
      comment:
        'If module A depends on module B then module B must not depend on module A. ' +
        'This is also forbidden if the dependency is transitive.',
      severity: 'error',
      from: { path: '^src/([^/]+)/.+' },
      to: { viaNot: '^src/$1/.+', circular: true },
    },

But the result is the same as just putting no-circular to error severity as it also matches circles inside a single module.

Hence my question is: Is it possible to achieve this behaviour? If yes, how would the rule for this need to look?

Environment

  • Version used: 11.6.0
  • Node version: v12.22.11
  • Operating System and version: Ubuntu 20.04
@sverweij
Copy link
Owner

Hi @PetFeld-ed thanks for submitting this question - it's nice to see what interesting cases dc is used for.

As far as I can see your approach should work - except that for via and viaNot group matching isn't implemented yet (😱 ; I'll add it this weekend). There might be other ways to get to your goal; I'll be AfK soon, so today won't be able to come up with examples, but if I have them I'll drop them here.

@sverweij
Copy link
Owner

sverweij commented Apr 28, 2022

Hi @PetFeld-ed as you can see in the linked PR I've added group matching support for the via and viaNot restrictions and added two new via* ones that will likely suit your use case. You can give it a spin by installing the beta version I've just published (📦 dependency-cruiser@11.7.0-beta-1 - 🔏 shasum: 8a1b9f0db878b80a16f343a2be202377934dec13). Be sure to read the background below, though.

I have a bit of an issue naming the new via* restrictions, though (see below) - so that will probably change before I merge the PR.

background

The path (and pathNot) restrictions only have to match one path; for the to side of the dependency only the one path of that dependency. The via (and viaNot) restrictions always almost have to check against multiple paths; all the "via"'s in the cycle. This leaves the question whether the restriction should fire if just one of those paths in the cycle match or when all of them do.

The examples below refer to this cycle: a/aa.js, a/ab.js, b/bb.js, a/aa.js

restriction what it does example input match? 🐝🐮🐮
via some of the modules in the cycle do match the expression ^a/.+ true a/aa.js and a/ab.js match
viaOnly all of the modules in the cycle do match the expression ^a/.+ false b/bb.js doesn't match
viaNot all of the modules in the cycle don't match the expression ^a/.+ false a/aa.js and a/ab.js match
viaNotSome viaSomeNot some of the modules in the cycle don't match the expression ^a/.+ true b/bb.js doesn't match

This is also why the regular viaNot typically wouldn't have suited your use case. Even with group matching enabled it would only have flagged when the whole cycle would be outside the current module (e.g. a/aa.js => b/ba.js => b/bb.js => a/aa.js) and not when only part of the cycle makes an excursion outside the module (a/aa.js => a/ab.js => b/bb.js => a/aa.js).

The new viaNotSome viaSomeNot restriction (which is looking for a better name) will probably do what you need it for.

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