import fs from 'fs' ; import { dirname , join } from 'path' ; import { parse } from '@babel/parser' ; import type { NodePath } from '@babel/traverse' ; import traverse from '@babel/traverse' ; import * as t from '@babel/types' ; import resolve from 'resolve' ; import { DEFAULT_CODE_EXTENSIONS } from '../constants' ; import type { Metadata } from '../types' ; import { getDefaultExport , getNamedExport } from './traversers' ; import type { PartialBindingWithMeta } from './types' ; param name param node param resolveFor export const resolveIdentifierComingFromDestructuring = ( { name , node , resolveFor = 'key' , } ) : t . ObjectProperty | undefined => { let resolvedDestructuringIdentifier ; if ( t . isObjectPattern ( node ) ) { const pattern = node as t . ObjectPattern ; return pattern . properties . find ( ( property ) => { if ( t . isObjectProperty ( property ) ) { if ( resolveFor === 'key' ) { return t . isIdentifier ( property . key ) && property . key . name === name ; } else if ( resolveFor === 'value' ) { return t . isIdentifier ( property . value ) && property . value . name === name ; } } return false ; } ) as t . ObjectProperty | undefined ; } else if ( t . isVariableDeclarator ( node ) ) { const declarator = node as t . VariableDeclarator ; resolvedDestructuringIdentifier = resolveIdentifierComingFromDestructuring ( { name , node : declarator . id as t . Expression , resolveFor , } ) ; } return resolvedDestructuringIdentifier ; } ; param expression param meta { Metadata } param referenceName { string } const resolveObjectPatternValueNode = ( expression , meta , referenceName ) : t . Node | undefined => { let objectPatternValueNode = undefined ; if ( t . isObjectExpression ( expression ) ) { traverse ( expression , { noScope : true , ObjectProperty : { exit ( path ) { if ( t . isIdentifier ( path . node . key , { name : referenceName } ) ) { objectPatternValueNode = path . node . value ; path . stop ( ) ; } } , } , } ) ; } else if ( t . isIdentifier ( expression ) ) { const resolvedBinding = resolveBinding ( expression . name , meta ) ; if ( resolvedBinding ) { const isResolvedToSameNode = resolvedBinding . path . node === expression ; if ( ! isResolvedToSameNode && resolvedBinding . constant && t . isExpression ( resolvedBinding . node ) ) { objectPatternValueNode = resolveObjectPatternValueNode ( resolvedBinding . node , meta , referenceName ) ; } } } return objectPatternValueNode ; } ; param node param referenceName const getDestructuredObjectPatternKey = ( node , referenceName ) : string => { let result = referenceName ; for ( const property of node . properties ) { if ( t . isObjectProperty ( property ) ) { const keyName = t . isIdentifier ( property . key ) ? property . key . name : '' ; const keyValue = t . isIdentifier ( property . value ) ? property . value . name : '' ; if ( keyName !== keyValue && keyValue === referenceName ) { result = keyName ; break ; } } } return result ; } ; const resolveRequest = ( request , extensions , meta ) => { const { filename , opts } = meta . state ; const { resolver } = opts ; if ( ! filename ) { throw new Error ( 'Unable to resolve request due to a missing filename, this is probably a bug!' ) ; } if ( ! resolver ) { const id = request . charAt ( 0 ) === '.' ? join ( dirname ( filename ) , request ) : request ; return resolve . sync ( id , { extensions , } ) ; } return resolver . resolveSync ( filename , request ) ; } ; param referenceName { string } param meta { Metadata } export const resolveBinding = ( referenceName , meta ) : PartialBindingWithMeta | undefined => { const binding = meta . ownPath ?. scope . getOwnBinding ( referenceName ) || meta . parentPath . scope . getBinding ( referenceName ) ; if ( ! binding || binding . path . isObjectPattern ( ) ) { return undefined ; } if ( t . isVariableDeclarator ( binding . path . node ) ) { let node = binding . path . node . init as t . Node ; if ( t . isObjectPattern ( binding . path . node . id ) && t . isExpression ( node ) ) { node = resolveObjectPatternValueNode ( node , meta , getDestructuredObjectPatternKey ( binding . path . node . id , referenceName ) ) as t . Node ; } return { meta , node , path : binding . path , constant : binding . constant , source : 'module' , } ; } if ( binding . path . parentPath ?. isImportDeclaration ( ) ) { if ( ! meta . state . filename ) { return ; } const moduleImportSource = binding . path . parentPath . node . source . value ; if ( moduleImportSource . startsWith ( '@compiled/' ) ) { return ; } const extensions = meta . state . opts . extensions ?? DEFAULT_CODE_EXTENSIONS ; const modulePath = resolveRequest ( moduleImportSource , extensions , meta ) ; if ( ! extensions . some ( ( extension ) => modulePath . endsWith ( extension ) ) ) { return ; } const moduleCode = meta . state . cache . load ( { namespace : 'read-file' , cacheKey : modulePath , value : ( ) => fs . readFileSync ( modulePath , 'utf-8' ) , } ) ; const ast = meta . state . cache . load ( { namespace : 'parse-module' , cacheKey : modulePath , value : ( ) => parse ( moduleCode , { sourceType : 'module' , sourceFilename : modulePath , plugins : meta . state . opts . babelPlugins || [ ] , } ) , } ) ; let foundNode = undefined ; let foundParentPath = undefined ; if ( binding . path . isImportDefaultSpecifier ( ) ) { ( { foundNode , foundParentPath } = meta . state . cache . load ( { namespace : 'find-default-export-module-node' , cacheKey : modulePath , value : ( ) => { const result = getDefaultExport ( ast ) ; return { foundNode : result ?. node , foundParentPath : result ?. path , } ; } , } ) ) ; } else if ( binding . path . isImportSpecifier ( ) ) { const { imported } = binding . path . node ; const exportName = t . isIdentifier ( imported ) ? imported . name : imported . value ; ( { foundNode , foundParentPath } = meta . state . cache . load ( { namespace : 'find-named-export-module-node' , cacheKey : `modulePath=${ modulePath }&exportName=${ exportName }` , value : ( ) => { const result = getNamedExport ( ast , exportName ) ; return { foundNode : result ?. node , foundParentPath : result ?. path , } ; } , } ) ) ; } else if ( binding . path . isImportNamespaceSpecifier ( ) ) { const { path } = binding ; foundNode = path . node ; foundParentPath = path . parentPath ; } if ( ! foundNode || ! foundParentPath ) { return undefined ; } return { constant : binding . constant , node : foundNode , path : foundParentPath , source : 'import' , meta : { ... meta , parentPath : foundParentPath , state : { ... meta . state , file : ast , filename : modulePath , } , } , } ; } return { node : binding . path . node as t . Node , path : binding . path , constant : binding . constant , source : 'module' , meta , } ; } ;