state-machine-cat 10.1.2 → 10.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/README.md +1 -1
  2. package/bin/smcat.mjs +5 -0
  3. package/dist/commonjs/bundle.js +64 -64
  4. package/package.json +40 -69
  5. package/src/cli/actions.mjs +9 -0
  6. package/src/cli/file-name-to-stream.mjs +5 -6
  7. package/src/cli/make-description.mjs +9 -0
  8. package/src/cli/normalize.mjs +100 -47
  9. package/src/cli/validations.mjs +27 -1
  10. package/src/index-node.mjs +15 -47
  11. package/src/index.mjs +9 -9
  12. package/src/options.mjs +19 -13
  13. package/src/parse/index.mjs +13 -0
  14. package/src/parse/parser-helpers.mjs +70 -6
  15. package/src/parse/scxml/index.mjs +72 -20
  16. package/src/parse/scxml/normalize-machine.mjs +27 -21
  17. package/src/render/dot/README.md +12 -0
  18. package/src/render/dot/attributebuilder.mjs +7 -0
  19. package/src/render/dot/counter.mjs +13 -0
  20. package/src/render/dot/index.mjs +66 -28
  21. package/src/render/dot/render-dot-from-ast.js +37 -15
  22. package/src/render/dot/state-transformers.mjs +4 -2
  23. package/src/render/dot/utl.mjs +20 -0
  24. package/src/render/index-node.mjs +9 -3
  25. package/src/render/index.mjs +3 -3
  26. package/src/render/scjson/index.mjs +6 -0
  27. package/src/render/scjson/make-valid-xml-name.mjs +7 -1
  28. package/src/render/scxml/index.mjs +2 -0
  29. package/src/render/smcat/index.js +48 -14
  30. package/src/render/vector/dot-to-vector-native.mjs +1 -1
  31. package/src/render/vector/vector-native-dot-with-fallback.mjs +3 -2
  32. package/src/render/vector/vector-with-viz-js.mjs +3 -2
  33. package/src/state-machine-model.mjs +46 -4
  34. package/src/transform/desugar.mjs +67 -18
  35. package/src/transform/utl.mjs +8 -0
  36. package/src/version.mjs +1 -1
  37. package/types/state-machine-cat.d.ts +41 -14
@@ -1,52 +1,18 @@
1
+ // @ts-check
1
2
  import options from "./options.mjs";
2
3
  import parse from "./parse/index.mjs";
3
4
  import desugar from "./transform/desugar.mjs";
4
5
  import getRenderFunction from "./render/index-node.mjs";
5
6
  import { version } from "./version.mjs";
6
7
 
7
- const KNOWN_OPTIONS = [
8
- "outputType",
9
- "inputType",
10
- "engine",
11
- "direction",
12
- "dotNodeAttrs",
13
- "dotEdgeAttrs",
14
- "desugar",
15
- ];
16
-
17
- function isKnownOption(pKnownOptions) {
18
- return (pCandidateString) => pKnownOptions.includes(pCandidateString);
19
- }
20
-
21
- /**
22
- * Remove all attributes from the input object (which'd typically be
23
- * originating from commander) that are not known options options so
24
- * a clean object can be passed through.
25
- *
26
- * @param {any} pOptions - an options object e.g. as output from commander
27
- * @param {string[]} pKnownOptions - a list of known options
28
- * @return {any} - an options object that only contains stuff we care about
29
- */
30
- function ejectUnknownOptions(pOptions, pKnownOptions) {
31
- return Object.keys(pOptions)
32
- .filter(isKnownOption(pKnownOptions))
33
- .reduce((pAll, pKey) => {
34
- // eslint-disable-next-line security/detect-object-injection
35
- pAll[pKey] = pOptions[pKey];
36
- return pAll;
37
- }, {});
38
- }
39
-
40
8
  export default {
41
9
  /**
42
- * Translates the input script to an outputscript.
10
+ * Translates the input script to an output-script.
43
11
  *
44
- * @param {string} pScript The script to translate
45
- * @param {object} pOptions options influencing parsing & rendering.
46
- * See below for the complete list.
47
- * @return {string|void} nothing if a callback was passed, the
48
- * string with the rendered content if
49
- * no callback was passed and no error was found
12
+ * @param {string|import("../types/state-machine-cat").IStateMachine} pScript
13
+ * The script to translate
14
+ * @param {import("../types/state-machine-cat").IRenderOptions} pOptions
15
+ * options influencing parsing & rendering.
50
16
  * @throws {Error} if an error occurred and no callback
51
17
  * function was passed: the error
52
18
  *
@@ -54,13 +20,15 @@ export default {
54
20
  *
55
21
  */
56
22
  render(pScript, pOptions) {
57
- const lOptions = ejectUnknownOptions(pOptions, KNOWN_OPTIONS);
58
- const lAST = parse.getAST(pScript, lOptions);
59
- const lDesugar = options.getOptionValue(lOptions, "desugar");
23
+ const lStateMachine = parse.getAST(pScript, pOptions);
24
+ const lDesugar = options.getOptionValue(pOptions, "desugar");
60
25
 
61
- return getRenderFunction(options.getOptionValue(lOptions, "outputType"))(
62
- lDesugar ? desugar(lAST) : lAST,
63
- lOptions
26
+ // @ts-expect-error should be cast to OutputTypeType - or the getOptionValue
27
+ // function should be refactored to be more explicit in type it returns
28
+ // when we ask for outputType
29
+ return getRenderFunction(options.getOptionValue(pOptions, "outputType"))(
30
+ lDesugar ? desugar(lStateMachine) : lStateMachine,
31
+ pOptions
64
32
  );
65
33
  },
66
34
 
@@ -79,7 +47,7 @@ export default {
79
47
  * - the possible values in an array of objects, each of which
80
48
  * has the properties:
81
49
  * - name: the value
82
- *
50
+ * @returns {import("../types/state-machine-cat").IAllowedValues}
83
51
  */
84
52
  getAllowedValues() {
85
53
  return options.getAllowedValues();
package/src/index.mjs CHANGED
@@ -1,3 +1,4 @@
1
+ // @ts-check
1
2
  /* eslint-disable budapestian/global-constant-pattern */
2
3
  import options from "./options.mjs";
3
4
  import parse from "./parse/index.mjs";
@@ -8,12 +9,11 @@ import { version as _version } from "./version.mjs";
8
9
  /**
9
10
  * Translates the input script to an output-script.
10
11
  *
11
- * @param {string} pScript The script to translate
12
- * @param {object} pOptions options influencing parsing & rendering.
13
- * See below for the complete list.
14
- * @return {string|void} nothing if a callback was passed, the
15
- * string with the rendered content if
16
- * no callback was passed and no error was found
12
+ * @param {string|import("../types/state-machine-cat").IStateMachine} pScript
13
+ * The script to translate
14
+ * @param {import("../types/state-machine-cat").IRenderOptions} pOptions
15
+ * options influencing parsing & rendering.
16
+ * @return {string}
17
17
  * @throws {Error} if an error occurred and no callback
18
18
  * function was passed: the error
19
19
  *
@@ -21,11 +21,11 @@ import { version as _version } from "./version.mjs";
21
21
  *
22
22
  */
23
23
  export function render(pScript, pOptions) {
24
- const lAST = parse.getAST(pScript, pOptions);
24
+ const lStateMachine = parse.getAST(pScript, pOptions);
25
25
  const lDesugar = options.getOptionValue(pOptions, "desugar");
26
26
 
27
27
  return getRenderFunction(options.getOptionValue(pOptions, "outputType"))(
28
- lDesugar ? desugar(lAST) : lAST,
28
+ lDesugar ? desugar(lStateMachine) : lStateMachine,
29
29
  pOptions
30
30
  );
31
31
  }
@@ -45,7 +45,7 @@ export const version = _version;
45
45
  * - the possible values in an array of objects, each of which
46
46
  * has the properties:
47
47
  * - name: the value
48
- *
48
+ * @returns {import("../types/state-machine-cat").IAllowedValues}
49
49
  */
50
50
  export function getAllowedValues() {
51
51
  return options.getAllowedValues();
package/src/options.mjs CHANGED
@@ -1,3 +1,5 @@
1
+ // @ts-check
2
+ /** @type {import("../types/state-machine-cat").IAllowedValues} */
1
3
  const ALLOWED_VALUES = Object.freeze({
2
4
  inputType: {
3
5
  default: "smcat",
@@ -6,21 +8,22 @@ const ALLOWED_VALUES = Object.freeze({
6
8
  outputType: {
7
9
  default: "svg",
8
10
  values: [
9
- { name: "svg" },
10
- { name: "eps" },
11
- { name: "ps" },
12
- { name: "ps2" },
11
+ { name: "ast" },
13
12
  { name: "dot" },
14
- { name: "smcat" },
13
+ { name: "eps" },
15
14
  { name: "json" },
16
- { name: "ast" },
17
- { name: "scxml" },
18
- { name: "oldsvg" },
19
- { name: "oldps2" },
20
15
  { name: "oldeps" },
21
- { name: "scjson" },
16
+ { name: "oldps" },
17
+ { name: "oldps2" },
18
+ { name: "oldsvg" },
22
19
  { name: "pdf" },
23
20
  { name: "png" },
21
+ { name: "ps" },
22
+ { name: "ps2" },
23
+ { name: "scjson" },
24
+ { name: "scxml" },
25
+ { name: "smcat" },
26
+ { name: "svg" },
24
27
  ],
25
28
  },
26
29
  engine: {
@@ -53,15 +56,18 @@ const ALLOWED_VALUES = Object.freeze({
53
56
  * Returns the value for the option in the pOption object, and the default
54
57
  * for that option in all other cases
55
58
  *
56
- * @param {any} pOptions - the options as passed in the api `render` function
57
- * @param {string} pOptionName - the name of the option
58
- * @return {any} value
59
+ * @param {import("../types/state-machine-cat").IRenderOptions} pOptions - the options as passed in the api `render` function
60
+ * @param {keyof import("../types/state-machine-cat").IRenderOptions} pOptionName - the name of the option
61
+ * @return {string|boolean} value
59
62
  */
60
63
  function getOptionValue(pOptions, pOptionName) {
61
64
  // eslint-disable-next-line security/detect-object-injection
62
65
  return pOptions?.[pOptionName] ?? ALLOWED_VALUES[pOptionName].default;
63
66
  }
64
67
 
68
+ /**
69
+ * @returns {import("../types/state-machine-cat").IAllowedValues}
70
+ */
65
71
  function getAllowedValues() {
66
72
  return ALLOWED_VALUES;
67
73
  }
@@ -1,3 +1,4 @@
1
+ // @ts-check
1
2
  import Ajv from "ajv";
2
3
  import options from "../options.mjs";
3
4
  import { parse as parseSmCat } from "./smcat/smcat-parser.mjs";
@@ -6,6 +7,11 @@ import $schema from "./smcat-ast.schema.mjs";
6
7
 
7
8
  const ajv = new Ajv();
8
9
 
10
+ /**
11
+ * @param {typeof $schema} pSchema
12
+ * @param {any} pObject
13
+ * @throws {Error}
14
+ */
9
15
  function validateAgainstSchema(pSchema, pObject) {
10
16
  if (!ajv.validate(pSchema, pObject)) {
11
17
  throw new Error(
@@ -15,12 +21,18 @@ function validateAgainstSchema(pSchema, pObject) {
15
21
  }
16
22
 
17
23
  export default {
24
+ /**
25
+ * @param {string|import("../../types/state-machine-cat").IStateMachine} pScript
26
+ * @param {import("../../types/state-machine-cat").IRenderOptions} pOptions
27
+ * @returns {import("../../types/state-machine-cat").IStateMachine}
28
+ */
18
29
  getAST(pScript, pOptions) {
19
30
  let lReturnValue = pScript;
20
31
 
21
32
  if (options.getOptionValue(pOptions, "inputType") === "smcat") {
22
33
  lReturnValue = parseSmCat(pScript);
23
34
  } else if (options.getOptionValue(pOptions, "inputType") === "scxml") {
35
+ // @ts-expect-error inputType scxml => it's a string
24
36
  lReturnValue = parseSCXML(pScript);
25
37
  } else if (typeof pScript === "string") {
26
38
  // json
@@ -29,6 +41,7 @@ export default {
29
41
 
30
42
  validateAgainstSchema($schema, lReturnValue);
31
43
 
44
+ // @ts-expect-error by here lReturnValue is bound to be an IStateMachine
32
45
  return lReturnValue;
33
46
  },
34
47
  };
@@ -1,3 +1,4 @@
1
+ // @ts-check
1
2
  /* eslint-disable security/detect-object-injection */
2
3
  import StateMachineModel from "../state-machine-model.mjs";
3
4
 
@@ -6,10 +7,20 @@ const TRIGGER_RE_AS_A_STRING =
6
7
  /* eslint security/detect-non-literal-regexp:0 */
7
8
  const TRIGGER_RE = new RegExp(TRIGGER_RE_AS_A_STRING);
8
9
 
10
+ /**
11
+ * @param {string[]} pKnownStateNames
12
+ * @param {string} pName
13
+ * @returns {boolean}
14
+ */
9
15
  function stateExists(pKnownStateNames, pName) {
10
16
  return pKnownStateNames.includes(pName);
11
17
  }
12
-
18
+ /**
19
+ * @type {{
20
+ * re:RegExp;
21
+ * stateType: import("../../types/state-machine-cat.js").StateType;
22
+ * }[]}
23
+ */
13
24
  const RE2STATE_TYPE = [
14
25
  {
15
26
  re: /initial/,
@@ -45,11 +56,19 @@ function matches(pName) {
45
56
  return (pEntry) => pEntry.re.test(pName);
46
57
  }
47
58
 
59
+ /**
60
+ * @param {string} [pName]
61
+ * @returns {import("../../types/state-machine-cat.js").StateType}
62
+ */
48
63
  function getStateType(pName) {
49
64
  return (RE2STATE_TYPE.find(matches(pName)) || { stateType: "regular" })
50
65
  .stateType;
51
66
  }
52
67
 
68
+ /**
69
+ * @param {string} pName
70
+ * @returns {import("../../types/state-machine-cat.js").IState}
71
+ */
53
72
  function initState(pName) {
54
73
  return {
55
74
  name: pName,
@@ -57,30 +76,45 @@ function initState(pName) {
57
76
  };
58
77
  }
59
78
 
79
+ /**
80
+ * @param {import("../../types/state-machine-cat.js").IState} pState
81
+ * @returns {boolean}
82
+ */
60
83
  function isComposite(pState) {
61
84
  return Boolean(pState.statemachine);
62
85
  }
63
86
 
87
+ /**
88
+ * @param {import("../../types/state-machine-cat.js").IStateMachine} [pStateMachine]
89
+ * @returns {string[]}
90
+ */
64
91
  function getAlreadyDeclaredStates(pStateMachine) {
65
- const lStates = pStateMachine.states || [];
92
+ const lStates = pStateMachine?.states ?? [];
66
93
 
67
94
  return lStates.filter(isComposite).reduce(
68
95
  (pAllStateNames, pThisState) =>
69
96
  pAllStateNames.concat(getAlreadyDeclaredStates(pThisState.statemachine)),
70
- lStates.map((pState) => pState.name)
97
+ lStates.map(({ name }) => name)
71
98
  );
72
99
  }
73
100
 
101
+ /**
102
+ * @param {import("../../types/state-machine-cat.js").IStateMachine} pStateMachine
103
+ * @param {string[]} pKnownStateNames
104
+ * @returns {import("../../types/state-machine-cat.js").IState[]}
105
+ */
74
106
  function extractUndeclaredStates(pStateMachine, pKnownStateNames) {
75
107
  pKnownStateNames = pKnownStateNames
76
108
  ? pKnownStateNames
77
109
  : getAlreadyDeclaredStates(pStateMachine);
78
110
 
79
- pStateMachine.states = pStateMachine.states || [];
80
- const lTransitions = pStateMachine.transitions || [];
111
+ pStateMachine.states = pStateMachine?.states ?? [];
112
+ const lTransitions = pStateMachine?.transitions ?? [];
81
113
 
82
114
  pStateMachine.states.filter(isComposite).forEach((pState) => {
115
+ // @ts-expect-error isComposite guarantees the statemachine attribute exists, TS doesn't understand that yet, though
83
116
  pState.statemachine.states = extractUndeclaredStates(
117
+ // @ts-expect-error isComposite guarantees the statemachine attribute exists, TS doesn't understand that yet, though
84
118
  pState.statemachine,
85
119
  pKnownStateNames
86
120
  );
@@ -99,7 +133,13 @@ function extractUndeclaredStates(pStateMachine, pKnownStateNames) {
99
133
  return pStateMachine.states;
100
134
  }
101
135
 
136
+ /**
137
+ * @param {number} pInComingCount
138
+ * @param {number} pOutGoingCount
139
+ * @returns {import("../../types/state-machine-cat.js").StateType}
140
+ */
102
141
  function classifyForkJoin(pInComingCount, pOutGoingCount) {
142
+ /** @type {import("../../types/state-machine-cat.js").StateType} */
103
143
  let lReturnValue = "junction";
104
144
 
105
145
  if (pInComingCount <= 1 && pOutGoingCount > 1) {
@@ -112,10 +152,16 @@ function classifyForkJoin(pInComingCount, pOutGoingCount) {
112
152
  return lReturnValue;
113
153
  }
114
154
 
155
+ /**
156
+ * @param {import("../../types/state-machine-cat.js").IStateMachine} pStateMachine
157
+ * @param {StateMachineModel} pFlattenedStateMachineModel
158
+ * @returns {import("../../types/state-machine-cat.js").IStateMachine}
159
+ */
115
160
  function classifyForkJoins(
116
161
  pStateMachine,
117
162
  pFlattenedStateMachineModel = new StateMachineModel(pStateMachine)
118
163
  ) {
164
+ // TODO: mutates parameter
119
165
  pStateMachine.states = pStateMachine.states.map((pState) => {
120
166
  if (pState.type === "forkjoin" && !pState.typeExplicitlySet) {
121
167
  const lInComingCount = pFlattenedStateMachineModel.findTransitionsByTo(
@@ -139,6 +185,11 @@ function classifyForkJoins(
139
185
  return pStateMachine;
140
186
  }
141
187
 
188
+ /**
189
+ * @param {import("../../types/state-machine-cat.js").IState} pStateOne
190
+ * @param {import("../../types/state-machine-cat.js").IState} pStateTwo
191
+ * @returns {boolean}
192
+ */
142
193
  function stateEqual(pStateOne, pStateTwo) {
143
194
  return pStateOne.name === pStateTwo.name;
144
195
  }
@@ -158,12 +209,17 @@ function uniq(pArray, pEqualFunction) {
158
209
  }, []);
159
210
  }
160
211
 
212
+ /**
213
+ * @param {String} pString
214
+ * @returns {{event?: string; cond?: string; action?: string}}
215
+ */
161
216
  function parseTransitionExpression(pString) {
162
217
  // eslint-disable-next-line security/detect-unsafe-regex, unicorn/no-unsafe-regex
163
218
  const lTransitionExpressionRe = /([^[/]+)?(\[[^\]]+\])?[^/]*(\/.+)?/;
164
219
  const lReturnValue = {};
165
220
 
166
- // match has no fallback because lTransitionExpressionRe will match
221
+ /** @type {RegExpMatchArray} */
222
+ // @ts-expect-error match has no fallback because lTransitionExpressionRe will match
167
223
  // any string (every part is optional)
168
224
  const lMatchResult = pString.match(lTransitionExpressionRe);
169
225
  const lEventPos = 1;
@@ -195,6 +251,10 @@ function setIfNotEmpty(pObject, pProperty, pValue) {
195
251
  setIf(pObject, pProperty, pValue, (pX) => pX && pX.length > 0);
196
252
  }
197
253
 
254
+ /**
255
+ * @param {string} pActivityCandidate
256
+ * @returns {{type: string; body: string}}
257
+ */
198
258
  function extractAction(pActivityCandidate) {
199
259
  const lMatch = pActivityCandidate.match(TRIGGER_RE);
200
260
  const lTypePos = 1;
@@ -212,6 +272,10 @@ function extractAction(pActivityCandidate) {
212
272
  };
213
273
  }
214
274
 
275
+ /**
276
+ * @param {string} pString
277
+ * @returns {{type: string; body: string}[]}
278
+ */
215
279
  function extractActions(pString) {
216
280
  return pString
217
281
  .split(/\n\s*/g)
@@ -4,7 +4,7 @@ import he from "he";
4
4
  import castArray from "lodash/castArray.js";
5
5
  import utl from "../../transform/utl.mjs";
6
6
  import parserHelpers from "../parser-helpers.mjs";
7
- import normalizeMachine from "./normalize-machine.mjs";
7
+ import { normalizeMachine } from "./normalize-machine.mjs";
8
8
 
9
9
  const formatLabel = utl.formatLabel;
10
10
 
@@ -26,6 +26,15 @@ function extractActionsFromInvokes(pInvokeTriggers) {
26
26
  });
27
27
  }
28
28
 
29
+ /**
30
+ * @param {import("./scxml").INormalizedSCXMLState} pState
31
+ * @returns {{type: string; body: string;}[]}
32
+ */
33
+
34
+ /**
35
+ * @param {import("./scxml").INormalizedSCXMLState} pState
36
+ * @returns {any[]}
37
+ */
29
38
  function deriveActions(pState) {
30
39
  let lReturnValue = [];
31
40
 
@@ -43,12 +52,24 @@ function deriveActions(pState) {
43
52
  return lReturnValue;
44
53
  }
45
54
 
55
+ /**
56
+ * @param {import("../../..").StateType} pType
57
+ * param {import("./scxml").ISCXMLHistoryState} pState
58
+ * @param {any} pState
59
+ * @returns {import("../../..").StateType}
60
+ */
46
61
  function deriveStateType(pType, pState) {
47
62
  return pType === "history" && pState.type === "deep" ? "deephistory" : pType;
48
63
  }
49
64
 
65
+ /**
66
+ * @param {import("../../../types/state-machine-cat").StateType} pType
67
+ * returns {(pState: import("./scxml").INormalizedSCXMLState) => import("../../..").IState}
68
+ * @returns {(any) => import("../../..").IState}
69
+ */
50
70
  function mapState(pType) {
51
71
  return (pState) => {
72
+ /** @type {import("../../../types/state-machine-cat").IState} */
52
73
  const lReturnValue = {
53
74
  name: pState.id,
54
75
  type: deriveStateType(pType, pState),
@@ -73,6 +94,10 @@ function mapState(pType) {
73
94
  };
74
95
  }
75
96
 
97
+ /**
98
+ * @param {import("./scxml").ISCXMLTransition} pTransition
99
+ * @returns {{event?: string; cond?: string; action?: string; type?: string;}}
100
+ */
76
101
  function extractTransitionAttributesFromObject(pTransition) {
77
102
  const lReturnValue = {};
78
103
 
@@ -95,6 +120,11 @@ function extractTransitionAttributesFromObject(pTransition) {
95
120
  return lReturnValue;
96
121
  }
97
122
 
123
+ /**
124
+ *
125
+ * @param {import("./scxml").ISCXMLTransition} pTransition
126
+ * @returns {{action?: string; label?: string;event?: string; cond?: string; type?: string}}
127
+ */
98
128
  function extractTransitionAttributes(pTransition) {
99
129
  const lReturnValue = {};
100
130
 
@@ -120,11 +150,19 @@ function extractTransitionAttributes(pTransition) {
120
150
  return lReturnValue;
121
151
  }
122
152
 
153
+ /**
154
+ * @param {import("./scxml").INormalizedSCXMLState} pState
155
+ */
123
156
  function reduceTransition(pState) {
157
+ /**
158
+ * @param {import("./scxml").ISCXMLTransition[]} pAllTransitions
159
+ * @param {import("./scxml").ISCXMLTransition} pTransition
160
+ * @returns {import("../../..").ITransition}
161
+ */
124
162
  return (pAllTransitions, pTransition) => {
125
163
  // in SCXML spaces denote references to multiple states
126
164
  // => split into multiple transitions
127
- const lTargets = (pTransition.target || pState.id).split(/\s+/);
165
+ const lTargets = (pTransition?.target ?? pState.id).split(/\s+/);
128
166
  const lTransitionAttributes = extractTransitionAttributes(pTransition);
129
167
 
130
168
  return pAllTransitions.concat(
@@ -139,6 +177,10 @@ function reduceTransition(pState) {
139
177
  };
140
178
  }
141
179
 
180
+ /**
181
+ * @param {import("./scxml").INormalizedSCXMLState[]} pStates
182
+ * @returns {import("../../../types/state-machine-cat").ITransition[]}
183
+ */
142
184
  function extractTransitions(pStates) {
143
185
  return pStates
144
186
  .filter((pState) =>
@@ -156,20 +198,24 @@ function extractTransitions(pStates) {
156
198
  );
157
199
  }
158
200
 
159
- function mapMachine(pMachine) {
160
- const lMachine = normalizeMachine(pMachine);
161
- const lReturnValue = {};
162
-
163
- lReturnValue.states = lMachine.initial
164
- .map(mapState("initial"))
165
- .concat(lMachine.state.map(mapState("regular")))
166
- .concat(lMachine.parallel.map(mapState("parallel")))
167
- .concat(lMachine.history.map(mapState("history")))
168
- .concat(lMachine.final.map(mapState("final")));
201
+ /**
202
+ * @param {any} pSCXMLStateMachine
203
+ * @returns {import("../../..").IStateMachine}
204
+ */
205
+ function mapMachine(pSCXMLStateMachine) {
206
+ const lNormalizedMachine = normalizeMachine(pSCXMLStateMachine);
207
+ const lReturnValue = {
208
+ states: lNormalizedMachine.initial
209
+ .map(mapState("initial"))
210
+ .concat(lNormalizedMachine.state.map(mapState("regular")))
211
+ .concat(lNormalizedMachine.parallel.map(mapState("parallel")))
212
+ .concat(lNormalizedMachine.history.map(mapState("history")))
213
+ .concat(lNormalizedMachine.final.map(mapState("final"))),
214
+ };
169
215
 
170
- const lTransitions = extractTransitions(lMachine.initial)
171
- .concat(extractTransitions(lMachine.state))
172
- .concat(extractTransitions(lMachine.parallel));
216
+ const lTransitions = extractTransitions(lNormalizedMachine.initial)
217
+ .concat(extractTransitions(lNormalizedMachine.state))
218
+ .concat(extractTransitions(lNormalizedMachine.parallel));
173
219
 
174
220
  if (lTransitions.length > 0) {
175
221
  lReturnValue.transitions = lTransitions;
@@ -181,20 +227,26 @@ function mapMachine(pMachine) {
181
227
  * Parses SCXML into a state machine AST.
182
228
  *
183
229
  * @param {string} pSCXMLString The SCXML to parse
184
- * @returns {IStateMachine} state machine AST
230
+ * @returns {import("../../../types/state-machine-cat").IStateMachine} state machine AST
185
231
  */
186
232
  export function parse(pSCXMLString) {
187
- const lSCXMLString = pSCXMLString.trim();
233
+ const lTrimmedSCXMLString = pSCXMLString.trim();
188
234
 
189
- if (fastxml.validate(lSCXMLString) === true) {
190
- const lXMLAsJSON = fastxml.parse(lSCXMLString, {
235
+ if (fastxml.validate(lTrimmedSCXMLString) === true) {
236
+ /** @type {import("./scxml").ISCXMLAsJSON} */
237
+ const lXMLAsJSON = fastxml.parse(lTrimmedSCXMLString, {
191
238
  attributeNamePrefix: "",
192
239
  ignoreAttributes: false,
193
240
  tagValueProcessor: (pTagValue) => he.decode(pTagValue),
194
241
  stopNodes: ["onentry", "onexit", "transition"],
195
242
  });
196
243
 
197
- return mapMachine(lXMLAsJSON?.scxml ?? {});
244
+ return mapMachine(
245
+ lXMLAsJSON?.scxml ?? {
246
+ xmlns: "http://www.w3.org/2005/07/scxml",
247
+ version: "1.0",
248
+ }
249
+ );
198
250
  }
199
251
  throw new Error("That doesn't look like valid xml ...\n");
200
252
  }
@@ -1,61 +1,67 @@
1
1
  import castArray from "lodash/castArray.js";
2
2
 
3
- function normalizeInitialFromObject(pMachine) {
3
+ /**
4
+ * @param {Partial<import("./scxml").ISCXMLInitialState>} pInitialObject
5
+ * @param {string} [pId]
6
+ * @returns {import("./scxml").ISCXMLInitialState}
7
+ */
8
+ function normalizeInitialFromObject(pInitialObject, pId) {
4
9
  const lReturnValue = {
5
10
  // ensure the 'initial' state has a unique name
6
- id: pMachine.id ? `${pMachine.id}.initial` : "initial",
11
+ id: pId ? `${pId}.initial` : "initial",
7
12
  };
8
13
 
9
- if (pMachine.initial.transition) {
14
+ if (pInitialObject.transition) {
10
15
  Object.assign(lReturnValue, {
11
- transition: [pMachine.initial.transition],
16
+ transition: [pInitialObject.transition],
12
17
  });
13
18
  }
14
19
 
15
20
  return lReturnValue;
16
21
  }
17
22
 
18
- function normalizeInitialFromString(pMachine) {
23
+ /**
24
+ * @param {string} pString
25
+ * @returns {import("./scxml").ISCXMLInitialState}
26
+ */
27
+ function normalizeInitialFromString(pString) {
19
28
  return {
20
29
  id: "initial",
21
30
  transition: [
22
31
  {
23
- target: pMachine.initial,
32
+ target: pString,
24
33
  },
25
34
  ],
26
35
  };
27
36
  }
28
37
 
38
+ /**
39
+ * @param {any} pMachine
40
+ * @returns {import("./scxml").ISCXMLInitialState[]}
41
+ */
29
42
  function normalizeInitial(pMachine) {
30
43
  const lReturnValue = [];
31
- let lInitialObject = {};
32
44
 
33
45
  if (pMachine.initial) {
34
- // => it's an xml node. This detection isn't fool proof...;
35
- // if it's a node but it doesn't have a transtion (which
36
- // looks like an odd corner case) we won't recognize the
37
- // initial
38
- // the initial.id shouldn't occur (not allowed in scxml
39
- // land), but smcat scxml renderer generates it nonetheless
40
- if (pMachine.initial.transition || pMachine.initial.id) {
41
- lInitialObject = normalizeInitialFromObject(pMachine);
46
+ if (typeof pMachine.initial === "string") {
47
+ lReturnValue.push(normalizeInitialFromString(pMachine.initial));
42
48
  } else {
43
- lInitialObject = normalizeInitialFromString(pMachine);
49
+ lReturnValue.push(
50
+ normalizeInitialFromObject(pMachine.initial, pMachine.id)
51
+ );
44
52
  }
45
- lReturnValue.push(lInitialObject);
46
53
  }
47
54
  return lReturnValue;
48
55
  }
49
56
 
50
57
  /**
51
58
  * Massages SCXML-as-json to be uniform:
52
- * -
53
59
  *
54
60
  * @param {any} pMachine SCXML-as-json state machine
55
- * @returns {any} Still an SCXML-as-json state machine, but more consistent and
56
- * easier to use
61
+ * @returns {import("./scxml").INormalizedSCXMLMachine} Still an SCXML-as-json state machine,
62
+ * but more consistent and easier to use
57
63
  */
58
- export default function normalizeMachine(pMachine) {
64
+ export function normalizeMachine(pMachine) {
59
65
  return {
60
66
  ...pMachine,
61
67
  initial: normalizeInitial(pMachine),