835 lines
22 KiB
TypeScript
835 lines
22 KiB
TypeScript
import {
|
|
ProjectNode,
|
|
ProjectType
|
|
} from '@nrwl/workspace/src/command-line/affected-apps';
|
|
import {
|
|
Dependency,
|
|
DependencyType
|
|
} from '@nrwl/workspace/src/command-line/deps-calculator';
|
|
import { TSESLint } from '@typescript-eslint/experimental-utils';
|
|
import * as parser from '@typescript-eslint/parser';
|
|
import * as fs from 'fs';
|
|
import enforceModuleBoundaries, {
|
|
RULE_NAME as enforceModuleBoundariesRuleName
|
|
} from '../../src/rules/enforce-module-boundaries';
|
|
|
|
describe('Enforce Module Boundaries', () => {
|
|
beforeEach(() => {
|
|
spyOn(fs, 'writeFileSync');
|
|
});
|
|
|
|
it('should not error when everything is in order', () => {
|
|
const failures = runRule(
|
|
{ allow: ['@mycompany/mylib/deep'] },
|
|
`${process.cwd()}/proj/apps/myapp/src/main.ts`,
|
|
`
|
|
import '@mycompany/mylib';
|
|
import '@mycompany/mylib/deep';
|
|
import '../blah';
|
|
`,
|
|
[
|
|
{
|
|
name: 'myappName',
|
|
root: 'libs/myapp',
|
|
type: ProjectType.app,
|
|
tags: [],
|
|
implicitDependencies: [],
|
|
architect: {},
|
|
files: [`apps/myapp/src/main.ts`, `apps/myapp/blah.ts`],
|
|
fileMTimes: {
|
|
'apps/myapp/src/main.ts': 0,
|
|
'apps/myapp/blah.ts': 1
|
|
}
|
|
},
|
|
{
|
|
name: 'mylibName',
|
|
root: 'libs/mylib',
|
|
type: ProjectType.lib,
|
|
tags: [],
|
|
implicitDependencies: [],
|
|
architect: {},
|
|
files: [`libs/mylib/src/index.ts`, `libs/mylib/src/deep.ts`],
|
|
fileMTimes: {
|
|
'apps/mylib/src/index.ts': 0,
|
|
'apps/mylib/src/deep.ts': 1
|
|
}
|
|
}
|
|
]
|
|
);
|
|
|
|
expect(failures.length).toEqual(0);
|
|
});
|
|
|
|
it('should handle multiple projects starting with the same prefix properly', () => {
|
|
const failures = runRule(
|
|
{},
|
|
`${process.cwd()}/proj/apps/myapp/src/main.ts`,
|
|
`
|
|
import '@mycompany/myapp2/mylib';
|
|
`,
|
|
[
|
|
{
|
|
name: 'myappName',
|
|
root: 'libs/myapp',
|
|
type: ProjectType.app,
|
|
tags: [],
|
|
implicitDependencies: [],
|
|
architect: {},
|
|
files: [`apps/myapp/src/main.ts`, `apps/myapp/src/blah.ts`],
|
|
fileMTimes: {
|
|
'apps/myapp/src/main.ts': 0,
|
|
'apps/myapp/src/blah.ts': 1
|
|
}
|
|
},
|
|
{
|
|
name: 'myapp2Name',
|
|
root: 'libs/myapp2',
|
|
type: ProjectType.app,
|
|
tags: [],
|
|
implicitDependencies: [],
|
|
architect: {},
|
|
files: [],
|
|
fileMTimes: {}
|
|
},
|
|
{
|
|
name: 'myapp2-mylib',
|
|
root: 'libs/myapp2/mylib',
|
|
type: ProjectType.lib,
|
|
tags: [],
|
|
implicitDependencies: [],
|
|
architect: {},
|
|
files: ['libs/myapp2/mylib/src/index.ts'],
|
|
fileMTimes: {
|
|
'libs/myapp2/mylib/src/index.ts': 1
|
|
}
|
|
}
|
|
]
|
|
);
|
|
|
|
expect(failures.length).toEqual(0);
|
|
});
|
|
|
|
describe('depConstraints', () => {
|
|
const projectNodes: ProjectNode[] = [
|
|
{
|
|
name: 'apiName',
|
|
root: 'libs/api',
|
|
type: ProjectType.lib,
|
|
tags: ['api', 'domain1'],
|
|
implicitDependencies: [],
|
|
architect: {},
|
|
files: [`libs/api/src/index.ts`],
|
|
fileMTimes: {
|
|
'libs/api/src/index.ts': 1
|
|
}
|
|
},
|
|
{
|
|
name: 'implName',
|
|
root: 'libs/impl',
|
|
type: ProjectType.lib,
|
|
tags: ['impl', 'domain1'],
|
|
implicitDependencies: [],
|
|
architect: {},
|
|
files: [`libs/impl/src/index.ts`],
|
|
fileMTimes: {
|
|
'libs/impl/src/index.ts': 1
|
|
}
|
|
},
|
|
{
|
|
name: 'impl2Name',
|
|
root: 'libs/impl2',
|
|
type: ProjectType.lib,
|
|
tags: ['impl', 'domain1'],
|
|
implicitDependencies: [],
|
|
architect: {},
|
|
files: [`libs/impl2/src/index.ts`],
|
|
fileMTimes: {
|
|
'libs/impl2/src/index.ts': 1
|
|
}
|
|
},
|
|
{
|
|
name: 'impl-domain2Name',
|
|
root: 'libs/impl-domain2',
|
|
type: ProjectType.lib,
|
|
tags: ['impl', 'domain2'],
|
|
implicitDependencies: [],
|
|
architect: {},
|
|
files: [`libs/impl-domain2/src/index.ts`],
|
|
fileMTimes: {
|
|
'libs/impl-domain2/src/index.ts': 1
|
|
}
|
|
},
|
|
{
|
|
name: 'impl-both-domainsName',
|
|
root: 'libs/impl-both-domains',
|
|
type: ProjectType.lib,
|
|
tags: ['impl', 'domain1', 'domain2'],
|
|
implicitDependencies: [],
|
|
architect: {},
|
|
files: [`libs/impl-both-domains/src/index.ts`],
|
|
fileMTimes: {
|
|
'libs/impl-both-domains/src/index.ts': 1
|
|
}
|
|
},
|
|
{
|
|
name: 'untaggedName',
|
|
root: 'libs/untagged',
|
|
type: ProjectType.lib,
|
|
tags: [],
|
|
implicitDependencies: [],
|
|
architect: {},
|
|
files: [`libs/untagged/src/index.ts`],
|
|
fileMTimes: {
|
|
'libs/untagged/src/index.ts': 1
|
|
}
|
|
}
|
|
];
|
|
|
|
const depConstraints = {
|
|
depConstraints: [
|
|
{ sourceTag: 'api', onlyDependOnLibsWithTags: ['api'] },
|
|
{ sourceTag: 'impl', onlyDependOnLibsWithTags: ['api', 'impl'] },
|
|
{ sourceTag: 'domain1', onlyDependOnLibsWithTags: ['domain1'] },
|
|
{ sourceTag: 'domain2', onlyDependOnLibsWithTags: ['domain2'] }
|
|
]
|
|
};
|
|
|
|
it('should error when the target library does not have the right tag', () => {
|
|
const failures = runRule(
|
|
depConstraints,
|
|
`${process.cwd()}/proj/libs/api/src/index.ts`,
|
|
`
|
|
import '@mycompany/impl';
|
|
`,
|
|
projectNodes
|
|
);
|
|
|
|
expect(failures[0].message).toEqual(
|
|
'A project tagged with "api" can only depend on libs tagged with "api"'
|
|
);
|
|
});
|
|
|
|
it('should error when the target library is untagged', () => {
|
|
const failures = runRule(
|
|
depConstraints,
|
|
`${process.cwd()}/proj/libs/api/src/index.ts`,
|
|
`
|
|
import '@mycompany/untagged';
|
|
`,
|
|
projectNodes
|
|
);
|
|
|
|
expect(failures[0].message).toEqual(
|
|
'A project tagged with "api" can only depend on libs tagged with "api"'
|
|
);
|
|
});
|
|
|
|
it('should error when the source library is untagged', () => {
|
|
const failures = runRule(
|
|
depConstraints,
|
|
`${process.cwd()}/proj/libs/untagged/src/index.ts`,
|
|
`
|
|
import '@mycompany/api';
|
|
`,
|
|
projectNodes
|
|
);
|
|
|
|
expect(failures[0].message).toEqual(
|
|
'A project without tags cannot depend on any libraries'
|
|
);
|
|
});
|
|
|
|
it('should check all tags', () => {
|
|
const failures = runRule(
|
|
depConstraints,
|
|
`${process.cwd()}/proj/libs/impl/src/index.ts`,
|
|
`
|
|
import '@mycompany/impl-domain2';
|
|
`,
|
|
projectNodes
|
|
);
|
|
|
|
expect(failures[0].message).toEqual(
|
|
'A project tagged with "domain1" can only depend on libs tagged with "domain1"'
|
|
);
|
|
});
|
|
|
|
it('should allow a domain1 project to depend on a project that is tagged with domain1 and domain2', () => {
|
|
const failures = runRule(
|
|
depConstraints,
|
|
`${process.cwd()}/proj/libs/impl/src/index.ts`,
|
|
`
|
|
import '@mycompany/impl-both-domains';
|
|
`,
|
|
projectNodes
|
|
);
|
|
|
|
expect(failures.length).toEqual(0);
|
|
});
|
|
|
|
it('should allow a domain1/domain2 project depend on domain1', () => {
|
|
const failures = runRule(
|
|
depConstraints,
|
|
`${process.cwd()}/proj/libs/impl-both-domain/src/index.ts`,
|
|
`
|
|
import '@mycompany/impl';
|
|
`,
|
|
projectNodes
|
|
);
|
|
|
|
expect(failures.length).toEqual(0);
|
|
});
|
|
|
|
it('should not error when the constraints are satisfied', () => {
|
|
const failures = runRule(
|
|
depConstraints,
|
|
`${process.cwd()}/proj/libs/impl/src/index.ts`,
|
|
`
|
|
import '@mycompany/impl2';
|
|
`,
|
|
projectNodes
|
|
);
|
|
|
|
expect(failures.length).toEqual(0);
|
|
});
|
|
|
|
it('should support wild cards', () => {
|
|
const failures = runRule(
|
|
{
|
|
depConstraints: [{ sourceTag: '*', onlyDependOnLibsWithTags: ['*'] }]
|
|
},
|
|
`${process.cwd()}/proj/libs/api/src/index.ts`,
|
|
`
|
|
import '@mycompany/impl';
|
|
`,
|
|
projectNodes
|
|
);
|
|
|
|
expect(failures.length).toEqual(0);
|
|
});
|
|
});
|
|
|
|
describe('relative imports', () => {
|
|
it('should not error when relatively importing the same library', () => {
|
|
const failures = runRule(
|
|
{},
|
|
`${process.cwd()}/proj/libs/mylib/src/main.ts`,
|
|
'import "../other"',
|
|
[
|
|
{
|
|
name: 'mylibName',
|
|
root: 'libs/mylib',
|
|
type: ProjectType.lib,
|
|
tags: [],
|
|
implicitDependencies: [],
|
|
architect: {},
|
|
files: [`libs/mylib/src/main.ts`, `libs/mylib/other.ts`],
|
|
fileMTimes: {
|
|
'libs/mylib/src/main.ts': 1,
|
|
'libs/mylib/other.ts': 1
|
|
}
|
|
}
|
|
]
|
|
);
|
|
expect(failures.length).toEqual(0);
|
|
});
|
|
|
|
it('should not error when relatively importing the same library (index file)', () => {
|
|
const failures = runRule(
|
|
{},
|
|
`${process.cwd()}/proj/libs/mylib/src/main.ts`,
|
|
'import "../other"',
|
|
[
|
|
{
|
|
name: 'mylibName',
|
|
root: 'libs/mylib',
|
|
type: ProjectType.lib,
|
|
tags: [],
|
|
implicitDependencies: [],
|
|
architect: {},
|
|
files: [`libs/mylib/src/main.ts`, `libs/mylib/other/index.ts`],
|
|
fileMTimes: {
|
|
'libs/mylib/src/main.ts': 1,
|
|
'libs/mylib/other/index.ts': 1
|
|
}
|
|
}
|
|
]
|
|
);
|
|
expect(failures.length).toEqual(0);
|
|
});
|
|
|
|
it('should error when relatively importing another library', () => {
|
|
const failures = runRule(
|
|
{},
|
|
`${process.cwd()}/proj/libs/mylib/src/main.ts`,
|
|
'import "../../other"',
|
|
[
|
|
{
|
|
name: 'mylibName',
|
|
root: 'libs/mylib',
|
|
type: ProjectType.lib,
|
|
tags: [],
|
|
implicitDependencies: [],
|
|
architect: {},
|
|
files: [`libs/mylib/src/main.ts`],
|
|
fileMTimes: {
|
|
'libs/mylib/src/main.ts': 1
|
|
}
|
|
},
|
|
{
|
|
name: 'otherName',
|
|
root: 'libs/other',
|
|
type: ProjectType.lib,
|
|
tags: [],
|
|
implicitDependencies: [],
|
|
architect: {},
|
|
files: ['libs/other/src/index.ts'],
|
|
fileMTimes: {
|
|
'libs/other/src/main.ts': 1
|
|
}
|
|
}
|
|
]
|
|
);
|
|
expect(failures[0].message).toEqual(
|
|
'Library imports must start with @mycompany/'
|
|
);
|
|
});
|
|
|
|
it('should error when relatively importing the src directory of another library', () => {
|
|
const failures = runRule(
|
|
{},
|
|
`${process.cwd()}/proj/libs/mylib/src/main.ts`,
|
|
'import "../../other/src"',
|
|
[
|
|
{
|
|
name: 'mylibName',
|
|
root: 'libs/mylib',
|
|
type: ProjectType.lib,
|
|
tags: [],
|
|
implicitDependencies: [],
|
|
architect: {},
|
|
files: [`libs/mylib/src/main.ts`],
|
|
fileMTimes: {
|
|
'libs/mylib/src/main.ts': 1
|
|
}
|
|
},
|
|
{
|
|
name: 'otherName',
|
|
root: 'libs/other',
|
|
type: ProjectType.lib,
|
|
tags: [],
|
|
implicitDependencies: [],
|
|
architect: {},
|
|
files: ['libs/other/src/index.ts'],
|
|
fileMTimes: {
|
|
'libs/other/src/main.ts': 1
|
|
}
|
|
}
|
|
]
|
|
);
|
|
expect(failures[0].message).toEqual(
|
|
'Library imports must start with @mycompany/'
|
|
);
|
|
});
|
|
});
|
|
|
|
it('should error on absolute imports into libraries without using the npm scope', () => {
|
|
const failures = runRule(
|
|
{},
|
|
`${process.cwd()}/proj/libs/mylib/src/main.ts`,
|
|
'import "libs/src/other.ts"',
|
|
[
|
|
{
|
|
name: 'mylibName',
|
|
root: 'libs/mylib',
|
|
type: ProjectType.lib,
|
|
tags: [],
|
|
implicitDependencies: [],
|
|
architect: {},
|
|
files: [`libs/mylib/src/main.ts`, `libs/mylib/src/other.ts`],
|
|
fileMTimes: {
|
|
'libs/mylib/src/main.ts': 1,
|
|
'libs/mylib/src/other/index.ts': 1
|
|
}
|
|
}
|
|
]
|
|
);
|
|
|
|
expect(failures.length).toEqual(1);
|
|
expect(failures[0].message).toEqual(
|
|
'Library imports must start with @mycompany/'
|
|
);
|
|
});
|
|
|
|
it('should error about deep imports into libraries', () => {
|
|
const failures = runRule(
|
|
{},
|
|
`${process.cwd()}/proj/libs/mylib/src/main.ts`,
|
|
`
|
|
import "@mycompany/other/src/blah"
|
|
import "@mycompany/other/src/sublib/blah"
|
|
`,
|
|
[
|
|
{
|
|
name: 'mylibName',
|
|
root: 'libs/mylib',
|
|
type: ProjectType.lib,
|
|
tags: [],
|
|
implicitDependencies: [],
|
|
architect: {},
|
|
files: [`libs/mylib/src/main.ts`, `libs/mylib/src/another-file.ts`],
|
|
fileMTimes: {
|
|
'libs/mylib/src/main.ts': 1,
|
|
'libs/mylib/src/another-file.ts': 1
|
|
}
|
|
},
|
|
{
|
|
name: 'otherName',
|
|
root: 'libs/other',
|
|
type: ProjectType.lib,
|
|
tags: [],
|
|
implicitDependencies: [],
|
|
architect: {},
|
|
files: [`libs/other/src/blah.ts`],
|
|
fileMTimes: {
|
|
'libs/other/src/blah.ts': 1
|
|
}
|
|
},
|
|
{
|
|
name: 'otherSublibName',
|
|
root: 'libs/other/sublib',
|
|
type: ProjectType.lib,
|
|
tags: [],
|
|
implicitDependencies: [],
|
|
architect: {},
|
|
files: [`libs/other/sublib/src/blah.ts`],
|
|
fileMTimes: {
|
|
'libs/other/sublib/src/blah.ts': 1
|
|
}
|
|
}
|
|
]
|
|
);
|
|
expect(failures[0].message).toEqual(
|
|
'Deep imports into libraries are forbidden'
|
|
);
|
|
expect(failures[1].message).toEqual(
|
|
'Deep imports into libraries are forbidden'
|
|
);
|
|
});
|
|
|
|
it('should not error about deep imports into library when fixed exception is set', () => {
|
|
const failures = runRule(
|
|
{ allow: ['@mycompany/other/src/blah'] },
|
|
`${process.cwd()}/proj/libs/mylib/src/main.ts`,
|
|
`
|
|
import "@mycompany/other/src/blah"
|
|
`,
|
|
[
|
|
{
|
|
name: 'mylibName',
|
|
root: 'libs/mylib',
|
|
type: ProjectType.lib,
|
|
tags: [],
|
|
implicitDependencies: [],
|
|
architect: {},
|
|
files: [`libs/mylib/src/main.ts`, `libs/mylib/src/another-file.ts`],
|
|
fileMTimes: {
|
|
'libs/mylib/src/main.ts': 1,
|
|
'libs/mylib/src/another-file.ts': 1
|
|
}
|
|
},
|
|
{
|
|
name: 'otherName',
|
|
root: 'libs/other',
|
|
type: ProjectType.lib,
|
|
tags: [],
|
|
implicitDependencies: [],
|
|
architect: {},
|
|
files: [`libs/other/src/blah.ts`],
|
|
fileMTimes: {
|
|
'libs/other/src/blah.ts': 1
|
|
}
|
|
}
|
|
]
|
|
);
|
|
expect(failures.length).toEqual(0);
|
|
});
|
|
|
|
it('should not error about deep imports into library when exception is specified with a wildcard', () => {
|
|
const failures = runRule(
|
|
{ allow: ['@mycompany/other/*'] },
|
|
`${process.cwd()}/proj/libs/mylib/src/main.ts`,
|
|
`
|
|
import "@mycompany/other/src/blah"
|
|
`,
|
|
[
|
|
{
|
|
name: 'mylibName',
|
|
root: 'libs/mylib',
|
|
type: ProjectType.lib,
|
|
tags: [],
|
|
implicitDependencies: [],
|
|
architect: {},
|
|
files: [`libs/mylib/src/main.ts`, `libs/mylib/src/another-file.ts`],
|
|
fileMTimes: {
|
|
'libs/mylib/src/main.ts': 1,
|
|
'libs/mylib/src/another-file.ts': 1
|
|
}
|
|
},
|
|
{
|
|
name: 'otherName',
|
|
root: 'libs/other',
|
|
type: ProjectType.lib,
|
|
tags: [],
|
|
implicitDependencies: [],
|
|
architect: {},
|
|
files: [`libs/other/src/blah.ts`],
|
|
fileMTimes: {
|
|
'libs/other/src/blah.ts': 1
|
|
}
|
|
}
|
|
]
|
|
);
|
|
expect(failures.length).toEqual(0);
|
|
});
|
|
|
|
it('should error on importing a lazy-loaded library', () => {
|
|
const failures = runRule(
|
|
{},
|
|
`${process.cwd()}/proj/libs/mylib/src/main.ts`,
|
|
'import "@mycompany/other";',
|
|
[
|
|
{
|
|
name: 'mylibName',
|
|
root: 'libs/mylib',
|
|
type: ProjectType.lib,
|
|
tags: [],
|
|
implicitDependencies: [],
|
|
architect: {},
|
|
files: [`libs/mylib/src/main.ts`],
|
|
fileMTimes: {
|
|
'libs/mylib/src/main.ts': 1
|
|
}
|
|
},
|
|
{
|
|
name: 'otherName',
|
|
root: 'libs/other',
|
|
type: ProjectType.lib,
|
|
tags: [],
|
|
implicitDependencies: [],
|
|
architect: {},
|
|
files: [`libs/other/index.ts`],
|
|
fileMTimes: {
|
|
'libs/other/index.ts': 1
|
|
}
|
|
}
|
|
],
|
|
{
|
|
mylibName: [
|
|
{ projectName: 'otherName', type: DependencyType.loadChildren }
|
|
]
|
|
}
|
|
);
|
|
expect(failures[0].message).toEqual(
|
|
'Imports of lazy-loaded libraries are forbidden'
|
|
);
|
|
});
|
|
|
|
it('should error on importing an app', () => {
|
|
const failures = runRule(
|
|
{},
|
|
`${process.cwd()}/proj/libs/mylib/src/main.ts`,
|
|
'import "@mycompany/myapp"',
|
|
[
|
|
{
|
|
name: 'mylibName',
|
|
root: 'libs/mylib',
|
|
type: ProjectType.lib,
|
|
tags: [],
|
|
implicitDependencies: [],
|
|
architect: {},
|
|
files: [`libs/mylib/src/main.ts`],
|
|
fileMTimes: {
|
|
'libs/mylib/src/main.ts': 1
|
|
}
|
|
},
|
|
{
|
|
name: 'myappName',
|
|
root: 'apps/myapp',
|
|
type: ProjectType.app,
|
|
tags: [],
|
|
implicitDependencies: [],
|
|
architect: {},
|
|
files: [`apps/myapp/src/index.ts`],
|
|
fileMTimes: {
|
|
'apps/myapp/src/index.ts': 1
|
|
}
|
|
}
|
|
]
|
|
);
|
|
expect(failures[0].message).toEqual('Imports of apps are forbidden');
|
|
});
|
|
|
|
it('should error when circular dependency detected', () => {
|
|
const failures = runRule(
|
|
{},
|
|
`${process.cwd()}/proj/libs/anotherlib/src/main.ts`,
|
|
'import "@mycompany/mylib"',
|
|
[
|
|
{
|
|
name: 'mylibName',
|
|
root: 'libs/mylib',
|
|
type: ProjectType.lib,
|
|
tags: [],
|
|
implicitDependencies: [],
|
|
architect: {},
|
|
files: [`libs/mylib/src/main.ts`],
|
|
fileMTimes: {
|
|
'libs/mylib/src/main.ts': 1
|
|
}
|
|
},
|
|
{
|
|
name: 'anotherlibName',
|
|
root: 'libs/anotherlib',
|
|
type: ProjectType.lib,
|
|
tags: [],
|
|
implicitDependencies: [],
|
|
architect: {},
|
|
files: [`libs/anotherlib/src/main.ts`],
|
|
fileMTimes: {
|
|
'libs/anotherlib/src/main.ts': 1
|
|
}
|
|
},
|
|
{
|
|
name: 'myappName',
|
|
root: 'apps/myapp',
|
|
type: ProjectType.app,
|
|
tags: [],
|
|
implicitDependencies: [],
|
|
architect: {},
|
|
files: [`apps/myapp/src/index.ts`],
|
|
fileMTimes: {
|
|
'apps/myapp/src/index.ts': 1
|
|
}
|
|
}
|
|
],
|
|
{
|
|
mylibName: [
|
|
{ projectName: 'anotherlibName', type: DependencyType.es6Import }
|
|
]
|
|
}
|
|
);
|
|
expect(failures[0].message).toEqual(
|
|
'Circular dependency between "anotherlibName" and "mylibName" detected'
|
|
);
|
|
});
|
|
|
|
it('should error when circular dependency detected (indirect)', () => {
|
|
const failures = runRule(
|
|
{},
|
|
`${process.cwd()}/proj/libs/mylib/src/main.ts`,
|
|
'import "@mycompany/badcirclelib"',
|
|
[
|
|
{
|
|
name: 'mylibName',
|
|
root: 'libs/mylib',
|
|
type: ProjectType.lib,
|
|
tags: [],
|
|
implicitDependencies: [],
|
|
architect: {},
|
|
files: [`libs/mylib/src/main.ts`],
|
|
fileMTimes: {
|
|
'libs/mylib/src/main.ts': 1
|
|
}
|
|
},
|
|
{
|
|
name: 'anotherlibName',
|
|
root: 'libs/anotherlib',
|
|
type: ProjectType.lib,
|
|
tags: [],
|
|
implicitDependencies: [],
|
|
architect: {},
|
|
files: [`libs/anotherlib/src/main.ts`],
|
|
fileMTimes: {
|
|
'libs/mylib/src/main.ts': 1
|
|
}
|
|
},
|
|
{
|
|
name: 'badcirclelibName',
|
|
root: 'libs/badcirclelib',
|
|
type: ProjectType.lib,
|
|
tags: [],
|
|
implicitDependencies: [],
|
|
architect: {},
|
|
files: [`libs/badcirclelib/src/main.ts`],
|
|
fileMTimes: {
|
|
'libs/badcirclelib/src/main.ts': 1
|
|
}
|
|
},
|
|
{
|
|
name: 'myappName',
|
|
root: 'apps/myapp',
|
|
type: ProjectType.app,
|
|
tags: [],
|
|
implicitDependencies: [],
|
|
architect: {},
|
|
files: [`apps/myapp/index.ts`],
|
|
fileMTimes: {
|
|
'apps/myapp/index.ts': 1
|
|
}
|
|
}
|
|
],
|
|
{
|
|
mylibName: [
|
|
{ projectName: 'badcirclelibName', type: DependencyType.es6Import }
|
|
],
|
|
badcirclelibName: [
|
|
{ projectName: 'anotherlibName', type: DependencyType.es6Import }
|
|
],
|
|
anotherlibName: [
|
|
{ projectName: 'mylibName', type: DependencyType.es6Import }
|
|
]
|
|
}
|
|
);
|
|
expect(failures[0].message).toEqual(
|
|
'Circular dependency between "mylibName" and "badcirclelibName" detected'
|
|
);
|
|
});
|
|
});
|
|
|
|
const linter = new TSESLint.Linter();
|
|
const baseConfig = {
|
|
parser: '@typescript-eslint/parser',
|
|
parserOptions: {
|
|
ecmaVersion: 2018 as const,
|
|
sourceType: 'module' as const
|
|
},
|
|
rules: {
|
|
[enforceModuleBoundariesRuleName]: 'error'
|
|
}
|
|
};
|
|
linter.defineParser('@typescript-eslint/parser', parser);
|
|
linter.defineRule(enforceModuleBoundariesRuleName, enforceModuleBoundaries);
|
|
|
|
function runRule(
|
|
ruleArguments: any,
|
|
contentPath: string,
|
|
content: string,
|
|
projectNodes: ProjectNode[],
|
|
deps: { [projectName: string]: Dependency[] } = {}
|
|
): TSESLint.Linter.LintMessage[] {
|
|
(global as any).projectPath = `${process.cwd()}/proj`;
|
|
(global as any).npmScope = 'mycompany';
|
|
(global as any).projectNodes = projectNodes;
|
|
(global as any).deps = deps;
|
|
|
|
const config = {
|
|
...baseConfig,
|
|
rules: {
|
|
[enforceModuleBoundariesRuleName]: ['error', ruleArguments]
|
|
}
|
|
};
|
|
|
|
return linter.verifyAndFix(content, config as any, contentPath).messages;
|
|
}
|