import { XMLParser } from "ui5plugin-parser" ; import { SAPNodeDAO } from "ui5plugin-parser/dist/classes/librarydata/SAPNodeDAO" ; import { ResourceModelData } from "ui5plugin-parser/dist/classes/UI5Classes/ResourceModelData" ; import { IUIAggregation , AbstractUIClass , IUIProperty , IUIEvent , ITypeValue } from "ui5plugin-parser/dist/classes/UI5Classes/UI5Parser/UIClass/AbstractUIClass" ; import { IXMLFile } from "ui5plugin-parser/dist/classes/utils/FileReader" ; import { TextDocumentTransformer } from "ui5plugin-parser/dist/classes/utils/TextDocumentTransformer" ; import { URLBuilder } from "ui5plugin-parser/dist/classes/utils/URLBuilder" ; import { PositionType } from "ui5plugin-parser/dist/classes/utils/XMLParser" ; import * as vscode from "vscode" ; import { UI5Plugin } from "../../../../../UI5Plugin" ; import { RangeAdapter } from "../../../../adapters/vscode/RangeAdapter" ; import { TextDocumentAdapter } from "../../../../adapters/vscode/TextDocumentAdapter" ; import { VSCodeFileReader } from "../../../../utils/VSCodeFileReader" ; import { CustomCompletionItem } from "../../CustomCompletionItem" ; import { ICompletionItemFactory } from "../abstraction/ICompletionItemFactory" ; import { StandardXMLCompletionItemFactory } from "./StandardXMLCompletionItemFactory" ; export class XMLDynamicCompletionItemFactory implements ICompletionItemFactory { async createCompletionItems ( document , position ) { let completionItems = [ ] ; const XMLFile = document && TextDocumentTransformer . toXMLFile ( new TextDocumentAdapter ( document ) ) ; if ( XMLFile && XMLFile . XMLParserData ?. areAllStringsClosed ) { const currentPositionOffset = document . offsetAt ( position ) ; const positionType = XMLParser . getPositionType ( XMLFile , currentPositionOffset ) ; if ( positionType === PositionType . InTheTagAttributes ) { completionItems = this . _getAttributeCompletionItems ( document , position ) ; } else if ( positionType === PositionType . InTheString ) { completionItems = this . _getAttributeValuesCompletionItems ( document , position ) ; } else if ( positionType === PositionType . InTheClassName ) { completionItems = this . _getTagCompletionItems ( document , position ) ; } else if ( positionType === PositionType . InBodyOfTheClass ) { completionItems = this . _getCompletionItemsFromClassBody ( document , position ) ; } completionItems = this . _removeDuplicateCompletionItems ( completionItems ) ; const range = this . _generateRangeForReplacement ( positionType , XMLFile , document , position ) ; if ( range ) { completionItems . forEach ( completionItem => { completionItem . range = range ; } ) ; } } return completionItems ; } private _generateRangeForReplacement ( positionType , XMLFile , document , position ) { let range = document . getWordRangeAtPosition ( position ) ; const positionOffset = document . offsetAt ( position ) ; if ( positionType === PositionType . InTheString ) { const tagPosition = XMLParser . getTagBeginEndPosition ( XMLFile , positionOffset ) ; const tag = XMLParser . getTagInPosition ( XMLFile , positionOffset ) ; const attributes = XMLParser . getAttributesOfTheTag ( tag ) ; const attribute = attributes ?. find ( attribute => { const index = tag . text . indexOf ( attribute ) ; const offset = tag . positionBegin + index ; return positionOffset > offset && positionOffset < offset + attribute . length ; } ) ; if ( attribute ) { const indexOfAttribute = tag . text . indexOf ( attribute ) ; const { attributeValue } = XMLParser . getAttributeNameAndValue ( attribute ) ; if ( attributeValue ) { const indexOfValue = attribute . indexOf ( attributeValue ) ; const positionBegin = tagPosition . positionBegin + indexOfAttribute + indexOfValue ; const positionEnd = positionBegin + attributeValue . length ; range = RangeAdapter . offsetsToVSCodeRange ( XMLFile . content , positionBegin , positionEnd - 1 ) ; } } } return range ; } private _getAllFileSpecificCompletionItems ( addPrefix = true ) { let completionItems = [ ] ; const textEditor = vscode . window . activeTextEditor ; if ( textEditor ) { const document = textEditor . document ; const XMLText = document . getText ( ) ; completionItems = this . _convertToFileSpecificCompletionItems ( StandardXMLCompletionItemFactory . XMLStandardLibCompletionItems , XMLText , addPrefix ) ; } return completionItems ; } private _getAttributeValuesCompletionItems ( document , position ) { let completionItems = [ ] ; const XMLFile = TextDocumentTransformer . toXMLFile ( new TextDocumentAdapter ( document ) ) ; const currentPositionOffset = document . offsetAt ( position ) ; if ( document && XMLFile && currentPositionOffset ) { const positionBeforeString = XMLParser . getPositionBeforeStringBegining ( XMLFile . content , currentPositionOffset ) ; const className = XMLParser . getClassNameInPosition ( XMLFile , positionBeforeString ) ; if ( className ) { const UIClass = UI5Plugin . getInstance ( ) . parser . classFactory . getUIClass ( className ) ; const attributeName = XMLParser . getNearestAttribute ( XMLFile . content , positionBeforeString ) ; const UIProperty = this . _getUIPropertyRecursively ( UIClass , attributeName ) ; if ( UIProperty && UIProperty . typeValues . length > 0 ) { completionItems = this . _generateCompletionItemsFromTypeValues ( UIProperty . typeValues ) ; } else if ( UIProperty ?. type === "string" ) { const currentComponentName = VSCodeFileReader . getComponentNameOfAppInCurrentWorkspaceFolder ( ) ; if ( currentComponentName && ResourceModelData . resourceModels [ currentComponentName ] ) { const typeValues = ResourceModelData . resourceModels [ currentComponentName ] ; completionItems = this . _generateCompletionItemsFromTypeValues ( typeValues ) ; } } else { const UIEvent = this . _getUIEventRecursively ( UIClass , attributeName ) ; if ( UIEvent ) { let methods = XMLParser . getMethodsOfTheControl ( VSCodeFileReader . getControllerNameOfTheCurrentDocument ( ) || "" ) ; if ( methods . length === 0 ) { const className = UI5Plugin . getInstance ( ) . parser . fileReader . getResponsibleClassForXMLDocument ( new TextDocumentAdapter ( document ) ) ; if ( className ) { methods = XMLParser . getMethodsOfTheControl ( className ) ; } } const mappedMethods = methods . map ( classMethod => ( { text : classMethod . name , description : classMethod . description } ) ) ; completionItems = this . _generateCompletionItemsFromTypeValues ( mappedMethods ) ; } } } } return completionItems ; } private _getTagCompletionItems ( document , position ) { let completionItems = [ ] ; const currentPositionOffset = document . offsetAt ( position ) ; const XMLFile = TextDocumentTransformer . toXMLFile ( new TextDocumentAdapter ( document ) ) ; if ( XMLFile && currentPositionOffset ) { try { const libName = XMLParser . getLibraryNameInPosition ( XMLFile , currentPositionOffset ) ; const currentTagText = XMLParser . getTagInPosition ( XMLFile , currentPositionOffset ) . text ; const isTagEmpty = ! currentTagText [ 1 ] . match ( /[a-zA-Z]/ ) ; if ( isTagEmpty ) { const { positionBegin : currentTagPositionBegin } = XMLParser . getTagBeginEndPosition ( XMLFile , currentPositionOffset - 1 ) ; completionItems = this . _getParentTagCompletionItems ( document , position , currentTagPositionBegin - 1 ) ; completionItems = this . _convertToFileSpecificCompletionItems ( completionItems , XMLFile . content ) ; } else if ( libName ) { let tagPrefix = XMLParser . getTagPrefix ( currentTagText ) ; tagPrefix = tagPrefix ? `${ tagPrefix }:` : "" ; const isThisClassFromAProject = ! ! UI5Plugin . getInstance ( ) . parser . fileReader . getManifestForClass ( libName + "." ) ; if ( ! isThisClassFromAProject ) { completionItems = this . _getStandardCompletionItemsFilteredByLibraryName ( libName ) ; completionItems = this . _filterCompletionItemsByAggregationsType ( document , position , completionItems ) ; } else { completionItems = this . _getCompletionItemsForCustomClasses ( libName , tagPrefix ) ; } } } catch ( error ) { if ( ( < any > error ) . name === "LibraryPathException" ) { completionItems = [ ] ; } } } return completionItems ; } private _convertToFileSpecificCompletionItems ( completionItems , XMLText , addPrefix = true ) { const nodeDAO = new SAPNodeDAO ( ) ; return completionItems . reduce ( ( accumulator , completionItem ) => { const node = nodeDAO . findNode ( completionItem . className ) ; if ( node ) { const libName = node . getName ( ) . replace ( `.${ node . getDisplayName ( ) }` , "" ) ; const tagPrefix = XMLParser . getPrefixForLibraryName ( libName , XMLText ) ; if ( tagPrefix !== undefined ) { let classPrefix = "" ; if ( addPrefix ) { classPrefix = tagPrefix . length > 0 ? `${ tagPrefix }:` : tagPrefix ; } const completionItem = this . _getStandardCompletionItemWithPrefix ( node , tagPrefix , classPrefix ) ; accumulator . push ( completionItem ) ; } } else { accumulator . push ( completionItem ) ; } return accumulator ; } , [ ] ) ; } private _getCompletionItemsForCustomClasses ( libName , tagPrefix ) { const xmlClassFactory = new StandardXMLCompletionItemFactory ( ) ; const wsFolders = vscode . workspace . workspaceFolders || [ ] ; const classNames = wsFolders . reduce ( ( accumulator , wsFolder ) => { const classNames = UI5Plugin . getInstance ( ) . parser . fileReader . getAllJSClassNamesFromProject ( { fsPath : wsFolder . uri . fsPath } ) ; accumulator = accumulator . concat ( classNames ) ; return accumulator ; } , [ ] ) ; const classNamesForLibName = classNames . filter ( className => className . startsWith ( libName ) ) ; const UIClassesForLibName = classNamesForLibName . map ( className => UI5Plugin . getInstance ( ) . parser . classFactory . getUIClass ( className ) ) ; const UIClassesThatExtendsUIControl = UIClassesForLibName . filter ( UIClass => UI5Plugin . getInstance ( ) . parser . classFactory . isClassAChildOfClassB ( UIClass . className , "sap.ui.core.Control" ) ) ; const completionItems = UIClassesThatExtendsUIControl . map ( UIClass => xmlClassFactory . generateXMLClassCompletionItemFromUIClass ( UIClass , tagPrefix ) ) ; return completionItems ; } private _getStandardCompletionItemsFilteredByLibraryName ( libName ) { const standardCompletionItems = this . _getAllFileSpecificCompletionItems ( false ) ; const completionItems = standardCompletionItems . filter ( completionItem => completionItem . className . startsWith ( libName ) ) ; return completionItems ; } private _getStandardCompletionItemWithPrefix ( node , tagPrefix , classPrefix = "" ) { const XMLClassFactoryInstance = new StandardXMLCompletionItemFactory ( ) ; const completionItem = XMLClassFactoryInstance . generateXMLClassCompletionItemFromSAPNode ( node , tagPrefix , classPrefix ) ; return completionItem ; } private _filterCompletionItemsByAggregationsType ( document , position , completionItems ) { const XMLFile = vscode . window . activeTextEditor ?. document && TextDocumentTransformer . toXMLFile ( new TextDocumentAdapter ( vscode . window . activeTextEditor . document ) ) ; const currentPositionOffset = vscode . window . activeTextEditor ?. document . offsetAt ( vscode . window . activeTextEditor ?. selection . start ) ; if ( XMLFile && currentPositionOffset ) { const { positionBegin : currentTagPositionBegin } = XMLParser . getTagBeginEndPosition ( XMLFile , currentPositionOffset ) ; completionItems = this . _getParentTagCompletionItems ( document , position , currentTagPositionBegin - 1 , completionItems ) ; } return completionItems ; } private _getParentTagCompletionItems ( document , position , currentPosition , completionItems = this . _getAllFileSpecificCompletionItems ( ) ) { const XMLFile = TextDocumentTransformer . toXMLFile ( new TextDocumentAdapter ( document ) ) ; const currentPositionOffset = document . offsetAt ( position ) ; if ( XMLFile && currentPositionOffset ) { const parentTagInfo = XMLParser . getParentTagAtPosition ( XMLFile , currentPosition ) ; const parentTagName = XMLParser . getClassNameFromTag ( parentTagInfo . text ) ; const parentTagIsAClass = parentTagName [ 0 ] === parentTagName [ 0 ] . toUpperCase ( ) ; if ( parentTagIsAClass ) { const classTagPrefix = XMLParser . getTagPrefix ( parentTagInfo . text ) ; const className = XMLParser . getClassNameFromTag ( parentTagInfo . text ) ; const libraryPath = XMLParser . getLibraryPathFromTagPrefix ( XMLFile , classTagPrefix , parentTagInfo . positionEnd ) ; const classOfTheTag = [ libraryPath , className ] . join ( "." ) ; const UIClass = UI5Plugin . getInstance ( ) . parser . classFactory . getUIClass ( classOfTheTag ) ; const aggregations = this . _getAllAggregationsRecursively ( UIClass ) ; const aggregationCompletionItems = this . _generateAggregationCompletionItems ( aggregations , classTagPrefix ) ; const defaultAggregation = aggregations . find ( aggregation => aggregation . default ) ; if ( defaultAggregation ) { const aggregationType = defaultAggregation . type ; if ( aggregationType ) { const nodeDAO = new SAPNodeDAO ( ) ; completionItems = aggregationCompletionItems . concat ( completionItems . filter ( completionItem => { return nodeDAO . isInstanceOf ( aggregationType , completionItem . className ) ; } ) ) ; } } else if ( ( classOfTheTag === "sap.ui.core.FragmentDefinition" || classOfTheTag === "sap.ui.core.mvc.View" ) && libraryPath ) { const nodeDAO = new SAPNodeDAO ( ) ; completionItems = completionItems . filter ( completionItem => { return nodeDAO . isInstanceOf ( "sap.ui.core.Control" , completionItem . className ) ; } ) ; } else { completionItems = aggregationCompletionItems ; } } else { const aggregationName = XMLParser . getClassNameFromTag ( parentTagInfo . text ) ; const classTagInfo = XMLParser . getParentTagAtPosition ( XMLFile , parentTagInfo . positionBegin - 1 ) ; const classTagPrefix = XMLParser . getTagPrefix ( classTagInfo . text ) ; const className = XMLParser . getClassNameFromTag ( classTagInfo . text ) ; const libraryPath = XMLParser . getLibraryPathFromTagPrefix ( XMLFile , classTagPrefix , classTagInfo . positionEnd ) ; const classOfTheTag = [ libraryPath , className ] . join ( "." ) ; const UIClass = UI5Plugin . getInstance ( ) . parser . classFactory . getUIClass ( classOfTheTag ) ; const UIAggregation = this . _getUIAggregationRecursively ( UIClass , aggregationName ) ; if ( UIAggregation ?. type ) { const aggregationType = UIAggregation . type ; const nodeDAO = new SAPNodeDAO ( ) ; completionItems = completionItems . filter ( completionItem => { return nodeDAO . isInstanceOf ( aggregationType , completionItem . className ) ; } ) ; } } } return completionItems ; } private _generateAggregationCompletionItems ( aggregations , classTagPrefix ) { let completionItems = [ ] ; completionItems = aggregations . map ( aggregation => { const completionItem = new CustomCompletionItem ( aggregation . name ) ; completionItem . kind = vscode . CompletionItemKind . Class ; completionItem . insertText = this . _generateInsertTextForAggregation ( aggregation , classTagPrefix ) ; completionItem . detail = aggregation . type || "" ; return completionItem ; } ) ; return completionItems ; } private _generateInsertTextForAggregation ( aggregation , prefix ) { if ( prefix ) { prefix = `${ prefix }:` ; } return new vscode . SnippetString ( `${ prefix }${ aggregation . name }>\n\t$0\n</${ prefix }${ aggregation . name }>` ) ; } private _cloneCompletionItem ( completionItem ) { const clone = new CustomCompletionItem ( completionItem . label , completionItem . kind ) ; for ( const attribute in completionItem ) { clone [ attribute ] = completionItem [ attribute ] ; } return clone ; } private _getCompletionItemsFromClassBody ( document , position ) { let completionItems = [ ] ; const XMLText = TextDocumentTransformer . toXMLFile ( new TextDocumentAdapter ( document ) ) ?. content ; const currentPositionOffset = document . offsetAt ( position ) ; if ( XMLText && currentPositionOffset ) { completionItems = this . _getParentTagCompletionItems ( document , position , currentPositionOffset ) ; completionItems = completionItems . map ( completionItem => { completionItem = this . _cloneCompletionItem ( completionItem ) ; if ( completionItem . insertText instanceof vscode . SnippetString ) { completionItem . insertText . value = "<" + completionItem . insertText . value ; } else { completionItem . insertText = "<" + completionItem . insertText ; } return completionItem ; } ) ; } return completionItems ; } private _getAttributeCompletionItems ( document , position ) { let completionItems = [ ] ; const XMLFile = TextDocumentTransformer . toXMLFile ( new TextDocumentAdapter ( document ) ) ; const currentPositionOffset = document . offsetAt ( position ) ; if ( XMLFile && currentPositionOffset ) { const className = XMLParser . getClassNameInPosition ( XMLFile , currentPositionOffset ) ; if ( className ) { const UIClass = UI5Plugin . getInstance ( ) . parser . classFactory . getUIClass ( className ) ; let controllerMethods = XMLParser . getMethodsOfTheControl ( VSCodeFileReader . getControllerNameOfTheCurrentDocument ( ) || "" ) . map ( method => method . name ) ; controllerMethods = [ ... new Set ( controllerMethods ) ] ; completionItems = this . _getPropertyCompletionItemsFromClass ( UIClass ) ; completionItems = completionItems . concat ( this . _getEventCompletionItemsFromClass ( UIClass , controllerMethods ) ) ; completionItems = completionItems . concat ( this . _getAggregationCompletionItemsFromClass ( UIClass ) ) ; completionItems = completionItems . concat ( this . _getAssociationCompletionItemsFromClass ( UIClass ) ) ; } } return completionItems ; } private _getUIAggregationRecursively ( UIClass , aggregationName ) { let aggregation ; aggregation = UIClass . aggregations . find ( aggregation => aggregation . name === aggregationName ) ; if ( ! aggregation && UIClass . parentClassNameDotNotation ) { const parentClass = UI5Plugin . getInstance ( ) . parser . classFactory . getUIClass ( UIClass . parentClassNameDotNotation ) ; aggregation = this . _getUIAggregationRecursively ( parentClass , aggregationName ) ; } return aggregation ; } private _getAllAggregationsRecursively ( UIClass ) { let aggregations = UIClass . aggregations ; if ( UIClass . parentClassNameDotNotation ) { const parentClass = UI5Plugin . getInstance ( ) . parser . classFactory . getUIClass ( UIClass . parentClassNameDotNotation ) ; aggregations = aggregations . concat ( this . _getAllAggregationsRecursively ( parentClass ) ) ; } return aggregations ; } private _getUIPropertyRecursively ( UIClass , propertyName ) { let property ; property = UIClass . properties . find ( property => property . name === propertyName ) ; if ( ! property && UIClass . parentClassNameDotNotation ) { const parentClass = UI5Plugin . getInstance ( ) . parser . classFactory . getUIClass ( UIClass . parentClassNameDotNotation ) ; property = this . _getUIPropertyRecursively ( parentClass , propertyName ) ; } return property ; } private _getUIEventRecursively ( UIClass , eventName ) { let event ; event = UIClass . events . find ( event => event . name === eventName ) ; if ( ! event && UIClass . parentClassNameDotNotation ) { const parentClass = UI5Plugin . getInstance ( ) . parser . classFactory . getUIClass ( UIClass . parentClassNameDotNotation ) ; event = this . _getUIEventRecursively ( parentClass , eventName ) ; } return event ; } private _generateCompletionItemsFromTypeValues ( typeValues ) { return this . _removeDuplicateCompletionItems ( typeValues . map ( typeValue => { const completionItem = new CustomCompletionItem ( typeValue . text , vscode . CompletionItemKind . Keyword ) ; completionItem . detail = typeValue . text ; completionItem . documentation = typeValue . description ; return completionItem ; } ) ) ; } private _getPropertyCompletionItemsFromClass ( UIClass ) { let completionItems = [ ] ; completionItems = UIClass . properties . map ( property => { const completionItem = new CustomCompletionItem ( property . name ) ; completionItem . kind = vscode . CompletionItemKind . Property ; completionItem . insertText = new vscode . SnippetString ( `${ property . name }` ) ; completionItem . detail = `${ property . name }: ${ property . type }` ; const UI5ApiUri = URLBuilder . getInstance ( ) . getMarkupUrlForPropertiesApi ( UIClass ) ; completionItem . documentation = new vscode . MarkdownString ( `${ UI5ApiUri }\n${ property . description }` ) ; completionItem . sortText = "1" ; return completionItem ; } ) ; if ( UIClass . parentClassNameDotNotation ) { const parentClass = UI5Plugin . getInstance ( ) . parser . classFactory . getUIClass ( UIClass . parentClassNameDotNotation ) ; completionItems = completionItems . concat ( this . _getPropertyCompletionItemsFromClass ( parentClass ) ) ; } return completionItems ; } private _getEventCompletionItemsFromClass ( UIClass , eventValues = [ ] ) { let completionItems = [ ] ; completionItems = UIClass . events . map ( event => { const completionItem = new CustomCompletionItem ( event . name ) ; completionItem . kind = vscode . CompletionItemKind . Event ; completionItem . insertText = new vscode . SnippetString ( `${ event . name }` ) ; completionItem . detail = event . name ; const UI5ApiUri = URLBuilder . getInstance ( ) . getMarkupUrlForEventsApi ( UIClass , event . name ) ; completionItem . documentation = new vscode . MarkdownString ( `${ UI5ApiUri }\n${ event . description }` ) ; completionItem . sortText = "2" ; return completionItem ; } ) ; if ( UIClass . parentClassNameDotNotation ) { const parentClass = UI5Plugin . getInstance ( ) . parser . classFactory . getUIClass ( UIClass . parentClassNameDotNotation ) ; completionItems = completionItems . concat ( this . _getEventCompletionItemsFromClass ( parentClass , eventValues ) ) ; } return completionItems ; } private _getAggregationCompletionItemsFromClass ( UIClass ) { let completionItems = [ ] ; completionItems = UIClass . aggregations . map ( aggregation => { const completionItem = new CustomCompletionItem ( aggregation . name ) ; completionItem . kind = vscode . CompletionItemKind . Property ; completionItem . insertText = new vscode . SnippetString ( `${ aggregation . name }` ) ; completionItem . detail = aggregation . name ; const UI5ApiUri = URLBuilder . getInstance ( ) . getMarkupUrlForAggregationApi ( UIClass ) ; completionItem . documentation = new vscode . MarkdownString ( `${ UI5ApiUri }\n${ aggregation . description }\n${ aggregation . type }` ) ; completionItem . sortText = "3" ; return completionItem ; } ) ; if ( UIClass . parentClassNameDotNotation ) { const parentClass = UI5Plugin . getInstance ( ) . parser . classFactory . getUIClass ( UIClass . parentClassNameDotNotation ) ; completionItems = completionItems . concat ( this . _getAggregationCompletionItemsFromClass ( parentClass ) ) ; } return completionItems ; } private _getAssociationCompletionItemsFromClass ( UIClass ) { let completionItems = [ ] ; completionItems = UIClass . associations . map ( association => { const completionItem = new CustomCompletionItem ( association . name ) ; completionItem . kind = vscode . CompletionItemKind . Property ; completionItem . insertText = new vscode . SnippetString ( `${ association . name }` ) ; completionItem . detail = association . name ; const UI5ApiUri = URLBuilder . getInstance ( ) . getMarkupUrlForAssociationApi ( UIClass ) ; completionItem . documentation = new vscode . MarkdownString ( `${ UI5ApiUri }\n${ association . description }` ) ; completionItem . sortText = "4" ; return completionItem ; } ) ; if ( UIClass . parentClassNameDotNotation ) { const parentClass = UI5Plugin . getInstance ( ) . parser . classFactory . getUIClass ( UIClass . parentClassNameDotNotation ) ; completionItems = completionItems . concat ( this . _getAssociationCompletionItemsFromClass ( parentClass ) ) ; } return completionItems ; } private _removeDuplicateCompletionItems ( completionItems ) { completionItems = completionItems . reduce ( ( accumulator , completionItem ) => { const methodInAccumulator = accumulator . find ( accumulatedCompletionItem => accumulatedCompletionItem . label === completionItem . label ) ; if ( ! methodInAccumulator ) { accumulator . push ( completionItem ) ; } return accumulator ; } , [ ] ) ; return completionItems ; } }