/******************************************************************************** * Copyright (c) 2021-2022 CS GROUP - France and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at * https://www.eclipse.org/legal/epl-2.0, or the MIT License which is * available at https://opensource.org/licenses/MIT. * * SPDX-License-Identifier: EPL-2.0 OR MIT *******************************************************************************/ import { AnyObject, isArray, isNumber, isString } from '../utils/type-util'; /** The bit mask value for okay severity */ export const OK = 0x0; /** The bit mask value for informational message severity */ export const INFO = 0x1; /** The bit mask value for warning message severity */ export const WARNING = 0x2; /** The bit mask value for error message severity */ export const ERROR = 0x4; /** The bit mask value for canceled diagnosis severity */ export const CANCEL = 0x8; /** * Information about the result of a model validation. * @see org.eclipse.emf.common.util.Diagnostic */ export interface Diagnostic { /** indicator of the severity of the problem */ severity: number; /** message describing the situation */ message: string; /** unique identifier of the source */ source: string; /** source-specific identity code */ code: number; /** the relevant low-level exception */ exception?: Error | any; /** * The arbitrary associated list of data. * The first element is typically the object that is the primary source of the problem; * the second element is typically some object describing the problematic feature or aspect of the primary source, * and the remaining elements are additional objects associated with or describing the problem. */ data: any[]; /** list of child Diagnostic */ children: Diagnostic[]; /** The id or URI fragment of the validated model element */ id: string; } export namespace Diagnostic { /** * Typeguard function to check wether the given object is of type {@link Diagnostic}. * @param object the object to check * @returns the given object as {@link Diagnostic} or `false` */ export function is(object: unknown): object is Diagnostic { return ( AnyObject.is(object) && isNumber(object, 'severity') && isString(object, 'message') && isString(object, 'source') && isNumber(object, 'code') && isArray(object, 'data') && isArray(object, 'children') && isString(object, 'id') ); } /** * Recompute the severity of a composed diagnostic by looking at its children. * Contrary to the org.eclipse.emf.common.util.BasicDiagnostic.recomputeSeverity() implementation, * we combine the severity values and do not just keep the highest number value. * This means the severity number will keep trace of all non-OK severities found in children. * If you need a simple severity value, just use getSeverityLabel method afterward. * @param diagnostic diagnostic to analyze * @returns the updated severity value */ export function recomputeSeverity(diagnostic: Diagnostic): number { if (diagnostic.children.length > 0) { // reset this diagnostic's severity diagnostic.severity = OK; // then use children to update with bit OR diagnostic.children.forEach(child => { const childSeverity = recomputeSeverity(child); diagnostic.severity = diagnostic.severity | childSeverity; }); } // return updated severity return diagnostic.severity; } /** * Get the label severity value of the diagnostic using masks. * @param diagnostic diagnostic to analyze * @returns severity label which the mask matches the severity number */ export function getSeverityLabel(diagnostic: Diagnostic): 'OK' | 'INFO' | 'WARNING' | 'ERROR' | 'CANCEL' { const severityCode = diagnostic.severity; if ((severityCode & CANCEL) > 0) { return 'CANCEL'; } else if ((severityCode & ERROR) > 0) { return 'ERROR'; } else if ((severityCode & WARNING) > 0) { return 'WARNING'; } else if ((severityCode & INFO) > 0) { return 'INFO'; } else { return 'OK'; } } /** * Collect leaf diagnostics, which are children diagnostics without children. * An OK diagnostic without children is not returned. * @param diagnostic diagnostic to analyze * @returns diagnostics without children (eventually including this one or empty) */ export function collectLeaves(diagnostic: Diagnostic): Diagnostic[] { if (diagnostic.children.length > 0) { const leavesByChild = diagnostic.children.map(collectLeaves); return leavesByChild.reduce((accumulator, values) => accumulator.concat(values), []); } else if (diagnostic.severity > 0) { return [diagnostic]; } else { return []; } } /** * Get an OK diagnostic. * * @returns an OK diagnostic */ export function ok(): Diagnostic { return { severity: OK, code: 0, message: 'OK', source: '', data: [], children: [], id: '' }; } /** * Merge any number of `diagnostics` (even zero) into a single result. * Any OK diagnostics are elided and the result may itself just be OK. * * @param diagnostics diagnostics to merge * @returns the merged diagnostic */ export function merge(...diagnostics: Diagnostic[]): Diagnostic { const nonOK = diagnostics.filter(d => d && d.severity > OK); if (nonOK.length === 0) { return ok(); } if (nonOK.length === 1) { return nonOK[0]; } const max = worstOf(diagnostics); return { severity: max.severity, message: `Diagnosis of ${nonOK.length} problems.`, source: max.source, code: max.code, data: [], children: nonOK, id: '/' }; } /** * Find the worst severity in a list of `diagnostics`. * * @param diagnostics an array of diagnostics, possibly empty * @returns the first diagnostic of the great severity amongst the `diagnostics` * or else an OK diagnostic if none */ export function worstOf(diagnostics: Diagnostic[]): Diagnostic { if (!diagnostics || diagnostics.length === 0) { return ok(); } let result = diagnostics[0]; for (const d of diagnostics) { if (d.severity > result.severity) { result = d; } if (d.severity === CANCEL) { break; // Cannot get worse than this } } return result; } }