import * as fs from "fs" ; import * as ts from "typescript" ; import XRegExp = require ( "xregexp" ) ; export interface ICustomFunctionsMetadata { functions : IFunction [ ] ; } export interface IFunction { id : string ; name : string ; description ? : string ; helpUrl ? : string ; parameters : IFunctionParameter [ ] ; result ? : IFunctionResult ; options ? : IFunctionOptions ; } export interface IFunctionOptions { cancelable ? : boolean ; requiresAddress ? : boolean ; stream ? : boolean ; volatile ? : boolean ; requiresParameterAddresses ? : boolean ; } export interface IFunctionParameter { name : string ; description ? : string ; type : string ; dimensionality ? : string ; optional ? : boolean ; repeating ? : boolean ; } export interface IFunctionResult { type ? : string ; dimensionality ? : string ; } export interface IGenerateResult { metadataJson : string ; associate : IAssociate [ ] ; errors : string [ ] ; } export interface IFunctionExtras { errors : string [ ] ; javascriptFunctionName : string ; } export interface IParseTreeResult { associate : IAssociate [ ] ; extras : IFunctionExtras [ ] ; functions : IFunction [ ] ; } export interface IAssociate { functionName : string ; id : string ; } export interface IExperimentalOptions { deprecated allowRepeatingParameters ? : boolean ; } export interface IOptions { experimental ? : IExperimentalOptions ; } interface IArrayType { dimensionality : number ; type : ts . SyntaxKind ; } interface IGetParametersArguments { enumList : string [ ] ; extra : IFunctionExtras ; jsDocParamInfo : { [ key ] : string ; } ; jsDocParamOptionalInfo : { [ key ] : string ; } ; jsDocParamTypeInfo : { [ key ] : string ; } ; parametersToParse : ts . ParameterDeclaration [ ] ; } const CUSTOM_FUNCTION = "customfunction" ; const HELPURL_PARAM = "helpurl" ; const VOLATILE = "volatile" ; const STREAMING = "streaming" ; const CANCELABLE = "cancelable" ; const REQUIRESADDRESS = "requiresaddress" ; const REQUIRESPARAMETERADDRESSES = "requiresparameteraddresses" ; const TYPE_MAPPINGS_SIMPLE = { [ ts . SyntaxKind . NumberKeyword ] : "number" , [ ts . SyntaxKind . StringKeyword ] : "string" , [ ts . SyntaxKind . BooleanKeyword ] : "boolean" , [ ts . SyntaxKind . AnyKeyword ] : "any" , } ; const TYPE_MAPPINGS = { [ ts . SyntaxKind . NumberKeyword ] : "number" , [ ts . SyntaxKind . StringKeyword ] : "string" , [ ts . SyntaxKind . BooleanKeyword ] : "boolean" , [ ts . SyntaxKind . AnyKeyword ] : "any" , [ ts . SyntaxKind . UnionType ] : "any" , [ ts . SyntaxKind . TupleType ] : "any" , [ ts . SyntaxKind . EnumKeyword ] : "any" , [ ts . SyntaxKind . ObjectKeyword ] : "any" , [ ts . SyntaxKind . VoidKeyword ] : "any" , } ; const TYPE_CUSTOM_FUNCTIONS_STREAMING = { [ "customfunctions.streaminghandler<string>" ] : "string" , [ "customfunctions.streaminghandler<number>" ] : "number" , [ "customfunctions.streaminghandler<boolean>" ] : "boolean" , [ "customfunctions.streaminghandler<any>" ] : "any" , [ "customfunctions.streaminginvocation<string>" ] : "string" , [ "customfunctions.streaminginvocation<number>" ] : "number" , [ "customfunctions.streaminginvocation<boolean>" ] : "boolean" , [ "customfunctions.streaminginvocation<any>" ] : "any" , } ; const TYPE_CUSTOM_FUNCTION_CANCELABLE = { [ "customfunctions.cancelablehandler" ] : 1 , [ "customfunctions.cancelableinvocation" ] : 2 , } ; const TYPE_CUSTOM_FUNCTION_INVOCATION = "customfunctions.invocation" ; type CustomFunctionsSchemaDimensionality = "invalid" | "scalar" | "matrix" ; param inputFile param outputFileName export async function generateCustomFunctionsMetadata ( inputFile , wantConsoleOutput = false ) { const generateResults = { metadataJson : "" , associate : [ ] , errors : [ ] , } ; if ( fs . existsSync ( inputFile ) ) { const sourceCode = fs . readFileSync ( inputFile , "utf-8" ) ; const parseTreeResult = parseTree ( sourceCode , inputFile ) ; parseTreeResult . extras . forEach ( ( extra ) => extra . errors . forEach ( ( err ) => generateResults . errors . push ( err ) ) ) ; if ( generateResults . errors . length > 0 ) { if ( wantConsoleOutput ) { console . error ( "Errors in file: " + inputFile ) ; generateResults . errors . forEach ( ( err ) => console . error ( err ) ) ; } } else { generateResults . metadataJson = JSON . stringify ( { functions : parseTreeResult . functions } , null , 4 ) ; generateResults . associate = [ ... parseTreeResult . associate ] ; } } else { throw new Error ( `File not found: ${ inputFile }` ) ; } return generateResults ; } param sourceCode param sourceFileName param parseTreeOptions export function parseTree ( sourceCode , sourceFileName ) { const associate = [ ] ; const functions = [ ] ; const extras = [ ] ; const enumList = [ ] ; const functionNames = [ ] ; const metadataFunctionNames = [ ] ; const ids = [ ] ; const sourceFile = ts . createSourceFile ( sourceFileName , sourceCode , ts . ScriptTarget . Latest , true ) ; buildEnums ( sourceFile ) ; visit ( sourceFile ) ; const parseTreeResult = { associate , extras , functions , } ; return parseTreeResult ; function buildEnums ( node ) { if ( ts . isEnumDeclaration ( node ) ) { enumList . push ( node . name . getText ( ) ) ; } ts . forEachChild ( node , buildEnums ) ; } function visit ( node ) { if ( ts . isFunctionDeclaration ( node ) ) { if ( node . parent && node . parent . kind === ts . SyntaxKind . SourceFile ) { const functionDeclaration = node as ts . FunctionDeclaration ; const position = getPosition ( functionDeclaration ) ; const functionErrors = [ ] ; const functionName = functionDeclaration . name ? functionDeclaration . name . text : "" ; if ( checkForDuplicate ( functionNames , functionName ) ) { const errorString = `Duplicate function name: ${ functionName }` ; functionErrors . push ( logError ( errorString , position ) ) ; } functionNames . push ( functionName ) ; if ( isCustomFunction ( functionDeclaration ) ) { const extra = { errors : functionErrors , javascriptFunctionName : functionName , } ; const idName = getTagComment ( functionDeclaration , CUSTOM_FUNCTION ) ; const idNameArray = idName . split ( " " ) ; const jsDocParamInfo = getJSDocParams ( functionDeclaration ) ; const jsDocParamTypeInfo = getJSDocParamsType ( functionDeclaration ) ; const jsDocParamOptionalInfo = getJSDocParamsOptionalType ( functionDeclaration ) ; const [ lastParameter ] = functionDeclaration . parameters . slice ( - 1 ) ; const isStreamingFunction = hasStreamingInvocationParameter ( lastParameter , jsDocParamTypeInfo ) ; const isCancelableFunction = hasCancelableInvocationParameter ( lastParameter , jsDocParamTypeInfo ) ; const isInvocationFunction = hasInvocationParameter ( lastParameter , jsDocParamTypeInfo ) ; const parametersToParse = isStreamingFunction || isCancelableFunction || isInvocationFunction ? functionDeclaration . parameters . slice ( 0 , functionDeclaration . parameters . length - 1 ) : functionDeclaration . parameters . slice ( 0 , functionDeclaration . parameters . length ) ; const parameterItems = { enumList , extra , jsDocParamInfo , jsDocParamOptionalInfo , jsDocParamTypeInfo , parametersToParse , } ; const parameters = getParameters ( parameterItems ) ; const description = getDescription ( functionDeclaration ) ; const helpUrl = normalizeLineEndings ( getTagComment ( functionDeclaration , HELPURL_PARAM ) ) ; const result = getResults ( functionDeclaration , isStreamingFunction , lastParameter , jsDocParamTypeInfo , extra , enumList ) ; const options = getOptions ( functionDeclaration , isStreamingFunction , isCancelableFunction , isInvocationFunction , extra ) ; const funcName = functionDeclaration . name ? functionDeclaration . name . text : "" ; const id = normalizeCustomFunctionId ( idNameArray [ 0 ] || funcName ) ; const name = idNameArray [ 1 ] || id ; validateId ( id , position , extra ) ; validateName ( name , position , extra ) ; if ( checkForDuplicate ( metadataFunctionNames , name ) ) { const errorString = `@customfunction tag specifies a duplicate name: ${ name }` ; functionErrors . push ( logError ( errorString , position ) ) ; } metadataFunctionNames . push ( name ) ; if ( checkForDuplicate ( ids , id ) ) { const errorString = `@customfunction tag specifies a duplicate id: ${ id }` ; functionErrors . push ( logError ( errorString , position ) ) ; } ids . push ( id ) ; associate . push ( { functionName , id } ) ; const functionMetadata = { description , helpUrl , id , name , options , parameters , result , } ; if ( ! options . cancelable && ! options . requiresAddress && ! options . stream && ! options . volatile && ! options . requiresParameterAddresses ) { delete functionMetadata . options ; } else { if ( ! options . cancelable ) { delete options . cancelable ; } if ( ! options . requiresAddress ) { delete options . requiresAddress ; } if ( ! options . stream ) { delete options . stream ; } if ( ! options . volatile ) { delete options . volatile ; } if ( ! options . requiresParameterAddresses ) { delete options . requiresParameterAddresses ; } } if ( ! functionMetadata . helpUrl ) { delete functionMetadata . helpUrl ; } if ( ! functionMetadata . description ) { delete functionMetadata . description ; } if ( ! functionMetadata . result ) { delete functionMetadata . result ; } extras . push ( extra ) ; functions . push ( functionMetadata ) ; } } } ts . forEachChild ( node , visit ) ; } } param list param item function checkForDuplicate ( list , item ) { let duplicate = false ; list . forEach ( ( value ) => { if ( areStringsEqual ( value , item ) ) { duplicate = true ; } } ) ; return duplicate ; } param first param second param ignoreCase function areStringsEqual ( first , second , ignoreCase = true ) { return typeof first === "string" && typeof second === "string" ? first . localeCompare ( second , undefined , ignoreCase ? { sensitivity : "accent" } : undefined ) === 0 : first === second ; } param node function getPosition ( node , position ? ) { let positionLocation = null ; if ( node ) { const pos = position ? position : node . pos ; positionLocation = node . getSourceFile ( ) . getLineAndCharacterOfPosition ( pos ) ; } return positionLocation ; } param id function validateId ( id , position , extra ) { const idRegExString = "^[a-zA-Z0-9._]*$" ; const idRegEx = new RegExp ( idRegExString ) ; if ( ! idRegEx . test ( id ) ) { if ( ! id ) { id = "Function name is invalid" ; } const errorString = `The custom function id contains invalid characters. Allowed characters are ('A-Z','a-z','0-9','.','_'):${ id }` ; extra . errors . push ( logError ( errorString , position ) ) ; } if ( id . length > 128 ) { const errorString = `The custom function id exceeds the maximum of 128 characters allowed.` ; extra . errors . push ( logError ( errorString , position ) ) ; } } param name function validateName ( name , position , extra ) { const startsWithLetterRegEx = XRegExp ( "^[\\pL]" ) ; const validNameRegEx = XRegExp ( "^[\\pL][\\pL0-9._]*$" ) ; let errorString ; if ( ! name ) { errorString = `You need to provide a custom function name.` ; extra . errors . push ( logError ( errorString , position ) ) ; } if ( ! startsWithLetterRegEx . test ( name ) ) { errorString = `The custom function name "${ name }" should start with an alphabetic character.` ; extra . errors . push ( logError ( errorString , position ) ) ; } if ( ! validNameRegEx . test ( name ) ) { errorString = `The custom function name "${ name }" should contain only alphabetic characters, numbers (0-9), period (.), and underscore (_).` ; extra . errors . push ( logError ( errorString , position ) ) ; } if ( name . length > 128 ) { errorString = `The custom function name is too long. It must be 128 characters or less.` ; extra . errors . push ( logError ( errorString , position ) ) ; } } param id function normalizeCustomFunctionId ( id ) { return id ? id . toLocaleUpperCase ( ) : id ; } param func param isStreamingFunction function getOptions ( func , isStreamingFunction , isCancelableFunction , isInvocationFunction , extra ) { const optionsItem = { cancelable : isCancelableTag ( func , isCancelableFunction ) , requiresAddress : isAddressRequired ( func ) , stream : isStreaming ( func , isStreamingFunction ) , volatile : isVolatile ( func ) , requiresParameterAddresses : isRequiresParameterAddresses ( func ) , } ; if ( optionsItem . requiresAddress || optionsItem . requiresParameterAddresses ) { let errorParam = optionsItem . requiresAddress ? "@requiresAddress" : "@requiresParameterAddresses" ; if ( ! isStreamingFunction && ! isCancelableFunction && ! isInvocationFunction ) { const functionPosition = getPosition ( func , func . parameters . end ) ; const errorString = `Since ${ errorParam } is present, the last function parameter should be of type CustomFunctions.Invocation :` ; extra . errors . push ( logError ( errorString , functionPosition ) ) ; } if ( isStreamingFunction ) { const functionPosition = getPosition ( func ) ; const errorString = `${ errorParam } cannot be used with @streaming.` ; extra . errors . push ( logError ( errorString , functionPosition ) ) ; } } return optionsItem ; } param func param isStreaming param lastParameter function getResults ( func , isStreamingFunction , lastParameter , jsDocParamTypeInfo , extra , enumList ) { let resultType = "any" ; let resultDim = "scalar" ; const defaultResultItem = { dimensionality : resultDim , type : resultType , } ; const lastParameterPosition = getPosition ( lastParameter ) ; if ( isStreamingFunction ) { const lastParameterType = lastParameter . type as ts . TypeReferenceNode ; if ( ! lastParameterType ) { const name = ( lastParameter . name as ts . Identifier ) . text ; const ptype = jsDocParamTypeInfo [ name ] ; resultType = TYPE_CUSTOM_FUNCTIONS_STREAMING [ ptype . toLocaleLowerCase ( ) ] ; const paramResultItem = { dimensionality : resultDim , type : resultType , } ; if ( paramResultItem . dimensionality === "scalar" ) { delete paramResultItem . dimensionality ; } return paramResultItem ; } if ( ! lastParameterType . typeArguments || lastParameterType . typeArguments . length !== 1 ) { const errorString = "The 'CustomFunctions.StreamingHandler' needs to be passed in a single result type (e.g., 'CustomFunctions.StreamingHandler < number >') :" ; extra . errors . push ( logError ( errorString , lastParameterPosition ) ) ; return defaultResultItem ; } const returnType = func . type as ts . TypeReferenceNode ; if ( returnType && returnType . getFullText ( ) . trim ( ) !== "void" ) { const errorString = `A streaming function should return 'void'. Use CustomFunctions.StreamingHandler.setResult() to set results.` ; extra . errors . push ( logError ( errorString , lastParameterPosition ) ) ; return defaultResultItem ; } resultType = getParamType ( lastParameterType . typeArguments [ 0 ] , extra , enumList ) ; resultDim = getParamDim ( lastParameterType . typeArguments [ 0 ] ) ; } else if ( func . type ) { if ( func . type . kind === ts . SyntaxKind . TypeReference && ( func . type as ts . TypeReferenceNode ) . typeName . getText ( ) === "Promise" && ( func . type as ts . TypeReferenceNode ) . typeArguments && ( func . type as ts . TypeReferenceNode ) . typeArguments . length === 1 ) { resultType = getParamType ( ( func . type as ts . TypeReferenceNode ) . typeArguments [ 0 ] , extra , enumList ) ; resultDim = getParamDim ( ( func . type as ts . TypeReferenceNode ) . typeArguments [ 0 ] ) ; } else { resultType = getParamType ( func . type , extra , enumList ) ; resultDim = getParamDim ( func . type ) ; } } const returnTypeFromJSDoc = ts . getJSDocReturnType ( func ) ; if ( returnTypeFromJSDoc ) { if ( func . type && func . type . kind !== returnTypeFromJSDoc . kind ) { const name = ( func . name as ts . Identifier ) . text ; const returnPosition = getPosition ( returnTypeFromJSDoc ) ; const errorString = `Type {${ ts . SyntaxKind [ func . type . kind ] }:${ ts . SyntaxKind [ returnTypeFromJSDoc . kind ] }} doesn't match for return type : ${ name }` ; extra . errors . push ( logError ( errorString , returnPosition ) ) ; } if ( returnTypeFromJSDoc . kind === ts . SyntaxKind . TypeReference && ( returnTypeFromJSDoc as ts . TypeReferenceNode ) . typeName . getText ( ) === "Promise" && ( returnTypeFromJSDoc as ts . TypeReferenceNode ) . typeArguments && ( returnTypeFromJSDoc as ts . TypeReferenceNode ) . typeArguments . length === 1 ) { resultType = getParamType ( ( returnTypeFromJSDoc as ts . TypeReferenceNode ) . typeArguments [ 0 ] , extra , enumList ) ; resultDim = getParamDim ( ( returnTypeFromJSDoc as ts . TypeReferenceNode ) . typeArguments [ 0 ] ) ; } else { resultType = getParamType ( returnTypeFromJSDoc , extra , enumList ) ; resultDim = getParamDim ( returnTypeFromJSDoc ) ; } } const resultItem = { dimensionality : resultDim , type : resultType , } ; if ( resultDim === "scalar" ) { delete resultItem . dimensionality ; } if ( resultType === "any" ) { delete resultItem . type ; } return resultItem ; } param params param jsDocParamTypeInfo param jsDocParamInfo function getParameters ( parameterItem ) { const parameterMetadata = [ ] ; parameterItem . parametersToParse . map ( ( p ) => { const parameterPosition = getPosition ( p ) ; let typeNode = p . type as ts . TypeNode ; const name = ( p . name as ts . Identifier ) . text ; const parameterJSDocTypeNode = ts . getJSDocType ( p ) ; if ( parameterJSDocTypeNode && typeNode ) { if ( parameterJSDocTypeNode . kind !== typeNode . kind ) { const errorString = `Type {${ ts . SyntaxKind [ parameterJSDocTypeNode . kind ] }:${ ts . SyntaxKind [ typeNode . kind ] }} doesn't match for parameter : ${ name }` ; parameterItem . extra . errors . push ( logError ( errorString , parameterPosition ) ) ; } } if ( ! typeNode && parameterJSDocTypeNode ) { typeNode = parameterJSDocTypeNode ; } const ptype = getParamType ( typeNode , parameterItem . extra , parameterItem . enumList ) ; const pMetadataItem = { description : parameterItem . jsDocParamInfo [ name ] , dimensionality : getParamDim ( typeNode ) , name , optional : getParamOptional ( p , parameterItem . jsDocParamOptionalInfo ) , repeating : isRepeatingParameter ( typeNode ) , type : ptype , } ; if ( pMetadataItem . dimensionality === "scalar" ) { delete pMetadataItem . dimensionality ; } if ( ! pMetadataItem . optional ) { delete pMetadataItem . optional ; } if ( ! pMetadataItem . description ) { delete pMetadataItem . description ; } if ( ! pMetadataItem . repeating ) { delete pMetadataItem . repeating ; } parameterMetadata . push ( pMetadataItem ) ; } ) . filter ( ( meta ) => meta ) ; return parameterMetadata ; } param type param jsDocParamType function isRepeatingParameter ( type ) { let repeating = false ; if ( type ) { if ( ts . isTypeReferenceNode ( type ) || ts . isArrayTypeNode ( type ) ) { const array = getArrayDimensionalityAndType ( type ) ; if ( array . dimensionality === 1 || array . dimensionality === 3 ) { repeating = true ; } } } return repeating ; } function normalizeLineEndings ( text ) { return text ? text . replace ( /\r\n|\r/g , "\n" ) : text ; } param node function getDescription ( node ) { let description = "" ; if ( node . jsDoc [ 0 ] ) { description = node . jsDoc [ 0 ] . comment ; } return normalizeLineEndings ( description ) ; } param node returns function findTag ( node , tagName ) { return ts . getJSDocTags ( node ) . find ( ( tag ) => containsTag ( tag , tagName ) ) ; } /**
 * If a node contains the named tag, returns the tag comment, otherwise returns "".
 */ function getTagComment ( node , tagName ) { const tag = findTag ( node , tagName ) ; return tag ?. comment ?. toString ( ) || "" ; } param node returns function hasTag ( node , tagName ) { return findTag ( node , tagName ) !== undefined ; } param node function isCustomFunction ( node ) { return hasTag ( node , CUSTOM_FUNCTION ) ; } param node function isVolatile ( node ) { return hasTag ( node , VOLATILE ) ; } param node function isAddressRequired ( node ) { return hasTag ( node , REQUIRESADDRESS ) ; } param node function isRequiresParameterAddresses ( node ) { return hasTag ( node , REQUIRESPARAMETERADDRESSES ) ; } function containsTag ( tag , tagName ) { return ( tag . tagName . escapedText as string ) . toLowerCase ( ) === tagName ; } param node param streamFunction function isStreaming ( node , streamFunction ) { return streamFunction || hasTag ( node , STREAMING ) ; } param node function isCancelableTag ( node , cancelableFunction ) { return cancelableFunction || hasTag ( node , CANCELABLE ) ; } param node function getJSDocParams ( node ) { const jsDocParamInfo = { } ; ts . getAllJSDocTagsOfKind ( node , ts . SyntaxKind . JSDocParameterTag ) . forEach ( ( tag ) => { if ( tag . comment ) { const tagComment = tag . comment . toString ( ) ; const comment = ( tagComment . startsWith ( "-" ) ? tagComment . slice ( 1 ) : tagComment ) . trim ( ) ; jsDocParamInfo [ ( tag as ts . JSDocPropertyLikeTag ) . name . getFullText ( ) ] = comment ; } else { jsDocParamInfo [ ( tag as ts . JSDocPropertyLikeTag ) . name . getFullText ( ) ] = "" ; } } ) ; return jsDocParamInfo ; } param node function getJSDocParamsType ( node ) { const jsDocParamTypeInfo = { } ; ts . getAllJSDocTagsOfKind ( node , ts . SyntaxKind . JSDocParameterTag ) . forEach ( ( tag ) => { if ( tag . typeExpression ) { const paramType = tag . typeExpression . getFullText ( ) . slice ( 1 , tag . typeExpression . getFullText ( ) . length - 1 ) ; jsDocParamTypeInfo [ ( tag as ts . JSDocPropertyLikeTag ) . name . getFullText ( ) ] = paramType ; } else { jsDocParamTypeInfo [ ( tag as ts . JSDocPropertyLikeTag ) . name . getFullText ( ) ] = "any" ; } } ) ; return jsDocParamTypeInfo ; } param node function getJSDocParamsOptionalType ( node ) { const jsDocParamOptionalTypeInfo = { } ; ts . getAllJSDocTagsOfKind ( node , ts . SyntaxKind . JSDocParameterTag ) . forEach ( ( tag ) => { jsDocParamOptionalTypeInfo [ ( tag as ts . JSDocPropertyLikeTag ) . name . getFullText ( ) ] = tag . isBracketed ; } ) ; return jsDocParamOptionalTypeInfo ; } param param function hasStreamingInvocationParameter ( param , jsDocParamTypeInfo ) { const isTypeReferenceNode = param && param . type && ts . isTypeReferenceNode ( param . type ) ; if ( param ) { const name = ( param . name as ts . Identifier ) . text ; if ( name ) { const ptype = jsDocParamTypeInfo [ name ] ; if ( ptype ) { const typecheck = TYPE_CUSTOM_FUNCTIONS_STREAMING [ ptype . toLocaleLowerCase ( ) ] ; if ( typecheck ) { return true ; } } } } if ( ! isTypeReferenceNode ) { return false ; } const typeRef = param . type as ts . TypeReferenceNode ; const typeName = typeRef . typeName . getText ( ) ; return ( typeName === "CustomFunctions.StreamingInvocation" || typeName === "CustomFunctions.StreamingHandler" || typeName === "IStreamingCustomFunctionHandler" ) ; } param param param jsDocParamTypeInfo function hasCancelableInvocationParameter ( param , jsDocParamTypeInfo ) { const isTypeReferenceNode = param && param . type && ts . isTypeReferenceNode ( param . type ) ; if ( param ) { const name = ( param . name as ts . Identifier ) . text ; if ( name ) { const ptype = jsDocParamTypeInfo [ name ] ; if ( ptype ) { const cancelableTypeCheck = TYPE_CUSTOM_FUNCTION_CANCELABLE [ ptype . toLocaleLowerCase ( ) ] ; if ( cancelableTypeCheck ) { return true ; } } } } if ( ! isTypeReferenceNode ) { return false ; } const typeRef = param . type as ts . TypeReferenceNode ; const typeName = typeRef . typeName . getText ( ) ; return typeName === "CustomFunctions.CancelableHandler" || typeName === "CustomFunctions.CancelableInvocation" ; } param param param jsDocParamTypeInfo function hasInvocationParameter ( param , jsDocParamTypeInfo ) { const isTypeReferenceNode = param && param . type && ts . isTypeReferenceNode ( param . type ) ; if ( param ) { const name = ( param . name as ts . Identifier ) . text ; if ( name ) { const ptype = jsDocParamTypeInfo [ name ] ; if ( ptype ) { if ( ptype . toLocaleLowerCase ( ) === TYPE_CUSTOM_FUNCTION_INVOCATION ) { return true ; } } } } if ( ! isTypeReferenceNode ) { return false ; } const typeRef = param . type as ts . TypeReferenceNode ; return typeRef . typeName . getText ( ) === "CustomFunctions.Invocation" ; } param t function getParamType ( t , extra , enumList ) { let type = "any" ; if ( t ) { let kind = t . kind ; const typePosition = getPosition ( t ) ; if ( ts . isTypeReferenceNode ( t ) || ts . isArrayTypeNode ( t ) ) { let arrayType = { dimensionality : 0 , type : ts . SyntaxKind . AnyKeyword , } ; if ( ts . isTypeReferenceNode ( t ) ) { const array = t as ts . TypeReferenceNode ; if ( enumList . indexOf ( array . typeName . getText ( ) ) >= 0 ) { return type ; } if ( array . typeName . getText ( ) !== "Array" ) { extra . errors . push ( logError ( "Invalid type: " + array . typeName . getText ( ) , typePosition ) ) ; return type ; } } arrayType = getArrayDimensionalityAndType ( t ) ; kind = arrayType . type ; } type = TYPE_MAPPINGS [ kind ] ; if ( ! type ) { extra . errors . push ( logError ( "Type doesn't match mappings" , typePosition ) ) ; } } return type ; } param node function getArrayDimensionalityAndType ( node ) { let array = { dimensionality : 0 , type : ts . SyntaxKind . AnyKeyword , } ; if ( ts . isArrayTypeNode ( node ) ) { array = getArrayDimensionalityAndTypeForArrayTypeNode ( node ) ; } else if ( ts . isTypeReferenceNode ( node ) ) { array = getArrayDimensionalityAndTypeForReferenceNode ( node as ts . TypeReferenceNode ) ; } return array ; } param node function getArrayDimensionalityAndTypeForArrayTypeNode ( node ) { const array = { dimensionality : 1 , type : ts . SyntaxKind . AnyKeyword , } ; let nodeCheck = ( node as ts . ArrayTypeNode ) . elementType ; array . type = nodeCheck . kind ; while ( ts . isArrayTypeNode ( nodeCheck ) ) { array . dimensionality ++ ; nodeCheck = ( nodeCheck as ts . ArrayTypeNode ) . elementType ; array . type = nodeCheck . kind ; } return array ; } param node function getArrayDimensionalityAndTypeForReferenceNode ( node ) { const array = { dimensionality : 0 , type : ts . SyntaxKind . AnyKeyword , } ; if ( node . typeArguments && node . typeArguments . length === 1 ) { let nodeCheck = node ; let dimensionalityCount = 1 ; while ( nodeCheck . typeArguments ) { if ( TYPE_MAPPINGS_SIMPLE [ nodeCheck . typeArguments [ 0 ] . kind ] ) { array . dimensionality = dimensionalityCount ; array . type = nodeCheck . typeArguments [ 0 ] . kind ; } nodeCheck = nodeCheck . typeArguments [ 0 ] as ts . TypeReferenceNode ; dimensionalityCount ++ ; } } return array ; } param t function getParamDim ( t ) { let dimensionality = "scalar" ; if ( t ) { if ( ts . isTypeReferenceNode ( t ) || ts . isArrayTypeNode ( t ) ) { const array = getArrayDimensionalityAndType ( t ) ; if ( array . dimensionality > 1 ) { dimensionality = "matrix" ; } } } return dimensionality ; } function getParamOptional ( p , jsDocParamOptionalInfo ) { let optional = false ; const name = ( p . name as ts . Identifier ) . text ; const isOptional = p . questionToken != null || p . initializer != null || p . dotDotDotToken != null ; if ( isOptional ) { optional = true ; } else { optional = jsDocParamOptionalInfo [ name ] ; } return optional ; } param error export function logError ( error , position ? ) { if ( position ) { error = `${ error } (${ position . line + 1 },${ position . character + 1 })` ; } return error ; }