From 66c2aaac9e5d1be9c26c7dd2a6857fc3638d15ea Mon Sep 17 00:00:00 2001 From: nicosammito Date: Thu, 9 Jul 2026 13:44:39 +0200 Subject: [PATCH] feat: enhance suggestion logic for nested nullable object properties in ReferenceValue paths --- src/util/references.util.ts | 8 ++-- test/schema/schema.test.ts | 74 +++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 3 deletions(-) diff --git a/src/util/references.util.ts b/src/util/references.util.ts index 6347d27..c45b3b9 100644 --- a/src/util/references.util.ts +++ b/src/util/references.util.ts @@ -271,9 +271,11 @@ const extractObjectProperties = ( results.push({ path: currentPath, type }); } - // Recursively traverse into object properties - if (isRealObjectType(type)) { - const properties = type.getProperties(); + // Recursively traverse into object properties. Traversal also runs on the + // non-nullable type: a nullable object in the chain (e.g. `{bla?: TEXT} | null`) + // is a union whose getProperties() is empty, which would cut off all nested paths. + if (isRealObjectType(nonNullableType)) { + const properties = nonNullableType.getProperties(); if (properties && properties.length > 0) { properties.forEach((property) => { const propType = checker.getTypeOfSymbolAtLocation(property, property.valueDeclaration!); diff --git a/test/schema/schema.test.ts b/test/schema/schema.test.ts index fabdd45..3ca4c1d 100644 --- a/test/schema/schema.test.ts +++ b/test/schema/schema.test.ts @@ -825,4 +825,78 @@ describe("Schema", () => { ).toBe(true); }); + it('offers a nested nullable object property as a ReferenceValue path suggestion for a plain TEXT parameter', () => { + // Node 1: custom::text::nested_nullable_object(): {test?: {bla?: TEXT | null} | null} + // — no parameters, returns an object whose `test` property is an optional, + // nullable object which itself holds an optional, nullable `bla` property. + // Node 2: std::text::split(value: TEXT, delimiter: TEXT): LIST + // + // The `value` parameter is declared as plain TEXT (string). Node 1's `test.bla` + // property has type string | null | undefined behind a nullable `test` object. + // Strict assignability would reject it, but the editor must still offer node 1's + // `test.bla` property as a ReferenceValue suggestion with referencePath + // [{path: "test"}, {path: "bla"}]: the nullish parts along the reference chain + // should be ignored for suggestion scoping. + const NESTED_NULLABLE_OBJECT_FN: FunctionDefinition = { + id: "gid://sagittarius/FunctionDefinition/9003", + identifier: "custom::text::nested_nullable_object", + signature: "(): {test?: {bla?: TEXT | null} | null}", + }; + + const flow: Flow = { + id: "gid://sagittarius/Flow/1", + startingNodeId: "gid://sagittarius/NodeFunction/1", + signature: "(): void", + nodes: { + nodes: [ + { + id: "gid://sagittarius/NodeFunction/1", + functionDefinition: {identifier: "custom::text::nested_nullable_object"}, + nextNodeId: "gid://sagittarius/NodeFunction/2", + parameters: { + nodes: [], + }, + }, + { + id: "gid://sagittarius/NodeFunction/2", + functionDefinition: {identifier: "std::text::split"}, + parameters: { + nodes: [ + {value: null}, + {value: {__typename: "LiteralValue", value: ","}}, + ], + }, + }, + ], + }, + }; + + const [valueSchema] = getSignatureSchema( + flow, + DATA_TYPES, + [...FUNCTION_SIGNATURES, NESTED_NULLABLE_OBJECT_FN], + "gid://sagittarius/NodeFunction/2", + ); + + // value: TEXT → free-form text input. + expect(valueSchema.schema.input).toBe("text"); + + // Node 1's `test.bla` property is TEXT | null | undefined nested inside the + // nullable `test` object; stripping the nullish parts leaves TEXT, which is + // assignable to the TEXT parameter → node 1 must be offered with + // referencePath [{path: "test"}, {path: "bla"}]. + const suggestions = (valueSchema.schema.suggestions ?? []) as any[]; + expect( + suggestions.some( + (s) => + s.__typename === "ReferenceValue" && + s.nodeFunctionId === "gid://sagittarius/NodeFunction/1" && + Array.isArray(s.referencePath) && + s.referencePath.length === 2 && + s.referencePath[0].path === "test" && + s.referencePath[1].path === "bla", + ), + ).toBe(true); + }); + }) \ No newline at end of file