UNPKG

@nrwl/workspace

Version:

Smart, Fast and Extensible Build System.

178 lines • 10 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Rule = void 0; const Lint = require("tslint"); const devkit_1 = require("@nrwl/devkit"); const runtime_lint_utils_1 = require("../utils/runtime-lint-utils"); const path_1 = require("path"); const target_project_locator_1 = require("nx/src/utils/target-project-locator"); const graph_utils_1 = require("../utils/graph-utils"); const fileutils_1 = require("../utilities/fileutils"); const devkit_2 = require("@nrwl/devkit"); class Rule extends Lint.Rules.AbstractRule { constructor(options, projectPath, projectGraph, targetProjectLocator, workspaceLayout) { super(options); this.projectPath = projectPath; this.projectGraph = projectGraph; this.targetProjectLocator = targetProjectLocator; this.workspaceLayout = workspaceLayout; if (!projectPath) { this.projectPath = (0, path_1.normalize)(devkit_1.workspaceRoot); if (!global.projectGraph) { const nxJson = (0, devkit_2.readNxJson)(); global.workspaceLayout = nxJson.workspaceLayout; /** * Because there are a number of ways in which the rule can be invoked (executor vs TSLint CLI vs IDE Plugin), * the ProjectGraph may or may not exist by the time the lint rule is invoked for the first time. */ try { global.projectGraph = (0, runtime_lint_utils_1.mapProjectGraphFiles)((0, devkit_2.readCachedProjectGraph)()); } catch (_a) { } } this.workspaceLayout = global.workspaceLayout; this.projectGraph = global.projectGraph; if (!global.targetProjectLocator && this.projectGraph) { global.targetProjectLocator = new target_project_locator_1.TargetProjectLocator(this.projectGraph.nodes, this.projectGraph.externalNodes); } this.targetProjectLocator = global.targetProjectLocator; } } apply(sourceFile) { if (!this.projectGraph) return []; return this.applyWithWalker(new EnforceModuleBoundariesWalker(sourceFile, this.getOptions(), this.projectPath, this.projectGraph, this.targetProjectLocator, this.workspaceLayout)); } } exports.Rule = Rule; class EnforceModuleBoundariesWalker extends Lint.RuleWalker { constructor(sourceFile, options, projectPath, projectGraph, targetProjectLocator, workspaceLayout) { super(sourceFile, options); this.projectPath = projectPath; this.projectGraph = projectGraph; this.targetProjectLocator = targetProjectLocator; this.workspaceLayout = workspaceLayout; this.enforceBuildableLibDependency = false; // for backwards compat this.allowCircularSelfDependency = false; this.allow = Array.isArray(this.getOptions()[0].allow) ? this.getOptions()[0].allow.map((a) => `${a}`) : []; this.depConstraints = Array.isArray(this.getOptions()[0].depConstraints) ? this.getOptions()[0].depConstraints : []; this.enforceBuildableLibDependency = this.getOptions()[0].enforceBuildableLibDependency === true; this.allowCircularSelfDependency = this.getOptions()[0].allowCircularSelfDependency === true; } visitImportDeclaration(node) { const imp = node.moduleSpecifier .getText() .substring(1, node.moduleSpecifier.getText().length - 1); // whitelisted import if (this.allow.some((a) => (0, runtime_lint_utils_1.matchImportWithWildcard)(a, imp))) { super.visitImportDeclaration(node); return; } const filePath = (0, runtime_lint_utils_1.getSourceFilePath)(this.getSourceFile().fileName, this.projectPath); const sourceProject = (0, runtime_lint_utils_1.findSourceProject)(this.projectGraph, filePath); if (!sourceProject) { super.visitImportDeclaration(node); return; } let targetProject = (0, runtime_lint_utils_1.getTargetProjectBasedOnRelativeImport)(imp, this.projectPath, this.projectGraph, filePath); // check for relative and absolute imports if ((targetProject && sourceProject !== targetProject) || (0, runtime_lint_utils_1.isAbsoluteImportIntoAnotherProject)(imp, this.workspaceLayout)) { this.addFailureAt(node.getStart(), node.getWidth(), `libraries cannot be imported by a relative or absolute path, and must begin with a npm scope`); return; } targetProject = targetProject || (0, runtime_lint_utils_1.findProjectUsingImport)(this.projectGraph, this.targetProjectLocator, filePath, imp); // If source or target are not part of an nx workspace, return. if (!targetProject || targetProject.type === 'npm') { super.visitImportDeclaration(node); return; } // same project => allow if (sourceProject === targetProject) { if (!this.allowCircularSelfDependency && !(0, fileutils_1.isRelativePath)(imp) && !(0, runtime_lint_utils_1.isAngularSecondaryEntrypoint)(this.targetProjectLocator, imp)) { const error = `Projects should use relative imports to import from other files within the same project. Use "./path/to/file" instead of import from "${imp}"`; this.addFailureAt(node.getStart(), node.getWidth(), error); } else { super.visitImportDeclaration(node); } return; } // check for circular dependency const circularPath = (0, graph_utils_1.checkCircularPath)(this.projectGraph, sourceProject, targetProject); if (circularPath.length !== 0) { const path = circularPath.reduce((acc, v) => `${acc} -> ${v.name}`, sourceProject.name); const circularFilePath = (0, graph_utils_1.findFilesInCircularPath)(circularPath); const filePaths = circularFilePath .map((files) => (files.length > 1 ? `[${files.join(',')}]` : files[0])) .reduce((acc, files) => `${acc}\n- ${files}`, `- ${filePath}`); const error = `Circular dependency between "${sourceProject.name}" and "${targetProject.name}" detected: ${path}\n\nCircular file chain:\n${filePaths}`; this.addFailureAt(node.getStart(), node.getWidth(), error); return; } // cannot import apps if (targetProject.type === 'app') { this.addFailureAt(node.getStart(), node.getWidth(), 'imports of apps are forbidden'); return; } // cannot import e2e projects if (targetProject.type === 'e2e') { this.addFailureAt(node.getStart(), node.getWidth(), 'imports of e2e projects are forbidden'); return; } // buildable-lib is not allowed to import non-buildable-lib if (this.enforceBuildableLibDependency === true && sourceProject.type === 'lib' && targetProject.type === 'lib') { if ((0, runtime_lint_utils_1.hasBuildExecutor)(sourceProject) && !(0, runtime_lint_utils_1.hasBuildExecutor)(targetProject)) { this.addFailureAt(node.getStart(), node.getWidth(), 'buildable libraries cannot import non-buildable libraries'); return; } } // if we import a library using loadChildren, we should not import it using es6imports if ((0, runtime_lint_utils_1.onlyLoadChildren)(this.projectGraph, sourceProject.name, targetProject.name, [])) { this.addFailureAt(node.getStart(), node.getWidth(), 'imports of lazy-loaded libraries are forbidden'); return; } // check that dependency constraints are satisfied if (this.depConstraints.length > 0) { const constraints = (0, runtime_lint_utils_1.findConstraintsFor)(this.depConstraints, sourceProject); // when no constrains found => error. Force the user to provision them. if (constraints.length === 0) { this.addFailureAt(node.getStart(), node.getWidth(), `A project without tags matching at least one constraint cannot depend on any libraries`); return; } for (let constraint of constraints) { if (constraint.onlyDependOnLibsWithTags && (0, runtime_lint_utils_1.hasNoneOfTheseTags)(targetProject, constraint.onlyDependOnLibsWithTags)) { const error = `A project tagged with "${constraint.sourceTag}" can only depend on libs tagged with ${(0, runtime_lint_utils_1.stringifyTags)(constraint.onlyDependOnLibsWithTags)}`; this.addFailureAt(node.getStart(), node.getWidth(), error); return; } if (constraint.notDependOnLibsWithTags && constraint.notDependOnLibsWithTags.length) { const projectPaths = (0, runtime_lint_utils_1.findDependenciesWithTags)(targetProject, constraint.notDependOnLibsWithTags, this.projectGraph); if (projectPaths.length > 0) { const error = `A project tagged with "${constraint.sourceTag}" can not depend on libs tagged with ${(0, runtime_lint_utils_1.stringifyTags)(constraint.notDependOnLibsWithTags)}\n\nViolation detected in:\n${projectPaths .map((projectPath) => `- ${projectPath.map((p) => p.name).join(' -> ')}`) .join('\n')}`; this.addFailureAt(node.getStart(), node.getWidth(), error); return; } } } } super.visitImportDeclaration(node); } } //# sourceMappingURL=nxEnforceModuleBoundariesRule.js.map