Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions astro-docs/src/content/docs/kb/enforce-module-boundaries.mdoc
Original file line number Diff line number Diff line change
Expand Up @@ -73,16 +73,16 @@ export default [

## Options

| Property | Type | Default | Description |
| ---------------------------------- | ------------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| allow | _Array<string>_ | _[]_ | List of imports that should be allowed without any checks |
| allowCircularSelfDependency | _boolean_ | _false_ | Disable check for self circular dependency when project imports from itself via alias path |
| banTransitiveDependencies | _boolean_ | _false_ | Ban import of dependencies that were not specified in the root or project's `package.json` |
| ignoredCircularDependencies | _Array<[string, string]>_ | _[]_ | List of project pairs that should be skipped from `Circular dependencies` checks, including the self-circular dependency check. E.g. `['feature-project-a', 'myapp']`. Project name can be replaced by catch all `*` for more generic matches. |
| checkDynamicDependenciesExceptions | _Array<string>_ | _[]_ | List of imports that should be skipped for `Imports of lazy-loaded libraries forbidden` checks. E.g. `['@myorg/lazy-project/component/*', '@myorg/other-project']` |
| checkNestedExternalImports | _boolean_ | _false_ | Enable to enforce the check for banned external imports in the nested packages. Check [Dependency constraints](#dependency-constraints) for more information |
| enforceBuildableLibDependency | _boolean_ | _false_ | Enable to restrict the buildable libs from importing non-buildable libraries |
| depConstraints | _Array<object>_ | _[]_ | List of dependency constraints between projects |
| Property | Type | Default | Description |
| ---------------------------------- | ------------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| allow | _Array<string>_ | _[]_ | List of imports that should be allowed without any checks |
| allowCircularSelfDependency | _boolean_ | _false_ | Disable check for self circular dependency when project imports from itself via alias path |
| banTransitiveDependencies | _boolean_ | _false_ | Ban import of dependencies that were not specified in the root or project's `package.json` |
| ignoredCircularDependencies | _Array<[string, string]>_ | _[]_ | List of project pairs that should be skipped from `Circular dependencies` checks, including the self-circular dependency check. E.g. `['feature-project-a', 'myapp']`. Project name can be replaced by catch all `*` for more generic matches. |
| checkDynamicDependenciesExceptions | _Array<string>_ | _[]_ | List of imports that should be skipped for `Imports of lazy-loaded libraries forbidden` checks. E.g. `['@myorg/lazy-project/component/*', '@myorg/other-project']` |
| checkNestedExternalImports | _boolean_ | _false_ | Enable to also apply the `bannedExternalImports` and `allowedExternalImports` constraints to the external packages that imported projects depend on, transitively. The nested check compares whole package names. In `bannedExternalImports`, a subpath entry such as `lodash/fp` only applies to direct imports. In `allowedExternalImports`, a subpath entry does not allow the package for the nested check, so the rule reports the nested dependency. Check [Dependency constraints](#dependency-constraints) for more information |
| enforceBuildableLibDependency | _boolean_ | _false_ | Enable to restrict the buildable libs from importing non-buildable libraries |
| depConstraints | _Array<object>_ | _[]_ | List of dependency constraints between projects |

### Dependency constraints

Expand Down
283 changes: 283 additions & 0 deletions packages/eslint-plugin/src/rules/enforce-module-boundaries.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -664,6 +664,289 @@ describe('Enforce Module Boundaries (eslint)', () => {
expect(failures[1].message).toEqual(message);
});

it('should error when importing a project that transitively depends on a banned external package', () => {
const failures = runRule(
{
depConstraints: [
{ sourceTag: 'api', bannedExternalImports: ['npm-package'] },
],
checkNestedExternalImports: true,
},
`${process.cwd()}/proj/libs/api/src/index.ts`,
`
import '@mycompany/impl';
import('@mycompany/impl');
`,
{
...graph,
dependencies: {
...graph.dependencies,
implName: [
{
source: 'implName',
target: 'npm:npm-package',
type: DependencyType.static,
},
],
},
},
fileMap
);

const message =
'A project tagged with "api" is not allowed to import "@mycompany/impl". Nested import of "npm-package" found at implName';
expect(failures.length).toEqual(2);
expect(failures[0].message).toEqual(message);
expect(failures[1].message).toEqual(message);
});

it('should not error when importing a project whose transitive external dependencies are not banned', () => {
const failures = runRule(
{
depConstraints: [
{ sourceTag: 'api', bannedExternalImports: ['npm-package'] },
],
checkNestedExternalImports: true,
},
`${process.cwd()}/proj/libs/api/src/index.ts`,
`
import '@mycompany/impl';
import('@mycompany/impl');
`,
{
...graph,
dependencies: {
...graph.dependencies,
implName: [
{
source: 'implName',
target: 'npm:npm-awesome-package',
type: DependencyType.static,
},
],
},
},
fileMap
);

expect(failures.length).toEqual(0);
});

describe.each([
{
name: 'a mixed banned and allowed constraint',
constraint: {
sourceTag: 'api',
bannedExternalImports: ['npm-package'],
allowedExternalImports: ['npm-package2'],
},
},
{
name: 'an allowed-only constraint',
constraint: {
sourceTag: 'api',
allowedExternalImports: ['npm-package2'],
},
},
{
name: 'an empty allowed constraint',
constraint: { sourceTag: 'api', allowedExternalImports: [] },
},
])('nested external imports with $name', ({ constraint }) => {
it('should error when importing a project that transitively depends on an external package outside the allowed list', () => {
const failures = runRule(
{ depConstraints: [constraint], checkNestedExternalImports: true },
`${process.cwd()}/proj/libs/api/src/index.ts`,
`
import '@mycompany/impl';
import('@mycompany/impl');
`,
{
...graph,
dependencies: {
...graph.dependencies,
implName: [
{
source: 'implName',
target: 'npm:npm-awesome-package',
type: DependencyType.static,
},
],
},
},
fileMap
);

const message =
'A project tagged with "api" is not allowed to import "@mycompany/impl". Nested import of "npm-awesome-package" found at implName';
expect(failures.length).toEqual(2);
expect(failures[0].message).toEqual(message);
expect(failures[1].message).toEqual(message);
});
});

it('should not error when importing a project whose transitive external dependencies are in the allowed list', () => {
const failures = runRule(
{
depConstraints: [
{ sourceTag: 'api', allowedExternalImports: ['npm-awesome-*'] },
],
checkNestedExternalImports: true,
},
`${process.cwd()}/proj/libs/api/src/index.ts`,
`
import '@mycompany/impl';
import('@mycompany/impl');
`,
{
...graph,
dependencies: {
...graph.dependencies,
implName: [
{
source: 'implName',
target: 'npm:npm-awesome-package',
type: DependencyType.static,
},
],
},
},
fileMap
);

expect(failures.length).toEqual(0);
});

it('should report the project owning the nested import when the banned package is more than one hop away', () => {
const failures = runRule(
{
depConstraints: [
{ sourceTag: 'api', bannedExternalImports: ['npm-package'] },
],
checkNestedExternalImports: true,
},
`${process.cwd()}/proj/libs/api/src/index.ts`,
`
import '@mycompany/impl';
`,
{
...graph,
dependencies: {
...graph.dependencies,
implName: [
{
source: 'implName',
target: 'impl2Name',
type: DependencyType.static,
},
],
impl2Name: [
{
source: 'impl2Name',
target: 'npm:npm-package',
type: DependencyType.static,
},
],
},
},
fileMap
);

expect(failures.length).toEqual(1);
expect(failures[0].message).toEqual(
'A project tagged with "api" is not allowed to import "@mycompany/impl". Nested import of "npm-package" found at impl2Name'
);
});

it('should report a single violation when multiple dependency edges reach the same banned package', () => {
const failures = runRule(
{
depConstraints: [
{ sourceTag: 'api', bannedExternalImports: ['npm-package'] },
],
checkNestedExternalImports: true,
},
`${process.cwd()}/proj/libs/api/src/index.ts`,
`
import '@mycompany/impl';
`,
{
...graph,
dependencies: {
...graph.dependencies,
implName: [
{
source: 'implName',
target: 'npm:npm-package',
type: DependencyType.static,
},
{
source: 'implName',
target: 'npm:npm-package',
type: DependencyType.dynamic,
},
],
},
},
fileMap
);

expect(failures.length).toEqual(1);
expect(failures[0].message).toEqual(
'A project tagged with "api" is not allowed to import "@mycompany/impl". Nested import of "npm-package" found at implName'
);
});

it('should report a single violation when multiple versions of the same banned package are reached', () => {
const failures = runRule(
{
depConstraints: [
{ sourceTag: 'api', bannedExternalImports: ['npm-package'] },
],
checkNestedExternalImports: true,
},
`${process.cwd()}/proj/libs/api/src/index.ts`,
`
import '@mycompany/impl';
`,
{
...graph,
externalNodes: {
...graph.externalNodes,
'npm:npm-package@2.0.0': {
name: 'npm:npm-package@2.0.0',
type: 'npm',
data: {
packageName: 'npm-package',
version: '2.0.0',
},
},
},
dependencies: {
...graph.dependencies,
implName: [
{
source: 'implName',
target: 'npm:npm-package',
type: DependencyType.static,
},
{
source: 'implName',
target: 'npm:npm-package@2.0.0',
type: DependencyType.static,
},
],
},
},
fileMap
);

expect(failures.length).toEqual(1);
expect(failures[0].message).toEqual(
'A project tagged with "api" is not allowed to import "@mycompany/impl". Nested import of "npm-package" found at implName'
);
});

it('should error when importing transitive npm packages', () => {
const failures = runRule(
{
Expand Down
Loading
Loading