arch-unit-ts
Version:
237 lines • 10.6 kB
JavaScript
"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