@eclipse-emfcloud/model-validation
Version:
Generic model validation framework.
157 lines • 6.23 kB
JavaScript
;
// *****************************************************************************
// Copyright (C) 2023-2024 STMicroelectronics.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: MIT License which is
// available at https://opensource.org/licenses/MIT.
//
// SPDX-License-Identifier: EPL-2.0 OR MIT
// *****************************************************************************
Object.defineProperty(exports, "__esModule", { value: true });
exports.merge = exports.ok = void 0;
const severity_1 = require("./severity");
/** A default diagnostic source to assign when none other is available. */
const DEFAULT_SOURCE = '@eclipse-emfcloud/model-validation';
/**
* Obtain a diagnostic of `'ok'` severity indicating that validation found no problem.
*
* @param [source] an optional source for the resulting diagnostic.
* The default indicates this validation framework package
* @return an `'ok'` diagnostic with empty (`''`) path as the path does not really matter in the absence of a problem
*/
const ok = (source = DEFAULT_SOURCE) => ({
severity: 'ok',
message: 'OK.',
source,
path: '',
});
exports.ok = ok;
/**
* Merge zero or more diagnostics into one. All `'ok'` diagnostics
* are elided. As a special case, an empty list of `diagnostics`
* or an array containing only `'ok'` diagnostics merges to an
* {@link ok} diagnostic. As another special case, a single
* input merges to itself.
*
* For any input to the merge that has {@link Diagnostic.children children},
* those children (except any that are OK) are added to the merge result
* in place of the original input, as that parent diagnostic is expected
* to be the result of a previous merge by this same algorithm.
* In consequence, consistent use of this merge function will always
* result in a flat structure of a diagnostic with zero or more children
* that all have themselves no children.
*
* In a merge result that combines multiple non-OK diagnostics from the
* inputs, all of the following conditions hold:
*
* - the {@link Diagnostic.severity severity} of the merge result is the
* {@link severityComparator.max maximum} of the severities of its children
* - the {@link Diagnostic.source source} of the merge result is the
* same as the source of its children if they all have the same source.
* Otherwise, it is the empty string (`''`)
* - the {@link Diagnostic.code code} of the merge result is `undefined`
* - the {@link Diagnostic.path path} of the merge result is the longest
* common prefix of all of the paths of its children, which may be
* their path if they all have the same path, or may be the empty string
* (`''`) denoting the model as a whole if they have no common prefix
* - the {@link Diagnostic.message message} of the merge result is a
* non-localized string indicating how many problems it aggregates
*
* @param diagnostics zero or more diagnostics to merge
* @return a compact merge of the `diagnostics`
*/
const merge = (...diagnostics) => {
const nonOK = diagnostics.filter(notOK);
if (!nonOK || !nonOK.length) {
return (0, exports.ok)();
}
if (nonOK.length === 1 && !hasChildren(nonOK[0])) {
return nonOK[0];
}
const [first] = nonOK.splice(0, 1);
const result = {
severity: first.severity,
source: first.source,
path: first.path,
message: '', // Will replace this later
children: hasChildren(first) ? [...first.children.filter(notOK)] : [first],
};
nonOK.forEach((d) => {
if (d.children !== undefined && d.children.length > 0) {
result.children.push(...d.children.filter(notOK));
}
else {
result.children.push(d);
}
});
// After all the filtering we may now have only one child or even none.
switch (result.children.length) {
case 0:
// Canonically, no problem.
return (0, exports.ok)();
case 1:
// It stands for itself.
return result.children[0];
default:
// Continue, below
break;
}
result.severity = result.children
.map((d) => d.severity)
.reduce(severity_1.severityComparator.max, 'ok');
result.path = result.children
.slice(1)
.map((d) => d.path)
.reduce(longestCommonPrefix, result.children[0].path);
result.source = unique(result.children.map((d) => d.source)) || '';
result.message = `${result.children.length} problems found.`;
return result;
};
exports.merge = merge;
/** A function determining whether a diagnostic is not OK. */
const notOK = (d) => (0, severity_1.severityComparator)(d.severity, 'ok') > 0;
/** A guard determining whether a diagnostic has children. */
const hasChildren = (d) => d.children !== undefined && d.children.length > 0;
/** Compute the longest common prefix of two JSON pointers. */
const longestCommonPrefix = (path1, path2) => {
const segs1 = (path1 || '').split('/');
const segs2 = (path2 || '').split('/');
const count = Math.min(segs1.length, segs2.length);
let prefLength = 0;
for (; prefLength < count; prefLength++) {
if (segs1[prefLength] !== segs2[prefLength]) {
break;
}
}
if (prefLength === 0) {
return '';
}
return segs1.slice(0, prefLength).join('/');
};
/**
* Extract the unique value of the given `items` or else
* `undefined` if they are not unique.
*
* @param items some items
* @returns the unique value, or `undefined` if `items` is empty
*/
const unique = (items) => {
let result;
for (const next of items) {
if (result === undefined) {
result = next;
}
else if (result !== next) {
result = undefined;
break;
}
}
return result;
};
//# sourceMappingURL=diagnostic.js.map