From a0dce8e2b56860643667f72101714bba13a95ef8 Mon Sep 17 00:00:00 2001 From: Farzad Yousefzadeh Date: Mon, 22 Jan 2024 13:53:42 +0200 Subject: [PATCH 1/3] Extract action parameters --- .../ts-project/__tests__/actions.test.ts | 123 ++++++++++++++++++ new-packages/ts-project/src/state.ts | 13 +- 2 files changed, 135 insertions(+), 1 deletion(-) diff --git a/new-packages/ts-project/__tests__/actions.test.ts b/new-packages/ts-project/__tests__/actions.test.ts index d4819fa3..6e5cb969 100644 --- a/new-packages/ts-project/__tests__/actions.test.ts +++ b/new-packages/ts-project/__tests__/actions.test.ts @@ -987,3 +987,126 @@ test('should not register multiple transition actions with duplicated actions pr ] `); }); +test('should extract action params from parameterized action', async () => { + const tmpPath = await testdir({ + 'tsconfig.json': JSON.stringify({}), + 'index.ts': ts` + import { createMachine } from "xstate"; + + createMachine({ + initial: "foo", + states: { + foo: { + entry: { + type: "callDavid", + params: { + str: "some string", + num: 123, + bool: true, + arr: [1, 2, [3, 4]], + obj: { + x: { + y: 1, + }, + }, + }, + }, + }, + }, + }); + `, + }); + + const project = await createTestProject(tmpPath); + expect(replaceUniqueIds(project.extractMachines('index.ts'))) + .toMatchInlineSnapshot(` + [ + [ + { + "blocks": { + "block-0": { + "blockType": "action", + "parentId": "state-1", + "properties": { + "params": { + "arr": [ + 1, + 2, + [ + 3, + 4, + ], + ], + "bool": true, + "num": 123, + "obj": { + "x": { + "y": 1, + }, + }, + "str": "some string", + }, + "type": "callDavid", + }, + "sourceId": "callDavid", + "uniqueId": "block-0", + }, + }, + "data": { + "context": {}, + }, + "edges": {}, + "implementations": { + "actions": { + "callDavid": { + "id": "callDavid", + "name": "callDavid", + "type": "action", + }, + }, + "actors": {}, + "guards": {}, + }, + "nodes": { + "state-0": { + "data": { + "description": undefined, + "entry": [], + "exit": [], + "history": undefined, + "initial": "foo", + "invoke": [], + "metaEntries": [], + "tags": [], + "type": "normal", + }, + "parentId": undefined, + "type": "node", + "uniqueId": "state-0", + }, + "state-1": { + "data": { + "description": undefined, + "entry": [ + "block-0", + ], + "exit": [], + "history": undefined, + "initial": undefined, + "invoke": [], + "metaEntries": [], + "tags": [], + "type": "normal", + }, + "parentId": "state-0", + "type": "node", + "uniqueId": "state-1", + }, + }, + "root": "state-0", + }, + [], + ], + ] + `); +}); diff --git a/new-packages/ts-project/src/state.ts b/new-packages/ts-project/src/state.ts index d8490d81..f47e800a 100644 --- a/new-packages/ts-project/src/state.ts +++ b/new-packages/ts-project/src/state.ts @@ -10,6 +10,7 @@ import { EventTypeData, ExtractionContext, GuardBlock, + JsonObject, Node, TreeNode, } from './types'; @@ -28,9 +29,11 @@ import { const createActionBlock = ({ sourceId, parentId, + params, }: { sourceId: string; parentId: string; + params?: JsonObject | undefined; }): ActionBlock => { const blockId = uniqueId(); return { @@ -40,7 +43,7 @@ const createActionBlock = ({ sourceId, properties: { type: sourceId, - params: {}, + params: params ?? {}, }, }; }; @@ -169,10 +172,18 @@ function extractActionBlocks( return; } + const paramsProperty = findProperty(ctx, ts, element, 'params'); + if (ts.isStringLiteralLike(typeProperty.initializer)) { return createActionBlock({ sourceId: typeProperty.initializer.text, parentId, + params: + // TODO: Handle invalid params values + paramsProperty && + ts.isObjectLiteralExpression(paramsProperty.initializer) + ? getJsonObject(ctx, ts, paramsProperty.initializer) + : {}, }); } ctx.errors.push({ From 7300cface4ef0dcd610d822bcee80cf4df00490a Mon Sep 17 00:00:00 2001 From: Farzad Yousefzadeh Date: Mon, 22 Jan 2024 13:57:39 +0200 Subject: [PATCH 2/3] Extract guard params --- .../ts-project/__tests__/guards.test.ts | 162 ++++++++++++++++++ new-packages/ts-project/src/state.ts | 17 +- 2 files changed, 178 insertions(+), 1 deletion(-) diff --git a/new-packages/ts-project/__tests__/guards.test.ts b/new-packages/ts-project/__tests__/guards.test.ts index 905a1758..617919c5 100644 --- a/new-packages/ts-project/__tests__/guards.test.ts +++ b/new-packages/ts-project/__tests__/guards.test.ts @@ -1408,3 +1408,165 @@ test('should raise error for parameterized guard with invalid type property', as ] `); }); +test('should extract guard parameters from parameterized guards', async () => { + const tmpPath = await testdir({ + 'tsconfig.json': JSON.stringify({}), + 'index.ts': ts` + import { createMachine } from "xstate"; + + createMachine({ + initial: "foo", + states: { + foo: { + on: { + EV: { + target: "bar", + guard: { + type: "my condition", + params: { + string: "some string", + number: 123, + bool: true, + object: { + x: { + y: 1, + }, + }, + array: [1, 2, [3, 4]], + }, + }, + }, + }, + }, + bar: {}, + }, + }); + `, + }); + + const project = await createTestProject(tmpPath); + expect(replaceUniqueIds(project.extractMachines('index.ts'))) + .toMatchInlineSnapshot(` + [ + [ + { + "blocks": { + "block-0": { + "blockType": "guard", + "parentId": "edge-0", + "properties": { + "params": { + "array": [ + 1, + 2, + [ + 3, + 4, + ], + ], + "bool": true, + "number": 123, + "object": { + "x": { + "y": 1, + }, + }, + "string": "some string", + }, + "type": "my condition", + }, + "sourceId": "my condition", + "uniqueId": "block-0", + }, + }, + "data": { + "context": {}, + }, + "edges": { + "edge-0": { + "data": { + "actions": [], + "description": undefined, + "eventTypeData": { + "eventType": "EV", + "type": "named", + }, + "guard": "block-0", + "internal": true, + }, + "source": "state-1", + "targets": [ + "state-2", + ], + "type": "edge", + "uniqueId": "edge-0", + }, + }, + "implementations": { + "actions": {}, + "actors": {}, + "guards": { + "my condition": { + "id": "my condition", + "name": "my condition", + "type": "guard", + }, + }, + }, + "nodes": { + "state-0": { + "data": { + "description": undefined, + "entry": [], + "exit": [], + "history": undefined, + "initial": "foo", + "invoke": [], + "metaEntries": [], + "tags": [], + "type": "normal", + }, + "parentId": undefined, + "type": "node", + "uniqueId": "state-0", + }, + "state-1": { + "data": { + "description": undefined, + "entry": [], + "exit": [], + "history": undefined, + "initial": undefined, + "invoke": [], + "metaEntries": [], + "tags": [], + "type": "normal", + }, + "parentId": "state-0", + "type": "node", + "uniqueId": "state-1", + }, + "state-2": { + "data": { + "description": undefined, + "entry": [], + "exit": [], + "history": undefined, + "initial": undefined, + "invoke": [], + "metaEntries": [], + "tags": [], + "type": "normal", + }, + "parentId": "state-0", + "type": "node", + "uniqueId": "state-2", + }, + }, + "root": "state-0", + }, + [], + ], + ] + `); +}); diff --git a/new-packages/ts-project/src/state.ts b/new-packages/ts-project/src/state.ts index f47e800a..8676f17f 100644 --- a/new-packages/ts-project/src/state.ts +++ b/new-packages/ts-project/src/state.ts @@ -71,9 +71,11 @@ export function createActorBlock({ const createGuardBlock = ({ sourceId, parentId, + params, }: { sourceId: string; parentId: string; + params?: JsonObject | undefined; }): GuardBlock => { const blockId = uniqueId(); return { @@ -83,7 +85,7 @@ const createGuardBlock = ({ sourceId, properties: { type: sourceId, - params: {}, + params: params ?? {}, }, }; }; @@ -322,9 +324,22 @@ function extractEdgeGroup( typeProperty && ts.isStringLiteralLike(typeProperty.initializer) ) { + const paramsProperty = findProperty( + ctx, + ts, + prop.initializer, + 'params', + ); + const block = createGuardBlock({ sourceId: typeProperty.initializer.text, parentId: edge.uniqueId, + params: + // TODO: Handle invalid params values + paramsProperty && + ts.isObjectLiteralExpression(paramsProperty.initializer) + ? getJsonObject(ctx, ts, paramsProperty.initializer) + : {}, }); registerGuardBlock(ctx, block, edge); return; From e27d7deb8f85cba25c0669099e7101298fe3487f Mon Sep 17 00:00:00 2001 From: Farzad Yousefzadeh Date: Mon, 22 Jan 2024 14:05:04 +0200 Subject: [PATCH 3/3] Extract actor input --- .../ts-project/__tests__/actors.test.ts | 130 ++++++++++++++++++ .../ts-project/__tests__/transitions.test.ts | 5 + new-packages/ts-project/src/state.ts | 13 ++ new-packages/ts-project/src/types.ts | 1 + 4 files changed, 149 insertions(+) diff --git a/new-packages/ts-project/__tests__/actors.test.ts b/new-packages/ts-project/__tests__/actors.test.ts index f2aa5f40..0498c442 100644 --- a/new-packages/ts-project/__tests__/actors.test.ts +++ b/new-packages/ts-project/__tests__/actors.test.ts @@ -27,6 +27,7 @@ test('should extract an actor with string src (direct)', async () => { "parentId": "state-0", "properties": { "id": "inline:actor-id-0", + "input": {}, "src": "foo", }, "sourceId": "foo", @@ -111,6 +112,7 @@ test('should extract multiple actors with different string sources (direct)', as "parentId": "state-1", "properties": { "id": "inline:actor-id-0", + "input": {}, "src": "actor1", }, "sourceId": "actor1", @@ -121,6 +123,7 @@ test('should extract multiple actors with different string sources (direct)', as "parentId": "state-2", "properties": { "id": "inline:actor-id-1", + "input": {}, "src": "actor2", }, "sourceId": "actor2", @@ -244,6 +247,7 @@ test('should extract multiple actors with the same string source (direct)', asyn "parentId": "state-1", "properties": { "id": "inline:actor-id-0", + "input": {}, "src": "actor1", }, "sourceId": "actor1", @@ -254,6 +258,7 @@ test('should extract multiple actors with the same string source (direct)', asyn "parentId": "state-2", "properties": { "id": "inline:actor-id-1", + "input": {}, "src": "actor1", }, "sourceId": "actor1", @@ -372,6 +377,7 @@ test('should extract multiple actors with string source (array)', async () => { "parentId": "state-1", "properties": { "id": "inline:actor-id-0", + "input": {}, "src": "actor1", }, "sourceId": "actor1", @@ -382,6 +388,7 @@ test('should extract multiple actors with string source (array)', async () => { "parentId": "state-1", "properties": { "id": "inline:actor-id-1", + "input": {}, "src": "actor2", }, "sourceId": "actor2", @@ -488,6 +495,7 @@ test('should extract actor with inline source (direct)', async () => { "parentId": "state-1", "properties": { "id": "inline:actor-id-0", + "input": {}, "src": "inline:actor-0", }, "sourceId": "inline:actor-0", @@ -498,6 +506,7 @@ test('should extract actor with inline source (direct)', async () => { "parentId": "state-2", "properties": { "id": "inline:actor-id-1", + "input": {}, "src": "inline:actor-1", }, "sourceId": "inline:actor-1", @@ -695,6 +704,127 @@ test('should extract actor id if it is present with a string value', async () => "parentId": "state-1", "properties": { "id": "user-provided-id", + "input": {}, + "src": "actor1", + }, + "sourceId": "actor1", + "uniqueId": "block-0", + }, + }, + "data": { + "context": {}, + }, + "edges": {}, + "implementations": { + "actions": {}, + "actors": { + "actor1": { + "id": "actor1", + "name": "actor1", + "type": "actor", + }, + }, + "guards": {}, + }, + "nodes": { + "state-0": { + "data": { + "description": undefined, + "entry": [], + "exit": [], + "history": undefined, + "initial": "foo", + "invoke": [], + "metaEntries": [], + "tags": [], + "type": "normal", + }, + "parentId": undefined, + "type": "node", + "uniqueId": "state-0", + }, + "state-1": { + "data": { + "description": undefined, + "entry": [], + "exit": [], + "history": undefined, + "initial": undefined, + "invoke": [ + "block-0", + ], + "metaEntries": [], + "tags": [], + "type": "normal", + }, + "parentId": "state-0", + "type": "node", + "uniqueId": "state-1", + }, + }, + "root": "state-0", + }, + [], + ], + ] + `); +}); +test('should extract actor input', async () => { + const tmpPath = await testdir({ + 'tsconfig.json': JSON.stringify({}), + 'index.ts': ts` + import { createMachine } from "xstate"; + + createMachine({ + initial: "foo", + states: { + foo: { + invoke: { + src: "actor1", + input: { + str: "some string", + num: 123, + bool: true, + arr: [1, 2, [3, 4]], + obj: { x: { y: 1 } }, + }, + }, + }, + }, + }); + `, + }); + + const project = await createTestProject(tmpPath); + expect(replaceUniqueIds(project.extractMachines('index.ts'))) + .toMatchInlineSnapshot(` + [ + [ + { + "blocks": { + "block-0": { + "blockType": "actor", + "parentId": "state-1", + "properties": { + "id": "inline:actor-id-0", + "input": { + "arr": [ + 1, + 2, + [ + 3, + 4, + ], + ], + "bool": true, + "num": 123, + "obj": { + "x": { + "y": 1, + }, + }, + "str": "some string", + }, "src": "actor1", }, "sourceId": "actor1", diff --git a/new-packages/ts-project/__tests__/transitions.test.ts b/new-packages/ts-project/__tests__/transitions.test.ts index e5b7c1fd..1dba8549 100644 --- a/new-packages/ts-project/__tests__/transitions.test.ts +++ b/new-packages/ts-project/__tests__/transitions.test.ts @@ -2283,6 +2283,7 @@ test('should extract invoke.onDone transition (with invoke.id)', async () => { "parentId": "state-1", "properties": { "id": "urgentCall", + "input": {}, "src": "callDavid", }, "sourceId": "callDavid", @@ -2415,6 +2416,7 @@ test('should extract invoke.onDone transition (without invoke.id)', async () => "parentId": "state-1", "properties": { "id": "inline:actor-id-0", + "input": {}, "src": "callDavid", }, "sourceId": "callDavid", @@ -2560,6 +2562,7 @@ test('should extract invoke.onDone action', async () => { "parentId": "state-1", "properties": { "id": "inline:actor-id-0", + "input": {}, "src": "callDavid", }, "sourceId": "callDavid", @@ -2701,6 +2704,7 @@ test('should extract invoke.onError transition (with invoke.id)', async () => { "parentId": "state-1", "properties": { "id": "urgentCall", + "input": {}, "src": "callDavid", }, "sourceId": "callDavid", @@ -2833,6 +2837,7 @@ test('should extract invoke.onError transition (without invoke.id)', async () => "parentId": "state-1", "properties": { "id": "inline:actor-id-0", + "input": {}, "src": "callDavid", }, "sourceId": "callDavid", diff --git a/new-packages/ts-project/src/state.ts b/new-packages/ts-project/src/state.ts index 8676f17f..8a6f008d 100644 --- a/new-packages/ts-project/src/state.ts +++ b/new-packages/ts-project/src/state.ts @@ -51,10 +51,12 @@ export function createActorBlock({ sourceId, parentId, actorId, + input, }: { sourceId: string; parentId: string; actorId: string; + input?: JsonObject | undefined; }): ActorBlock { const blockId = uniqueId(); return { @@ -65,6 +67,7 @@ export function createActorBlock({ properties: { src: sourceId, id: actorId, + input: input ?? {}, }, }; } @@ -673,6 +676,7 @@ export function extractState( let actorId: string | undefined; let onDone: PropertyAssignment | undefined; let onError: PropertyAssignment | undefined; + let input: PropertyAssignment | undefined; forEachStaticProperty(ctx, ts, element, (prop, key) => { switch (key) { @@ -691,6 +695,10 @@ export function extractState( onError = prop; return; } + case 'input': { + input = prop; + return; + } } }); @@ -700,6 +708,11 @@ export function extractState( : `inline:${uniqueId()}`, parentId: node.uniqueId, actorId: actorId ?? `inline:${uniqueId()}`, + input: + // TODO: Handle invalid input values + input && ts.isObjectLiteralExpression(input.initializer) + ? getJsonObject(ctx, ts, input.initializer) + : {}, }); if (onDone) { diff --git a/new-packages/ts-project/src/types.ts b/new-packages/ts-project/src/types.ts index e8eed0db..3d9e75f0 100644 --- a/new-packages/ts-project/src/types.ts +++ b/new-packages/ts-project/src/types.ts @@ -76,6 +76,7 @@ export interface ActorBlock extends BlockBase { properties: { src: string; id: string; + input: JsonObject; }; }