Warning 1: if elements have the same name, show a warning.
Warning 2: if a goal/triggerNode has no/empty plan, show a warning.
Warning 3: if (non-phase) elements start with or are a number,
show a warning.
251 lines
7.1 KiB
TypeScript
251 lines
7.1 KiB
TypeScript
// This program has been developed by students from the bachelor Computer Science at Utrecht
|
|
// University within the Software Project course.
|
|
// © Copyright Utrecht University (Department of Information and Computing Sciences)
|
|
/* contains all logic for the VisProgEditor warning system
|
|
*
|
|
* Missing but desirable features:
|
|
* - Warning filtering:
|
|
* - if there is no completely connected chain of startNode-[PhaseNodes]-EndNode
|
|
* then hide any startNode, phaseNode, or endNode specific warnings
|
|
*/
|
|
import useFlowStore from "../VisProgStores.tsx";
|
|
import type {FlowState} from "../VisProgTypes.tsx";
|
|
|
|
// --| Type definitions |--
|
|
|
|
export type WarningId = NodeId | "GLOBAL_WARNINGS";
|
|
export type NodeId = string;
|
|
|
|
|
|
export type WarningType =
|
|
| 'MISSING_INPUT'
|
|
| 'MISSING_OUTPUT'
|
|
| 'PLAN_IS_UNDEFINED'
|
|
| 'INCOMPLETE_PROGRAM'
|
|
| 'NOT_CONNECTED_TO_PROGRAM'
|
|
| 'ELEMENT_STARTS_WITH_NUMBER' //(non-phase)elements are not allowed to be or start with a number
|
|
| 'DUPLICATE_ELEMENT_NAME' // elements are not allowed to have the same name as another element
|
|
| string
|
|
|
|
export type WarningSeverity =
|
|
| 'INFO' // Acceptable, but important to be aware of
|
|
| 'WARNING' // Acceptable, but probably undesirable behavior
|
|
| 'ERROR' // Prevents running program, should be fixed before running program is allowed
|
|
|
|
/**
|
|
* warning scope, include a handleId if the warning is handle specific
|
|
*/
|
|
export type WarningScope = {
|
|
id: string;
|
|
handleId?: string;
|
|
}
|
|
|
|
export type EditorWarning = {
|
|
scope: WarningScope;
|
|
type: WarningType;
|
|
severity: WarningSeverity;
|
|
description: string;
|
|
};
|
|
|
|
/**
|
|
* a scoped WarningKey,
|
|
* the handleId scoping is only needed for handle specific errors
|
|
*
|
|
* "`WarningType`:`handleId`"
|
|
*/
|
|
export type WarningKey = string; // for warnings that can occur on a per-handle basis
|
|
|
|
/**
|
|
* a composite key used in the severityIndex
|
|
*
|
|
* "`WarningId`|`WarningKey`"
|
|
*/
|
|
export type CompositeWarningKey = string;
|
|
|
|
export type WarningRegistry = Map<WarningId , Map<WarningKey, EditorWarning>>;
|
|
export type SeverityIndex = Map<WarningSeverity, Set<CompositeWarningKey>>;
|
|
|
|
type ZustandSet = (partial: Partial<FlowState> | ((state: FlowState) => Partial<FlowState>)) => void;
|
|
type ZustandGet = () => FlowState;
|
|
|
|
export type EditorWarningRegistry = {
|
|
/**
|
|
* stores all editor warnings
|
|
*/
|
|
editorWarningRegistry: WarningRegistry;
|
|
/**
|
|
* index of warnings by severity
|
|
*/
|
|
severityIndex: SeverityIndex;
|
|
|
|
/**
|
|
* gets all warnings and returns them as a list of warnings
|
|
* @returns {EditorWarning[]}
|
|
*/
|
|
getWarnings: () => EditorWarning[];
|
|
|
|
/**
|
|
* gets all warnings with the current severity
|
|
* @param {WarningSeverity} warningSeverity
|
|
* @returns {EditorWarning[]}
|
|
*/
|
|
getWarningsBySeverity: (warningSeverity: WarningSeverity) => EditorWarning[];
|
|
|
|
/**
|
|
* checks if there are no warnings of breaking severity
|
|
* @returns {boolean}
|
|
*/
|
|
isProgramValid: () => boolean;
|
|
|
|
/**
|
|
* registers a warning to the warningRegistry and the SeverityIndex
|
|
* @param {EditorWarning} warning
|
|
*/
|
|
registerWarning: (warning: EditorWarning) => void;
|
|
|
|
/**
|
|
* unregisters a warning from the warningRegistry and the SeverityIndex
|
|
* @param {EditorWarning} warning
|
|
*/
|
|
unregisterWarning: (id: WarningId, warningKey: WarningKey) => void
|
|
|
|
/**
|
|
* unregisters warnings from the warningRegistry and the SeverityIndex
|
|
* @param {WarningId} warning
|
|
*/
|
|
unregisterWarningsForId: (id: WarningId) => void;
|
|
}
|
|
|
|
// --| implemented logic |--
|
|
|
|
/**
|
|
* the id to use for global editor warnings
|
|
* @type {string}
|
|
*/
|
|
export const globalWarning = "GLOBAL_WARNINGS";
|
|
|
|
export function editorWarningRegistry(get: ZustandGet, set: ZustandSet) : EditorWarningRegistry { return {
|
|
editorWarningRegistry: new Map<NodeId, Map<WarningKey, EditorWarning>>(),
|
|
severityIndex: new Map([
|
|
['INFO', new Set<CompositeWarningKey>()],
|
|
['WARNING', new Set<CompositeWarningKey>()],
|
|
['ERROR', new Set<CompositeWarningKey>()],
|
|
]),
|
|
|
|
getWarningsBySeverity: (warningSeverity) => {
|
|
const wRegistry = new Map([...get().editorWarningRegistry].map(([k, v]) => [k, new Map(v)]));
|
|
const sIndex = new Map(get().severityIndex);
|
|
const warningKeys = sIndex.get(warningSeverity);
|
|
const warnings: EditorWarning[] = [];
|
|
|
|
warningKeys?.forEach(
|
|
(compositeKey) => {
|
|
const [id, warningKey] = compositeKey.split('|');
|
|
const warning = wRegistry.get(id)?.get(warningKey);
|
|
|
|
if (warning) {
|
|
warnings.push(warning);
|
|
}
|
|
}
|
|
)
|
|
|
|
return warnings;
|
|
},
|
|
|
|
isProgramValid: () => {
|
|
const sIndex = get().severityIndex;
|
|
return (sIndex.get("ERROR")!.size === 0);
|
|
},
|
|
|
|
getWarnings: () => Array.from(get().editorWarningRegistry.values())
|
|
.flatMap(innerMap => Array.from(innerMap.values())),
|
|
|
|
|
|
registerWarning: (warning) => {
|
|
const { scope: {id, handleId}, type, severity } = warning;
|
|
const warningKey = handleId ? `${type}:${handleId}` : type;
|
|
const compositeKey = `${id}|${warningKey}`;
|
|
const wRegistry = new Map([...get().editorWarningRegistry].map(([k, v]) => [k, new Map(v)]));
|
|
const sIndex = new Map(get().severityIndex);
|
|
// add to warning registry
|
|
if (!wRegistry.has(id)) {
|
|
wRegistry.set(id, new Map());
|
|
}
|
|
wRegistry.get(id)!.set(warningKey, warning);
|
|
|
|
|
|
// add to severityIndex
|
|
if (!sIndex.get(severity)!.has(compositeKey)) {
|
|
sIndex.get(severity)!.add(compositeKey);
|
|
}
|
|
|
|
set({
|
|
editorWarningRegistry: wRegistry,
|
|
severityIndex: sIndex
|
|
})
|
|
},
|
|
|
|
unregisterWarning: (id, warningKey) => {
|
|
const wRegistry = new Map([...get().editorWarningRegistry].map(([k, v]) => [k, new Map(v)]));
|
|
const sIndex = new Map(get().severityIndex);
|
|
// verify if the warning was created already
|
|
const warning = wRegistry.get(id)?.get(warningKey);
|
|
if (!warning) return;
|
|
|
|
// remove from warning registry
|
|
wRegistry.get(id)!.delete(warningKey);
|
|
|
|
|
|
// remove from severityIndex
|
|
sIndex.get(warning.severity)!.delete(`${id}|${warningKey}`);
|
|
|
|
set({
|
|
editorWarningRegistry: wRegistry,
|
|
severityIndex: sIndex
|
|
})
|
|
},
|
|
|
|
unregisterWarningsForId: (id) => {
|
|
const wRegistry = new Map([...get().editorWarningRegistry].map(([k, v]) => [k, new Map(v)]));
|
|
const sIndex = new Map(get().severityIndex);
|
|
|
|
const nodeWarnings = wRegistry.get(id);
|
|
|
|
// remove from severity index
|
|
if (nodeWarnings) {
|
|
nodeWarnings.forEach((warning) => {
|
|
const warningKey = warning.scope.handleId
|
|
? `${warning.type}:${warning.scope.handleId}`
|
|
: warning.type;
|
|
sIndex.get(warning.severity)?.delete(`${id}|${warningKey}`);
|
|
});
|
|
}
|
|
|
|
// remove from warning registry
|
|
wRegistry.delete(id);
|
|
|
|
set({
|
|
editorWarningRegistry: wRegistry,
|
|
severityIndex: sIndex
|
|
})
|
|
},
|
|
}}
|
|
|
|
|
|
|
|
/**
|
|
* returns a summary of the warningRegistry
|
|
* @returns {{info: number, warning: number, error: number, isValid: boolean}}
|
|
*/
|
|
export function warningSummary() {
|
|
const {severityIndex, isProgramValid} = useFlowStore.getState();
|
|
return {
|
|
info: severityIndex.get('INFO')!.size,
|
|
warning: severityIndex.get('WARNING')!.size,
|
|
error: severityIndex.get('ERROR')!.size,
|
|
isValid: isProgramValid(),
|
|
};
|
|
}
|
|
|
|
|