Skip to content

Commit f437874

Browse files
committed
fix: correct suggestions and schema extraction
1 parent b5c1afe commit f437874

1 file changed

Lines changed: 71 additions & 20 deletions

File tree

src/schema/getSignatureSchema.ts

Lines changed: 71 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,22 @@
11
import {DataType, Flow, FunctionDefinition, NodeFunction} from "@code0-tech/sagittarius-graphql-types"
22
import {createCompilerHost, generateFlowSourceCode, sanitizeId} from "../utils"
33
import ts, {Type} from "typescript"
4-
import {getSchema, Schema} from "../util/schema.util"
4+
import {getSchema, mergeSchemas, Schema} from "../util/schema.util"
55

66
/**
77
* Represents the schema information for a node parameter.
88
* Includes the parameter's schema definition and any parameter dependencies that block it.
99
*/
1010
export interface NodeSchema {
1111
nodeId: NodeFunction["id"]
12-
/** The schema definition for this node parameter */
12+
/**
13+
* The schema definition for this node parameter. Produced by merging the
14+
* function-declared parameter schema with the node's concrete value schema:
15+
* the function schema drives the structural shape, the node schema contributes
16+
* additional suggestions, and a generic function parameter falls back to the
17+
* node's concrete shape (never as a select).
18+
*/
1319
schema: Schema
14-
/** The schema definition for the function parameter */
15-
functionSchema: Schema
1620
/** Array of parameter indices that must be resolved before this parameter */
1721
blockedBy?: number[]
1822
}
@@ -95,6 +99,14 @@ export const getSignatureSchema = (
9599
// Identify parameter dependencies based on type parameters
96100
const funktionDependencies = getParameterDependencies(funktion!, nodeParameterTypes)
97101

102+
// Track which parameter slots actually carry a user-supplied value. The merge
103+
// uses this as a last-resort signal: if the function- and node-side schemas
104+
// both came out generic but the user did set something, the lift falls back
105+
// to `data` so the UI has an open object to render against.
106+
const valueProvidedByIndex = (targetNode?.parameters?.nodes ?? []).map(
107+
(p) => p?.value != null
108+
)
109+
98110
// Generate schema for each parameter
99111
return generateNodeSchemas(
100112
nodeId,
@@ -105,6 +117,7 @@ export const getSignatureSchema = (
105117
funktionDependencies,
106118
nodeId ? declaredFunctionsMap : new Map(),
107119
nodeId ? functions : [],
120+
valueProvidedByIndex,
108121
)
109122
}
110123

@@ -292,9 +305,11 @@ const getParameterDependencies = (
292305
* @param checker - The TypeScript type checker
293306
* @param node - The node's variable declaration
294307
* @param nodeParameterTypes - Merged parameter types to use for schema generation
308+
* @param functionParameterTypes
295309
* @param funktionDependencies - Parameter dependencies to link with each parameter
296310
* @param declaredFunctionsMap - Map of available functions for schema context
297311
* @param functions - Array of function definitions
312+
* @param valueProvidedByIndex
298313
* @returns Array of NodeSchema objects
299314
*/
300315
const generateNodeSchemas = (
@@ -306,31 +321,67 @@ const generateNodeSchemas = (
306321
funktionDependencies: ParameterDependency[],
307322
declaredFunctionsMap: Map<string, ts.FunctionDeclaration>,
308323
functions: FunctionDefinition[],
324+
valueProvidedByIndex: boolean[],
309325
): NodeSchema[] => {
310326
if (!nodeParameterTypes) {
311327
return []
312328
}
313329

314-
return nodeParameterTypes.map((parameterType, index) => ({
315-
nodeId: nodeId,
316-
schema: getSchema(
330+
return nodeParameterTypes.map((parameterType, index) => {
331+
const functionParameterType = functionParameterTypes?.[index]
332+
// Suggestions are scoped by what the *function* parameter accepts (e.g.
333+
// `T` widens to `any`, so anything in scope is a valid candidate), even
334+
// when the node value has narrowed the actual parameter type — otherwise
335+
// setting a boolean literal in a generic slot would silently hide all
336+
// other suggestions.
337+
const suggestionType = functionParameterType
338+
? widenForSuggestions(checker, functionParameterType)
339+
: undefined
340+
341+
const nodeSchema = getSchema(
317342
checker,
318343
node,
319344
parameterType,
320345
Array.from(declaredFunctionsMap.values()),
321-
functions
322-
),
323-
functionSchema: getSchema(
324-
checker,
325-
node,
326-
functionParameterTypes?.[index]!,
327-
Array.from(declaredFunctionsMap.values()),
328346
functions,
329-
false
330-
),
331-
blockedBy: funktionDependencies
332-
.filter((dep) => dep.parameterIndex === index)
333-
.map((dep) => dep.dependsOnIndex),
334-
}))
347+
true,
348+
suggestionType,
349+
)
350+
const functionSchema = functionParameterType
351+
? getSchema(
352+
checker,
353+
node,
354+
functionParameterType,
355+
Array.from(declaredFunctionsMap.values()),
356+
functions,
357+
false
358+
)
359+
: undefined
360+
361+
return {
362+
nodeId: nodeId,
363+
schema: mergeSchemas(
364+
functionSchema,
365+
nodeSchema,
366+
valueProvidedByIndex[index] ?? false,
367+
),
368+
blockedBy: funktionDependencies
369+
.filter((dep) => dep.parameterIndex === index)
370+
.map((dep) => dep.dependsOnIndex),
371+
}
372+
})
373+
}
374+
375+
// Widen a function parameter type so that suggestion collection asks "what could
376+
// the function accept here", not "what does the current value narrow this to".
377+
// An unconstrained type parameter accepts anything → `any`. A constrained type
378+
// parameter is replaced by its constraint. Everything else is used as-is.
379+
const widenForSuggestions = (checker: ts.TypeChecker, type: ts.Type): ts.Type => {
380+
if ((type.flags & ts.TypeFlags.TypeParameter) === 0) return type
381+
const decl = type.symbol?.declarations?.[0]
382+
if (decl && ts.isTypeParameterDeclaration(decl) && decl.constraint) {
383+
return checker.getTypeFromTypeNode(decl.constraint)
384+
}
385+
return checker.getAnyType()
335386
}
336387

0 commit comments

Comments
 (0)