@@ -3,16 +3,12 @@ import {
33 Flow ,
44 FunctionDefinition ,
55 LiteralValue ,
6- NodeFunction ,
6+ NodeFunction , ReferencePath ,
77 ReferenceValue
88} from "@code0-tech/sagittarius-graphql-types"
99import { createCompilerHost , generateFlowSourceCode , sanitizeId } from "../utils"
1010import ts , { NumberLiteralType , StringLiteralType , Type } from "typescript"
1111
12- export interface TemporaryLiteralValue extends LiteralValue {
13- references ?: Record < string , ReferenceValue >
14- }
15-
1612interface Input {
1713 input ?: string
1814 suggestions ?: ( NodeFunction | ReferenceValue | LiteralValue ) [ ]
@@ -107,39 +103,41 @@ export const getSchema = (
107103
108104 const funktionParameterTypes : Type [ ] | undefined = funktion ?. parameters ?. map ( p => {
109105 const symbol = checker . getSymbolAtLocation ( p . name )
110- return checker . getTypeOfSymbolAtLocation ( symbol ! , funktion )
106+ return checker . getTypeOfSymbolAtLocation ( symbol ! , node ?. initializer as ts . CallExpression )
111107 } )
112108
113109 const combinedParameterTypes : Type [ ] | undefined = funktionParameterTypes ?. map ( ( p , i ) => {
114- const nodeType = nodeParameterTypes ?. [ i ] ;
115- if ( ! nodeType ) return p ;
110+ const nodeType = nodeParameterTypes ?. [ i ]
111+ if ( ! nodeType ) return p
116112
117- const pSymbol = p . getSymbol ( ) ;
118- const nodeSymbol = nodeType . getSymbol ( ) ;
113+ const pSymbol = p . getSymbol ( )
114+ const nodeSymbol = nodeType . getSymbol ( )
119115
120116 if ( pSymbol && nodeSymbol && pSymbol === nodeSymbol ) {
121- return nodeType ;
117+ return nodeType
122118 }
123119
124120 if ( p . isTypeParameter ( ) ) {
125- const constraint = checker . getBaseConstraintOfType ( p ) ;
121+ const constraint = checker . getBaseConstraintOfType ( p )
126122 if ( ! constraint || checker . isTypeAssignableTo ( nodeType , constraint ) ) {
127- return nodeType ;
123+ return nodeType
128124 }
129125 }
130126
131127 if ( checker . isTypeAssignableTo ( nodeType , p ) ) {
132- return nodeType ;
128+ return nodeType
133129 }
134130
135- return p ;
131+ return p
136132 } )
137133
138134 const generateSchema = ( type : ts . Type ) : Schema => {
139135
140136 const literalValueSuggestions = getLiteralValueSuggestions ( type )
137+ const referenceSuggestions = getReferenceSuggestions ( checker , node ! , type , checker . getSymbolsInScope ( node ! , ts . SymbolFlags . Variable ) )
138+ const nodeSuggestions = getNodeSuggestions ( checker , Array . from ( declaredFunctionsMap . values ( ) ) , functions , type )
141139 const suggestions = {
142- suggestions : [ ...literalValueSuggestions ]
140+ suggestions : [ ...literalValueSuggestions , ... referenceSuggestions , ... nodeSuggestions ] ,
143141 }
144142
145143 if ( isPrimitiveLiteralUnion ( type ) ) return { input : "select" , ...suggestions }
@@ -249,6 +247,156 @@ function getLiteralValueSuggestions(type: ts.Type): LiteralValue[] {
249247 return [ ]
250248}
251249
250+ function getNodeSuggestions ( checker : ts . TypeChecker , functionDeclarations : ts . FunctionDeclaration [ ] , functions : FunctionDefinition [ ] , paramType : ts . Type ) : NodeFunction [ ] {
251+
252+ //TODO: if paramType is callable than we should parse in all functions that match the parameters
253+ //TODO: otherwise we should only parse in functions that match the return type
254+ //TODO: we should differentiate between inline usable suggestions and not
255+
256+ return functionDeclarations . flatMap ( func => {
257+
258+ const signature = checker . getSignatureFromDeclaration ( func )
259+ const returnType = checker . getReturnTypeOfSignature ( signature ! )
260+
261+ const simplifiedReturnType = returnType . isTypeParameter ( )
262+ ? ( checker . getBaseConstraintOfType ( returnType ) || checker . getAnyType ( ) )
263+ : returnType
264+
265+ if ( checker . isTypeAssignableTo ( simplifiedReturnType , paramType ) ) {
266+ const functionName = func . name ?. getText ( ) . replace ( "fn_" , "" ) . replace ( "_" , "::" ) . replace ( "_" , "::" )
267+ const funktion = functions . find ( f => f . identifier === functionName )
268+
269+ const node : NodeFunction = {
270+ __typename : "NodeFunction" ,
271+ id : `gid://sagittarius/NodeFunction/1` ,
272+ functionDefinition : {
273+ __typename : "FunctionDefinition" ,
274+ id : funktion ?. id ,
275+ identifier : funktion ?. identifier ,
276+ } ,
277+ parameters : {
278+ __typename : "NodeParameterConnection" ,
279+ nodes : ( funktion ?. parameterDefinitions ?. nodes || [ ] ) . map ( p => ( {
280+ __typename : "NodeParameter" ,
281+ parameterDefinition : {
282+ __typename : "ParameterDefinition" ,
283+ id : p ?. id ,
284+ identifier : p ?. identifier
285+ } ,
286+ value : p ?. defaultValue ? {
287+ __typename : "LiteralValue" ,
288+ value : p . defaultValue . value
289+ } : null
290+ } ) )
291+ }
292+ }
293+
294+ return node
295+
296+ }
297+
298+ return [ ]
299+
300+
301+ } )
302+
303+ }
304+
305+ function getReferenceSuggestions ( checker : ts . TypeChecker , node : ts . VariableDeclaration , paramType : ts . Type , symbols : ts . Symbol [ ] ) : ReferenceValue [ ] {
306+
307+ return symbols . flatMap ( symbol => {
308+ const name = symbol . getName ( )
309+
310+ if ( ! name . startsWith ( "node_" ) && ! name . startsWith ( "p_" ) && ! name . startsWith ( "flow_" ) ) return [ ]
311+
312+ const symbolDeclaration = symbol . getDeclarations ( ) ?. [ 0 ]
313+ if ( ! symbolDeclaration ) return [ ]
314+ if ( symbolDeclaration . getEnd ( ) >= node . getEnd ( ) ! ) return [ ]
315+
316+ const symbolType = checker . getTypeOfSymbolAtLocation ( symbol , node )
317+
318+ if ( name . startsWith ( "node_" ) ) {
319+ if ( ! ( ( symbolType . flags & ts . TypeFlags . Void ) !== 0 ) ) {
320+
321+ const nodeFunctionId = name
322+ . replace ( "node_" , "" )
323+ . replace ( / _ _ _ / g, "://" )
324+ . replace ( / _ _ / g, "/" )
325+ . replace ( / _ / g, "/" )
326+
327+ const propertyPaths = extractObjectProperties ( symbolType , checker , paramType )
328+
329+ return propertyPaths . flatMap ( ( { path} ) => {
330+ const referenceValue : ReferenceValue = {
331+ __typename : 'ReferenceValue' ,
332+ nodeFunctionId : nodeFunctionId as any
333+ }
334+
335+ if ( path . length > 0 ) referenceValue . referencePath = path
336+
337+ return referenceValue
338+ } )
339+
340+ }
341+ } else if ( name . startsWith ( "p_" ) ) {
342+
343+ const idPart = name . replace ( "p_" , "" )
344+ const lastUnderscoreIndex = idPart . lastIndexOf ( "_" )
345+ const rawId = idPart . substring ( 0 , lastUnderscoreIndex )
346+ const paramIndexFromName = parseInt ( idPart . substring ( lastUnderscoreIndex + 1 ) , 10 )
347+
348+ const nodeFunctionId = rawId
349+ . replace ( "p_" , "" )
350+ . replace ( / _ _ _ / g, "://" )
351+ . replace ( / _ _ / g, "/" )
352+ . replace ( / _ / g, "/" )
353+
354+ if ( checker . isTupleType ( symbolType ) ) {
355+ const typeReference = symbolType as ts . TypeReference
356+ const typeArguments = checker . getTypeArguments ( typeReference )
357+
358+ return typeArguments . flatMap ( ( tupleElementType , tupleIndex ) => {
359+ const propertyPaths = extractObjectProperties ( tupleElementType , checker , paramType )
360+
361+ return propertyPaths . flatMap ( ( { path } ) => {
362+ const referenceValue : ReferenceValue = {
363+ __typename : 'ReferenceValue' ,
364+ nodeFunctionId : nodeFunctionId as any ,
365+ parameterIndex : isNaN ( paramIndexFromName ) ? 0 : paramIndexFromName ,
366+ inputIndex : tupleIndex ,
367+ inputTypeIdentifier : ( typeReference . target as any ) . labeledElementDeclarations ?. [ tupleIndex ] . name . getText ( )
368+ }
369+
370+ if ( path . length > 0 ) {
371+ referenceValue . referencePath = path
372+ }
373+
374+ return referenceValue
375+ } )
376+
377+ } )
378+ }
379+
380+ } else if ( name . startsWith ( "flow_" ) ) {
381+ const propertyPaths = extractObjectProperties ( symbolType , checker , paramType )
382+
383+ return propertyPaths . flatMap ( ( { path } ) => {
384+ const referenceValue : ReferenceValue = {
385+ __typename : 'ReferenceValue' ,
386+ nodeFunctionId : null
387+ }
388+
389+ if ( path . length > 0 ) referenceValue . referencePath = path
390+
391+ return referenceValue
392+ } )
393+ }
394+
395+ return [ ]
396+ } )
397+
398+ }
399+
252400function isBoolean ( type : ts . Type ) : boolean {
253401 return (
254402 ( type . flags & ts . TypeFlags . Boolean ) !== 0 ||
@@ -290,18 +438,57 @@ function isArrayType(checker: ts.TypeChecker, type: ts.Type): boolean {
290438}
291439
292440function getParameterDependencies ( node : ts . FunctionDeclaration ) {
293- const typeParamNames = node . typeParameters ?. map ( tp => tp . name . getText ( ) ) || [ ] ;
294- const usage : Record < string , number [ ] > = { } ;
441+ const typeParamNames = node . typeParameters ?. map ( tp => tp . name . getText ( ) ) || [ ]
442+ const usage : Record < string , number [ ] > = { }
295443
296444 node . parameters . forEach ( ( p , i ) => {
297- const text = p . type ?. getText ( ) || "" ;
445+ const text = p . type ?. getText ( ) || ""
298446 typeParamNames . forEach ( t => {
299- if ( text . includes ( t ) ) ( usage [ t ] ??= [ ] ) . push ( i ) ;
300- } ) ;
301- } ) ;
447+ if ( text . includes ( t ) ) ( usage [ t ] ??= [ ] ) . push ( i )
448+ } )
449+ } )
302450
303451 return Object . values ( usage )
304452 . filter ( indices => indices . length > 1 )
305453 . map ( ( [ first , ...rest ] ) => rest . map ( idx => ( { parameterIndex : idx , dependsOnIndex : first } ) ) )
306- . flat ( ) ;
454+ . flat ( )
455+ }
456+
457+ const extractObjectProperties = (
458+ type : ts . Type ,
459+ checker : ts . TypeChecker ,
460+ expectedType : ts . Type ,
461+ currentPath : ReferencePath [ ] = [ ]
462+ ) : Array < { path : ReferencePath [ ] , type : ts . Type } > => {
463+ const results : Array < { path : ReferencePath [ ] , type : ts . Type } > = [ ]
464+
465+ if ( checker . isTypeAssignableTo ( type , expectedType ) ) results . push ( { path : currentPath , type } )
466+
467+ if ( isRealObjectType ( type ) ) {
468+ const properties = type . getProperties ( )
469+ if ( properties && properties . length > 0 ) {
470+ properties . forEach ( property => {
471+ const propType = checker . getTypeOfSymbolAtLocation ( property , property . valueDeclaration ! )
472+ const propName = property . getName ( )
473+ const newPath = [ ...currentPath , { path : propName } ]
474+
475+ results . push ( ...extractObjectProperties ( propType , checker , expectedType , newPath ) )
476+ } )
477+ }
478+ }
479+
480+ return results
481+ }
482+
483+ const isRealObjectType = ( type : ts . Type ) : boolean => {
484+ const primitiveFlags =
485+ ts . TypeFlags . String |
486+ ts . TypeFlags . Number |
487+ ts . TypeFlags . Boolean |
488+ ts . TypeFlags . Undefined |
489+ ts . TypeFlags . Null |
490+ ts . TypeFlags . BigInt |
491+ ts . TypeFlags . ESSymbol
492+
493+ return ( type . flags & primitiveFlags ) === 0
307494}
0 commit comments