diff --git a/packages/schematics/src/tslint/nxEnforceModuleBoundariesRule.spec.ts b/packages/schematics/src/tslint/nxEnforceModuleBoundariesRule.spec.ts index 1b09007306f41..88fc8cf6ae013 100644 --- a/packages/schematics/src/tslint/nxEnforceModuleBoundariesRule.spec.ts +++ b/packages/schematics/src/tslint/nxEnforceModuleBoundariesRule.spec.ts @@ -463,6 +463,56 @@ describe('Enforce Module Boundaries', () => { 'Circular dependency between "anotherlib" and "mylib" detected' ); }); + + it('should error when circular dependency detected (indirect)', () => { + const failures = runRule( + {}, + `${process.cwd()}/proj/libs/mylib/src/main.ts`, + 'import "@mycompany/badcirclelib"', + [ + { + name: 'mylib', + root: 'libs/mylib/src', + type: ProjectType.lib, + tags: [], + files: [`libs/mylib/src/main.ts`] + }, + { + name: 'anotherlib', + root: 'libs/anotherlib/src', + type: ProjectType.lib, + tags: [], + files: [`libs/anotherlib/src/main.ts`] + }, + { + name: 'badcirclelib', + root: 'libs/badcirclelib/src', + type: ProjectType.lib, + tags: [], + files: [`libs/badcirclelib/src/main.ts`] + }, + { + name: 'myapp', + root: 'apps/myapp/src', + type: ProjectType.app, + tags: [], + files: [`apps/myapp/index.ts`] + } + ], + { + mylib: [ + { projectName: 'badcirclelib', type: DependencyType.es6Import } + ], + badcirclelib: [ + { projectName: 'anotherlib', type: DependencyType.es6Import } + ], + anotherlib: [{ projectName: 'mylib', type: DependencyType.es6Import }] + } + ); + expect(failures[0].getFailure()).toEqual( + 'Circular dependency between "mylib" and "badcirclelib" detected' + ); + }); }); function runRule( diff --git a/packages/schematics/src/tslint/nxEnforceModuleBoundariesRule.ts b/packages/schematics/src/tslint/nxEnforceModuleBoundariesRule.ts index aed61482d7d86..f347e6c81776f 100644 --- a/packages/schematics/src/tslint/nxEnforceModuleBoundariesRule.ts +++ b/packages/schematics/src/tslint/nxEnforceModuleBoundariesRule.ts @@ -214,9 +214,27 @@ class EnforceModuleBoundariesWalker extends Lint.RuleWalker { targetProject: ProjectNode ): boolean { if (!this.deps[targetProject.name]) return false; - return this.deps[targetProject.name].some( - dep => dep.projectName == sourceProject.name - ); + return this.isDependingOn(targetProject.name, sourceProject.name); + } + + private isDependingOn( + sourceProjectName: string, + targetProjectName: string, + done: { [projectName: string]: boolean } = {} + ): boolean { + if (done[sourceProjectName]) return false; + if (!this.deps[sourceProjectName]) return false; + return this.deps[sourceProjectName] + .map( + dep => + dep.projectName === targetProjectName + ? true + : this.isDependingOn(dep.projectName, targetProjectName, { + ...done, + [`${sourceProjectName}`]: true + }) + ) + .some(result => result); } private onlyLoadChildren(