diff --git a/packages/eslint-plugin-nx/src/rules/enforce-module-boundaries.spec.ts b/packages/eslint-plugin-nx/src/rules/enforce-module-boundaries.spec.ts index 9768cd19fbac76..03e6176deac295 100644 --- a/packages/eslint-plugin-nx/src/rules/enforce-module-boundaries.spec.ts +++ b/packages/eslint-plugin-nx/src/rules/enforce-module-boundaries.spec.ts @@ -249,6 +249,28 @@ describe('Enforce Module Boundaries (eslint)', () => { files: [createFile(`libs/impl/src/index.ts`)], }, }, + publicName: { + name: 'publicName', + type: ProjectType.lib, + data: { + root: 'libs/public', + tags: ['public'], + implicitDependencies: [], + architect: {}, + files: [createFile(`libs/public/src/index.ts`)], + }, + }, + privateName: { + name: 'privateName', + type: ProjectType.lib, + data: { + root: 'libs/private', + tags: ['private'], + implicitDependencies: [], + architect: {}, + files: [createFile(`libs/private/src/index.ts`)], + }, + }, untaggedName: { name: 'untaggedName', type: ProjectType.lib, @@ -304,6 +326,7 @@ describe('Enforce Module Boundaries (eslint)', () => { { sourceTag: 'impl', onlyDependOnLibsWithTags: ['api', 'impl'] }, { sourceTag: 'domain1', onlyDependOnLibsWithTags: ['domain1'] }, { sourceTag: 'domain2', onlyDependOnLibsWithTags: ['domain2'] }, + { sourceTag: 'public', notDependOnLibsWithTags: ['private'] }, ], }; @@ -447,6 +470,24 @@ describe('Enforce Module Boundaries (eslint)', () => { expect(failures[1].message).toEqual(message); }); + it('should error when the target library has a disallowed tag', () => { + const failures = runRule( + depConstraints, + `${process.cwd()}/proj/libs/public/src/index.ts`, + ` + import '@mycompany/private'; + import('@mycompany/private'); + `, + graph + ); + + const message = + 'A project tagged with "public" can not depend on libs tagged with "private"'; + expect(failures.length).toEqual(2); + expect(failures[0].message).toEqual(message); + expect(failures[1].message).toEqual(message); + }); + it('should error when the source library is untagged', () => { const failures = runRule( depConstraints, diff --git a/packages/eslint-plugin-nx/src/rules/enforce-module-boundaries.ts b/packages/eslint-plugin-nx/src/rules/enforce-module-boundaries.ts index d1723c1222cc23..c9cf24e9c20059 100644 --- a/packages/eslint-plugin-nx/src/rules/enforce-module-boundaries.ts +++ b/packages/eslint-plugin-nx/src/rules/enforce-module-boundaries.ts @@ -7,6 +7,7 @@ import { getSourceFilePath, hasBuildExecutor, hasNoneOfTheseTags, + hasAnyOfTheseTags, isAbsoluteImportIntoAnotherProject, isRelativeImportIntoAnotherProject, mapProjectGraphFiles, @@ -55,9 +56,10 @@ export type MessageIds = | 'noImportOfNonBuildableLibraries' | 'noImportsOfLazyLoadedLibraries' | 'projectWithoutTagsCannotHaveDependencies' - | 'tagConstraintViolation' | 'bannedExternalImportsViolation' - | 'noTransitiveDependencies'; + | 'noTransitiveDependencies' + | 'onlyTagsConstraintViolation' + | 'notTagsConstraintViolation'; export const RULE_NAME = 'enforce-module-boundaries'; export default createESLintRule({ @@ -84,6 +86,7 @@ export default createESLintRule({ sourceTag: { type: 'string' }, onlyDependOnLibsWithTags: [{ type: 'string' }], bannedExternalImports: [{ type: 'string' }], + notDependOnLibsWithTags: [{ type: 'string' }], }, additionalProperties: false, }, @@ -102,9 +105,10 @@ export default createESLintRule({ 'Buildable libraries cannot import or export from non-buildable libraries', noImportsOfLazyLoadedLibraries: `Imports of lazy-loaded libraries are forbidden`, projectWithoutTagsCannotHaveDependencies: `A project without tags matching at least one constraint cannot depend on any libraries`, - tagConstraintViolation: `A project tagged with "{{sourceTag}}" can only depend on libs tagged with {{allowedTags}}`, bannedExternalImportsViolation: `A project tagged with "{{sourceTag}}" is not allowed to import the "{{package}}" package`, noTransitiveDependencies: `Transitive dependencies are not allowed. Only packages defined in the "package.json" can be imported`, + onlyTagsConstraintViolation: `A project tagged with "{{sourceTag}}" can only depend on libs tagged with {{allowedTags}}`, + notTagsConstraintViolation: `A project tagged with "{{sourceTag}}" can not depend on libs tagged with {{disallowedTags}}`, }, }, defaultOptions: [ @@ -393,9 +397,10 @@ export default createESLintRule({ for (let constraint of constraints) { if ( + constraint.onlyDependOnLibsWithTags && hasNoneOfTheseTags( targetProject, - constraint.onlyDependOnLibsWithTags || [] + constraint.onlyDependOnLibsWithTags ) ) { const allowedTags = constraint.onlyDependOnLibsWithTags @@ -403,7 +408,7 @@ export default createESLintRule({ .join(', '); context.report({ node, - messageId: 'tagConstraintViolation', + messageId: 'onlyTagsConstraintViolation', data: { sourceTag: constraint.sourceTag, allowedTags, @@ -411,6 +416,23 @@ export default createESLintRule({ }); return; } + if ( + constraint.notDependOnLibsWithTags && + hasAnyOfTheseTags(targetProject, constraint.notDependOnLibsWithTags) + ) { + const disallowedTags = constraint.notDependOnLibsWithTags + .map((s) => `"${s}"`) + .join(', '); + context.report({ + node, + messageId: 'notTagsConstraintViolation', + data: { + sourceTag: constraint.sourceTag, + disallowedTags: disallowedTags, + }, + }); + return; + } } } } diff --git a/packages/workspace/src/utils/runtime-lint-utils.ts b/packages/workspace/src/utils/runtime-lint-utils.ts index 2c5c66f719b8d8..e6d86664a9b822 100644 --- a/packages/workspace/src/utils/runtime-lint-utils.ts +++ b/packages/workspace/src/utils/runtime-lint-utils.ts @@ -26,6 +26,7 @@ export type Deps = { [projectName: string]: ProjectGraphDependency[] }; export type DepConstraint = { sourceTag: string; onlyDependOnLibsWithTags: string[]; + notDependOnLibsWithTags: string[]; bannedExternalImports?: string[]; }; @@ -36,6 +37,12 @@ export function hasNoneOfTheseTags( return tags.filter((tag) => hasTag(proj, tag)).length === 0; } +export function hasAnyOfTheseTags(proj: ProjectGraphProjectNode, tags: string[]) { + return ( + tags.filter((disallowedTag) => hasTag(proj, disallowedTag)).length !== 0 + ); +} + function hasTag(proj: ProjectGraphProjectNode, tag: string) { return tag === '*' || (proj.data.tags || []).indexOf(tag) > -1; }