@@ -1523,8 +1523,24 @@ function complete(line, callback) {
15231523      return ; 
15241524    } 
15251525
1526+     // If the target ends with a dot (e.g. `obj.foo.`) such code won't be valid for AST parsing 
1527+     // so in order to make it correct we add an identifier to its end (e.g. `obj.foo.x`) 
1528+     const  parsableCompleteTarget  =  completeTarget . endsWith ( '.' )  ? `${ completeTarget }   : completeTarget ; 
1529+ 
1530+     let  completeTargetAst ; 
1531+     try  { 
1532+       completeTargetAst  =  acornParse ( 
1533+         parsableCompleteTarget ,  {  __proto__ : null ,  sourceType : 'module' ,  ecmaVersion : 'latest'  } , 
1534+       ) ; 
1535+     }  catch  {  /* No need to specifically handle parse errors */  } 
1536+ 
1537+     if  ( ! completeTargetAst )  { 
1538+       return  completionGroupsLoaded ( ) ; 
1539+     } 
1540+ 
15261541    return  includesProxiesOrGetters ( 
1527-       StringPrototypeSplit ( completeTarget ,  '.' ) , 
1542+       completeTargetAst . body [ 0 ] . expression , 
1543+       parsableCompleteTarget , 
15281544      this . eval , 
15291545      this . context , 
15301546      ( includes )  =>  { 
@@ -1729,32 +1745,156 @@ function findExpressionCompleteTarget(code) {
17291745  return  code ; 
17301746} 
17311747
1732- function  includesProxiesOrGetters ( exprSegments ,  evalFn ,  context ,  callback ,  currentExpr  =  '' ,  idx  =  0 )  { 
1733-   const  currentSegment  =  exprSegments [ idx ] ; 
1734-   currentExpr  +=  `${ currentExpr . length  ===  0  ? ''  : '.' } ${ currentSegment }  ; 
1735-   evalFn ( `try { ${ currentExpr }  ,  context ,  getREPLResourceName ( ) ,  ( _ ,  currentObj )  =>  { 
1736-     if  ( typeof  currentObj  !==  'object'  ||  currentObj  ===  null )  { 
1737-       return  callback ( false ) ; 
1738-     } 
1748+ /** 
1749+  * Utility used to determine if an expression includes object getters or proxies. 
1750+  * 
1751+  * Example: given `obj.foo`, the function lets you know if `foo` has a getter function 
1752+  * associated to it, or if `obj` is a proxy 
1753+  * @param  {any } expr The expression, in AST format to analyze 
1754+  * @param  {string } exprStr The string representation of the expression 
1755+  * @param  {(str: string, ctx: any, resourceName: string, cb: (error, evaled) => void) => void } evalFn 
1756+  *        Eval function to use 
1757+  * @param  {any } ctx The context to use for any code evaluation 
1758+  * @param  {(includes: boolean) => void } callback Callback that will be called with the result of the operation 
1759+  * @returns  {void } 
1760+  */ 
1761+ function  includesProxiesOrGetters ( expr ,  exprStr ,  evalFn ,  ctx ,  callback )  { 
1762+   if  ( expr ?. type  !==  'MemberExpression' )  { 
1763+     // If the expression is not a member one for obvious reasons no getters are involved 
1764+     return  callback ( false ) ; 
1765+   } 
17391766
1740-     if  ( isProxy ( currentObj ) )  { 
1741-       return  callback ( true ) ; 
1767+   if  ( expr . object . type  ===  'MemberExpression' )  { 
1768+     // The object itself is a member expression, so we need to recurse (e.g. the expression is `obj.foo.bar`) 
1769+     return  includesProxiesOrGetters ( 
1770+       expr . object , 
1771+       exprStr . slice ( 0 ,  expr . object . end ) , 
1772+       evalFn , 
1773+       ctx , 
1774+       ( includes ,  lastEvaledObj )  =>  { 
1775+         if  ( includes )  { 
1776+           // If the recurred call found a getter we can also terminate 
1777+           return  callback ( includes ) ; 
1778+         } 
1779+ 
1780+         if  ( isProxy ( lastEvaledObj ) )  { 
1781+           return  callback ( true ) ; 
1782+         } 
1783+ 
1784+         // If a getter/proxy hasn't been found by the recursion call we need to check if maybe a getter/proxy 
1785+         // is present here (e.g. in `obj.foo.bar` we found that `obj.foo` doesn't involve any getters so we now 
1786+         // need to check if `bar` on `obj.foo` (i.e. `lastEvaledObj`) has a getter or if `obj.foo.bar` is a proxy) 
1787+         return  hasGetterOrIsProxy ( lastEvaledObj ,  expr . property ,  ( doesHaveGetterOrIsProxy )  =>  { 
1788+           return  callback ( doesHaveGetterOrIsProxy ) ; 
1789+         } ) ; 
1790+       } , 
1791+     ) ; 
1792+   } 
1793+ 
1794+   // This is the base of the recursion we have an identifier for the object and an identifier or literal 
1795+   // for the property (e.g. we have `obj.foo` or `obj['foo']`, `obj` is the object identifier and `foo` 
1796+   // is the property identifier/literal) 
1797+   if  ( expr . object . type  ===  'Identifier' )  { 
1798+     return  evalFn ( `try { ${ expr . object . name }  ,  ctx ,  getREPLResourceName ( ) ,  ( err ,  obj )  =>  { 
1799+       if  ( err )  { 
1800+         return  callback ( false ) ; 
1801+       } 
1802+ 
1803+       if  ( isProxy ( obj ) )  { 
1804+         return  callback ( true ) ; 
1805+       } 
1806+ 
1807+       return  hasGetterOrIsProxy ( obj ,  expr . property ,  ( doesHaveGetterOrIsProxy )  =>  { 
1808+         if  ( doesHaveGetterOrIsProxy )  { 
1809+           return  callback ( true ) ; 
1810+         } 
1811+ 
1812+         return  evalFn ( 
1813+           `try { ${ exprStr }  ,  ctx ,  getREPLResourceName ( ) ,  ( err ,  obj )  =>  { 
1814+             if  ( err )  { 
1815+               return  callback ( false ) ; 
1816+             } 
1817+             return  callback ( false ,  obj ) ; 
1818+           } ) ; 
1819+       } ) ; 
1820+     } ) ; 
1821+   } 
1822+ 
1823+   /** 
1824+    * Utility to see if a property has a getter associated to it or if 
1825+    * the property itself is a proxy object. 
1826+    */ 
1827+   function  hasGetterOrIsProxy ( obj ,  astProp ,  cb )  { 
1828+     if  ( ! obj  ||  ! astProp )  { 
1829+       return  cb ( false ) ; 
17421830    } 
17431831
1744-     const  nextIdx  =  idx  +  1 ; 
1832+     if  ( astProp . type  ===  'Literal' )  { 
1833+       // We have something like `obj['foo'].x` where `x` is the literal 
17451834
1746-     if  ( nextIdx  >=  exprSegments . length )  { 
1747-       return  callback ( false ) ; 
1835+       if  ( isProxy ( obj [ astProp . value ] ) )  { 
1836+         return  cb ( true ) ; 
1837+       } 
1838+ 
1839+       const  propDescriptor  =  ObjectGetOwnPropertyDescriptor ( 
1840+         obj , 
1841+         `${ astProp . value }  , 
1842+       ) ; 
1843+       const  propHasGetter  =  typeof  propDescriptor ?. get  ===  'function' ; 
1844+       return  cb ( propHasGetter ) ; 
17481845    } 
17491846
1750-     const  nextSegmentProp  =  ObjectGetOwnPropertyDescriptor ( currentObj ,  exprSegments [ nextIdx ] ) ; 
1751-     const  nextSegmentPropHasGetter  =  typeof  nextSegmentProp ?. get  ===  'function' ; 
1752-     if  ( nextSegmentPropHasGetter )  { 
1753-       return  callback ( true ) ; 
1847+     if  ( 
1848+       astProp . type  ===  'Identifier'  && 
1849+       exprStr . at ( astProp . start  -  1 )  ===  '.' 
1850+     )  { 
1851+       // We have something like `obj.foo.x` where `foo` is the identifier 
1852+ 
1853+       if  ( isProxy ( obj [ astProp . name ] ) )  { 
1854+         return  cb ( true ) ; 
1855+       } 
1856+ 
1857+       const  propDescriptor  =  ObjectGetOwnPropertyDescriptor ( 
1858+         obj , 
1859+         `${ astProp . name }  , 
1860+       ) ; 
1861+       const  propHasGetter  =  typeof  propDescriptor ?. get  ===  'function' ; 
1862+       return  cb ( propHasGetter ) ; 
17541863    } 
17551864
1756-     return  includesProxiesOrGetters ( exprSegments ,  evalFn ,  context ,  callback ,  currentExpr ,  nextIdx ) ; 
1757-   } ) ; 
1865+     return  evalFn ( 
1866+       // Note: this eval runs the property expression, which might be side-effectful, for example 
1867+       //       the user could be running `obj[getKey()].` where `getKey()` has some side effects. 
1868+       //       Arguably this behavior should not be too surprising, but if it turns out that it is, 
1869+       //       then we can revisit this behavior and add logic to analyze the property expression 
1870+       //       and eval it only if we can confidently say that it can't have any side effects 
1871+       `try { ${ exprStr . slice ( astProp . start ,  astProp . end ) }  , 
1872+       ctx , 
1873+       getREPLResourceName ( ) , 
1874+       ( err ,  evaledProp )  =>  { 
1875+         if  ( err )  { 
1876+           return  callback ( false ) ; 
1877+         } 
1878+ 
1879+         if  ( typeof  evaledProp  ===  'string' )  { 
1880+           if  ( isProxy ( obj [ evaledProp ] ) )  { 
1881+             return  cb ( true ) ; 
1882+           } 
1883+ 
1884+           const  propDescriptor  =  ObjectGetOwnPropertyDescriptor ( 
1885+             obj , 
1886+             evaledProp , 
1887+           ) ; 
1888+           const  propHasGetter  =  typeof  propDescriptor ?. get  ===  'function' ; 
1889+           return  cb ( propHasGetter ) ; 
1890+         } 
1891+ 
1892+         return  callback ( false ) ; 
1893+       } , 
1894+     ) ; 
1895+   } 
1896+ 
1897+   return  callback ( false ) ; 
17581898} 
17591899
17601900REPLServer . prototype . completeOnEditorMode  =  ( callback )  =>  ( err ,  results )  =>  { 
0 commit comments