module author Amir Marcel, Christopher Johnson, Corey Van Splinter, Sean Arseneault
 *  date description import { getRanges } from './utils/ast' ; import { FILENAME_AND_PATH , ROUTE_PARAMS } from '../constants/expressPatterns' ; import { readFile , findImportedFiles , getLocalRoute } from './utils/genericFileOps' ; import { findExpressImport , findExpressServer , findRouters , findRoutes , findRouterPath , } from './utils/expressFileOps' ; import { ext } from '../extensionVariables' ; /** Class representing parsed express server data */ class ExpressParser { serverPort : number ; serverFile : any ; supportFiles : Map < string , File > ; expressData : ExpressData [ ] ; routerData : Map < string , RouterData > ; routes : Route [ ] ; param { string } serverPath param { number } portNum constructor ( serverPath , portNum ) { this . serverPort = portNum ; this . serverFile = this . initializeServerFile ( serverPath ) ; this . supportFiles = new Map ( ) ; this . expressData = [ ] ; this . routerData = new Map ( ) ; this . routes = [ ] ; } param { string } serverPath return { File } initializeServerFile ( serverPath ) { const SPLIT_PATH = serverPath . match ( FILENAME_AND_PATH ) ; if ( SPLIT_PATH !== null ) return { path : SPLIT_PATH [ 1 ] , fileName : SPLIT_PATH [ 2 ] , contents : '' } ; return { path : '' , fileName : '' , contents : '' } ; } return { MasterDataObject } parse ( ) { this . serverFile . contents = readFile ( this . serverFile ) ; this . findSupportFiles ( ) ; this . findExpressImports ( ) ; this . findServerName ( ) ; this . findRouterFiles ( this . serverFile ) ; this . supportFiles . forEach ( ( file ) => this . findRouterFiles ( file ) ) ; this . findAllRoutes ( ) ; this . findRouteEndLines ( ) ; return this . buildMasterDataObject ( ) ; } /**
     * Finds all files imported by the top-level server file, and all files that they import
     */ findSupportFiles ( ) { let queue = this . addSupportFiles ( findImportedFiles ( this . serverFile ) ) ; let currentFile = queue . shift ( ) ; while ( currentFile !== undefined ) { queue = queue . concat ( this . readSupportFile ( currentFile ) ) ; currentFile = queue . shift ( ) ; } } param { File [ ] } importedFiles return { string [ ] } addSupportFiles ( importedFiles ) { const addedFiles = [ ] ; for ( const importedFile of importedFiles ) { const FILE_NAME = importedFile . path . concat ( importedFile . fileName ) ; if ( this . supportFiles . get ( FILE_NAME ) === undefined ) { this . supportFiles . set ( FILE_NAME , importedFile ) ; addedFiles . push ( FILE_NAME ) ; } } return addedFiles ; } param { string } fileName return { string [ ] } readSupportFile ( fileName ) { let addedFiles = [ ] ; const FILE = this . supportFiles . get ( fileName ) ; if ( FILE !== undefined ) { FILE . contents = readFile ( FILE ) ; addedFiles = this . addSupportFiles ( findImportedFiles ( FILE ) ) ; } return addedFiles ; } /**
     * Finds and stores any express import statements in the code base,
     * starting at the top-level server file
     */ findExpressImports ( ) { const IMPORT_NAME = findExpressImport ( this . serverFile ) ; if ( IMPORT_NAME !== undefined ) { this . storeExpressImport ( this . serverFile , IMPORT_NAME ) ; } this . searchSupportFilesForExpress ( ) ; } /**
     * Finds and stores any express import statements in the support files
     */ searchSupportFilesForExpress ( ) { const FILE_LIST = this . supportFiles . entries ( ) ; let currentFile = FILE_LIST . next ( ) . value ; while ( currentFile !== undefined ) { const IMPORT_NAME = findExpressImport ( currentFile [ 1 ] ) ; if ( IMPORT_NAME !== undefined ) { this . storeExpressImport ( currentFile [ 1 ] , IMPORT_NAME ) ; } currentFile = FILE_LIST . next ( ) . value ; } } param file param importName storeExpressImport ( file , importName ) { const filePath = file . path . concat ( file . fileName ) ; this . expressData . push ( { filePath , importName , serverName : '' } ) ; } /**
     * Find the name of the variable used to store each invocation of express
     */ findServerName ( ) { for ( let i = 0 ; i < this . expressData . length ; i += 1 ) { const EXPRESS_FILE = this . supportFiles . get ( this . expressData [ i ] . filePath ) || this . serverFile ; const EXPRESS_NAME = this . expressData [ i ] . importName ; const SERVER_NAME = findExpressServer ( EXPRESS_FILE , EXPRESS_NAME ) ; if ( SERVER_NAME ) this . expressData [ i ] . serverName = SERVER_NAME ; } } /**
     * Identifies all router files used by the express server
     */ findRouterFiles ( file ) { const ROUTERS = findRouters ( file , this . serverPort ) ; ROUTERS . forEach ( ( router ) => { router . path = findRouterPath ( router , file , this . supportFiles ) ; this . routerData . set ( router . path , router ) ; } ) ; } /**
     * Finds all routes in the top-level server file and all support files
     */ findAllRoutes ( ) { const BASE_ROUTE = `http://localhost:${ this . serverPort }` ; const PATH = this . serverFile . path . concat ( this . serverFile . fileName ) ; this . routes = findRoutes ( this . serverFile . contents , PATH , BASE_ROUTE ) ; this . supportFiles . forEach ( ( file ) => { let route = BASE_ROUTE ; const ROUTER_DATA = this . routerData . get ( file . path + file . fileName ) ; if ( ROUTER_DATA !== undefined ) route = ROUTER_DATA . baseRoute ; this . routes = this . routes . concat ( findRoutes ( file . contents , file . path + file . fileName , route ) ) ; } ) ; } /**
     * Find the ending line of any route functions
     */ findRouteEndLines ( ) { this . routes . forEach ( ( route ) => { const ROUTER_FILE = this . supportFiles . get ( route . path ) || this . serverFile ; const FILE_RANGES = getRanges ( ROUTER_FILE . contents ) ; route . endLine = FILE_RANGES [ route . startLine ] ; } ) ; } buildMasterDataObject ( ) { const data = ext . context . workspaceState . get ( `masterDataObject` ) ; const serverPath = this . serverFile . path . concat ( this . serverFile . fileName ) ; const urls = { } ; const paths = { } ; const index = { } ; this . routes . forEach ( ( route ) => { const { origin , pathname , port , href } = new URL ( route . route ) ; const params = this . findParams ( getLocalRoute ( route . route ) ) ; let presets = { } ; const range = route . startLine === route . endLine ? route . startLine : `${ route . startLine }-${ route . endLine }` ; index [ route . path ] = { serverPath , port , } ; if ( paths [ route . path ] === undefined ) { paths [ route . path ] = { } ; } paths [ route . path ] [ range ] = { method : route . method , origin , pathname , port , params , range : [ route . startLine , route . endLine ] , } ; if ( data && data . domains [ serverPath ] && data . domains [ serverPath ] . urls [ href ] ) { presets = data . domains [ serverPath ] . urls [ href ] . presets ; } if ( ! urls [ href ] ) { urls [ href ] = { serverPath , filePath : route . path , href , port , pathname , methods : [ route . method ] , presets , params , } ; } else if ( ! urls [ href ] . methods . includes ( route . method ) ) { urls [ href ] . methods . push ( route . method ) ; } } ) ; const output = { paths , urls , index } ; return output ; } param { string } route return { Record < string , string > } findParams ( route ) { const output = { } ; const PARAMS_FOUND = route . match ( ROUTE_PARAMS ) ; if ( PARAMS_FOUND ) { for ( let i = 1 ; i < PARAMS_FOUND . length ; i ++ ) { output [ PARAMS_FOUND [ i ] . slice ( 1 ) ] = '' ; } } return output ; } } export default ExpressParser ;