UNPKG

@eclipse-emfcloud/model-validation

Version:

Generic model validation framework.

157 lines 6.23 kB
"use strict"; // ***************************************************************************** // 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