UNPKG

arch-unit-ts

Version:
237 lines 10.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Architectures = void 0; const Optional_1 = require("../../common/domain/Optional"); const Assert_1 = require("../../error/domain/Assert"); const main_1 = require("../../main"); const DescribedPredicate_1 = require("../base/DescribedPredicate"); const Formatters_1 = require("../core/domain/Formatters"); const TypeScriptClass_1 = require("../core/domain/TypeScriptClass"); const ArchCondition_1 = require("../lang/ArchCondition"); const ArchConditions_1 = require("../lang/conditions/ArchConditions"); const EvaluationResult_1 = require("../lang/EvaluationResult"); const SimpleConditionEvent_1 = require("../lang/SimpleConditionEvent"); const SimpleConditionEvents_1 = require("../lang/SimpleConditionEvents"); class Architectures { static layeredArchitecture() { return DependencySettings.default(); } } exports.Architectures = Architectures; class DependencySettings { description; ignoreExcludedDependencies; constructor(description, ignoreExludedDependencies) { this.description = Optional_1.Optional.ofUndefinable(description); this.ignoreExcludedDependencies = ignoreExludedDependencies; } static default() { return new DependencySettings(undefined, undefined); } consideringOnlyDependenciesInAnyPackage(...packageIdentifiers) { return LayeredArchitecture.fromDependencySettings(this.setToConsideringOnlyDependenciesInAnyPackage(...packageIdentifiers)); } getDescription() { return this.description.orElse(''); } setToConsideringOnlyDependenciesInAnyPackage(...packageIdentifiers) { const outsideOfRelevantPackage = TypeScriptClass_1.TypeScriptClass.resideOutsideOfPackages(...packageIdentifiers); return new DependencySettings(`considering only dependencies in any package [${Formatters_1.Formatters.joinSingleQuoted(...packageIdentifiers)}]`, (predicate) => predicate.or(this.originOrTargetIs(outsideOfRelevantPackage))); } originOrTargetIs(predicate) { return TypeScriptClass_1.Functions.GET_ORIGIN_CLASS.is(predicate).or(TypeScriptClass_1.Functions.GET_TARGET_CLASS.is(predicate)); } } class LayeredArchitecture { layerDefinitions; dependencySpecifications; dependencySettings; overriddenDescription; optionalLayers; constructor(layerDefinitions, dependencySpecifications, dependencySettings, overriddenDescription, optionalLayers) { this.layerDefinitions = layerDefinitions; this.dependencySpecifications = dependencySpecifications; this.dependencySettings = dependencySettings; this.overriddenDescription = overriddenDescription; this.optionalLayers = optionalLayers; } static fromDependencySettings(dependencySettings) { return new LayeredArchitecture(new LayerDefinitions(), new Set(), dependencySettings, Optional_1.Optional.empty(), false); } withOptionalLayers(optionalLayers) { return new LayeredArchitecture(this.layerDefinitions, this.dependencySpecifications, this.dependencySettings, this.overriddenDescription, optionalLayers); } because(reason) { this.overriddenDescription = Optional_1.Optional.ofUndefinable(`${this.getDescription()} because ${reason}`); return this; } check(classes) { const evaluationResult = this.evaluate(classes); if (evaluationResult.hasErrors()) { throw new Error(`Architecture violation : Rule ${this.getDescription()}.\nErrors : ${evaluationResult.violationReport()}`); } } evaluate(classes) { const conditionEvents = new SimpleConditionEvents_1.SimpleConditionEvents(); const result = new EvaluationResult_1.EvaluationResult(conditionEvents); this.checkEmptyLayers(classes, result); this.dependencySpecifications.forEach(specification => result.add(this.evaluateDependenciesShouldBeSatisfied(classes, specification))); return result; } checkEmptyLayers(classes, result) { if (!this.optionalLayers) { for (const layerDefinition of this.layerDefinitions) { if (!layerDefinition.isOptional()) { result.add(this.evaluateLayersShouldNotBeEmpty(classes, layerDefinition)); } } } } evaluateLayersShouldNotBeEmpty(typeScriptClasses, layerDefinition) { return (0, main_1.classes)() .thatWithPredicate(this.layerDefinitions.containsPredicateFor(layerDefinition.name)) .shouldWithConjunction(this.notBeEmptyFor(layerDefinition)) .evaluate(typeScriptClasses); } notBeEmptyFor(layerDefinition) { return new LayerShouldNotBeEmptyCondition(layerDefinition); } evaluateDependenciesShouldBeSatisfied(typeScriptClasses, specification) { const satisfyLayerDependenciesCondition = ArchConditions_1.ArchConditions.onlyHaveDependentsWhere(this.originMatchesIfDependencyIsRelevant(specification.layerName, specification.allowedLayers)); return (0, main_1.classes)() .thatWithPredicate(this.layerDefinitions.containsPredicateFor(specification.layerName)) .shouldWithConjunction(satisfyLayerDependenciesCondition) .evaluate(typeScriptClasses); } originMatchesIfDependencyIsRelevant(ownLayer, allowedAccessors) { const originPackageMatches = TypeScriptClass_1.Predicates.dependencyOrigin(this.layerDefinitions.containsPredicateForLayers(Array.from(allowedAccessors))).or(TypeScriptClass_1.Predicates.dependencyOrigin(this.layerDefinitions.containsPredicateFor(ownLayer))); return this.dependencySettings.ignoreExcludedDependencies.apply(this, [originPackageMatches]); } getDescription() { if (this.overriddenDescription.isPresent()) { return this.overriddenDescription.orElseThrow(); } const prefix = 'Layered architecture ' + this.dependencySettings.getDescription(); const lines = [prefix + ', consisting of' + (this.optionalLayers ? ' (optional)' : '')]; for (const layerDefinition of this.layerDefinitions) { lines.push(layerDefinition.toString()); } this.dependencySpecifications.forEach(dependencySpecification => lines.push(dependencySpecification.toString())); return lines.join('\n'); } layer(name, ...packageIdentifiers) { this.layerDefinitions.add(new LayerDefinition(name, false, TypeScriptClass_1.TypeScriptClass.resideInAnyPackage(packageIdentifiers))); return this; } optionalLayer(name, ...packageIdentifiers) { this.layerDefinitions.add(new LayerDefinition(name, true, TypeScriptClass_1.TypeScriptClass.resideInAnyPackage(packageIdentifiers))); return this; } whereLayer(name) { this.checkLayerNamesExist(name); return new LayerDependencySpecification(name, this); } checkLayerNamesExist(...layerNames) { layerNames.forEach(layerName => { if (!this.layerDefinitions.containLayer(layerName)) { throw new Error(`There is no layer named ${layerName}`); } }); } addDependencySpecification(dependencySpecification) { this.dependencySpecifications.add(dependencySpecification); return this; } } class LayerShouldNotBeEmptyCondition extends ArchCondition_1.ArchCondition { layerDefinition; empty = true; constructor(layerDefinition) { super('not be empty'); this.layerDefinition = layerDefinition; } check() { this.empty = false; } finish(events) { if (this.empty) { events.add(SimpleConditionEvent_1.SimpleConditionEvent.violated(`Layer '${this.layerDefinition.name}' is empty`)); } } } class LayerDefinition { name; optional; containsPredicate; constructor(name, optional, containsPredicate) { Assert_1.Assert.notNullOrUndefined('name', name); Assert_1.Assert.notNullOrUndefined('optional', optional); Assert_1.Assert.notNullOrUndefined('containsPredicate', containsPredicate); this.name = name; this.optional = optional; this.containsPredicate = containsPredicate; } toString() { return `${this.optional ? 'optional ' : ''}layer '${this.name}' (${this.containsPredicate.description})`; } isOptional() { return this.optional; } } class LayerDefinitions { layerDefinitions = new Map(); add(definition) { this.layerDefinitions.set(definition.name, definition); } [Symbol.iterator]() { return this.layerDefinitions.values()[Symbol.iterator](); } containLayer(layerName) { return this.layerDefinitions.has(layerName); } containsPredicateFor(layerName) { return this.containsPredicateForLayers([layerName]); } containsPredicateForLayers(layerNames) { let result = DescribedPredicate_1.DescribedPredicate.alwaysFalse(); this.get(layerNames).forEach(definition => { result = result.or(definition.containsPredicate); }); return result; } get(layerNames) { return layerNames.map(layerName => this.layerDefinitions.get(layerName)); } } class LayerDependencySpecification { layerName; allowedLayers = new Set(); layeredArchitecture; descriptionSuffix; constructor(layerName, layeredArchitecture) { this.layerName = layerName; this.layeredArchitecture = layeredArchitecture; } toString() { return `where layer '${this.layerName}' ${this.descriptionSuffix}`; } mayOnlyBeAccessedByLayers(...layerNames) { return this.restrictLayers(`may only be accessed by layers [${Formatters_1.Formatters.joinSingleQuoted(...layerNames)}]`, ...layerNames); } restrictLayers(description, ...layerNames) { Assert_1.Assert.notNullOrUndefined('layerNames', layerNames); Assert_1.Assert.notEmpty('layerNames', layerNames); layerNames.forEach(layerName => this.allowedLayers.add(layerName)); this.descriptionSuffix = description; return this.layeredArchitecture.addDependencySpecification(this); } mayNotBeAccessedByAnyLayer() { return this.denyLayerAccess('may not be accessed by any layer'); } denyLayerAccess(description) { this.allowedLayers.clear(); this.descriptionSuffix = description; return this.layeredArchitecture.addDependencySpecification(this); } } //# sourceMappingURL=Architectures.js.map