diff --git a/.vscode/launch.json b/.vscode/launch.json index a7f6aedd..68224f5f 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -14,7 +14,7 @@ "--extensionDevelopmentPath=${workspaceFolder}/extension" ], "outFiles": [ - "${workspaceFolder}/extension/pack/*.js" + "${workspaceFolder}/extension/pack/**/*.cjs" ], "sourceMaps": true }, @@ -27,7 +27,7 @@ ], "sourceMaps": true, "outFiles": [ - "${workspaceFolder}/extension/pack/language-server.js" + "${workspaceFolder}/extension/pack/src-language-server/*.cjs" ], "sourceMapPathOverrides": { "meteor://💻app/*": "${workspaceFolder}/*", diff --git a/extension/esbuild.mjs b/extension/esbuild.mjs new file mode 100644 index 00000000..197ddefa --- /dev/null +++ b/extension/esbuild.mjs @@ -0,0 +1,89 @@ +//@ts-check +import * as esbuild from 'esbuild'; + +const options = { + watch: process.argv.includes('--watch'), + minify: process.argv.includes('--minify'), +}; + +const successMessage = options.watch + ? 'Watch build succeeded' + : 'Build succeeded'; + +/** @type {import('esbuild').Plugin[]} */ +const plugins = [ + { + name: 'watch-plugin', + setup(build) { + build.onEnd((result) => { + if (result.errors.length === 0) { + console.log(getTime() + successMessage); + } + }); + }, + }, +]; + +const nodeContext = await esbuild.context({ + entryPoints: [ + 'src/extension.ts', + 'src-language-server/main.ts' + ], + outdir: 'pack', + bundle: true, + target: 'es6', + format: 'cjs', + loader: { '.ts': 'ts' }, + outExtension: { + '.js': '.cjs', + }, + external: ['vscode'], + platform: 'node', + sourcemap: 'inline', + minify: options.minify, + plugins, +}); + +const browserContext = await esbuild.context({ + entryPoints: [ + 'src-webview/main.ts', + 'src-diagram-snippets/main.ts', + 'src-context-table/main.ts', + ], + outdir: 'pack', + bundle: true, + target: 'es6', + loader: { '.ts': 'ts', '.css': 'css' }, + platform: 'browser', + sourcemap: 'inline', + minify: options.minify, + plugins, +}); + +if (options.watch) { + await Promise.all([ + nodeContext.watch(), + browserContext.watch() + ]); +} else { + await Promise.all([ + nodeContext.rebuild(), + browserContext.rebuild() + ]); + nodeContext.dispose(); + browserContext.dispose(); +} + +function getTime() { + const date = new Date(); + return `[${`${padZeroes(date.getHours())}:${padZeroes( + date.getMinutes() + )}:${padZeroes(date.getSeconds())}`}] `; +} + +/** + * @param {number} i + */ +function padZeroes(i) { + return i.toString().padStart(2, '0'); +} diff --git a/extension/package.json b/extension/package.json index cf340cfd..61e6c921 100644 --- a/extension/package.json +++ b/extension/package.json @@ -535,51 +535,56 @@ "onCommand:pasta.getLTLFormula" ], "files": [ - "lib", "extension", - "server", + "src", + "src-context-table", + "src-diagram-snippets", + "src-language-server", "syntaxes", - "webview" + "src-webview", + "pack" ], - "main": "./pack/extension", + "main": "./pack/src/extension.cjs", "dependencies": { - "langium": "^1.2.0", - "langium-sprotty": "^1.2.0", - "sprotty-elk": "^0.13.0", - "vscode-languageserver": "^8.0.2", - "vscode-languageclient": "^8.0.2", - "reflect-metadata": "^0.1.13", + "langium": "^3.0.0", + "langium-sprotty": "^3.0.0", + "sprotty-elk": "^1.2.0", + "vscode-languageserver": "^9.0.1", + "vscode-languageclient": "^9.0.1", + "reflect-metadata": "^0.2.2", "feather-icons": "^4.28.0", - "sprotty-vscode-webview": "^0.5.0", - "@kieler/table-webview": "^0.0.5", + "sprotty-vscode-webview": "^1.0.0", + "@kieler/table-webview": "^0.0.7", "snabbdom": "^3.5.1", "dayjs": "^1.11.8" }, "devDependencies": { - "@types/node": "^12.12.6", - "@types/vscode": "^1.80.0", + "@types/node": "^14.17.3", + "@types/vscode": "^1.50.0", "@types/feather-icons": "^4.7.0", "rimraf": "^3.0.2", - "source-map-loader": "^3.0.0", - "sprotty-vscode": "^0.5.0", - "ts-loader": "^9.4.2", + "source-map-loader": "^4.0.1", + "sprotty-vscode": "^1.0.0", + "ts-loader": "^9.5.1", "typescript": "^4.9.3", - "webpack": "^5.75.0", - "webpack-cli": "^5.0.1", - "css-loader": "^6.7.2", + "webpack": "^5.89.0", + "webpack-cli": "^5.1.4", + "css-loader": "^6.8.1", "file-loader": "^6.2.0", - "langium-cli": "^1.2.1", - "style-loader": "^3.3.1", + "langium-cli": "^3.0.3", + "style-loader": "^3.3.3", "vsce": "^2.15.0", - "ovsx": "^0.6.0" + "ovsx": "^0.6.0", + "esbuild": "^0.21.2" }, + "type": "module", "scripts": { "prepare": "yarn run clean && yarn run langium:generate && yarn run build", "clean": "rimraf pack", "langium:generate": "langium generate", "langium:watch": "langium generate --watch", "lint": "eslint .", - "build": "yarn run langium:generate && webpack --mode development", + "build": "yarn run langium:generate && tsc -b src src-diagram-snippets src-language-server src-webview src-context-table && node esbuild.mjs", "watch": "webpack --watch", "package": "vsce package --yarn -o pasta.vsix", "predistribute": "yarn run package", diff --git a/extension/src-context-table/actions.ts b/extension/src-context-table/actions.ts index 19b69541..d502b9f7 100644 --- a/extension/src-context-table/actions.ts +++ b/extension/src-context-table/actions.ts @@ -16,7 +16,7 @@ * SPDX-License-Identifier: EPL-2.0 */ -import { ContextTableData } from "./utils"; +import { ContextTableData } from "./utils-classes"; interface Action { kind: string; @@ -28,16 +28,13 @@ export interface SendContextTableDataAction extends Action { data: ContextTableData; } - export namespace SendContextTableDataAction { export const KIND = "sendContextTableData"; - export function create( - data: ContextTableData - ): SendContextTableDataAction { + export function create(data: ContextTableData): SendContextTableDataAction { return { kind: SendContextTableDataAction.KIND, - data + data, }; } diff --git a/extension/src-context-table/context-table-logic.ts b/extension/src-context-table/context-table-logic.ts index 87847ae8..3351adf3 100644 --- a/extension/src-context-table/context-table-logic.ts +++ b/extension/src-context-table/context-table-logic.ts @@ -15,7 +15,8 @@ * SPDX-License-Identifier: EPL-2.0 */ -import { ContextCell, ContextTableRule, Type, ContextTableVariable } from "./utils"; +import { ContextCell } from "./utils"; +import { ContextTableRule, ContextTableVariable, Type } from "./utils-classes"; /** /** @@ -27,8 +28,13 @@ import { ContextCell, ContextTableRule, Type, ContextTableVariable } from "./uti * @param selectedType The currently selected control action type. * @returns the used rules, whereby the index determines the column they apply to. */ -export function determineUsedRules(variables: ContextTableVariable[], rules: ContextTableRule[], selectedController: string, - selectedAction: string, selectedType: number): ContextTableRule[][] { +export function determineUsedRules( + variables: ContextTableVariable[], + rules: ContextTableRule[], + selectedController: string, + selectedAction: string, + selectedType: number +): ContextTableRule[][] { // keeps track of the used rules, whereby the index determines the column let usedRules: ContextTableRule[][] = [[], [], [], []]; switch (selectedType) { @@ -46,19 +52,28 @@ export function determineUsedRules(variables: ContextTableVariable[], rules: Con } // determine the used rules rules.forEach(rule => { - // compare control action of the rule with the selected one and + // compare control action of the rule with the selected one and // the context of the rule with the current context - if (rule.controlAction.controller === selectedController && rule.controlAction.action === selectedAction - && checkValues(rule.variables, variables)) { + if ( + rule.controlAction.controller === selectedController && + rule.controlAction.action === selectedAction && + checkValues(rule.variables, variables) + ) { // determine the column for which the rule applies const ruleType = rule.type.toLowerCase(); if (selectedType === Type.NOT_PROVIDED && ruleType === "not-provided") { usedRules[0].push(rule); } else if (selectedType !== Type.NOT_PROVIDED && ruleType === "provided") { usedRules[0].push(rule); - } else if (selectedType !== Type.NOT_PROVIDED && (ruleType === "too-early" || ruleType === "too-late" || ruleType === "wrong-time")) { + } else if ( + selectedType !== Type.NOT_PROVIDED && + (ruleType === "too-early" || ruleType === "too-late" || ruleType === "wrong-time") + ) { usedRules[1].push(rule); - } else if (selectedType !== Type.NOT_PROVIDED && (ruleType === "stopped-too-soon" || ruleType === "applied-too-long")) { + } else if ( + selectedType !== Type.NOT_PROVIDED && + (ruleType === "stopped-too-soon" || ruleType === "applied-too-long") + ) { usedRules[2].push(rule); } else if (selectedType === Type.BOTH && ruleType === "not-provided") { usedRules[3].push(rule); @@ -73,7 +88,7 @@ export function determineUsedRules(variables: ContextTableVariable[], rules: Con * @param results The hazards and rules for the result columns. * @returns The cells for the "Hazardous?"-column. */ -export function createResults(results: { hazards: string[], rules: ContextTableRule[]; }[]): ContextCell[] { +export function createResults(results: { hazards: string[]; rules: ContextTableRule[] }[]): ContextCell[] { const cells: ContextCell[] = []; // keeps track on how many neihbouring columns have no rule applied let noAppliedRuleCounter: number = 0; @@ -95,7 +110,12 @@ export function createResults(results: { hazards: string[], rules: ContextTableR } const ucas = results[hazardColumn].rules.map(rule => rule.id); // add the hazards, defined by the rule, as a cell - cells.push({ cssClass: "result", value: ucas.toString(), colSpan: 1, title: results[hazardColumn].hazards.toString() }); + cells.push({ + cssClass: "result", + value: ucas.toString(), + colSpan: 1, + title: results[hazardColumn].hazards.toString(), + }); } } return cells; diff --git a/extension/src-context-table/main.ts b/extension/src-context-table/main.ts index 0ddef1d0..5ebd2067 100644 --- a/extension/src-context-table/main.ts +++ b/extension/src-context-table/main.ts @@ -15,26 +15,20 @@ * SPDX-License-Identifier: EPL-2.0 */ -import "./css/table.css"; import { Table } from "@kieler/table-webview/lib/table"; +import { VNode } from "snabbdom"; import { SendContextTableDataAction } from "./actions"; +import { createResults, determineUsedRules } from "./context-table-logic"; +import "./css/table.css"; import { createHeaderElement, createHeaderRow, createRow, createTable, createTHead, initContextTable, patch } from "./html"; import { addSelector, addText, ContextCell, - ContextTableControlAction, convertControlActionsToStrings, replaceSelector, - ContextTableRule, - ContextTableSystemVariables, - Type, - ContextTableVariable, - ContextTableVariableValues, - Row, } from "./utils"; -import { VNode } from "snabbdom"; -import { createResults, determineUsedRules } from "./context-table-logic"; +import { ContextTableControlAction, ContextTableRule, ContextTableSystemVariables, ContextTableVariable, ContextTableVariableValues, Row, Type } from './utils-classes'; interface vscode { postMessage(message: any): void; diff --git a/extension/src-context-table/tsconfig.json b/extension/src-context-table/tsconfig.json new file mode 100644 index 00000000..c7458036 --- /dev/null +++ b/extension/src-context-table/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "../pack/src-context-table", + "target": "ES2017", + "lib": [ + "es2017", + "dom" + ], + "module": "commonjs", + "moduleResolution": "node", + "jsx": "react", + "jsxFactory": "jsx" + } +} diff --git a/extension/src-context-table/utils-classes.ts b/extension/src-context-table/utils-classes.ts new file mode 100644 index 00000000..fbb7e4c2 --- /dev/null +++ b/extension/src-context-table/utils-classes.ts @@ -0,0 +1,69 @@ +/* + * KIELER - Kiel Integrated Environment for Layout Eclipse RichClient + * + * http://rtsys.informatik.uni-kiel.de/kieler + * + * Copyright 2022 by + * + Kiel University + * + Department of Computer Science + * + Real-Time and Embedded Systems Group + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + */ + +/** Type for control actions for the context table. */ +export class ContextTableControlAction { + controller: string; + action: string; +} + +/** The variables for each system, needed for the context table. */ +export class ContextTableSystemVariables { + system: string; + variables: ContextTableVariableValues[]; +} + +/** The possible values for a variable in the context table. */ +export class ContextTableVariableValues { + name: string; + values: string[]; +} + +/** An instantation of a variable, needed for the contexts in the context table. */ +export class ContextTableVariable { + name: string; + value: string; +} + +/** A rule for the context table. */ +export class ContextTableRule { + id: string; + controlAction: ContextTableControlAction; + type: string; + variables: ContextTableVariable[]; + hazards: string[]; +} + +/** Data the context table expects from the language server. */ +export class ContextTableData { + rules: ContextTableRule[]; + actions: ContextTableControlAction[]; + systemVariables: ContextTableSystemVariables[]; +} + +/** Types of control actions. */ +export enum Type { + PROVIDED, + NOT_PROVIDED, + BOTH, +} + +/** A row in the context table. */ +export class Row { + variables: ContextTableVariable[]; + results: { hazards: string[]; rules: ContextTableRule[] }[]; +} diff --git a/extension/src-context-table/utils.ts b/extension/src-context-table/utils.ts index dc24cfdd..066fd09a 100644 --- a/extension/src-context-table/utils.ts +++ b/extension/src-context-table/utils.ts @@ -18,53 +18,7 @@ import { Cell } from "@kieler/table-webview/lib/helper"; import { VNode } from "snabbdom"; import { createSelector, createText, patch } from "./html"; - -/** Type for control actions for the context table. */ -export class ContextTableControlAction { - controller: string; - action: string; -} - -/** The variables for each system, needed for the context table. */ -export class ContextTableSystemVariables { - system: string; - variables: ContextTableVariableValues[]; -} - -/** The possible values for a variable in the context table. */ -export class ContextTableVariableValues { - name: string; - values: string[]; -} - -/** An instantation of a variable, needed for the contexts in the context table. */ -export class ContextTableVariable { - name: string; - value: string; -} - -/** A rule for the context table. */ -export class ContextTableRule { - id: string; - controlAction: ContextTableControlAction; - type: string; - variables: ContextTableVariable[]; - hazards: string[]; -} - -/** Data the context table expects from the language server. */ -export class ContextTableData { - rules: ContextTableRule[]; - actions: ContextTableControlAction[]; - systemVariables: ContextTableSystemVariables[]; -} - -/** Types of control actions. */ -export enum Type { - PROVIDED, - NOT_PROVIDED, - BOTH -} +import { ContextTableControlAction } from './utils-classes'; /** A cell in the context table. Can span mutliple columns. */ export class ContextCell extends Cell { @@ -72,12 +26,6 @@ export class ContextCell extends Cell { title?: string; } -/** A row in the context table. */ -export class Row { - variables: ContextTableVariable[]; - results: { hazards: string[], rules: ContextTableRule[]; }[]; -} - /** * Concats the elements of each controlaction in {@code controlactions} with a dot. * @param controlactions A list containing controlactions that should be converted to strings. diff --git a/extension/src-diagram-snippets/tsconfig.json b/extension/src-diagram-snippets/tsconfig.json new file mode 100644 index 00000000..dcb44eee --- /dev/null +++ b/extension/src-diagram-snippets/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "../pack/src-diagram-snippets", + "target": "ES2017", + "lib": [ + "es2017", + "dom" + ], + "module": "commonjs", + "moduleResolution": "node", + "jsx": "react", + } +} diff --git a/extension/src-language-server/diagram-server.ts b/extension/src-language-server/diagram-server.ts index a34c64e6..fd9a5cf2 100644 --- a/extension/src-language-server/diagram-server.ts +++ b/extension/src-language-server/diagram-server.ts @@ -17,14 +17,14 @@ import { Action, DiagramServices, JsonMap, RequestAction, RequestModelAction, ResponseAction } from "sprotty-protocol"; import { Connection } from "vscode-languageserver"; -import { FtaServices } from "./fta/fta-module"; -import { SetSynthesisOptionsAction, UpdateOptionsAction } from "./options/actions"; -import { DropDownOption } from "./options/option-models"; -import { SnippetDiagramServer } from "./snippets/snippet-diagram-server"; -import { LanguageSnippet } from "./snippets/snippet-model"; -import { StpaDiagramSnippets } from "./snippets/stpa-snippets"; -import { GenerateSVGsAction, RequestSvgAction, SvgAction, UpdateDiagramAction } from "./stpa/actions"; -import { StpaSynthesisOptions, filteringUCAsID } from "./stpa/diagram/stpa-synthesis-options"; +import { FtaServices } from "./fta/fta-module.js"; +import { SetSynthesisOptionsAction, UpdateOptionsAction } from "./options/actions.js"; +import { DropDownOption } from "./options/option-models.js"; +import { SnippetDiagramServer } from "./snippets/snippet-diagram-server.js"; +import { LanguageSnippet } from "./snippets/snippet-model.js"; +import { StpaDiagramSnippets } from "./snippets/stpa-snippets.js"; +import { GenerateSVGsAction, RequestSvgAction, SvgAction, UpdateDiagramAction } from "./stpa/actions.js"; +import { StpaSynthesisOptions, filteringUCAsID } from "./stpa/diagram/stpa-synthesis-options.js"; import { COMPLETE_GRAPH_PATH, CONTROL_STRUCTURE_PATH, @@ -49,9 +49,9 @@ import { setScenarioWithFilteredUCAGraphOptions, setScenarioWithNoUCAGraphOptions, setSystemConstraintGraphOptions, -} from "./stpa/result-report/svg-generator"; -import { StpaServices } from "./stpa/stpa-module"; -import { SynthesisOptions } from "./synthesis-options"; +} from "./stpa/result-report/svg-generator.js"; +import { StpaServices } from "./stpa/stpa-module.js"; +import { SynthesisOptions } from "./synthesis-options.js"; export class PastaDiagramServer extends SnippetDiagramServer { protected synthesisOptions: SynthesisOptions | undefined; diff --git a/extension/src-language-server/fta/analysis/fta-cutSet-calculator.ts b/extension/src-language-server/fta/analysis/fta-cutSet-calculator.ts index abe13f54..7cd8d44a 100644 --- a/extension/src-language-server/fta/analysis/fta-cutSet-calculator.ts +++ b/extension/src-language-server/fta/analysis/fta-cutSet-calculator.ts @@ -26,8 +26,8 @@ import { isKNGate, isOR, isTopEvent, -} from "../../generated/ast"; -import { namedFtaElement } from "../utils"; +} from "../../generated/ast.js"; +import { namedFtaElement } from "../utils.js"; /* element for which the cut sets were determined */ export let topOfAnalysis: string | undefined; diff --git a/extension/src-language-server/fta/diagram/fta-diagram-generator.ts b/extension/src-language-server/fta/diagram/fta-diagram-generator.ts index 29938907..53abe990 100644 --- a/extension/src-language-server/fta/diagram/fta-diagram-generator.ts +++ b/extension/src-language-server/fta/diagram/fta-diagram-generator.ts @@ -17,13 +17,14 @@ import { AstNode } from "langium"; import { GeneratorContext, IdCache, LangiumDiagramGenerator } from "langium-sprotty"; -import { SModelElement, SModelRoot, SNode, SLabel } from "sprotty-protocol"; -import { Component, Condition, Gate, ModelFTA, isComponent, isCondition, isKNGate } from "../../generated/ast"; -import { getDescription } from "../../utils"; -import { topOfAnalysis } from "../analysis/fta-cutSet-calculator"; -import { FtaServices } from "../fta-module"; -import { namedFtaElement } from "../utils"; -import { DescriptionNode, FTAEdge, FTAGraph, FTANode, FTAPort } from "./fta-interfaces"; +import { SLabel, SModelElement, SModelRoot, SNode } from "sprotty-protocol"; +import { Component, Condition, Gate, ModelFTA, isComponent, isCondition, isKNGate } from "../../generated/ast.js"; +import { HEADER_LABEL_TYPE } from "../../stpa/diagram/stpa-model.js"; +import { getDescription } from "../../utils.js"; +import { topOfAnalysis } from "../analysis/fta-cutSet-calculator.js"; +import { FtaServices } from "../fta-module.js"; +import { namedFtaElement } from "../utils.js"; +import { DescriptionNode, FTAEdge, FTAGraph, FTANode, FTAPort } from "./fta-interfaces.js"; import { FTA_DESCRIPTION_NODE_TYPE, FTA_EDGE_TYPE, @@ -33,10 +34,9 @@ import { FTA_PORT_TYPE, FTNodeType, PortSide, -} from "./fta-model"; -import { FtaSynthesisOptions, noCutSet, spofsSet } from "./fta-synthesis-options"; -import { getFTNodeType, getTargets } from "./utils"; -import { HEADER_LABEL_TYPE } from "../../stpa/diagram/stpa-model"; +} from "./fta-model.js"; +import { FtaSynthesisOptions, noCutSet, spofsSet } from "./fta-synthesis-options.js"; +import { getFTNodeType, getTargets } from "./utils.js"; export class FtaDiagramGenerator extends LangiumDiagramGenerator { protected readonly options: FtaSynthesisOptions; diff --git a/extension/src-language-server/fta/diagram/fta-interfaces.ts b/extension/src-language-server/fta/diagram/fta-interfaces.ts index eb2c9b99..a2795f85 100644 --- a/extension/src-language-server/fta/diagram/fta-interfaces.ts +++ b/extension/src-language-server/fta/diagram/fta-interfaces.ts @@ -16,7 +16,7 @@ */ import { Point, SEdge, SGraph, SNode, SPort } from "sprotty-protocol"; -import { FTNodeType, PortSide } from "./fta-model"; +import { FTNodeType, PortSide } from "./fta-model.js"; /** * Node of a fault tree. diff --git a/extension/src-language-server/fta/diagram/fta-layout-config.ts b/extension/src-language-server/fta/diagram/fta-layout-config.ts index 6798fcc5..e7b29341 100644 --- a/extension/src-language-server/fta/diagram/fta-layout-config.ts +++ b/extension/src-language-server/fta/diagram/fta-layout-config.ts @@ -16,10 +16,10 @@ */ import { LayoutOptions } from "elkjs"; -import { DefaultLayoutConfigurator } from "sprotty-elk/lib/elk-layout"; +import { DefaultLayoutConfigurator } from "sprotty-elk/lib/elk-layout.js"; import { SModelIndex, SNode } from "sprotty-protocol"; -import { FTAGraph, FTANode, FTAPort } from "./fta-interfaces"; -import { FTA_DESCRIPTION_NODE_TYPE, FTA_NODE_TYPE, FTA_PORT_TYPE, FTNodeType, PortSide } from "./fta-model"; +import { FTAGraph, FTANode, FTAPort } from "./fta-interfaces.js"; +import { FTA_DESCRIPTION_NODE_TYPE, FTA_NODE_TYPE, FTA_PORT_TYPE, FTNodeType, PortSide } from "./fta-model.js"; export class FtaLayoutConfigurator extends DefaultLayoutConfigurator { protected graphOptions(sgraph: FTAGraph, _index: SModelIndex): LayoutOptions { diff --git a/extension/src-language-server/fta/diagram/fta-synthesis-options.ts b/extension/src-language-server/fta/diagram/fta-synthesis-options.ts index 8ffc6dc6..85c12411 100644 --- a/extension/src-language-server/fta/diagram/fta-synthesis-options.ts +++ b/extension/src-language-server/fta/diagram/fta-synthesis-options.ts @@ -15,8 +15,8 @@ * SPDX-License-Identifier: EPL-2.0 */ -import { DropDownOption, SynthesisOption, TransformationOptionType, ValuedSynthesisOption } from "../../options/option-models"; -import { SynthesisOptions, layoutCategory } from "../../synthesis-options"; +import { DropDownOption, SynthesisOption, TransformationOptionType, ValuedSynthesisOption } from "../../options/option-models.js"; +import { SynthesisOptions, layoutCategory } from "../../synthesis-options.js"; const cutSetsID = "cutSets"; const showGateDescriptionsID = "showGateDescriptions"; diff --git a/extension/src-language-server/fta/diagram/utils.ts b/extension/src-language-server/fta/diagram/utils.ts index 55ffd5a7..29b65109 100644 --- a/extension/src-language-server/fta/diagram/utils.ts +++ b/extension/src-language-server/fta/diagram/utils.ts @@ -25,8 +25,8 @@ import { isKNGate, isOR, isTopEvent, -} from "../../generated/ast"; -import { FTNodeType } from "./fta-model"; +} from "../../generated/ast.js"; +import { FTNodeType } from "./fta-model.js"; /** * Determines the type of the given {@code node}. diff --git a/extension/src-language-server/fta/fta-message-handler.ts b/extension/src-language-server/fta/fta-message-handler.ts index 4e271878..9935f06a 100644 --- a/extension/src-language-server/fta/fta-message-handler.ts +++ b/extension/src-language-server/fta/fta-message-handler.ts @@ -17,11 +17,11 @@ import { LangiumSprottySharedServices } from "langium-sprotty"; import { Connection } from "vscode-languageserver"; -import { ModelFTA } from "../generated/ast"; -import { getModel } from "../utils"; -import { determineCutSetsForFT, determineMinimalCutSets } from "./analysis/fta-cutSet-calculator"; -import { FtaServices } from "./fta-module"; -import { cutSetsToString, namedFtaElement } from "./utils"; +import { ModelFTA } from "../generated/ast.js"; +import { getModel } from "../utils.js"; +import { determineCutSetsForFT, determineMinimalCutSets } from "./analysis/fta-cutSet-calculator.js"; +import { FtaServices } from "./fta-module.js"; +import { cutSetsToString, namedFtaElement } from "./utils.js"; /** * Adds handlers for notifications regarding fta. diff --git a/extension/src-language-server/fta/fta-module.ts b/extension/src-language-server/fta/fta-module.ts index 9f997793..fd0c1b95 100644 --- a/extension/src-language-server/fta/fta-module.ts +++ b/extension/src-language-server/fta/fta-module.ts @@ -15,16 +15,17 @@ * SPDX-License-Identifier: EPL-2.0 */ -import ElkConstructor from "elkjs/lib/elk.bundled"; -import { Module, PartialLangiumServices } from "langium"; +const ElkConstructor = require('elkjs/lib/elk.bundled.js').default; +import { Module } from "langium"; import { LangiumSprottyServices, SprottyDiagramServices } from "langium-sprotty"; -import { DefaultElementFilter, ElkFactory, IElementFilter, ILayoutConfigurator } from "sprotty-elk/lib/elk-layout"; -import { LayoutEngine } from "../layout-engine"; -import { FtaDiagramGenerator } from "./diagram/fta-diagram-generator"; -import { FtaLayoutConfigurator } from "./diagram/fta-layout-config"; -import { FtaSynthesisOptions } from "./diagram/fta-synthesis-options"; -import { FtaScopeProvider } from "./fta-scopeProvider"; -import { FtaValidationRegistry, FtaValidator } from "./fta-validator"; +import { PartialLangiumServices } from 'langium/lsp'; +import { DefaultElementFilter, ElkFactory, IElementFilter, ILayoutConfigurator } from "sprotty-elk/lib/elk-layout.js"; +import { LayoutEngine } from "../layout-engine.js"; +import { FtaDiagramGenerator } from "./diagram/fta-diagram-generator.js"; +import { FtaLayoutConfigurator } from "./diagram/fta-layout-config.js"; +import { FtaSynthesisOptions } from "./diagram/fta-synthesis-options.js"; +import { FtaScopeProvider } from "./fta-scopeProvider.js"; +import { FtaValidationRegistry, FtaValidator } from "./fta-validator.js"; /** * Declaration of custom services. diff --git a/extension/src-language-server/fta/fta-scopeProvider.ts b/extension/src-language-server/fta/fta-scopeProvider.ts index ebc8945e..a15538aa 100644 --- a/extension/src-language-server/fta/fta-scopeProvider.ts +++ b/extension/src-language-server/fta/fta-scopeProvider.ts @@ -18,12 +18,12 @@ import { AstNode, AstNodeDescription, + AstUtils, DefaultScopeProvider, ReferenceInfo, Scope, Stream, - getDocument, - stream, + stream } from "langium"; export class FtaScopeProvider extends DefaultScopeProvider { @@ -32,15 +32,14 @@ export class FtaScopeProvider extends DefaultScopeProvider { const scopes: Array> = []; const referenceType = this.reflection.getReferenceType(context); - const precomputed = getDocument(context.container).precomputedScopes; + const precomputed = AstUtils.getDocument(context.container).precomputedScopes; if (precomputed) { let currentNode: AstNode | undefined = context.container; do { const allDescriptions = precomputed.get(currentNode); if (allDescriptions.length > 0) { - scopes.push( - stream(allDescriptions).filter((desc) => this.reflection.isSubtype(desc.type, referenceType)) - ); + scopes.push(stream(allDescriptions).filter( + desc => this.reflection.isSubtype(desc.type, referenceType))); } currentNode = currentNode.$container; } while (currentNode); diff --git a/extension/src-language-server/fta/fta-validator.ts b/extension/src-language-server/fta/fta-validator.ts index 7bef7c32..a85f8763 100644 --- a/extension/src-language-server/fta/fta-validator.ts +++ b/extension/src-language-server/fta/fta-validator.ts @@ -16,8 +16,8 @@ */ import { ValidationAcceptor, ValidationChecks, ValidationRegistry } from "langium"; -import { ModelFTA, PastaAstType } from "../generated/ast"; -import type { FtaServices } from "./fta-module"; +import { ModelFTA, PastaAstType } from "../generated/ast.js"; +import type { FtaServices } from "./fta-module.js"; /** * Registry for FTA validation checks. diff --git a/extension/src-language-server/fta/utils.ts b/extension/src-language-server/fta/utils.ts index 2dc8fb84..57d1bada 100644 --- a/extension/src-language-server/fta/utils.ts +++ b/extension/src-language-server/fta/utils.ts @@ -16,7 +16,7 @@ */ import { Range } from "vscode-languageserver"; -import { Component, Condition, Gate, ModelFTA, TopEvent, isAND, isInhibitGate, isKNGate, isOR } from "../generated/ast"; +import { Component, Condition, Gate, ModelFTA, TopEvent, isAND, isInhibitGate, isKNGate, isOR } from "../generated/ast.js"; /** FTA elements that have a name. */ export type namedFtaElement = Component | Condition | Gate | TopEvent; diff --git a/extension/src-language-server/handler.ts b/extension/src-language-server/handler.ts index 9c9d25d7..496fc327 100644 --- a/extension/src-language-server/handler.ts +++ b/extension/src-language-server/handler.ts @@ -17,21 +17,30 @@ import { LangiumSprottySharedServices } from "langium-sprotty"; import { Connection, Range } from "vscode-languageserver"; -import { handleFTAConfigInit, handleFTAConfigReset } from './fta/fta-message-handler'; -import { FtaServices } from './fta/fta-module'; -import { getRangeOfNodeFTA } from "./fta/utils"; -import { Model, isModel, isModelFTA } from "./generated/ast"; -import { handleSTPAConfigInit, handleSTPAConfigReset } from './stpa/message-handler'; -import { StpaServices } from './stpa/stpa-module'; -import { getRangeOfNodeSTPA } from "./stpa/utils"; -import { getModel } from "./utils"; +import { handleFTAConfigInit, handleFTAConfigReset } from "./fta/fta-message-handler.js"; +import { FtaServices } from "./fta/fta-module.js"; +import { getRangeOfNodeFTA } from "./fta/utils.js"; +import { Model, isModel, isModelFTA } from "./generated/ast.js"; +import { handleSTPAConfigInit, handleSTPAConfigReset } from "./stpa/message-handler.js"; +import { StpaServices } from "./stpa/stpa-module.js"; +import { getRangeOfNodeSTPA } from "./stpa/utils.js"; +import { getModel } from "./utils.js"; /** * Adds handler for notifications. * @param connection Connection to the extension. * @param shared Shared services containing the workspace. */ -export function addNotificationHandler(connection: Connection, shared: LangiumSprottySharedServices,stpaServices: StpaServices, ftaServices: FtaServices): void { +export function addNotificationHandler( + connection: Connection, + shared: LangiumSprottySharedServices, + stpaServices: StpaServices, + ftaServices: FtaServices +): void { + // tells the extension that the language server is ready + shared.lsp.LanguageServer.onInitialized(() => { + connection.sendNotification("ready"); + }); // node selection in diagram connection.onNotification("diagram/selected", async (msg: { label: string; uri: string }) => { // get the current model diff --git a/extension/src-language-server/layout-engine.ts b/extension/src-language-server/layout-engine.ts index 3fed65d5..0d21d7a2 100644 --- a/extension/src-language-server/layout-engine.ts +++ b/extension/src-language-server/layout-engine.ts @@ -15,28 +15,40 @@ * SPDX-License-Identifier: EPL-2.0 */ -import { ElkExtendedEdge, ElkNode, ElkPrimitiveEdge } from "elkjs/lib/elk-api"; -import { ElkLayoutEngine } from "sprotty-elk/lib/elk-layout"; +import { ElkExtendedEdge, ElkPrimitiveEdge } from "elkjs"; +import { ElkLayoutEngine } from "sprotty-elk/lib/elk-layout.js"; import { Point, SEdge, SGraph, SModelIndex } from "sprotty-protocol"; -import { FTAEdge } from "../src-webview/fta/fta-model"; -import { FTA_EDGE_TYPE } from "./fta/diagram/fta-model"; +import { FTAEdge } from "./fta/diagram/fta-interfaces.js"; +import { FTA_EDGE_TYPE } from "./fta/diagram/fta-model.js"; export class LayoutEngine extends ElkLayoutEngine { - layout(graph: SGraph, index?: SModelIndex | undefined): SGraph | Promise { - if (this.getBasicType(graph) !== "graph") { - return graph; + layout(sgraph: SGraph, index?: SModelIndex): SGraph | Promise { + if (this.getBasicType(sgraph) !== "graph") { + return sgraph; } if (!index) { index = new SModelIndex(); - index.add(graph); + index.add(sgraph); } - const elkGraph = this.transformToElk(graph, index) as ElkNode; + + // STEP 1: Transform the Sprotty graph into an ELK graph with optional pre-processing + const elkGraph = this.transformGraph(sgraph, index); /* used to inspect the elk graph in elklive */ // const debugElkGraph = JSON.stringify(elkGraph); // console.log(debugElkGraph); - return this.elk.layout(elkGraph).then((result) => { + + if (this.preprocessor) { + this.preprocessor.preprocess(elkGraph, sgraph, index); + } + + // STEP 2: Invoke the ELK layout engine + return this.elk.layout(elkGraph).then(result => { + // STEP 3: Apply the results with optional post-processing to the original graph + if (this.postprocessor) { + this.postprocessor.postprocess(result, sgraph, index!); + } this.applyLayout(result, index!); - return graph; + return sgraph; }); } @@ -48,18 +60,30 @@ export class LayoutEngine extends ElkLayoutEngine { } if (elkEdge.sections && elkEdge.sections.length > 0) { const section = elkEdge.sections[0]; - if (section.startPoint) { points.push(section.startPoint); } - if (section.bendPoints) { points.push(...section.bendPoints); } - if (section.endPoint) { points.push(section.endPoint); } + if (section.startPoint) { + points.push(section.startPoint); + } + if (section.bendPoints) { + points.push(...section.bendPoints); + } + if (section.endPoint) { + points.push(section.endPoint); + } } else if (isPrimitiveEdge(elkEdge)) { - if (elkEdge.sourcePoint) { points.push(elkEdge.sourcePoint); } - if (elkEdge.bendPoints) { points.push(...elkEdge.bendPoints); } - if (elkEdge.targetPoint) { points.push(elkEdge.targetPoint); } + if (elkEdge.sourcePoint) { + points.push(elkEdge.sourcePoint); + } + if (elkEdge.bendPoints) { + points.push(...elkEdge.bendPoints); + } + if (elkEdge.targetPoint) { + points.push(elkEdge.targetPoint); + } } sedge.routingPoints = points; if (elkEdge.labels) { - elkEdge.labels.forEach((elkLabel) => { + elkEdge.labels.forEach(elkLabel => { const sLabel = elkLabel.id && index.getById(elkLabel.id); if (sLabel) { this.applyShape(sLabel, elkLabel, index); diff --git a/extension/src-language-server/main.ts b/extension/src-language-server/main.ts index 007f5685..b9e99b78 100644 --- a/extension/src-language-server/main.ts +++ b/extension/src-language-server/main.ts @@ -15,14 +15,14 @@ * SPDX-License-Identifier: EPL-2.0 */ -import { startLanguageServer } from "langium"; import { addDiagramHandler } from "langium-sprotty"; +import { startLanguageServer } from "langium/lsp"; import { NodeFileSystem } from "langium/node"; -import { createConnection, ProposedFeatures } from "vscode-languageserver/node"; -import { addFTANotificationHandler } from "./fta/fta-message-handler"; -import { addNotificationHandler } from "./handler"; -import { createServices } from "./module"; -import { addSTPANotificationHandler } from "./stpa/message-handler"; +import { createConnection, ProposedFeatures } from "vscode-languageserver/node.js"; +import { addFTANotificationHandler } from "./fta/fta-message-handler.js"; +import { addNotificationHandler } from "./handler.js"; +import { createServices } from "./module.js"; +import { addSTPANotificationHandler } from "./stpa/message-handler.js"; // Create a connection to the client const connection = createConnection(ProposedFeatures.all); @@ -33,9 +33,9 @@ const { shared, stpa, fta } = createServices({ connection, ...NodeFileSystem }); // Start the language server with the language-specific services startLanguageServer(shared); addDiagramHandler(connection, shared); +//TODO: use tracing provider from langium to match snode IDs to text definition +// addDiagramSelectionHandler addSTPANotificationHandler(connection, stpa, shared); addFTANotificationHandler(connection, fta, shared); addNotificationHandler(connection, shared, stpa, fta); - -connection.onInitialized(() => connection.sendNotification("ready")); diff --git a/extension/src-language-server/module.ts b/extension/src-language-server/module.ts index f34a02ad..2e55401e 100644 --- a/extension/src-language-server/module.ts +++ b/extension/src-language-server/module.ts @@ -15,19 +15,22 @@ * SPDX-License-Identifier: EPL-2.0 */ -import { createDefaultModule, createDefaultSharedModule, DefaultSharedModuleContext, inject, Module } from "langium"; + +import { inject, Module } from 'langium'; import { DefaultDiagramServerManager, DiagramActionNotification, LangiumSprottySharedServices, - SprottySharedServices, + SprottySharedServices } from "langium-sprotty"; +import { createDefaultModule, createDefaultSharedModule, DefaultSharedModuleContext } from "langium/lsp"; import { DiagramOptions } from "sprotty-protocol"; import { URI } from "vscode-uri"; -import { PastaDiagramServer } from "./diagram-server"; -import { FtaModule, FtaServices } from "./fta/fta-module"; -import { FtaGeneratedModule, PastaGeneratedSharedModule, StpaGeneratedModule } from "./generated/module"; -import { STPAModule, StpaServices } from "./stpa/stpa-module"; +import { PastaDiagramServer } from "./diagram-server.js"; +import { FtaModule, FtaServices } from "./fta/fta-module.js"; +import { FtaGeneratedModule, PastaGeneratedSharedModule, StpaGeneratedModule } from "./generated/module.js"; +import { registerValidationChecks } from './stpa/services/stpa-validator.js'; +import { STPAModule, StpaServices } from "./stpa/stpa-module.js"; /** * Create the full set of services required by Langium. @@ -53,6 +56,7 @@ export function createServices(context: DefaultSharedModuleContext): { const stpa = inject(createDefaultModule({ shared }), StpaGeneratedModule, STPAModule); const fta = inject(createDefaultModule({ shared }), FtaGeneratedModule, FtaModule); shared.ServiceRegistry.register(stpa); + registerValidationChecks(stpa); shared.ServiceRegistry.register(fta); return { shared, stpa, fta }; } diff --git a/extension/src-language-server/options/actions.ts b/extension/src-language-server/options/actions.ts index 7cbc2b3f..86e76e9c 100644 --- a/extension/src-language-server/options/actions.ts +++ b/extension/src-language-server/options/actions.ts @@ -16,7 +16,7 @@ */ import { Action } from "sprotty-protocol"; -import { SynthesisOption, ValuedSynthesisOption } from "./option-models"; +import { SynthesisOption, ValuedSynthesisOption } from "./option-models.js"; /** Request message from the server to update the diagram options widget on the client. */ export interface UpdateOptionsAction extends Action { diff --git a/extension/src-language-server/snippets/actions.ts b/extension/src-language-server/snippets/actions.ts index 86165c16..ff79d813 100644 --- a/extension/src-language-server/snippets/actions.ts +++ b/extension/src-language-server/snippets/actions.ts @@ -17,7 +17,7 @@ import { VNode } from "snabbdom"; import { Action, RequestAction, ResponseAction, generateRequestId } from "sprotty-protocol"; -import { WebviewSnippet } from "./snippet-model"; +import { WebviewSnippet } from "./snippet-model.js"; /** Request message from the server to the client to get the svgs of the snippets. */ export interface RequestWebviewSnippetsAction extends RequestAction { diff --git a/extension/src-language-server/snippets/snippet-diagram-server.ts b/extension/src-language-server/snippets/snippet-diagram-server.ts index 99d6b32b..95a3314e 100644 --- a/extension/src-language-server/snippets/snippet-diagram-server.ts +++ b/extension/src-language-server/snippets/snippet-diagram-server.ts @@ -32,8 +32,8 @@ import { RequestWebviewSnippetsAction, SendDefaultSnippetsAction, SendWebviewSnippetsAction, -} from "./actions"; -import { LanguageSnippet, SnippetGraphGenerator, WebviewSnippet } from "./snippet-model"; +} from "./actions.js"; +import { LanguageSnippet, SnippetGraphGenerator, WebviewSnippet } from "./snippet-model.js"; export abstract class SnippetDiagramServer extends DiagramServer { protected clientId: string; diff --git a/extension/src-language-server/snippets/stpa-snippets.ts b/extension/src-language-server/snippets/stpa-snippets.ts index 1f74bcaf..40437caf 100644 --- a/extension/src-language-server/snippets/stpa-snippets.ts +++ b/extension/src-language-server/snippets/stpa-snippets.ts @@ -15,12 +15,14 @@ * SPDX-License-Identifier: EPL-2.0 */ -import { LangiumDocuments, LangiumServices } from "langium"; +import { LangiumDocument, LangiumDocuments } from "langium"; +import { LangiumServices } from "langium/lsp"; import { Position } from "vscode-languageserver"; import { TextDocument } from "vscode-languageserver-textdocument"; import { URI } from "vscode-uri"; -import { LanguageSnippet } from "./snippet-model"; +import { Model } from '../generated/ast.js'; import * as defaultSnippets from "./default-stpa-snippets.json"; +import { LanguageSnippet } from "./snippet-model.js"; /** * Class that handles snippets for the STPA diagram. @@ -43,7 +45,7 @@ export class StpaDiagramSnippets { */ protected generateDefaultSnippets(): LanguageSnippet[] { const list: LanguageSnippet[] = []; - defaultSnippets.snippets.forEach((snippet: { name: string; code: string }) => { + defaultSnippets.default.snippets.forEach((snippet: { name: string; code: string }) => { list.push(new CustomCSSnippet(this.langiumDocuments, snippet.code, snippet.name)); }); return list; @@ -75,14 +77,17 @@ export class StpaDiagramSnippets { * @param snippet The snippet that should be inserted. * @returns the position where the snippet should be added to the {@code document}. */ -function getPositionForCSSnippet(document: TextDocument, snippet: LanguageSnippet): Position { +function getPositionForCSSnippet(langDoc: LangiumDocument, snippet: LanguageSnippet): Position { + const document = langDoc.textDocument; const docText = document.getText(); // determine range of already existing definition of control structure - const titleIndex = docText.indexOf("ControlStructure"); - const startIndex = titleIndex !== -1 ? titleIndex : 0; - const nextTitleIndex = docText.indexOf("Responsibilities"); - const endIndex = nextTitleIndex !== -1 ? nextTitleIndex - 1 : docText.length - 1; - if (titleIndex === -1) { + // TODO: only working for STPA not FTA + const model = (langDoc.parseResult.value as Model); + const csStartIndex = model.controlStructure?.$cstNode?.offset ?? -1; + const startIndex = csStartIndex !== -1 ? csStartIndex : 0; + const csEnd = model.controlStructure?.$cstNode?.range.end; + const endIndex = csEnd ? document.offsetAt(csEnd) : docText.length - 1; + if (csStartIndex === -1) { return document.positionAt(endIndex); } else { // delete the control structure keyword @@ -99,7 +104,7 @@ function getPositionForCSSnippet(document: TextDocument, snippet: LanguageSnippe snippet.insertText.lastIndexOf("}") ); const bracketIndex = csText.lastIndexOf("}"); - return document.positionAt(titleIndex + bracketIndex); + return document.positionAt(csStartIndex + bracketIndex); } } } @@ -190,9 +195,12 @@ export class STPALanguageSnippet implements LanguageSnippet { } getPosition(uri: string): Position { - const document = this.documents.getOrCreateDocument(URI.parse(uri)).textDocument; - this.insertText = addNodeIDs(this.baseCode, document); - return getPositionForCSSnippet(document, this); + const document = this.documents.getDocument(URI.parse(uri)); + if (document) { + this.insertText = addNodeIDs(this.baseCode, document.textDocument); + return getPositionForCSSnippet(document, this); + } + return { line: 0, character: 0 }; } } diff --git a/extension/src-language-server/stpa/contextTable/context-dataProvider.ts b/extension/src-language-server/stpa/contextTable/context-dataProvider.ts index 5701ff9a..abc71e44 100644 --- a/extension/src-language-server/stpa/contextTable/context-dataProvider.ts +++ b/extension/src-language-server/stpa/contextTable/context-dataProvider.ts @@ -17,6 +17,9 @@ import { LangiumDocument } from "langium"; import { Range, URI } from "vscode-languageserver"; +import { Model, Node } from "../../generated/ast.js"; +import { getModel } from "../../utils.js"; +import { StpaServices } from "../stpa-module.js"; import { ContextTableControlAction, ContextTableData, @@ -24,10 +27,7 @@ import { ContextTableSystemVariables, ContextTableVariable, ContextTableVariableValues, -} from "../../../src-context-table/utils"; -import { Model, Node } from "../../generated/ast"; -import { getModel } from "../../utils"; -import { StpaServices } from "../stpa-module"; +} from "./utils-classes.js"; export class ContextTableProvider { protected services: StpaServices; @@ -45,7 +45,7 @@ export class ContextTableProvider { getRangeOfUCA(uri: URI, ucaName: string): Range | undefined { // get the current model const textDocuments = this.services.shared.workspace.LangiumDocuments; - const currentDoc = textDocuments.getOrCreateDocument(uri as any) as LangiumDocument; + const currentDoc = textDocuments.getDocument(uri as any) as LangiumDocument; const model: Model = currentDoc.parseResult.value; let range: Range | undefined = undefined; diff --git a/extension/src-language-server/stpa/contextTable/utils-classes.ts b/extension/src-language-server/stpa/contextTable/utils-classes.ts new file mode 100644 index 00000000..fbb7e4c2 --- /dev/null +++ b/extension/src-language-server/stpa/contextTable/utils-classes.ts @@ -0,0 +1,69 @@ +/* + * KIELER - Kiel Integrated Environment for Layout Eclipse RichClient + * + * http://rtsys.informatik.uni-kiel.de/kieler + * + * Copyright 2022 by + * + Kiel University + * + Department of Computer Science + * + Real-Time and Embedded Systems Group + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + */ + +/** Type for control actions for the context table. */ +export class ContextTableControlAction { + controller: string; + action: string; +} + +/** The variables for each system, needed for the context table. */ +export class ContextTableSystemVariables { + system: string; + variables: ContextTableVariableValues[]; +} + +/** The possible values for a variable in the context table. */ +export class ContextTableVariableValues { + name: string; + values: string[]; +} + +/** An instantation of a variable, needed for the contexts in the context table. */ +export class ContextTableVariable { + name: string; + value: string; +} + +/** A rule for the context table. */ +export class ContextTableRule { + id: string; + controlAction: ContextTableControlAction; + type: string; + variables: ContextTableVariable[]; + hazards: string[]; +} + +/** Data the context table expects from the language server. */ +export class ContextTableData { + rules: ContextTableRule[]; + actions: ContextTableControlAction[]; + systemVariables: ContextTableSystemVariables[]; +} + +/** Types of control actions. */ +export enum Type { + PROVIDED, + NOT_PROVIDED, + BOTH, +} + +/** A row in the context table. */ +export class Row { + variables: ContextTableVariable[]; + results: { hazards: string[]; rules: ContextTableRule[] }[]; +} diff --git a/extension/src-language-server/stpa/diagram/diagram-controlStructure.ts b/extension/src-language-server/stpa/diagram/diagram-controlStructure.ts index a7e0820a..dc8072ef 100644 --- a/extension/src-language-server/stpa/diagram/diagram-controlStructure.ts +++ b/extension/src-language-server/stpa/diagram/diagram-controlStructure.ts @@ -18,9 +18,9 @@ import { AstNode } from "langium"; import { IdCache } from "langium-sprotty"; import { SModelElement, SNode } from "sprotty-protocol"; -import { Command, Graph, Node, Variable, VerticalEdge } from "../../generated/ast"; -import { createControlStructureEdge, createDummyNode, createLabel, createPort } from "./diagram-elements"; -import { CSEdge, CSNode, ParentNode } from "./stpa-interfaces"; +import { Command, Graph, Node, Variable, VerticalEdge } from "../../generated/ast.js"; +import { createControlStructureEdge, createDummyNode, createLabel, createPort } from "./diagram-elements.js"; +import { CSEdge, CSNode, ParentNode } from "./stpa-interfaces.js"; import { CS_EDGE_TYPE, CS_INTERMEDIATE_EDGE_TYPE, @@ -29,11 +29,12 @@ import { EdgeType, HEADER_LABEL_TYPE, PARENT_TYPE, + PASTA_LABEL_TYPE, PROCESS_MODEL_PARENT_NODE_TYPE, PortSide, -} from "./stpa-model"; -import { StpaSynthesisOptions } from "./stpa-synthesis-options"; -import { getCommonAncestor, setLevelOfCSNodes, sortPorts } from "./utils"; +} from "./stpa-model.js"; +import { StpaSynthesisOptions } from "./stpa-synthesis-options.js"; +import { getCommonAncestor, sortPorts } from "./utils.js"; /** * Creates the control structure diagram for the given {@code controlStructure}. @@ -88,7 +89,7 @@ export function createControlStructureNode( ): CSNode { const label = node.label ? node.label : node.name; const nodeId = idCache.uniqueId(node.name, node); - const children: SModelElement[] = createLabel([label], nodeId, idCache); + const children: SModelElement[] = createLabel([label], nodeId, idCache, PASTA_LABEL_TYPE); if (options.getShowProcessModels()) { // add nodes representing the process model children.push(createProcessModelNodes(node.variables, idCache)); @@ -146,7 +147,7 @@ export function createProcessModelNodes(variables: Variable[], idCache: IdCache< const values = variable.values?.map(value => value.name); const children = [ ...createLabel([label], nodeId, idCache, HEADER_LABEL_TYPE), - ...createLabel(values, nodeId, idCache), + ...createLabel(values, nodeId, idCache, PASTA_LABEL_TYPE), ]; // create the actual node with the created labels const csNode = { diff --git a/extension/src-language-server/stpa/diagram/diagram-elements.ts b/extension/src-language-server/stpa/diagram/diagram-elements.ts index cf8d7922..4e343291 100644 --- a/extension/src-language-server/stpa/diagram/diagram-elements.ts +++ b/extension/src-language-server/stpa/diagram/diagram-elements.ts @@ -18,19 +18,20 @@ import { AstNode } from "langium"; import { IdCache } from "langium-sprotty"; import { SLabel, SModelElement } from "sprotty-protocol"; -import { getDescription } from "../../utils"; -import { CSEdge, CSNode, PastaPort, STPAEdge, STPANode } from "./stpa-interfaces"; +import { getDescription } from "../../utils.js"; +import { CSEdge, CSNode, PastaPort, STPAEdge, STPANode } from "./stpa-interfaces.js"; import { DUMMY_NODE_TYPE, + EDGE_LABEL_TYPE, EdgeType, HEADER_LABEL_TYPE, PORT_TYPE, PortSide, STPAAspect, STPA_NODE_TYPE, -} from "./stpa-model"; -import { StpaSynthesisOptions } from "./stpa-synthesis-options"; -import { getAspect } from "./utils"; +} from "./stpa-model.js"; +import { StpaSynthesisOptions } from "./stpa-synthesis-options.js"; +import { getAspect } from "./utils.js"; /** * Creates an STPANode. @@ -135,7 +136,7 @@ export function createControlStructureEdge( sourceId: sourceId!, targetId: targetId!, edgeType: edgeType, - children: createLabel(label, edgeId, idCache, undefined, dummyLabel), + children: createLabel(label, edgeId, idCache, EDGE_LABEL_TYPE, dummyLabel), }; } @@ -152,7 +153,7 @@ export function createLabel( label: string[], id: string, idCache: IdCache, - type: string = "label:xref", + type: string, dummyLabel: boolean = true ): SLabel[] { const children: SLabel[] = []; diff --git a/extension/src-language-server/stpa/diagram/diagram-generator.ts b/extension/src-language-server/stpa/diagram/diagram-generator.ts index dac4a97a..a4dfa761 100644 --- a/extension/src-language-server/stpa/diagram/diagram-generator.ts +++ b/extension/src-language-server/stpa/diagram/diagram-generator.ts @@ -20,14 +20,14 @@ import { GeneratorContext, IdCache, IdCacheImpl } from "langium-sprotty"; import { SModelElement, SModelRoot, SNode } from "sprotty-protocol"; import { CancellationToken } from "vscode-languageserver"; import { URI } from "vscode-uri"; -import { Model } from "../../generated/ast"; -import { LanguageSnippet, SnippetGraphGenerator } from "../../snippets/snippet-model"; -import { StpaDocumentBuilder } from "../../stpa-document-builder"; -import { StpaServices } from "../stpa-module"; -import { createControlStructure } from "./diagram-controlStructure"; -import { createRelationshipGraph } from "./diagram-relationshipGraph"; -import { filterModel } from "./filtering"; -import { StpaSynthesisOptions } from "./stpa-synthesis-options"; +import { Model } from "../../generated/ast.js"; +import { LanguageSnippet, SnippetGraphGenerator } from "../../snippets/snippet-model.js"; +import { StpaDocumentBuilder } from "../../stpa-document-builder.js"; +import { StpaServices } from "../stpa-module.js"; +import { createControlStructure } from "./diagram-controlStructure.js"; +import { createRelationshipGraph } from "./diagram-relationshipGraph.js"; +import { filterModel } from "./filtering.js"; +import { StpaSynthesisOptions } from "./stpa-synthesis-options.js"; export class StpaDiagramGenerator extends SnippetGraphGenerator { protected readonly options: StpaSynthesisOptions; @@ -79,7 +79,7 @@ export class StpaDiagramGenerator extends SnippetGraphGenerator { ); await (this.services.shared.workspace.DocumentBuilder as StpaDocumentBuilder).buildDocuments( [doc], - { validationChecks: "none" }, + { validation: false }, CancellationToken.None ); diff --git a/extension/src-language-server/stpa/diagram/diagram-relationshipGraph.ts b/extension/src-language-server/stpa/diagram/diagram-relationshipGraph.ts index 6dd17ba9..67f0ac4d 100644 --- a/extension/src-language-server/stpa/diagram/diagram-relationshipGraph.ts +++ b/extension/src-language-server/stpa/diagram/diagram-relationshipGraph.ts @@ -18,28 +18,29 @@ import { AstNode } from "langium"; import { IdCache } from "langium-sprotty"; import { SModelElement, SNode } from "sprotty-protocol"; -import { Hazard, Model, SystemConstraint, isContext, isHazard, isSystemConstraint, isUCA } from "../../generated/ast"; -import { collectElementsWithSubComps, leafElement } from "../utils"; -import { createLabel, createPort, createSTPAEdge, createSTPANode, generateDescriptionLabels } from "./diagram-elements"; -import { CustomModel } from "./filtering"; -import { ParentNode, STPAEdge, STPANode } from "./stpa-interfaces"; +import { Hazard, Model, SystemConstraint, isContext, isHazard, isSystemConstraint, isUCA } from "../../generated/ast.js"; +import { labelManagementValue } from "../../synthesis-options.js"; +import { collectElementsWithSubComps, leafElement } from "../utils.js"; +import { createLabel, createPort, createSTPAEdge, createSTPANode, generateDescriptionLabels } from "./diagram-elements.js"; +import { CustomModel } from "./filtering.js"; +import { ParentNode, STPAEdge, STPANode } from "./stpa-interfaces.js"; import { + EDGE_LABEL_TYPE, PARENT_TYPE, PortSide, STPAAspect, STPA_EDGE_TYPE, STPA_INTERMEDIATE_EDGE_TYPE, STPA_NODE_TYPE, -} from "./stpa-model"; -import { StpaSynthesisOptions, showLabelsValue } from "./stpa-synthesis-options"; +} from "./stpa-model.js"; +import { StpaSynthesisOptions, showLabelsValue } from "./stpa-synthesis-options.js"; import { createUCAContextDescription, getAspect, getAspectsThatShouldHaveDesriptions, getTargets, setLevelsForSTPANodes, -} from "./utils"; -import { labelManagementValue } from "../../synthesis-options"; +} from "./utils.js"; /** * Creates the relationship graph for the STPA model. @@ -411,7 +412,7 @@ export function generateSTPAEdge( // create the label of the edge let children: SModelElement[] = []; if (label !== "") { - children = createLabel([label], edgeId, idCache); + children = createLabel([label], edgeId, idCache, EDGE_LABEL_TYPE); } if ((isHazard(target) || isSystemConstraint(target)) && target.$container?.$type !== "Model") { diff --git a/extension/src-language-server/stpa/diagram/filtering.ts b/extension/src-language-server/stpa/diagram/filtering.ts index 7fd626c0..ed7252e6 100644 --- a/extension/src-language-server/stpa/diagram/filtering.ts +++ b/extension/src-language-server/stpa/diagram/filtering.ts @@ -23,12 +23,12 @@ import { Loss, LossScenario, Model, - SystemResponsibilities, Rule, SafetyConstraint, SystemConstraint, -} from "../../generated/ast"; -import { StpaSynthesisOptions } from "./stpa-synthesis-options"; + SystemResponsibilities, +} from "../../generated/ast.js"; +import { StpaSynthesisOptions } from "./stpa-synthesis-options.js"; /** * Needed to work on a filtered model without changing the original model. diff --git a/extension/src-language-server/stpa/diagram/layout-config.ts b/extension/src-language-server/stpa/diagram/layout-config.ts index c52ece9b..77b26d39 100644 --- a/extension/src-language-server/stpa/diagram/layout-config.ts +++ b/extension/src-language-server/stpa/diagram/layout-config.ts @@ -17,18 +17,18 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ import { LayoutOptions } from "elkjs"; -import { DefaultLayoutConfigurator } from "sprotty-elk/lib/elk-layout"; -import { SGraph, SModelIndex, SNode, SPort } from "sprotty-protocol"; -import { CSNode, ParentNode, STPANode, PastaPort } from "./stpa-interfaces"; +import { DefaultLayoutConfigurator } from "sprotty-elk/lib/elk-layout.js"; +import { SGraph, SLabel, SModelIndex, SNode, SPort } from "sprotty-protocol"; +import { CSNode, ParentNode, PastaPort, STPANode } from "./stpa-interfaces.js"; import { - CS_NODE_TYPE, CS_INVISIBLE_SUBCOMPONENT_TYPE, + CS_NODE_TYPE, PARENT_TYPE, + PORT_TYPE, PROCESS_MODEL_PARENT_NODE_TYPE, PortSide, STPA_NODE_TYPE, - PORT_TYPE, -} from "./stpa-model"; +} from "./stpa-model.js"; export class StpaLayoutConfigurator extends DefaultLayoutConfigurator { protected graphOptions(sgraph: SGraph, index: SModelIndex): LayoutOptions { @@ -234,4 +234,11 @@ export class StpaLayoutConfigurator extends DefaultLayoutConfigurator { }; } } + + protected labelOptions(slabel: SLabel, index: SModelIndex): LayoutOptions | undefined { + return { + // enables on edge labels + "org.eclipse.elk.edgeLabels.inline": "true", + }; + } } diff --git a/extension/src-language-server/stpa/diagram/stpa-interfaces.ts b/extension/src-language-server/stpa/diagram/stpa-interfaces.ts index d6361900..619731ac 100644 --- a/extension/src-language-server/stpa/diagram/stpa-interfaces.ts +++ b/extension/src-language-server/stpa/diagram/stpa-interfaces.ts @@ -16,7 +16,7 @@ */ import { SEdge, SNode, SPort } from "sprotty-protocol"; -import { EdgeType, PortSide, STPAAspect } from "./stpa-model"; +import { EdgeType, PortSide, STPAAspect } from "./stpa-model.js"; export interface ParentNode extends SNode { modelOrder: boolean; diff --git a/extension/src-language-server/stpa/diagram/stpa-model.ts b/extension/src-language-server/stpa/diagram/stpa-model.ts index 286688ee..46f9d11b 100644 --- a/extension/src-language-server/stpa/diagram/stpa-model.ts +++ b/extension/src-language-server/stpa/diagram/stpa-model.ts @@ -29,6 +29,8 @@ export const STPA_EDGE_TYPE = 'edge:stpa'; export const STPA_INTERMEDIATE_EDGE_TYPE = 'edge:stpa-intermediate'; export const PORT_TYPE = 'port:pasta'; export const HEADER_LABEL_TYPE = 'label:header'; +export const PASTA_LABEL_TYPE = 'label'; +export const EDGE_LABEL_TYPE = 'label:xref'; /** * The different aspects of STPA. diff --git a/extension/src-language-server/stpa/diagram/stpa-synthesis-options.ts b/extension/src-language-server/stpa/diagram/stpa-synthesis-options.ts index 8f502764..ec7e406c 100644 --- a/extension/src-language-server/stpa/diagram/stpa-synthesis-options.ts +++ b/extension/src-language-server/stpa/diagram/stpa-synthesis-options.ts @@ -20,8 +20,8 @@ import { SynthesisOption, TransformationOptionType, ValuedSynthesisOption, -} from "../../options/option-models"; -import { SynthesisOptions, layoutCategory } from "../../synthesis-options"; +} from "../../options/option-models.js"; +import { SynthesisOptions, layoutCategory } from "../../synthesis-options.js"; const hierarchyID = "hierarchy"; const groupingUCAsID = "groupingUCAs"; diff --git a/extension/src-language-server/stpa/diagram/utils.ts b/extension/src-language-server/stpa/diagram/utils.ts index d870ede7..c60839b3 100644 --- a/extension/src-language-server/stpa/diagram/utils.ts +++ b/extension/src-language-server/stpa/diagram/utils.ts @@ -34,10 +34,10 @@ import { isSystemConstraint, isSystemResponsibilities, isUCA, -} from "../../generated/ast"; -import { CSNode, PastaPort, STPANode } from "./stpa-interfaces"; -import { STPAAspect } from "./stpa-model"; -import { groupValue } from "./stpa-synthesis-options"; +} from "../../generated/ast.js"; +import { CSNode, PastaPort, STPANode } from "./stpa-interfaces.js"; +import { STPAAspect } from "./stpa-model.js"; +import { groupValue } from "./stpa-synthesis-options.js"; /** * Getter for the references contained in {@code node}. diff --git a/extension/src-language-server/stpa/ftaGeneration/fta-generation.ts b/extension/src-language-server/stpa/ftaGeneration/fta-generation.ts index 853dfe82..086af6fb 100644 --- a/extension/src-language-server/stpa/ftaGeneration/fta-generation.ts +++ b/extension/src-language-server/stpa/ftaGeneration/fta-generation.ts @@ -17,8 +17,8 @@ import type { Reference } from "langium"; import { LangiumSprottySharedServices } from "langium-sprotty"; -import { Children, Component, Hazard, LossScenario, Model, ModelFTA, OR, TopEvent } from "../../generated/ast"; -import { getModel } from "../../utils"; +import { Children, Component, Hazard, LossScenario, Model, ModelFTA, OR, TopEvent } from "../../generated/ast.js"; +import { getModel } from "../../utils.js"; /** * Create the fault trees for an stpa model as ASTs diff --git a/extension/src-language-server/stpa/message-handler.ts b/extension/src-language-server/stpa/message-handler.ts index ce6d51d5..57b4d859 100644 --- a/extension/src-language-server/stpa/message-handler.ts +++ b/extension/src-language-server/stpa/message-handler.ts @@ -19,15 +19,15 @@ import { DocumentState } from "langium"; import { LangiumSprottySharedServices } from "langium-sprotty"; import { TextDocumentContentChangeEvent } from "vscode"; import { Connection, URI } from "vscode-languageserver"; -import { diagramSizes } from "../diagram-server"; -import { serializeFTAAST } from "../fta/utils"; -import { updateValidationChecks } from "../utils"; -import { setCurrentCursorOffset } from "./diagram/utils"; -import { createFaultTrees } from "./ftaGeneration/fta-generation"; -import { generateLTLFormulae } from "./modelChecking/model-checking"; -import { createResultData } from "./result-report/result-generator"; -import { StpaServices } from "./stpa-module"; -import { getControlActions } from "./utils"; +import { diagramSizes } from "../diagram-server.js"; +import { serializeFTAAST } from "../fta/utils.js"; +import { updateValidationChecks } from "../utils.js"; +import { setCurrentCursorOffset } from "./diagram/utils.js"; +import { createFaultTrees } from "./ftaGeneration/fta-generation.js"; +import { generateLTLFormulae } from "./modelChecking/model-checking.js"; +import { createResultData } from "./result-report/result-generator.js"; +import { StpaServices } from "./stpa-module.js"; +import { getControlActions } from "./utils.js"; let lastUri: URI; diff --git a/extension/src-language-server/stpa/modelChecking/model-checking.ts b/extension/src-language-server/stpa/modelChecking/model-checking.ts index 69a554e8..bf66c798 100644 --- a/extension/src-language-server/stpa/modelChecking/model-checking.ts +++ b/extension/src-language-server/stpa/modelChecking/model-checking.ts @@ -18,8 +18,8 @@ import { Reference } from "langium"; import { LangiumSprottySharedServices } from "langium-sprotty"; import { URI } from "vscode-uri"; -import { DCARule, Model, Rule, Variable, VariableValue, isRule } from "../../generated/ast"; -import { getModel } from "../../utils"; +import { DCARule, Model, Rule, Variable, VariableValue, isRule } from "../../generated/ast.js"; +import { getModel } from "../../utils.js"; /** * Respresents an LTL formula. diff --git a/extension/src-language-server/stpa/result-report/result-generator.ts b/extension/src-language-server/stpa/result-report/result-generator.ts index 31c48e64..cc28fb65 100644 --- a/extension/src-language-server/stpa/result-report/result-generator.ts +++ b/extension/src-language-server/stpa/result-report/result-generator.ts @@ -28,9 +28,9 @@ import { SafetyConstraint, SystemConstraint, UCA -} from "../../generated/ast"; -import { getModel } from "../../utils"; -import { StpaComponent, StpaResult, UCA_TYPE } from "../utils"; +} from "../../generated/ast.js"; +import { getModel } from "../../utils.js"; +import { StpaComponent, StpaResult, UCA_TYPE } from "../utils.js"; /** * Creates the STPA result data. diff --git a/extension/src-language-server/stpa/result-report/svg-generator.ts b/extension/src-language-server/stpa/result-report/svg-generator.ts index 3eb77a6c..1bc9c3c5 100644 --- a/extension/src-language-server/stpa/result-report/svg-generator.ts +++ b/extension/src-language-server/stpa/result-report/svg-generator.ts @@ -15,9 +15,9 @@ * SPDX-License-Identifier: EPL-2.0 */ -import { SetSynthesisOptionsAction } from "../../options/actions"; -import { labelManagementValue } from '../../synthesis-options'; -import { StpaSynthesisOptions } from "../diagram/stpa-synthesis-options"; +import { SetSynthesisOptionsAction } from "../../options/actions.js"; +import { labelManagementValue } from '../../synthesis-options.js'; +import { StpaSynthesisOptions } from "../diagram/stpa-synthesis-options.js"; /* the paths for the several diagrams of the STPA aspects */ export const SVG_PATH = "/images"; diff --git a/extension/src-language-server/stpa/services/ID-enforcer.ts b/extension/src-language-server/stpa/services/ID-enforcer.ts index 2c4f6625..a1b14f73 100644 --- a/extension/src-language-server/stpa/services/ID-enforcer.ts +++ b/extension/src-language-server/stpa/services/ID-enforcer.ts @@ -33,10 +33,10 @@ import { Rule, SafetyConstraint, SystemConstraint, - SystemResponsibilities -} from "../../generated/ast"; -import { StpaServices } from "../stpa-module"; -import { collectElementsWithSubComps, elementWithName, elementWithRefs } from "../utils"; + SystemResponsibilities, +} from "../../generated/ast.js"; +import { StpaServices } from "../stpa-module.js"; +import { collectElementsWithSubComps, elementWithName, elementWithRefs } from "../utils.js"; /** * Default prefixes for the different STPA aspects. @@ -100,7 +100,7 @@ export class IDEnforcer { } // update current document information this.currentUri = uri; - this.currentDocument = this.stpaServices.shared.workspace.LangiumDocuments.getOrCreateDocument( + this.currentDocument = this.stpaServices.shared.workspace.LangiumDocuments.getDocument( uri as any ) as LangiumDocument; @@ -167,11 +167,15 @@ export class IDEnforcer { if (modifiedElement && modifiedElement.$cstNode && modifiedElement.name !== prefix + index) { // calculate the range of the ID of the modified element const range = modifiedElement.$cstNode.range; - this.fixHazardRange(range, prefix, modifiedElement); - range.end.character = range.start.character + modifiedElement.name.length; - range.end.line = range.start.line; + const newRange = { + start: { line: range.start.line, character: range.start.character }, + end: { line: range.start.line, character: range.start.character + modifiedElement.name.length }, + }; + this.fixHazardRange(newRange, prefix, modifiedElement); + newRange.end.character = newRange.start.character + modifiedElement.name.length; + newRange.end.line = newRange.start.line; // create the edit - const modifiedElementEdit = TextEdit.replace(range, prefix + index); + const modifiedElementEdit = TextEdit.replace(newRange, prefix + index); edits.push(modifiedElementEdit); } return edits; diff --git a/extension/src-language-server/stpa/services/stpa-completion-provider.ts b/extension/src-language-server/stpa/services/stpa-completion-provider.ts index 939f2c22..9558cf71 100644 --- a/extension/src-language-server/stpa/services/stpa-completion-provider.ts +++ b/extension/src-language-server/stpa/services/stpa-completion-provider.ts @@ -15,27 +15,27 @@ * SPDX-License-Identifier: EPL-2.0 */ +import { MaybePromise } from "langium"; import { CompletionAcceptor, CompletionContext, CompletionValueItem, DefaultCompletionProvider, - MaybePromise, NextFeature, -} from "langium"; +} from "langium/lsp"; import { CompletionItemKind } from "vscode-languageserver"; import { Context, ControllerConstraint, - isModel, - isVerticalEdge, LossScenario, Model, Node, Rule, UCA, VerticalEdge, -} from "../../generated/ast"; + isModel, + isVerticalEdge, +} from "../../generated/ast.js"; /** * Generates UCA text for loss scenarios by providing an additional completion item. @@ -56,7 +56,7 @@ export class STPACompletionProvider extends DefaultCompletionProvider { ): MaybePromise { super.completionFor(context, next, acceptor); if (this.enabled) { - this.completionForSystemComponent(next, acceptor); + this.completionForSystemComponent(context, next, acceptor); this.completionForScenario(context, next, acceptor); this.completionForUCA(context, next, acceptor); this.completionForUCARule(context, next, acceptor); @@ -116,7 +116,7 @@ export class STPACompletionProvider extends DefaultCompletionProvider { }); // add the generated text as completion item - acceptor({ + acceptor(context, { label: "Generate Constraints for the UCAs", kind: CompletionItemKind.Snippet, insertText: generatedText, @@ -129,10 +129,12 @@ export class STPACompletionProvider extends DefaultCompletionProvider { /** * Adds a completion item for generating a system component if the current context is a system component. + * @param context The completion context. * @param next The next feature of the current rule to be called. * @param acceptor The completion acceptor to add the completion items. */ - protected completionForSystemComponent(next: NextFeature, acceptor: CompletionAcceptor): void { + protected completionForSystemComponent( + context: CompletionContext, next: NextFeature, acceptor: CompletionAcceptor): void { if (next.type === Node && next.property === "name") { const generatedText = `Comp { hierarchyLevel 0 @@ -144,7 +146,7 @@ export class STPACompletionProvider extends DefaultCompletionProvider { feedback { } }`; - acceptor({ + acceptor(context, { label: "Generate System Component", kind: CompletionItemKind.Text, insertText: generatedText, @@ -163,15 +165,15 @@ export class STPACompletionProvider extends DefaultCompletionProvider { protected completionForUCARule(context: CompletionContext, next: NextFeature, acceptor: CompletionAcceptor): void { if ((context.node?.$type === Rule || next.type === Rule) && next.property === "name") { const templateRuleItem = this.generateTemplateRuleItem(); - acceptor(templateRuleItem); + acceptor(context, templateRuleItem); const model = context.node?.$type === Model ? context.node : context.node?.$container; if (isModel(model)) { const controlActions = this.collectControlActions(model); const rulesForEverythingItem = this.generateRulesForEverythingItem(controlActions); - acceptor(rulesForEverythingItem); + acceptor(context, rulesForEverythingItem); const ruleForSpecificControlActionItems = this.generateRuleForSpecificControlActionItems(controlActions); - ruleForSpecificControlActionItems.forEach(item => acceptor(item)); + ruleForSpecificControlActionItems.forEach(item => acceptor(context, item)); } } } @@ -302,7 +304,7 @@ export class STPACompletionProvider extends DefaultCompletionProvider { ); if (generatedItems.length > 0) { - generatedItems.forEach(item => acceptor(item)); + generatedItems.forEach(item => acceptor(context, item)); } } } @@ -386,7 +388,7 @@ export class STPACompletionProvider extends DefaultCompletionProvider { if (context.node?.$type === LossScenario && next.property === "description") { const generatedText = this.generateScenarioForUCA(context.node as LossScenario); if (generatedText !== "") { - acceptor({ + acceptor(context, { label: "Generate UCA Text", kind: CompletionItemKind.Text, insertText: generatedText, @@ -401,7 +403,7 @@ export class STPACompletionProvider extends DefaultCompletionProvider { if (isModel(model)) { const generatedBasicScenariosText = this.generateBasicScenarios(model); if (generatedBasicScenariosText !== "") { - acceptor({ + acceptor(context, { label: "Generate Basic Scenarios", kind: CompletionItemKind.Snippet, insertText: generatedBasicScenariosText, diff --git a/extension/src-language-server/stpa/services/stpa-fold-provider.ts b/extension/src-language-server/stpa/services/stpa-fold-provider.ts index 11d5a97d..e05a2e56 100644 --- a/extension/src-language-server/stpa/services/stpa-fold-provider.ts +++ b/extension/src-language-server/stpa/services/stpa-fold-provider.ts @@ -17,14 +17,16 @@ import { AstNode, + AstUtils, + LangiumDocument +} from "langium"; +import { DefaultFoldingRangeProvider, FoldingRangeAcceptor, - LangiumDocument, - streamAllContents, -} from "langium"; +} from "langium/lsp"; import { Model -} from "../../generated/ast"; +} from "../../generated/ast.js"; export class STPAFoldingRangeProvider extends DefaultFoldingRangeProvider { @@ -33,7 +35,7 @@ export class STPAFoldingRangeProvider extends DefaultFoldingRangeProvider { const root = document.parseResult?.value; if (root) { if (this.shouldProcessContent(root)) { - const treeIterator = streamAllContents(root).iterator(); + const treeIterator = AstUtils.streamAllContents(root).iterator(); let result: IteratorResult; // save the type of the previous top-most node let previousType = ""; diff --git a/extension/src-language-server/stpa/services/stpa-scopeProvider.ts b/extension/src-language-server/stpa/services/stpa-scopeProvider.ts index f49f6f02..c6fbe00b 100644 --- a/extension/src-language-server/stpa/services/stpa-scopeProvider.ts +++ b/extension/src-language-server/stpa/services/stpa-scopeProvider.ts @@ -18,13 +18,13 @@ import { AstNode, AstNodeDescription, + AstUtils, DefaultScopeProvider, EMPTY_SCOPE, PrecomputedScopes, ReferenceInfo, Scope, Stream, - getDocument, stream, } from "langium"; import { @@ -59,8 +59,8 @@ import { isSystemConstraint, isSystemResponsibilities, isVerticalEdge -} from "../../generated/ast"; -import { StpaServices } from "../stpa-module"; +} from "../../generated/ast.js"; +import { StpaServices } from "../stpa-module.js"; export class StpaScopeProvider extends DefaultScopeProvider { /* the types of the different aspects */ @@ -81,7 +81,7 @@ export class StpaScopeProvider extends DefaultScopeProvider { const referenceType = this.reflection.getReferenceType(context); const node = context.container; - const precomputed = getDocument(node).precomputedScopes; + const precomputed = AstUtils.getDocument(node).precomputedScopes; // get the root container which should be the Model let model = node.$container; while (model && !isModel(model)) { diff --git a/extension/src-language-server/stpa/services/stpa-validator.ts b/extension/src-language-server/stpa/services/stpa-validator.ts index 8f0df350..e0f46531 100644 --- a/extension/src-language-server/stpa/services/stpa-validator.ts +++ b/extension/src-language-server/stpa/services/stpa-validator.ts @@ -15,7 +15,7 @@ * SPDX-License-Identifier: EPL-2.0 */ -import { Reference, ValidationAcceptor, ValidationChecks, ValidationRegistry } from "langium"; +import { Reference, ValidationAcceptor, ValidationChecks } from "langium"; import { Position } from "vscode-languageserver-types"; import { Context, @@ -34,29 +34,24 @@ import { SystemConstraint, isModel, isRule, -} from "../../generated/ast"; -import { StpaServices } from "../stpa-module"; -import { UCA_TYPE, collectElementsWithSubComps, elementWithName, elementWithRefs } from "../utils"; - -/** - * Registry for validation checks. - */ -export class StpaValidationRegistry extends ValidationRegistry { - constructor(services: StpaServices) { - super(services); - const validator = services.validation.StpaValidator; - const checks: ValidationChecks = { - Model: validator.checkModel, - Hazard: validator.checkHazard, - SystemConstraint: validator.checkSystemConstraint, - Responsibility: validator.checkResponsibility, - ControllerConstraint: validator.checkControllerConstraints, - HazardList: validator.checkHazardList, - Node: validator.checkNode, - Graph: validator.checkControlStructure, - }; - this.register(checks, validator); - } +} from "../../generated/ast.js"; +import { StpaServices } from "../stpa-module.js"; +import { UCA_TYPE, collectElementsWithSubComps, elementWithName, elementWithRefs } from "../utils.js"; + +export function registerValidationChecks(services: StpaServices): void { + const registry = services.validation.ValidationRegistry; + const validator = services.validation.StpaValidator; + const checks: ValidationChecks = { + Model: validator.checkModel, + Hazard: validator.checkHazard, + SystemConstraint: validator.checkSystemConstraint, + Responsibility: validator.checkResponsibility, + ControllerConstraint: validator.checkControllerConstraints, + HazardList: validator.checkHazardList, + Node: validator.checkNode, + Graph: validator.checkControlStructure, + }; + registry.register(checks, validator); } /** @@ -406,8 +401,14 @@ export class StpaValidator { // a top-level hazard should reference loss(es) if (isModel(hazard.$container) && hazard.refs.length === 0) { const range = hazard.$cstNode?.range; - if (range) { - range.start.character = range.end.character - 1; + const newRange = range + ? { + start: { line: range.start.line, character: range.start.character }, + end: { line: range.end.line, character: range.end.character }, + } + : undefined; + if (newRange) { + newRange.start.character = newRange.end.character - 1; } accept("warning", "A hazard should reference loss(es)", { node: hazard, range: range }); } diff --git a/extension/src-language-server/stpa/stpa-completion-provider.ts b/extension/src-language-server/stpa/stpa-completion-provider.ts deleted file mode 100644 index e1439882..00000000 --- a/extension/src-language-server/stpa/stpa-completion-provider.ts +++ /dev/null @@ -1,568 +0,0 @@ -/* - * KIELER - Kiel Integrated Environment for Layout Eclipse RichClient - * - * http://rtsys.informatik.uni-kiel.de/kieler - * - * Copyright 2024 by - * + Kiel University - * + Department of Computer Science - * + Real-Time and Embedded Systems Group - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * SPDX-License-Identifier: EPL-2.0 - */ - -import { - CompletionAcceptor, - CompletionContext, - CompletionValueItem, - DefaultCompletionProvider, - MaybePromise, - NextFeature, -} from "langium"; -import { CompletionItemKind } from "vscode-languageserver"; -import { - Context, - ControllerConstraint, - isModel, - isVerticalEdge, - LossScenario, - Model, - Node, - Rule, - UCA, - VerticalEdge, -} from "../generated/ast"; - -/** - * Generates UCA text for loss scenarios by providing an additional completion item. - */ -export class STPACompletionProvider extends DefaultCompletionProvider { - protected enabled: boolean = true; - - /** - * Overrides the default completionFor method to provide an additional completion item for generating UCA text in loss scenarios. - * @param context The completion context. - * @param next The next feature of the current rule to be called. - * @param acceptor The completion acceptor to add the completion items. - */ - protected completionFor( - context: CompletionContext, - next: NextFeature, - acceptor: CompletionAcceptor - ): MaybePromise { - super.completionFor(context, next, acceptor); - if (this.enabled) { - this.completionForSystemComponent(next, acceptor); - this.completionForScenario(context, next, acceptor); - this.completionForUCA(context, next, acceptor); - this.completionForUCARule(context, next, acceptor); - this.completionForControllerConstraints(context, next, acceptor); - } - } - - /** - * Adds a completion item for generating controller constraints if the current context is a controller constraint. - * @param context The completion context. - * @param next The next feature of the current rule to be called. - * @param acceptor The completion acceptor to add the completion items. - */ - protected completionForControllerConstraints( - context: CompletionContext, - next: NextFeature, - acceptor: CompletionAcceptor - ): void { - if (next.type === ControllerConstraint && next.property === "name") { - // get the model for the current controller constraint - let model = context.node; - while (model && !isModel(model)) { - model = model.$container; - } - if (isModel(model)) { - let generatedText = ``; - model.rules.forEach(rule => { - const system = rule.system.ref?.label ?? rule.system.$refText; - const controlAction = `the control action '${rule.action.ref?.label}'`; - rule.contexts.forEach(context => { - // create constraint text for each context - generatedText += `C "${system}`; - const contextText = this.createContextText(context, true); - switch (rule.type) { - case "not-provided": - generatedText += ` must provide ${controlAction}, while ${contextText}.`; - break; - case "provided": - generatedText += ` must not provided ${controlAction}, while ${contextText}.`; - break; - case "too-late": - generatedText += ` must provide ${controlAction} in time, while ${contextText}.`; - break; - case "too-early": - generatedText += ` must not provide ${controlAction} before ${contextText}.`; - break; - case "stopped-too-soon": - generatedText += ` must not stop ${controlAction} too soon, while ${contextText}.`; - break; - case "applied-too-long": - generatedText += ` must not apply ${controlAction} too long, while ${contextText}.`; - break; - } - // add reference to the UCA - generatedText += `" [${context.name}]\n`; - }); - }); - - // add the generated text as completion item - acceptor({ - label: "Generate Constraints for the UCAs", - kind: CompletionItemKind.Snippet, - insertText: generatedText, - detail: "Inserts a controller constraint for each UCA.", - sortText: "0", - }); - } - } - } - - /** - * Adds a completion item for generating a system component if the current context is a system component. - * @param next The next feature of the current rule to be called. - * @param acceptor The completion acceptor to add the completion items. - */ - protected completionForSystemComponent(next: NextFeature, acceptor: CompletionAcceptor): void { - if (next.type === Node && next.property === "name") { - const generatedText = `Comp { - hierarchyLevel 0 - label "Component" - processModel { - } - controlActions { - } - feedback { - } -}`; - acceptor({ - label: "Generate System Component", - kind: CompletionItemKind.Text, - insertText: generatedText, - detail: "Inserts a system component.", - sortText: "0", - }); - } - } - - /** - * Adds completion items for generating rules for UCAs if the current context is a rule. - * @param context The completion context. - * @param next The next feature of the current rule to be called. - * @param acceptor The completion acceptor to add the completion items. - */ - protected completionForUCARule(context: CompletionContext, next: NextFeature, acceptor: CompletionAcceptor): void { - if ((context.node?.$type === Rule || next.type === Rule) && next.property === "name") { - const templateRuleItem = this.generateTemplateRuleItem(); - acceptor(templateRuleItem); - const model = context.node?.$type === Model ? context.node : context.node?.$container; - if (isModel(model)) { - const controlActions = this.collectControlActions(model); - const rulesForEverythingItem = this.generateRulesForEverythingItem(controlActions); - acceptor(rulesForEverythingItem); - const ruleForSpecificControlActionItems = - this.generateRuleForSpecificControlActionItems(controlActions); - ruleForSpecificControlActionItems.forEach(item => acceptor(item)); - } - } - } - - /** - * Determines all control actions in the given model. - * @param model The model for which the control actions should be collected. - */ - protected collectControlActions(model: Model): VerticalEdge[] { - const actions: VerticalEdge[] = []; - model.controlStructure?.nodes.forEach(node => { - actions.push(...this.getControlActions(node)); - }); - return actions; - } - - /** - * Gets all control actions for the given node and its children. - * @param node The node for which the control actions should be collected. - * @returns the control actions for the given node and its children. - */ - protected getControlActions(node: Node): VerticalEdge[] { - const actions = node.actions; - node.children.forEach(child => { - actions.push(...this.getControlActions(child)); - }); - return actions; - } - - /** - * Creates for each control action a completion item for generating a rule for this control action. - * @param controlActions The control actions for which the rules should be generated. - */ - protected generateRuleForSpecificControlActionItems(controlActions: VerticalEdge[]): CompletionValueItem[] { - const items: CompletionValueItem[] = []; - let counter = 3; - for (const controlAction of controlActions) { - for (const action of controlAction.comms) { - const item: CompletionValueItem = { - label: `Generate a rule for ${controlAction.$container.name}.${action.name}`, - kind: CompletionItemKind.Snippet, - insertText: `RL { - controlAction: ${controlAction.$container.name}.${action.name} - type: - contexts: { - } -}`, - detail: `Inserts a rule for ${controlAction.$container.name}.${action.name} with missing content.`, - sortText: `${counter}`, - }; - items.push(item); - counter++; - } - } - return items; - } - - /** - * Creates a completion item for generating a rule for every possible control action and type combination. - * @param controlActions The control actions for which the rules should be generated. - * @returns a completion item for generating a rule for every possible control action and type combination. - */ - protected generateRulesForEverythingItem(controlActions: VerticalEdge[]): CompletionValueItem { - let insertText = ``; - let counter = 0; - for (const controlAction of controlActions) { - for (const action of controlAction.comms) { - for (const type of [ - "not-provided", - "provided", - "too-early", - "too-late", - "wrong-time", - "applied-too-long", - "stopped-too-soon", - ]) { - insertText += `RL${counter} { - controlAction: ${controlAction.$container.name}.${action.name} - type: ${type} - contexts: { - } -} -`; - counter++; - } - } - } - const item: CompletionValueItem = { - label: `Generate rules for every control action and type combination`, - kind: CompletionItemKind.Snippet, - insertText: insertText, - detail: "Inserts for every control action rules for every UCA type.", - sortText: "1", - }; - return item; - } - - /** - * Creates a completion item for generating a template rule. - * @returns a completion item for generating a template rule. - */ - protected generateTemplateRuleItem(): CompletionValueItem { - return { - label: "Generate template Rule", - kind: CompletionItemKind.Snippet, - insertText: `RL { - controlAction: - type: - contexts: { - } -}`, - detail: "Inserts a rule with missing content.", - sortText: "0", - }; - } - - /** - * Adds completion items for generating UCA text for a UCA if the current context is a UCA. - * @param context The completion context. - * @param next The next feature of the current rule to be called. - * @param acceptor The completion acceptor to add the completion items. - */ - protected completionForUCA(context: CompletionContext, next: NextFeature, acceptor: CompletionAcceptor): void { - if (context.node?.$type === UCA && next.property === "description") { - const generatedItems = this.generateTextForUCAWithPlainText( - context.node as UCA, - context.node.$containerProperty - ); - - if (generatedItems.length > 0) { - generatedItems.forEach(item => acceptor(item)); - } - } - } - - /** - * Generates completion items for the given UCA {@code uca}. - * @param uca The UCA for which the completion items should be generated. - * @param property The property in which the UCA is contained. Should be one of "notProvidingUcas", "providingUcas", "wrongTimingUcas", or "continousUcas". - * @returns completion items for the given UCA. - */ - protected generateTextForUCAWithPlainText(uca: UCA, property?: string): CompletionValueItem[] { - const actionUca = uca.$container; - const system = actionUca.system.ref?.label ?? actionUca.system.$refText; - let controlAction = `the control action '${actionUca.action.ref?.label}'`; - const parent = actionUca.action.ref?.$container; - if (isVerticalEdge(parent)) { - controlAction += ` to ${parent.target.ref?.label ?? parent.target.$refText}`; - } - switch (property) { - case "notProvidingUcas": - const notProvidedItem = { - label: "Generate not provided UCA Text", - kind: CompletionItemKind.Text, - insertText: `${system} did not provide ${controlAction}, TODO`, - detail: "Inserts the starting text for this UCA.", - sortText: "0", - }; - return [notProvidedItem]; - case "providingUcas": - const providedItem = { - label: "Generate provided UCA Text", - kind: CompletionItemKind.Text, - insertText: `${system} provided ${controlAction}, TODO`, - detail: "Inserts the starting text for this UCA.", - sortText: "0", - }; - return [providedItem]; - case "wrongTimingUcas": - const tooEarlyItem = { - label: "Generate too-early UCA Text", - kind: CompletionItemKind.Text, - insertText: `${system} provided ${controlAction} before TODO`, - detail: "Inserts the starting text for this UCA.", - sortText: "0", - }; - const tooLateItem = { - label: "Generate too-late UCA Text", - kind: CompletionItemKind.Text, - insertText: `${system} provided ${controlAction} after TODO`, - detail: "Inserts the starting text for this UCA.", - sortText: "1", - }; - return [tooEarlyItem, tooLateItem]; - case "continousUcas": - const stoppedTooSoonItem = { - label: "Generate stopped-too-soon UCA Text", - kind: CompletionItemKind.Text, - insertText: `${system} stopped ${controlAction} before TODO`, - detail: "Inserts the starting text for this UCA.", - sortText: "0", - }; - const appliedTooLongItem = { - label: "Generate applied-too-long UCA Text", - kind: CompletionItemKind.Text, - insertText: `${system} still applied ${controlAction} after TODO`, - detail: "Inserts the starting text for this UCA.", - sortText: "1", - }; - return [stoppedTooSoonItem, appliedTooLongItem]; - } - return []; - } - - /** - * Adds a completion item for generating UCA text for a scenario and completion items to generate basic scenarios if the current context is a loss scenario. - * @param context The completion context. - * @param next The next feature of the current rule to be called. - * @param acceptor The completion acceptor to add the completion items. - */ - protected completionForScenario(context: CompletionContext, next: NextFeature, acceptor: CompletionAcceptor): void { - if (context.node?.$type === LossScenario && next.property === "description") { - const generatedText = this.generateScenarioForUCA(context.node as LossScenario); - if (generatedText !== "") { - acceptor({ - label: "Generate UCA Text", - kind: CompletionItemKind.Text, - insertText: generatedText, - detail: "Inserts the UCA text for this scenario.", - sortText: "0", - }); - } - } - if (next.type === LossScenario && next.property === "name") { - const model = - context.node?.$type === LossScenario ? context.node.$container : context.node?.$container?.$container; - if (isModel(model)) { - const generatedBasicScenariosText = this.generateBasicScenarios(model); - if (generatedBasicScenariosText !== "") { - acceptor({ - label: "Generate Basic Scenarios", - kind: CompletionItemKind.Snippet, - insertText: generatedBasicScenariosText, - detail: "Creates basic scenarios for all UCAs.", - sortText: "0", - }); - } - } - } - } - - /** - * Creates text for basic scenarios for all UCAs in the given {@code model}. - * @param model The model for which the basic scenarios should be generated. - * @returns the generated basic scenarios as text. - */ - protected generateBasicScenarios(model: Model): string { - let text = ``; - model.rules.forEach(rule => { - const system = rule.system.ref?.label ?? rule.system.$refText; - const controlAction = `the control action '${rule.action.ref?.label}'`; - rule.contexts.forEach(context => { - // add scenario for actuator/controlled process failure - let scenario = `${system}`; - const contextText = this.createContextText(context, false); - switch (rule.type) { - case "not-provided": - scenario += ` provided ${controlAction}, while ${contextText}, but it is not executed.`; - break; - case "provided": - scenario += ` not provided ${controlAction}, while ${contextText}, but it is executed.`; - break; - case "too-late": - scenario += ` provided ${controlAction} in time, while ${contextText}, but it is executed too late.`; - break; - case "too-early": - scenario += ` provided ${controlAction} in time, while ${contextText}, but it is already executed before.`; - break; - case "stopped-too-soon": - scenario += ` applied ${controlAction} long enough, while ${contextText}, but execution is stopped too soon.`; - break; - case "applied-too-long": - scenario += ` stopped ${controlAction} in time, while ${contextText}, but it is executed too long.`; - break; - } - text += `S for ${context.name} "${scenario} TODO"\n`; - // add scenarios for incorrect process model values - const scenarioStart = this.generateScenarioForUCAWithContextTable(context); - switch (rule.type) { - case "not-provided": - case "provided": - context.assignedValues.forEach(assignedValue => { - text += `S for ${context.name} "${scenarioStart} Because ${system} incorrectly believes that ${assignedValue.variable.$refText} is not ${assignedValue.value.$refText}. TODO"\n`; - }); - break; - case "too-late": - context.assignedValues.forEach(assignedValue => { - text += `S for ${context.name} "${scenarioStart} Because ${system} realized too late that ${assignedValue.variable.$refText} is ${assignedValue.value.$refText}. TODO"\n`; - }); - break; - case "stopped-too-soon": - context.assignedValues.forEach(assignedValue => { - text += `S for ${context.name} "${scenarioStart} Because ${system} incorrectly believes that ${assignedValue.variable.$refText} is not ${assignedValue.value.$refText} anymore. TODO"\n`; - }); - break; - case "applied-too-long": - context.assignedValues.forEach(assignedValue => { - text += `S for ${context.name} "${scenarioStart} Because ${system} realized too late that ${assignedValue.variable.$refText} is not ${assignedValue.value.$refText} anymore. TODO"\n`; - }); - break; - } - }); - }); - return text; - } - - /** - * Generates the UCA text for the given scenario {@code scenario}. - * If no UCA is reference an empty string is returned. - * @param scenario The scenario node for which the UCA text should be generated. - */ - protected generateScenarioForUCA(scenario: LossScenario): string { - const uca = scenario.uca?.ref; - if (uca) { - if (uca.$type === Context) { - return this.generateScenarioForUCAWithContextTable(uca) + " TODO"; - } else { - return this.generateScenarioForUCAWithPlainText(uca); - } - } - return ""; - } - - /** - * Generates a scenario text for a UCA defined with a context table. - * @param context The UCA context for which the scenario should be generated. - * @returns the generated scenario text. - */ - protected generateScenarioForUCAWithContextTable(context: Context): string { - const rule = context.$container; - const system = rule.system.ref?.label ?? rule.system.$refText; - let text = `${system}`; - const controlAction = `the control action '${rule.action.ref?.label}'`; - switch (rule.type) { - case "not-provided": - text += ` did not provide ${controlAction}`; - break; - case "provided": - text += ` provided ${controlAction}`; - break; - case "stopped-too-soon": - text += ` stopped ${controlAction} too soon`; - break; - case "applied-too-long": - text += ` applied ${controlAction} too long`; - break; - case "too-early": - text += ` provided ${controlAction} too early`; - break; - case "too-late": - text += ` provided ${controlAction} too late`; - break; - case "wrong-time": - text += ` provided ${controlAction} at the wrong time`; - break; - } - - text += `, while`; - text += this.createContextText(context, false); - text += "."; - - return text; - } - - /** - * Creates a text for the given context {@code context}. - * @param context The context for which the text should be generated. - * @param present If true the text is generated in present tense, otherwise in past tense. - * @returns the generated text. - */ - protected createContextText(context: Context, present: boolean): string { - let text = ``; - const tense = present ? "is" : "was"; - context.assignedValues.forEach((assignedValue, index) => { - if (index > 0) { - text += ","; - } - if (context.assignedValues.length > 1 && index === context.assignedValues.length - 1) { - text += " and"; - } - text += ` ${assignedValue.variable.$refText} ${tense} ${assignedValue.value.$refText}`; - }); - return text; - } - - /** - * Generates a scenario text for a UCA defined with plain text. - * @param uca The UCA for which the scenario should be generated. - * @returns the generated scenario text. - */ - protected generateScenarioForUCAWithPlainText(uca: UCA): string { - return `${uca.description} TODO`; - } -} diff --git a/extension/src-language-server/stpa/stpa-module.ts b/extension/src-language-server/stpa/stpa-module.ts index 63544046..a750dc7c 100644 --- a/extension/src-language-server/stpa/stpa-module.ts +++ b/extension/src-language-server/stpa/stpa-module.ts @@ -15,21 +15,22 @@ * SPDX-License-Identifier: EPL-2.0 */ -import ElkConstructor from "elkjs/lib/elk.bundled"; -import { Module, PartialLangiumServices } from "langium"; +const ElkConstructor = require('elkjs/lib/elk.bundled.js').default; +import { Module } from "langium"; import { LangiumSprottyServices, SprottyDiagramServices } from "langium-sprotty"; -import { DefaultElementFilter, ElkFactory, IElementFilter, ILayoutConfigurator } from "sprotty-elk/lib/elk-layout"; -import { LayoutEngine } from "../layout-engine"; -import { StpaDiagramSnippets } from "../snippets/stpa-snippets"; -import { ContextTableProvider } from "./contextTable/context-dataProvider"; -import { StpaDiagramGenerator } from "./diagram/diagram-generator"; -import { StpaLayoutConfigurator } from "./diagram/layout-config"; -import { StpaSynthesisOptions } from "./diagram/stpa-synthesis-options"; -import { IDEnforcer } from "./services/ID-enforcer"; -import { STPACompletionProvider } from "./services/stpa-completion-provider"; -import { STPAFoldingRangeProvider } from './services/stpa-fold-provider'; -import { StpaScopeProvider } from "./services/stpa-scopeProvider"; -import { StpaValidationRegistry, StpaValidator } from "./services/stpa-validator"; +import { PartialLangiumServices } from "langium/lsp"; +import { DefaultElementFilter, ElkFactory, IElementFilter, ILayoutConfigurator } from "sprotty-elk/lib/elk-layout.js"; +import { LayoutEngine } from '../layout-engine.js'; +import { StpaDiagramSnippets } from "../snippets/stpa-snippets.js"; +import { ContextTableProvider } from "./contextTable/context-dataProvider.js"; +import { StpaDiagramGenerator } from "./diagram/diagram-generator.js"; +import { StpaLayoutConfigurator } from "./diagram/layout-config.js"; +import { StpaSynthesisOptions } from "./diagram/stpa-synthesis-options.js"; +import { IDEnforcer } from "./services/ID-enforcer.js"; +import { STPACompletionProvider } from './services/stpa-completion-provider.js'; +import { STPAFoldingRangeProvider } from './services/stpa-fold-provider.js'; +import { StpaScopeProvider } from './services/stpa-scopeProvider.js'; +import { StpaValidator } from "./services/stpa-validator.js"; /** * Declaration of custom services - add your own service classes here. @@ -96,7 +97,6 @@ export const STPAModule: Module new StpaScopeProvider(services), }, validation: { - ValidationRegistry: services => new StpaValidationRegistry(services), StpaValidator: () => new StpaValidator(), }, layout: { diff --git a/extension/src-language-server/stpa/utils.ts b/extension/src-language-server/stpa/utils.ts index 0b341045..b60090e3 100644 --- a/extension/src-language-server/stpa/utils.ts +++ b/extension/src-language-server/stpa/utils.ts @@ -15,8 +15,8 @@ * SPDX-License-Identifier: EPL-2.0 */ -import { LangiumSharedServices } from "langium"; import { LangiumSprottySharedServices } from "langium-sprotty"; +import { LangiumSharedServices } from "langium/lsp"; import { Range } from "vscode-languageserver"; import { Command, @@ -37,8 +37,8 @@ import { SystemConstraint, UCA, Variable, -} from "../generated/ast"; -import { getModel } from "../utils"; +} from "../generated/ast.js"; +import { getModel } from "../utils.js"; export type leafElement = | Loss diff --git a/extension/src-language-server/synthesis-options.ts b/extension/src-language-server/synthesis-options.ts index e2d44a5e..c2c2e814 100644 --- a/extension/src-language-server/synthesis-options.ts +++ b/extension/src-language-server/synthesis-options.ts @@ -21,7 +21,7 @@ import { SynthesisOption, TransformationOptionType, ValuedSynthesisOption, -} from "./options/option-models"; +} from "./options/option-models.js"; const labelManagementID = "labelManagement"; const labelShorteningWidthID = "labelShorteningWidth"; diff --git a/extension/src-language-server/tsconfig.json b/extension/src-language-server/tsconfig.json new file mode 100644 index 00000000..4d6b139f --- /dev/null +++ b/extension/src-language-server/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "../pack/src-language-server", + "target": "ES2017", + "lib": [ + "es2019", + "dom" + ], + "module": "Node16", + "moduleResolution": "Node16", + }, + "exclude": ["../src-context-table"] +} diff --git a/extension/src-language-server/utils.ts b/extension/src-language-server/utils.ts index 16896b5f..25f137da 100644 --- a/extension/src-language-server/utils.ts +++ b/extension/src-language-server/utils.ts @@ -15,12 +15,13 @@ * SPDX-License-Identifier: EPL-2.0 */ -import { AstNode, LangiumSharedServices } from "langium"; +import { AstNode } from "langium"; import { IdCache, LangiumSprottySharedServices } from "langium-sprotty"; +import { LangiumSharedServices } from "langium/lsp"; import { SLabel } from "sprotty-protocol"; import { URI } from "vscode-uri"; -import { StpaValidator } from "./stpa/services/stpa-validator"; -import { labelManagementValue } from "./synthesis-options"; +import { StpaValidator } from "./stpa/services/stpa-validator.js"; +import { labelManagementValue } from "./synthesis-options.js"; /** * Determines the model for {@code uri}. @@ -33,7 +34,7 @@ export async function getModel( shared: LangiumSprottySharedServices | LangiumSharedServices ): Promise { const textDocuments = shared.workspace.LangiumDocuments; - const currentDoc = textDocuments.getOrCreateDocument(URI.parse(uri)); + const currentDoc = await textDocuments.getOrCreateDocument(URI.parse(uri)); return currentDoc.parseResult.value; } diff --git a/extension/src-webview/context-menu/context-menu-mouse-listener.ts b/extension/src-webview/context-menu/context-menu-mouse-listener.ts index 926e42f6..fbc95796 100644 --- a/extension/src-webview/context-menu/context-menu-mouse-listener.ts +++ b/extension/src-webview/context-menu/context-menu-mouse-listener.ts @@ -16,12 +16,12 @@ */ -import { ContextMenuMouseListener, SLabel, SModelElement } from "sprotty"; +import { ContextMenuMouseListener, SLabelImpl, SModelElementImpl } from "sprotty"; export class PastaContextMenuMouseListener extends ContextMenuMouseListener { - protected async showContextMenu(target: SModelElement, event: MouseEvent): Promise { - if (target instanceof SLabel) { + protected async showContextMenu(target: SModelElementImpl, event: MouseEvent): Promise { + if (target instanceof SLabelImpl) { super.showContextMenu(target.parent, event); } else { super.showContextMenu(target, event); diff --git a/extension/src-webview/context-menu/context-menu-provider.ts b/extension/src-webview/context-menu/context-menu-provider.ts index 2f68df95..db93feb3 100644 --- a/extension/src-webview/context-menu/context-menu-provider.ts +++ b/extension/src-webview/context-menu/context-menu-provider.ts @@ -16,14 +16,14 @@ */ import { injectable } from "inversify"; -import { IContextMenuItemProvider, LabeledAction, SModelRoot } from "sprotty"; +import { IContextMenuItemProvider, LabeledAction, SModelRootImpl } from "sprotty"; import { Point } from "sprotty-protocol"; -import { FTANode, FTA_GRAPH_TYPE, FTA_NODE_TYPE } from "../fta/fta-model"; import { CutSetAnalysisAction, MinimalCutSetAnalysisAction } from "../actions"; +import { FTANode, FTA_GRAPH_TYPE, FTA_NODE_TYPE } from "../fta/fta-model"; @injectable() export class ContextMenuProvider implements IContextMenuItemProvider { - getItems(root: Readonly, _lastMousePosition?: Point | undefined): Promise { + getItems(root: Readonly, _lastMousePosition?: Point): Promise { if (root.type === FTA_GRAPH_TYPE) { // find node that was clicked on let clickedNode: FTANode | undefined; diff --git a/extension/src-webview/di.config.ts b/extension/src-webview/di.config.ts index ec783bd8..7685d9b3 100644 --- a/extension/src-webview/di.config.ts +++ b/extension/src-webview/di.config.ts @@ -21,22 +21,22 @@ import "./css/diagram.css"; import { Container, ContainerModule } from "inversify"; import { ConsoleLogger, - HtmlRoot, + HtmlRootImpl, HtmlRootView, LogLevel, ModelViewer, - PreRenderedElement, + PreRenderedElementImpl, PreRenderedView, - SGraph, - SLabel, + SGraphImpl, + SLabelImpl, SLabelView, - SNode, + SNodeImpl, TYPES, configureCommand, configureModelElement, contextMenuModule, loadDefaultModules, - overrideViewerOptions, + overrideViewerOptions } from "sprotty"; import { SvgCommand } from "./actions"; import { ContextMenuProvider } from "./context-menu/context-menu-provider"; @@ -67,6 +67,7 @@ import { import { PastaModelViewer } from "./model-viewer"; import { optionsModule } from "./options/options-module"; import { sidebarModule } from "./sidebar"; +import { snippetModule } from './snippets/snippet-module'; import { CSEdge, CSNode, @@ -75,8 +76,11 @@ import { CS_INVISIBLE_SUBCOMPONENT_TYPE, CS_NODE_TYPE, DUMMY_NODE_TYPE, + EDGE_LABEL_TYPE, + EdgeLabel, HEADER_LABEL_TYPE, PARENT_TYPE, + PASTA_LABEL_TYPE, PORT_TYPE, PROCESS_MODEL_PARENT_NODE_TYPE, PastaPort, @@ -89,16 +93,15 @@ import { import { StpaMouseListener } from "./stpa/stpa-mouselistener"; import { CSNodeView, + EdgeLabelView, HeaderLabelView, IntermediateEdgeView, InvisibleNodeView, PolylineArrowEdgeView, PortView, STPAGraphView, - PastaLabelView, - STPANodeView, + STPANodeView } from "./stpa/stpa-views"; -import { snippetModule } from './snippets/snippet-module'; const pastaDiagramModule = new ContainerModule((bind, unbind, isBound, rebind) => { rebind(TYPES.ILogger).to(ConsoleLogger).inSingletonScope(); @@ -120,20 +123,20 @@ const pastaDiagramModule = new ContainerModule((bind, unbind, isBound, rebind) = // configure the diagram elements const context = { bind, unbind, isBound, rebind }; - configureModelElement(context, "label", SLabel, PastaLabelView); - configureModelElement(context, "label:xref", SLabel, PastaLabelView); - configureModelElement(context, HEADER_LABEL_TYPE, SLabel, HeaderLabelView); - configureModelElement(context, "html", HtmlRoot, HtmlRootView); - configureModelElement(context, "pre-rendered", PreRenderedElement, PreRenderedView); + configureModelElement(context, PASTA_LABEL_TYPE, SLabelImpl, SLabelView); + configureModelElement(context, EDGE_LABEL_TYPE, EdgeLabel, EdgeLabelView); + configureModelElement(context, HEADER_LABEL_TYPE, SLabelImpl, HeaderLabelView); + configureModelElement(context, "html", HtmlRootImpl, HtmlRootView); + configureModelElement(context, "pre-rendered", PreRenderedElementImpl, PreRenderedView); // STPA - configureModelElement(context, "graph", SGraph, STPAGraphView); - configureModelElement(context, CS_INVISIBLE_SUBCOMPONENT_TYPE, SNode, InvisibleNodeView); - configureModelElement(context, PROCESS_MODEL_PARENT_NODE_TYPE, SNode, InvisibleNodeView); + configureModelElement(context, "graph", SGraphImpl, STPAGraphView); + configureModelElement(context, CS_INVISIBLE_SUBCOMPONENT_TYPE, SNodeImpl, InvisibleNodeView); + configureModelElement(context, PROCESS_MODEL_PARENT_NODE_TYPE, SNodeImpl, InvisibleNodeView); configureModelElement(context, DUMMY_NODE_TYPE, CSNode, CSNodeView); configureModelElement(context, CS_NODE_TYPE, CSNode, CSNodeView); configureModelElement(context, STPA_NODE_TYPE, STPANode, STPANodeView); - configureModelElement(context, PARENT_TYPE, SNode, CSNodeView); + configureModelElement(context, PARENT_TYPE, SNodeImpl, CSNodeView); configureModelElement(context, STPA_EDGE_TYPE, STPAEdge, PolylineArrowEdgeView); configureModelElement(context, STPA_INTERMEDIATE_EDGE_TYPE, STPAEdge, IntermediateEdgeView); configureModelElement(context, CS_INTERMEDIATE_EDGE_TYPE, CSEdge, IntermediateEdgeView); @@ -152,7 +155,7 @@ const pastaDiagramModule = new ContainerModule((bind, unbind, isBound, rebind) = export function createPastaDiagramContainer(widgetId: string): Container { const container = new Container(); loadDefaultModules(container, { exclude: [contextMenuModule] }); - container.load(pastaContextMenuModule, pastaDiagramModule, sidebarModule, optionsModule, snippetModule); + container.load(pastaContextMenuModule, pastaDiagramModule, optionsModule, sidebarModule, snippetModule); overrideViewerOptions(container, { needsClientLayout: true, needsServerLayout: true, diff --git a/extension/src-webview/exportPostProcessor.ts b/extension/src-webview/exportPostProcessor.ts index 7db3ae38..6cf980af 100644 --- a/extension/src-webview/exportPostProcessor.ts +++ b/extension/src-webview/exportPostProcessor.ts @@ -17,7 +17,7 @@ import { inject, injectable } from "inversify"; import { VNode } from "snabbdom"; -import { IVNodePostprocessor, SModelElement, SModelRoot, TYPES } from "sprotty"; +import { IVNodePostprocessor, SModelElementImpl, SModelRootImpl, TYPES } from "sprotty"; import { Action } from "sprotty-protocol"; import { RequestSvgAction } from "./actions"; import { CustomSvgExporter } from "./exporter"; @@ -26,12 +26,12 @@ import { CustomSvgExporter } from "./exporter"; @injectable() export class SvgPostprocessor implements IVNodePostprocessor { - root: SModelRoot; + root: SModelRootImpl; @inject(TYPES.SvgExporter) protected svgExporter: CustomSvgExporter; - decorate(vnode: VNode, element: SModelElement): VNode { - if (element instanceof SModelRoot) { this.root = element; } + decorate(vnode: VNode, element: SModelElementImpl): VNode { + if (element instanceof SModelRootImpl) { this.root = element; } return vnode; } diff --git a/extension/src-webview/exporter.ts b/extension/src-webview/exporter.ts index 9b45d62a..4c83d2a9 100644 --- a/extension/src-webview/exporter.ts +++ b/extension/src-webview/exporter.ts @@ -16,7 +16,7 @@ */ import { injectable } from "inversify"; -import { SModelRoot, SNode, SvgExporter } from "sprotty"; +import { SModelRootImpl, SNodeImpl, SvgExporter } from "sprotty"; import { RequestAction } from "sprotty-protocol"; import { SvgAction } from "./actions"; @@ -27,7 +27,7 @@ export class CustomSvgExporter extends SvgExporter { * @param root The root of the model. * @param request The request action that triggered this method. */ - internalExport(root: SModelRoot, request?: RequestAction): void { + internalExport(root: SModelRootImpl, request?: RequestAction): void { if (typeof document !== "undefined") { const div = document.getElementById(this.options.hiddenDiv); if (div !== null && div.firstElementChild && div.firstElementChild.tagName === "svg") { @@ -35,8 +35,8 @@ export class CustomSvgExporter extends SvgExporter { const svg = this.createSvg(svgElement, root); const width = root.children.length > 1 - ? Math.max((root.children[0] as SNode).bounds.width, (root.children[1] as SNode).bounds.width) - : (root.children[0] as SNode).bounds.width; + ? Math.max((root.children[0] as SNodeImpl).bounds.width, (root.children[1] as SNodeImpl).bounds.width) + : (root.children[0] as SNodeImpl).bounds.width; this.actionDispatcher.dispatch(SvgAction.create(svg, width, request ? request.requestId : "")); } } diff --git a/extension/src-webview/feather-icons-snabbdom/feather-icons-snabbdom.tsx b/extension/src-webview/feather-icons-snabbdom/feather-icons-snabbdom.tsx index 7a18aa9d..1410b00d 100644 --- a/extension/src-webview/feather-icons-snabbdom/feather-icons-snabbdom.tsx +++ b/extension/src-webview/feather-icons-snabbdom/feather-icons-snabbdom.tsx @@ -26,15 +26,12 @@ import { html } from "sprotty"; // eslint-disable-line @typescript-eslint/no-unu * @param paramProps properties containing the ID of the feather icon. * @returns The SVG VNode resulting from this feather icon ID. */ -export function FeatherIcon(paramProps: { iconId: string }): VNode { - // Something goes wrong with snabbdom functional components as that the props are nested in an - // addional props property, which is removed here. - const props = (paramProps as any).props as { iconId: string } +export function FeatherIcon(props: { iconId: string }): VNode { // Imitates what feather would usually do, all attributes are put in the styles (if possible) and // the classes are written in as well. Missing are the xmlns and viewBox, but they do not seem to // be necessary anyways. - const classes: Record = {"feather": true} - classes[`feather-${props.iconId}`] = true + const classes: Record = {"feather": true}; + classes[`feather-${props.iconId}`] = true; return -} \ No newline at end of file + />; +} diff --git a/extension/src-webview/fta/fta-model.ts b/extension/src-webview/fta/fta-model.ts index 9df301b7..716a898d 100644 --- a/extension/src-webview/fta/fta-model.ts +++ b/extension/src-webview/fta/fta-model.ts @@ -16,11 +16,10 @@ */ import { - Point, - SEdge, - SGraph, - SNode, - SPort, + SEdgeImpl, + SGraphImpl, + SNodeImpl, + SPortImpl, connectableFeature, fadeFeature, hoverFeedbackFeature, @@ -28,6 +27,7 @@ import { popupFeature, selectFeature } from "sprotty"; +import { Point } from "sprotty-protocol"; /* fault tree element types */ export const FTA_NODE_TYPE = "node:fta"; @@ -40,7 +40,7 @@ export const FTA_PORT_TYPE = "port:fta"; /** * Node of a fault tree. */ -export class FTANode extends SNode { +export class FTANode extends SNodeImpl { static readonly DEFAULT_FEATURES = [ connectableFeature, selectFeature, @@ -63,14 +63,14 @@ export class FTANode extends SNode { /** * FTA Graph. */ -export class FTAGraph extends SGraph { +export class FTAGraph extends SGraphImpl { modelOrder?: boolean; } /** * Description node of a fault tree. */ -export class DescriptionNode extends SNode { +export class DescriptionNode extends SNodeImpl { static readonly DEFAULT_FEATURES = [ connectableFeature, selectFeature, @@ -88,13 +88,13 @@ export class DescriptionNode extends SNode { /** * Edge of a fault tree. */ -export class FTAEdge extends SEdge { +export class FTAEdge extends SEdgeImpl { notConnectedToSelectedCutSet?: boolean; junctionPoints?: Point[]; } /** Port representing a port in the FTA graph. */ -export class FTAPort extends SPort { +export class FTAPort extends SPortImpl { side?: PortSide; } diff --git a/extension/src-webview/fta/fta-views.tsx b/extension/src-webview/fta/fta-views.tsx index 6e2360ad..1b278fe1 100644 --- a/extension/src-webview/fta/fta-views.tsx +++ b/extension/src-webview/fta/fta-views.tsx @@ -18,7 +18,8 @@ /** @jsx svg */ import { injectable } from 'inversify'; import { VNode } from "snabbdom"; -import { IViewArgs, Point, PolylineEdgeView, RectangularNodeView, RenderingContext, SEdge, SGraph, SGraphView, svg } from 'sprotty'; +import { IViewArgs, PolylineEdgeView, RectangularNodeView, RenderingContext, SEdgeImpl, SGraphImpl, SGraphView, svg } from 'sprotty'; +import { Point } from "sprotty-protocol"; import { renderAndGate, renderEllipse, renderHorizontalLine, renderInhibitGate, renderKnGate, renderOrGate, renderRectangle, renderRoundedRectangle, renderVerticalLine } from "../views-rendering"; import { DescriptionNode, FTAEdge, FTAGraph, FTANode, FTAPort, FTA_DESCRIPTION_NODE_TYPE, FTA_EDGE_TYPE, FTA_NODE_TYPE, FTA_PORT_TYPE, FTNodeType } from './fta-model'; @@ -48,7 +49,7 @@ export class PolylineArrowEdgeViewFTA extends PolylineEdgeView { @injectable() export class FTAInvisibleEdgeView extends PolylineArrowEdgeViewFTA { - render(edge: Readonly, context: RenderingContext, args?: IViewArgs | undefined): VNode | undefined { + render(edge: Readonly, context: RenderingContext, args?: IViewArgs | undefined): VNode | undefined { return ; } } @@ -178,7 +179,7 @@ export class FTAGraphView extends SGraphView { * @param model The FTAGraph. * @param currentNode The current node, which should be handled including its targets. */ - protected highlightConnectedToCutSet(model: SGraph, currentNode: FTANode): void { + protected highlightConnectedToCutSet(model: SGraphImpl, currentNode: FTANode): void { for (const port of currentNode.children.filter(child => child.type === FTA_PORT_TYPE)) { const edge = model.children.find(child => child.type === FTA_EDGE_TYPE && (child as FTAEdge).sourceId === port.id) as FTAEdge; if (edge) { diff --git a/extension/src-webview/options/components/option-inputs.tsx b/extension/src-webview/options/components/option-inputs.tsx index 874894e6..899f228c 100644 --- a/extension/src-webview/options/components/option-inputs.tsx +++ b/extension/src-webview/options/components/option-inputs.tsx @@ -34,8 +34,6 @@ type CheckOptionProps = BaseProps; /** Render a labeled checkbox input. */ export function CheckOption(props: CheckOptionProps): VNode { - // The sprotty jsx function always puts an additional 'props' key around the element, requiring this hack. - props = (props as any as {props: CheckOptionProps}).props return (
@@ -60,8 +58,6 @@ interface ChoiceOptionProps extends BaseProps { /** Render a labeled group of radio inputs. */ export function ChoiceOption(props: ChoiceOptionProps): VNode { - // The sprotty jsx function always puts an additional 'props' key around the element, requiring this hack. - props = (props as any as {props: ChoiceOptionProps}).props return (
{props.name} @@ -73,7 +69,7 @@ export function ChoiceOption(props: ChoiceOptionProps): VNode { title={props.description ?? props.name} id={props.availableValuesLabels?.[i] ?? value} checked={props.value === value} - on-change={() => props.onChange(value)} + on-change={(): void => props.onChange(value)} /> {props.availableValuesLabels?.[i] ?? value} @@ -92,8 +88,6 @@ interface RangeOptionProps extends BaseProps { /** Render a labeled range slider as input. */ export function RangeOption(props: RangeOptionProps): VNode { - // The sprotty jsx function always puts an additional 'props' key around the element, requiring this hack. - props = (props as any as {props: RangeOptionProps}).props return (
); @@ -119,8 +113,6 @@ type TextOptionProps = BaseProps; /** Renders a labeled text input. */ export function TextOption(props: TextOptionProps): VNode { - // The sprotty jsx function always puts an additional 'props' key around the element, requiring this hack. - props = (props as any as {props: TextOptionProps}).props return (
@@ -130,7 +122,7 @@ export function TextOption(props: TextOptionProps): VNode { title={props.description ?? props.name} id={props.id} value={props.value} - on-change={(e: any) => props.onChange(e.target.value)} + on-change={(e: any): void => props.onChange(e.target.value)} />
); @@ -138,8 +130,6 @@ export function TextOption(props: TextOptionProps): VNode { /** Renders a named separator. */ export function SeparatorOption(props: { name: string; key?: string }): VNode { - // The sprotty jsx function always puts an additional 'props' key around the element, requiring this hack. - props = (props as any as {props: { name: string; key?: string }}).props return {props.name}; } @@ -150,9 +140,7 @@ interface CategoryOptionProps extends BaseProps { /** Renders a labeled options group. */ export function CategoryOption(props: CategoryOptionProps, children: VNode[]): VNode { - // The sprotty jsx function always puts an additional 'props' key around the element, requiring this hack. - props = (props as any as {props: CategoryOptionProps}).props - function handleToggle(e: any) { + function handleToggle(e: any): void { // The toggle event is also fired if the details are rendered default open. // To prevent an infinite toggle loop, change is only called if the state has really changed. if (e.target.open !== props.value) props.onChange(e.target.open); @@ -179,12 +167,10 @@ interface DropDownMenuProps extends BaseProps { /** Renders a dropdown menu. */ export function DropDownMenuOption(props: DropDownMenuProps): VNode { - // The sprotty jsx function always puts an additional 'props' key around the element, requiring this hack. - props = (props as any as {props: DropDownMenuProps}).props return (
- props.onChange(e.target.value)}> {props.availableValues.map((item) => (