feat(linter): allow banning of transitive dependencies from imports (#7906)
This commit is contained in:
parent
3add9caacf
commit
64d388e607
@ -14,12 +14,6 @@
|
||||
"ESLint",
|
||||
"CLI"
|
||||
],
|
||||
"files": [
|
||||
"src",
|
||||
"package.json",
|
||||
"README.md",
|
||||
"LICENSE"
|
||||
],
|
||||
"main": "./src/index.js",
|
||||
"typings": "./src/index.d.ts",
|
||||
"author": "Victor Savkin",
|
||||
|
||||
@ -49,6 +49,15 @@ const tsconfig = {
|
||||
include: ['**/*.ts'],
|
||||
};
|
||||
|
||||
const packageJson = {
|
||||
dependencies: {
|
||||
'npm-package': '2.3.4',
|
||||
},
|
||||
devDependencies: {
|
||||
'npm-awesome-package': '1.2.3',
|
||||
},
|
||||
};
|
||||
|
||||
const fileSys = {
|
||||
'./libs/impl/src/index.ts': '',
|
||||
'./libs/untagged/src/index.ts': '',
|
||||
@ -70,6 +79,7 @@ const fileSys = {
|
||||
'./libs/buildableLib/src/main.ts': '',
|
||||
'./libs/nonBuildableLib/src/main.ts': '',
|
||||
'./tsconfig.base.json': JSON.stringify(tsconfig),
|
||||
'./package.json': JSON.stringify(packageJson),
|
||||
};
|
||||
|
||||
describe('Enforce Module Boundaries (eslint)', () => {
|
||||
@ -257,7 +267,7 @@ describe('Enforce Module Boundaries (eslint)', () => {
|
||||
type: 'npm',
|
||||
data: {
|
||||
packageName: 'npm-package',
|
||||
version: '0.0.0',
|
||||
version: '2.3.4',
|
||||
},
|
||||
},
|
||||
'npm:npm-package2': {
|
||||
@ -281,7 +291,7 @@ describe('Enforce Module Boundaries (eslint)', () => {
|
||||
type: 'npm',
|
||||
data: {
|
||||
packageName: 'npm-awesome-package',
|
||||
version: '0.0.0',
|
||||
version: '1.2.3',
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -355,6 +365,46 @@ describe('Enforce Module Boundaries (eslint)', () => {
|
||||
expect(failures[1].message).toEqual(message);
|
||||
});
|
||||
|
||||
it('should error when importing transitive npm packages', () => {
|
||||
const failures = runRule(
|
||||
{
|
||||
...depConstraints,
|
||||
banTransitiveDependencies: true,
|
||||
},
|
||||
`${process.cwd()}/proj/libs/api/src/index.ts`,
|
||||
`
|
||||
import 'npm-package2';
|
||||
import('npm-package2');
|
||||
`,
|
||||
graph
|
||||
);
|
||||
|
||||
const message =
|
||||
'Transitive dependencies are not allowed. Only packages defined in the "package.json" can be imported';
|
||||
expect(failures.length).toEqual(2);
|
||||
expect(failures[0].message).toEqual(message);
|
||||
expect(failures[1].message).toEqual(message);
|
||||
});
|
||||
|
||||
it('should not error when importing direct npm dependencies', () => {
|
||||
const failures = runRule(
|
||||
{
|
||||
...depConstraints,
|
||||
banTransitiveDependencies: true,
|
||||
},
|
||||
`${process.cwd()}/proj/libs/api/src/index.ts`,
|
||||
`
|
||||
import 'npm-package';
|
||||
import('npm-package');
|
||||
import 'npm-awesome-package';
|
||||
import('npm-awesome-package');
|
||||
`,
|
||||
graph
|
||||
);
|
||||
|
||||
expect(failures.length).toEqual(0);
|
||||
});
|
||||
|
||||
it('should allow wildcards for defining forbidden npm packages', () => {
|
||||
const failures = runRule(
|
||||
{
|
||||
|
||||
@ -14,6 +14,7 @@ import {
|
||||
onlyLoadChildren,
|
||||
MappedProjectGraph,
|
||||
hasBannedImport,
|
||||
isDirectDependency,
|
||||
} from '@nrwl/workspace/src/utils/runtime-lint-utils';
|
||||
import {
|
||||
AST_NODE_TYPES,
|
||||
@ -22,7 +23,6 @@ import {
|
||||
import { createESLintRule } from '../utils/create-eslint-rule';
|
||||
import { normalizePath } from '@nrwl/devkit';
|
||||
import {
|
||||
isNpmProject,
|
||||
ProjectType,
|
||||
readCachedProjectGraph,
|
||||
} from '@nrwl/workspace/src/core/project-graph';
|
||||
@ -40,6 +40,7 @@ type Options = [
|
||||
depConstraints: DepConstraint[];
|
||||
enforceBuildableLibDependency: boolean;
|
||||
allowCircularSelfDependency: boolean;
|
||||
banTransitiveDependencies: boolean;
|
||||
}
|
||||
];
|
||||
export type MessageIds =
|
||||
@ -52,7 +53,8 @@ export type MessageIds =
|
||||
| 'noImportsOfLazyLoadedLibraries'
|
||||
| 'projectWithoutTagsCannotHaveDependencies'
|
||||
| 'tagConstraintViolation'
|
||||
| 'bannedExternalImportsViolation';
|
||||
| 'bannedExternalImportsViolation'
|
||||
| 'noTransitiveDependencies';
|
||||
export const RULE_NAME = 'enforce-module-boundaries';
|
||||
|
||||
export default createESLintRule<Options, MessageIds>({
|
||||
@ -70,6 +72,7 @@ export default createESLintRule<Options, MessageIds>({
|
||||
properties: {
|
||||
enforceBuildableLibDependency: { type: 'boolean' },
|
||||
allowCircularSelfDependency: { type: 'boolean' },
|
||||
banTransitiveDependencies: { type: 'boolean' },
|
||||
allow: [{ type: 'string' }],
|
||||
depConstraints: [
|
||||
{
|
||||
@ -98,6 +101,7 @@ export default createESLintRule<Options, MessageIds>({
|
||||
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`,
|
||||
},
|
||||
},
|
||||
defaultOptions: [
|
||||
@ -106,6 +110,7 @@ export default createESLintRule<Options, MessageIds>({
|
||||
depConstraints: [],
|
||||
enforceBuildableLibDependency: false,
|
||||
allowCircularSelfDependency: false,
|
||||
banTransitiveDependencies: false,
|
||||
},
|
||||
],
|
||||
create(
|
||||
@ -116,6 +121,7 @@ export default createESLintRule<Options, MessageIds>({
|
||||
depConstraints,
|
||||
enforceBuildableLibDependency,
|
||||
allowCircularSelfDependency,
|
||||
banTransitiveDependencies,
|
||||
},
|
||||
]
|
||||
) {
|
||||
@ -238,6 +244,12 @@ export default createESLintRule<Options, MessageIds>({
|
||||
|
||||
// project => npm package
|
||||
if (targetProject.type === 'npm') {
|
||||
if (banTransitiveDependencies && !isDirectDependency(targetProject)) {
|
||||
context.report({
|
||||
node,
|
||||
messageId: 'noTransitiveDependencies',
|
||||
});
|
||||
}
|
||||
const constraint = hasBannedImport(
|
||||
sourceProject,
|
||||
targetProject,
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import * as path from 'path';
|
||||
import { FileData } from '../core/file-utils';
|
||||
import { FileData, readFileIfExisting } from '../core/file-utils';
|
||||
import {
|
||||
ProjectGraph,
|
||||
ProjectGraphDependency,
|
||||
@ -7,8 +7,12 @@ import {
|
||||
ProjectGraphProjectNode,
|
||||
normalizePath,
|
||||
DependencyType,
|
||||
parseJson,
|
||||
ProjectGraphExternalNode,
|
||||
} from '@nrwl/devkit';
|
||||
import { TargetProjectLocator } from '../core/target-project-locator';
|
||||
import { join } from 'path';
|
||||
import { appRootPath } from './app-root';
|
||||
|
||||
export type MappedProjectGraphNode<T = any> = ProjectGraphProjectNode<T> & {
|
||||
data: {
|
||||
@ -193,6 +197,27 @@ export function hasBannedImport(
|
||||
);
|
||||
}
|
||||
|
||||
export function isDirectDependency(target: ProjectGraphExternalNode): boolean {
|
||||
const fileName = 'package.json';
|
||||
const content = readFileIfExisting(join(appRootPath, fileName));
|
||||
if (content) {
|
||||
const { dependencies, devDependencies, peerDependencies } =
|
||||
parseJson(content);
|
||||
if (dependencies && dependencies[target.data.packageName]) {
|
||||
return true;
|
||||
}
|
||||
if (peerDependencies && peerDependencies[target.data.packageName]) {
|
||||
return true;
|
||||
}
|
||||
if (devDependencies && devDependencies[target.data.packageName]) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps import with wildcards to regex pattern
|
||||
* @param importDefinition
|
||||
|
||||
@ -28,6 +28,7 @@
|
||||
"@nrwl/devkit/testing": ["./packages/devkit/testing"],
|
||||
"@nrwl/e2e/cli": ["./e2e/cli"],
|
||||
"@nrwl/e2e/utils": ["./e2e/utils"],
|
||||
"@nrwl/eslint-plugin-nx": ["./packages/eslint-plugin-nx/src"],
|
||||
"@nrwl/express": ["./packages/express"],
|
||||
"@nrwl/gatsby": ["./packages/gatsby"],
|
||||
"@nrwl/jest": ["./packages/jest"],
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user