Fix array-typed parameters in code/logic-function action forms (#21102)

## Problem

`any[]` type prevented value input:
<img width="544" height="349" alt="Screenshot 2026-06-01 at 14 08 47"
src="https://github.com/user-attachments/assets/956238d0-6fea-4be1-b75a-ab0e6e6424ac"
/>

In the workflow Code action (and the Logic Function action), parameters
typed as any[], string[], etc. rendered as an empty grey box instead of
an "Enter value" text input. After any debounced save, even a properly
initialised array field would also collapse into an empty container.

The Array<T> / ReadonlyArray<T> generic form fell through to a generic
text input by accident (which "looked" right, but for the wrong reason —
no schema info downstream).

## Root causes
Three places treated arrays as plain objects via @sniptt/guards'
isObject (which is true for arrays):

1. WorkflowEditActionCodeFields.tsx — arrays went into the nested-fields
branch; Object.entries([]) is empty → empty container, no placeholder.
2. mergeDefaultFunctionInputAndFunctionInput.ts — recursed into arrays
during merge, turning [] into {}. Triggered on every debounced save, so
the bug surfaced after any edit.
3. get-function-input-schema.ts — only handled T[]
(SyntaxKind.ArrayType); Array<T> (SyntaxKind.TypeReference) was
unrecognised, so the form lost any item-type info.
This commit is contained in:
Marie
2026-06-01 15:14:46 +02:00
committed by GitHub
parent da5e1152bb
commit 66afd5a1de
6 changed files with 127 additions and 7 deletions
@@ -26,6 +26,66 @@ describe('getFunctionInputSchema', () => {
expect(result).toEqual([{ type: 'string' }, { type: 'number' }]);
});
it('should parse any[] as an array with unknown items', () => {
const fileContent = `
export const main = (params: { briefs: any[] }): void => {
return;
};
`;
const result = getFunctionInputSchema(fileContent);
expect(result).toEqual([
{
type: 'object',
properties: {
briefs: { type: 'array', items: {} },
},
},
]);
});
it('should parse Array<T> generic syntax the same way as T[]', () => {
const fileContent = `
export const main = (params: {
briefs: Array<any>;
names: Array<string>;
readonlyNames: ReadonlyArray<string>;
}): void => {
return;
};
`;
const result = getFunctionInputSchema(fileContent);
expect(result).toEqual([
{
type: 'object',
properties: {
briefs: { type: 'array', items: {} },
names: { type: 'array', items: { type: 'string' } },
readonlyNames: { type: 'array', items: { type: 'string' } },
},
},
]);
});
it('should fall back to an unknown type for unrecognized type references', () => {
const fileContent = `
export const main = (params: { value: Map<string, number> }): void => {
return;
};
`;
const result = getFunctionInputSchema(fileContent);
expect(result).toEqual([
{
type: 'object',
properties: {
value: {},
},
},
]);
});
it('should analyze a complex function correctly', () => {
const fileContent = `
function testFunction(
@@ -4,6 +4,7 @@ import {
createSourceFile,
type FunctionDeclaration,
type FunctionLikeDeclaration,
type Identifier,
type LiteralTypeNode,
type Node,
type PropertySignature,
@@ -11,6 +12,7 @@ import {
type StringLiteral,
SyntaxKind,
type TypeNode,
type TypeReferenceNode,
type UnionTypeNode,
type VariableStatement,
} from 'typescript';
@@ -31,6 +33,24 @@ const getTypeString = (typeNode: TypeNode): InputJsonSchema => {
type: 'array',
items: getTypeString((typeNode as ArrayTypeNode).elementType),
};
case SyntaxKind.TypeReference: {
const typeReferenceNode = typeNode as TypeReferenceNode;
const typeName =
typeReferenceNode.typeName.kind === SyntaxKind.Identifier
? (typeReferenceNode.typeName as Identifier).text
: undefined;
if (typeName === 'Array' || typeName === 'ReadonlyArray') {
const elementType = typeReferenceNode.typeArguments?.[0];
return {
type: 'array',
items: isDefined(elementType) ? getTypeString(elementType) : {},
};
}
return {};
}
case SyntaxKind.ObjectKeyword:
return { type: 'object' };
case SyntaxKind.TypeLiteral: {