Skip to content

Commit 483086b

Browse files
authored
feat(core): add support for notDependOnLibsWithTags module boundaries (#592)
fixes #564
1 parent cdc9c09 commit 483086b

3 files changed

Lines changed: 109 additions & 5 deletions

File tree

packages/core/src/tasks/check-module-boundaries.spec.ts

Lines changed: 100 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { Tree, writeJson } from '@nrwl/devkit';
22
import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing';
3-
43
import * as ESLintNamespace from 'eslint';
4+
import * as fastGlob from 'fast-glob';
5+
import { vol } from 'memfs';
56

67
import {
78
CONFIG_FILE_PATH,
@@ -21,7 +22,7 @@ const MOCK_BOUNDARIES: ModuleBoundaries = [
2122
sourceTag: 'a',
2223
},
2324
{
24-
onlyDependOnLibsWithTags: ['b', 'shared'],
25+
notDependOnLibsWithTags: ['a', 'shared'],
2526
sourceTag: 'b',
2627
},
2728
{
@@ -74,7 +75,32 @@ describe('load-module-boundaries', () => {
7475
});
7576
});
7677

78+
// eslint-disable-next-line @typescript-eslint/no-var-requires
79+
jest.mock('fs', () => require('memfs').fs);
80+
7781
describe('enforce-module-boundaries', () => {
82+
beforeEach(() => {
83+
const appTree = createTreeWithEmptyWorkspace();
84+
jest.spyOn(ESLintNamespace, 'ESLint').mockReturnValue({
85+
calculateConfigForFile: jest.fn().mockResolvedValue({
86+
rules: {
87+
'@nrwl/nx/enforce-module-boundaries': [
88+
1,
89+
{ depConstraints: MOCK_BOUNDARIES },
90+
],
91+
},
92+
}),
93+
} as unknown as ESLintNamespace.ESLint);
94+
writeJson<NxDotnetConfig>(appTree, CONFIG_FILE_PATH, {
95+
nugetPackages: {},
96+
});
97+
});
98+
99+
afterEach(() => {
100+
jest.resetAllMocks();
101+
vol.reset();
102+
});
103+
78104
it('should exit early if no tags on project', async () => {
79105
const spy = jest.spyOn(checkModule, 'loadModuleBoundaries');
80106
const results = await checkModuleBoundariesForProject('a', {
@@ -87,4 +113,76 @@ describe('enforce-module-boundaries', () => {
87113
expect(spy).not.toHaveBeenCalled();
88114
expect(results).toHaveLength(0);
89115
});
116+
117+
it('should find violations with onlyDependOnLibsWithTags', async () => {
118+
const globResults = ['libs/a/a.csproj'];
119+
jest.spyOn(fastGlob, 'sync').mockImplementation(() => globResults);
120+
121+
vol.fromJSON({
122+
'libs/a/a.csproj':
123+
'<Project Sdk="Microsoft.NET.Sdk.Web"><ItemGroup><ProjectReference Include="..\\..\\libs\\ui\\ui.csproj" /></ItemGroup></Project>',
124+
});
125+
126+
const results = await checkModuleBoundariesForProject('a', {
127+
a: {
128+
tags: ['a'],
129+
targets: { ui: {} },
130+
root: 'libs/a',
131+
},
132+
ui: {
133+
tags: ['ui'],
134+
targets: {},
135+
root: 'libs/ui',
136+
},
137+
});
138+
expect(results).toHaveLength(1);
139+
});
140+
141+
it('should find violations with notDependOnLibsWithTags', async () => {
142+
const globResults = ['libs/b/b.csproj'];
143+
jest.spyOn(fastGlob, 'sync').mockImplementation(() => globResults);
144+
145+
vol.fromJSON({
146+
'libs/b/b.csproj':
147+
'<Project Sdk="Microsoft.NET.Sdk.Web"><ItemGroup><ProjectReference Include="..\\..\\libs\\a\\a.csproj" /></ItemGroup></Project>',
148+
});
149+
150+
const results = await checkModuleBoundariesForProject('b', {
151+
a: {
152+
tags: ['a'],
153+
targets: {},
154+
root: 'libs/a',
155+
},
156+
b: {
157+
tags: ['b'],
158+
targets: { a: {} },
159+
root: 'libs/b',
160+
},
161+
});
162+
expect(results).toHaveLength(1);
163+
});
164+
165+
it('should pass without violations', async () => {
166+
const globResults = ['libs/a/a.csproj'];
167+
jest.spyOn(fastGlob, 'sync').mockImplementation(() => globResults);
168+
169+
vol.fromJSON({
170+
'libs/a/a.csproj':
171+
'<Project Sdk="Microsoft.NET.Sdk.Web"><ItemGroup><ProjectReference Include="..\\..\\libs\\shared\\shared.csproj" /></ItemGroup></Project>',
172+
});
173+
174+
const results = await checkModuleBoundariesForProject('a', {
175+
a: {
176+
tags: ['a'],
177+
targets: { shared: {} },
178+
root: 'libs/a',
179+
},
180+
shared: {
181+
tags: ['shared'],
182+
targets: {},
183+
root: 'libs/shared',
184+
},
185+
});
186+
expect(results).toHaveLength(0);
187+
});
90188
});

packages/core/src/tasks/check-module-boundaries.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@ export async function checkModuleBoundariesForProject(
2727
const configuredConstraints = await loadModuleBoundaries(projectRoot);
2828
const relevantConstraints = configuredConstraints.filter(
2929
(x) =>
30-
tags.includes(x.sourceTag) && !x.onlyDependOnLibsWithTags.includes('*'),
30+
tags.includes(x.sourceTag) &&
31+
(!x.onlyDependOnLibsWithTags?.includes('*') ||
32+
x.notDependOnLibsWithTags?.length),
3133
);
3234
if (!relevantConstraints.length) {
3335
return [];
@@ -43,7 +45,10 @@ export async function checkModuleBoundariesForProject(
4345
for (const constraint of relevantConstraints) {
4446
if (
4547
!dependencyTags.some((x) =>
46-
constraint.onlyDependOnLibsWithTags.includes(x),
48+
constraint.onlyDependOnLibsWithTags?.includes(x),
49+
) ||
50+
dependencyTags.some((x) =>
51+
constraint.notDependOnLibsWithTags?.includes(x),
4752
)
4853
) {
4954
violations.push(
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export type ModuleBoundaries = {
22
sourceTag: '*' | string;
3-
onlyDependOnLibsWithTags: string[];
3+
onlyDependOnLibsWithTags?: string[];
4+
notDependOnLibsWithTags?: string[];
45
}[];

0 commit comments

Comments
 (0)