@istar-ts/core 0.11.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -304,6 +304,15 @@ toPistar(extended); // writes the "metamodel" block
304
304
  into a store (`store.load` or `store.replace`) to keep editing.
305
305
  - `pistarType` works as for any extension, so `istar.<Name>` types on disk still read.
306
306
 
307
+ ## Diagnostics
308
+
309
+ `GoalDiagnostic` (`{ elementId, key?, severity, message, source?, range?, code?, data? }`) is the
310
+ data shape for findings about elements and links. Analysers publish them per source to a
311
+ `createDiagnosticsStore()`; `getAll()` returns the union of every source, deduplicated by
312
+ `(elementId, key, message)`. `fromLspDiagnostics` and `fromNodeIdDiagnostics` adapt LSP and MutRoSe
313
+ diagnostics. Diagnostics are never part of the model and never serialized. `@istar-ts/react`
314
+ shows them as badges and in the inspector (see its README).
315
+
307
316
  ## Credits
308
317
 
309
318
  The metamodel, link constraints, element shapes, and file format are derived from
package/dist/index.cjs CHANGED
@@ -2027,12 +2027,183 @@ function validateModelProperties(model, schemas) {
2027
2027
  return issues;
2028
2028
  }
2029
2029
  //#endregion
2030
+ //#region src/diagnostics.ts
2031
+ /** Most severe first. */
2032
+ const DIAGNOSTIC_SEVERITIES = [
2033
+ "error",
2034
+ "warning",
2035
+ "info",
2036
+ "hint"
2037
+ ];
2038
+ const RANK = {
2039
+ error: 0,
2040
+ warning: 1,
2041
+ info: 2,
2042
+ hint: 3
2043
+ };
2044
+ /** Negative when `a` is more severe than `b`. */
2045
+ function compareSeverity(a, b) {
2046
+ return RANK[a] - RANK[b];
2047
+ }
2048
+ /** The most severe severity in a list (what a badge shows), or `undefined` if it is empty. */
2049
+ function worstDiagnosticSeverity(diagnostics) {
2050
+ let worst;
2051
+ for (const d of diagnostics ?? []) {
2052
+ if (worst === void 0 || RANK[d.severity] < RANK[worst]) worst = d.severity;
2053
+ if (worst === "error") break;
2054
+ }
2055
+ return worst;
2056
+ }
2057
+ function identity(d) {
2058
+ return `${d.elementId}\u0000${d.key ?? ""}\u0000${d.message}`;
2059
+ }
2060
+ /**
2061
+ * The union of several lists, in order, without duplicates: diagnostics with the same
2062
+ * `(elementId, key, message)` count once, keeping the first one's position and the most severe
2063
+ * severity among them.
2064
+ */
2065
+ function mergeDiagnostics(lists) {
2066
+ const result = [];
2067
+ const index = /* @__PURE__ */ new Map();
2068
+ for (const list of lists) for (const d of list ?? []) {
2069
+ const id = identity(d);
2070
+ const at = index.get(id);
2071
+ if (at === void 0) {
2072
+ index.set(id, result.length);
2073
+ result.push(d);
2074
+ } else if (RANK[d.severity] < RANK[result[at].severity]) result[at] = {
2075
+ ...result[at],
2076
+ severity: d.severity
2077
+ };
2078
+ }
2079
+ return result;
2080
+ }
2081
+ /** Diagnostics by element (or link) id, each list in the input order. */
2082
+ function groupDiagnostics(diagnostics) {
2083
+ const map = /* @__PURE__ */ new Map();
2084
+ for (const d of diagnostics ?? []) {
2085
+ const list = map.get(d.elementId);
2086
+ if (list) list.push(d);
2087
+ else map.set(d.elementId, [d]);
2088
+ }
2089
+ return map;
2090
+ }
2091
+ const NONE = [];
2092
+ function createDiagnosticsStore() {
2093
+ const bySource = /* @__PURE__ */ new Map();
2094
+ const listeners = /* @__PURE__ */ new Set();
2095
+ let merged = NONE;
2096
+ const changed = () => {
2097
+ merged = bySource.size === 0 ? NONE : mergeDiagnostics(bySource.values());
2098
+ for (const listener of listeners) listener();
2099
+ };
2100
+ return {
2101
+ publish(source, diagnostics) {
2102
+ if (diagnostics.length === 0) {
2103
+ if (bySource.delete(source)) changed();
2104
+ return;
2105
+ }
2106
+ bySource.set(source, diagnostics.map((d) => d.source === void 0 ? {
2107
+ ...d,
2108
+ source
2109
+ } : d));
2110
+ changed();
2111
+ },
2112
+ clear(source) {
2113
+ if (source === void 0) {
2114
+ if (bySource.size === 0) return;
2115
+ bySource.clear();
2116
+ } else if (!bySource.delete(source)) return;
2117
+ changed();
2118
+ },
2119
+ sources: () => [...bySource.keys()],
2120
+ get: (source) => bySource.get(source) ?? NONE,
2121
+ getAll: () => merged,
2122
+ subscribe(listener) {
2123
+ listeners.add(listener);
2124
+ return () => listeners.delete(listener);
2125
+ }
2126
+ };
2127
+ }
2128
+ const LSP_SEVERITIES = [
2129
+ "error",
2130
+ "warning",
2131
+ "info",
2132
+ "hint"
2133
+ ];
2134
+ function anchorOf(data) {
2135
+ if (typeof data !== "object" || data === null) return {};
2136
+ const record = data;
2137
+ const elementId = typeof record.elementId === "string" ? record.elementId : typeof record.nodeId === "string" ? record.nodeId : void 0;
2138
+ return {
2139
+ ...elementId ? { elementId } : {},
2140
+ ...typeof record.key === "string" ? { key: record.key } : {}
2141
+ };
2142
+ }
2143
+ function convertLsp(d, options) {
2144
+ const anchor = anchorOf(d.data);
2145
+ const elementId = anchor.elementId ?? options.elementIdFor?.(d);
2146
+ if (!elementId) return void 0;
2147
+ const base = options.severityNumbering === "vscode" ? 0 : 1;
2148
+ const severity = d.severity === void 0 ? "error" : LSP_SEVERITIES[d.severity - base] ?? "error";
2149
+ const code = typeof d.code === "object" ? d.code.value : d.code;
2150
+ return {
2151
+ elementId,
2152
+ ...anchor.key !== void 0 ? { key: anchor.key } : {},
2153
+ severity,
2154
+ message: typeof d.message === "string" ? d.message : d.message.value,
2155
+ ...d.source !== void 0 ? { source: d.source } : {},
2156
+ ...d.range ? { range: d.range } : {},
2157
+ ...code !== void 0 ? { code: String(code) } : {},
2158
+ ...d.data !== void 0 ? { data: d.data } : {}
2159
+ };
2160
+ }
2161
+ /**
2162
+ * One LSP diagnostic anchored by `data.elementId` (or `data.nodeId`), or `undefined` when it
2163
+ * names no element. For range-anchored diagnostics use `fromLspDiagnostics` with `elementIdFor`.
2164
+ */
2165
+ function fromLspDiagnostic(diagnostic) {
2166
+ return convertLsp(diagnostic, {});
2167
+ }
2168
+ /** LSP diagnostics as goal diagnostics; those that can't be anchored to an element are dropped. */
2169
+ function fromLspDiagnostics(diagnostics, options = {}) {
2170
+ const result = [];
2171
+ for (const d of diagnostics) {
2172
+ const converted = convertLsp(d, options);
2173
+ if (converted) result.push(converted);
2174
+ }
2175
+ return result;
2176
+ }
2177
+ /** One `{ nodeId, severity, message }` diagnostic, or `undefined` without a `nodeId`. */
2178
+ function fromNodeIdDiagnostic(diagnostic) {
2179
+ const { nodeId, severity, ...rest } = diagnostic;
2180
+ if (!nodeId) return void 0;
2181
+ return {
2182
+ ...rest,
2183
+ elementId: nodeId,
2184
+ severity: Object.hasOwn(RANK, severity) ? severity : "info"
2185
+ };
2186
+ }
2187
+ /**
2188
+ * `{ nodeId, severity, message }` diagnostics (MutRoSe) as goal diagnostics, dropping those
2189
+ * without a node: `store.publish('mutrose', fromNodeIdDiagnostics(message.diagnostics))`.
2190
+ */
2191
+ function fromNodeIdDiagnostics(diagnostics) {
2192
+ const result = [];
2193
+ for (const d of diagnostics) {
2194
+ const converted = fromNodeIdDiagnostic(d);
2195
+ if (converted) result.push(converted);
2196
+ }
2197
+ return result;
2198
+ }
2199
+ //#endregion
2030
2200
  exports.ACTOR_KINDS = ACTOR_KINDS;
2031
2201
  exports.ACTOR_LINK_KINDS = ACTOR_LINK_KINDS;
2032
2202
  exports.CONTRIBUTION_LABELS = CONTRIBUTION_LABELS;
2033
2203
  exports.DEFAULT_DIAGRAM = DEFAULT_DIAGRAM;
2034
2204
  exports.DEFAULT_ELEMENT_SIZE = DEFAULT_ELEMENT_SIZE;
2035
2205
  exports.DEPENDENCY_LINK_KINDS = DEPENDENCY_LINK_KINDS;
2206
+ exports.DIAGNOSTIC_SEVERITIES = DIAGNOSTIC_SEVERITIES;
2036
2207
  exports.ELEMENT_KINDS = ELEMENT_KINDS;
2037
2208
  exports.FILE_METAMODEL_KEY = FILE_METAMODEL_KEY;
2038
2209
  exports.ISTAR_2_0 = ISTAR_2_0;
@@ -2054,7 +2225,9 @@ exports.addElement = addElement;
2054
2225
  exports.boolean = boolean;
2055
2226
  exports.canLink = canLink;
2056
2227
  exports.childrenOf = childrenOf;
2228
+ exports.compareSeverity = compareSeverity;
2057
2229
  exports.connect = connect;
2230
+ exports.createDiagnosticsStore = createDiagnosticsStore;
2058
2231
  exports.createEmptyModel = createEmptyModel;
2059
2232
  exports.createModelStore = createModelStore;
2060
2233
  exports.defaultIdGenerator = defaultIdGenerator;
@@ -2071,7 +2244,12 @@ exports.enumOf = enumOf;
2071
2244
  exports.extendMetamodel = extendMetamodel;
2072
2245
  exports.fileMetamodelExtension = fileMetamodelExtension;
2073
2246
  exports.fileMetamodelOf = fileMetamodelOf;
2247
+ exports.fromLspDiagnostic = fromLspDiagnostic;
2248
+ exports.fromLspDiagnostics = fromLspDiagnostics;
2249
+ exports.fromNodeIdDiagnostic = fromNodeIdDiagnostic;
2250
+ exports.fromNodeIdDiagnostics = fromNodeIdDiagnostics;
2074
2251
  exports.getSourceLayout = getSourceLayout;
2252
+ exports.groupDiagnostics = groupDiagnostics;
2075
2253
  exports.hostMetamodelOf = hostMetamodelOf;
2076
2254
  exports.inheritMetamodel = inheritMetamodel;
2077
2255
  exports.inheritSourceLayout = inheritSourceLayout;
@@ -2091,6 +2269,7 @@ exports.isNodeKindIn = isNodeKindIn;
2091
2269
  exports.isNodeLinkKind = isNodeLinkKind;
2092
2270
  exports.linkKindDefinition = linkKindDefinition;
2093
2271
  exports.linksOf = linksOf;
2272
+ exports.mergeDiagnostics = mergeDiagnostics;
2094
2273
  exports.metamodelOf = metamodelOf;
2095
2274
  exports.moveElement = moveElement;
2096
2275
  exports.nestElement = nestElement;
@@ -2112,5 +2291,6 @@ exports.validateModel = validateModel;
2112
2291
  exports.validateModelProperties = validateModelProperties;
2113
2292
  exports.withFileMetamodel = withFileMetamodel;
2114
2293
  exports.withMetamodel = withMetamodel;
2294
+ exports.worstDiagnosticSeverity = worstDiagnosticSeverity;
2115
2295
 
2116
2296
  //# sourceMappingURL=index.cjs.map