import assert from "assert" ; import * as types from "ast-types" ; import * as util from "./util" ; const n = types . namedTypes ; const isArray = types . builtInTypes . array ; const isNumber = types . builtInTypes . number ; const PRECEDENCE = { } ; [ [ "||" ] , [ "&&" ] , [ "|" ] , [ "^" ] , [ "&" ] , [ "==" , "===" , "!=" , "!==" ] , [ "<" , ">" , "<=" , ">=" , "in" , "instanceof" ] , [ ">>" , "<<" , ">>>" ] , [ "+" , "-" ] , [ "*" , "/" , "%" ] , [ "**" ] , ] . forEach ( function ( tier , i ) { tier . forEach ( function ( op ) { PRECEDENCE [ op ] = i ; } ) ; } ) ; interface FastPathType { stack : any [ ] ; copy ( ) : any ; getName ( ) : any ; getValue ( ) : any ; valueIsDuplicate ( ) : any ; getNode ( count ? ) : any ; getParentNode ( count ? ) : any ; getRootValue ( ) : any ; call ( callback , ... names ) : any ; each ( callback , ... names ) : any ; map ( callback , ... names ) : any ; hasParens ( ) : any ; getPrevToken ( node ) : any ; getNextToken ( node ) : any ; needsParens ( assumeExpressionContext ? ) : any ; canBeFirstInStatement ( ) : any ; firstInStatement ( ) : any ; } interface FastPathConstructor { new ( value ) : FastPathType ; from ( obj ) : any ; } const FastPath = ( function FastPath ( this , value ) { assert . ok ( this instanceof FastPath ) ; this . stack = [ value ] ; } as any ) as FastPathConstructor ; const FPp = FastPath . prototype ; FastPath . from = function ( obj ) { if ( obj instanceof FastPath ) { return obj . copy ( ) ; } if ( obj instanceof types . NodePath ) { const copy = Object . create ( FastPath . prototype ) ; const stack = [ obj . value ] ; for ( let pp ; ( pp = obj . parentPath ) ; obj = pp ) stack . push ( obj . name , pp . value ) ; copy . stack = stack . reverse ( ) ; return copy ; } return new FastPath ( obj ) ; } ; FPp . copy = function copy ( ) { const copy = Object . create ( FastPath . prototype ) ; copy . stack = this . stack . slice ( 0 ) ; return copy ; } ; FPp . getName = function getName ( ) { const s = this . stack ; const len = s . length ; if ( len > 1 ) { return s [ len - 2 ] ; } return null ; } ; FPp . getValue = function getValue ( ) { const s = this . stack ; return s [ s . length - 1 ] ; } ; FPp . valueIsDuplicate = function ( ) { const s = this . stack ; const valueIndex = s . length - 1 ; return s . lastIndexOf ( s [ valueIndex ] , valueIndex - 1 ) >= 0 ; } ; function getNodeHelper ( path , count ) { const s = path . stack ; for ( let i = s . length - 1 ; i >= 0 ; i -= 2 ) { const value = s [ i ] ; if ( n . Node . check ( value ) && -- count < 0 ) { return value ; } } return null ; } FPp . getNode = function getNode ( count = 0 ) { return getNodeHelper ( this , ~ ~ count ) ; } ; FPp . getParentNode = function getParentNode ( count = 0 ) { return getNodeHelper ( this , ~ ~ count + 1 ) ; } ; FPp . getRootValue = function getRootValue ( ) { const s = this . stack ; if ( s . length % 2 === 0 ) { return s [ 1 ] ; } return s [ 0 ] ; } ; FPp . call = function call ( callback ) { const s = this . stack ; const origLen = s . length ; let value = s [ origLen - 1 ] ; const argc = arguments . length ; for ( let i = 1 ; i < argc ; ++ i ) { const name = arguments [ i ] ; value = value [ name ] ; s . push ( name , value ) ; } const result = callback ( this ) ; s . length = origLen ; return result ; } ; FPp . each = function each ( callback ) { const s = this . stack ; const origLen = s . length ; let value = s [ origLen - 1 ] ; const argc = arguments . length ; for ( let i = 1 ; i < argc ; ++ i ) { const name = arguments [ i ] ; value = value [ name ] ; s . push ( name , value ) ; } for ( let i = 0 ; i < value . length ; ++ i ) { if ( i in value ) { s . push ( i , value [ i ] ) ; callback ( this ) ; s . length -= 2 ; } } s . length = origLen ; } ; FPp . map = function map ( callback ) { const s = this . stack ; const origLen = s . length ; let value = s [ origLen - 1 ] ; const argc = arguments . length ; for ( let i = 1 ; i < argc ; ++ i ) { const name = arguments [ i ] ; value = value [ name ] ; s . push ( name , value ) ; } const result = new Array ( value . length ) ; for ( let i = 0 ; i < value . length ; ++ i ) { if ( i in value ) { s . push ( i , value [ i ] ) ; result [ i ] = callback ( this , i ) ; s . length -= 2 ; } } s . length = origLen ; return result ; } ; FPp . hasParens = function ( ) { const node = this . getNode ( ) ; const prevToken = this . getPrevToken ( node ) ; if ( ! prevToken ) { return false ; } const nextToken = this . getNextToken ( node ) ; if ( ! nextToken ) { return false ; } if ( prevToken . value === "(" ) { if ( nextToken . value === ")" ) { return true ; } const justNeedsOpeningParen = ! this . canBeFirstInStatement ( ) && this . firstInStatement ( ) && ! this . needsParens ( true ) ; if ( justNeedsOpeningParen ) { return true ; } } return false ; } ; FPp . getPrevToken = function ( node ) { node = node || this . getNode ( ) ; const loc = node && node . loc ; const tokens = loc && loc . tokens ; if ( tokens && loc . start . token > 0 ) { const token = tokens [ loc . start . token - 1 ] ; if ( token ) { const rootLoc = this . getRootValue ( ) . loc ; if ( util . comparePos ( rootLoc . start , token . loc . start ) <= 0 ) { return token ; } } } return null ; } ; FPp . getNextToken = function ( node ) { node = node || this . getNode ( ) ; const loc = node && node . loc ; const tokens = loc && loc . tokens ; if ( tokens && loc . end . token < tokens . length ) { const token = tokens [ loc . end . token ] ; if ( token ) { const rootLoc = this . getRootValue ( ) . loc ; if ( util . comparePos ( token . loc . end , rootLoc . end ) <= 0 ) { return token ; } } } return null ; } ; FPp . needsParens = function ( assumeExpressionContext ) { const node = this . getNode ( ) ; if ( node . type === "AssignmentExpression" && node . left . type === "ObjectPattern" ) { return true ; } const parent = this . getParentNode ( ) ; if ( ! parent ) { return false ; } const name = this . getName ( ) ; if ( this . getValue ( ) !== node ) { return false ; } if ( n . Statement . check ( node ) ) { return false ; } if ( node . type === "Identifier" ) { return false ; } if ( parent . type === "ParenthesizedExpression" || ( node . extra && node . extra . parenthesized ) ) { return false ; } switch ( node . type ) { case "UnaryExpression" : case "SpreadElement" : case "SpreadProperty" : return ( parent . type === "MemberExpression" && name === "object" && parent . object === node ) ; case "BinaryExpression" : case "LogicalExpression" : switch ( parent . type ) { case "CallExpression" : return name === "callee" && parent . callee === node ; case "UnaryExpression" : case "SpreadElement" : case "SpreadProperty" : return true ; case "MemberExpression" : return name === "object" && parent . object === node ; case "BinaryExpression" : case "LogicalExpression" : { const po = parent . operator ; const pp = PRECEDENCE [ po ] ; const no = node . operator ; const np = PRECEDENCE [ no ] ; if ( pp > np ) { return true ; } if ( pp === np && name === "right" ) { assert . strictEqual ( parent . right , node ) ; return true ; } break ; } default : return false ; } break ; case "SequenceExpression" : switch ( parent . type ) { case "ReturnStatement" : return false ; case "ForStatement" : return false ; case "ExpressionStatement" : return name !== "expression" ; default : return true ; } case "OptionalIndexedAccessType" : return node . optional && parent . type === "IndexedAccessType" ; case "IntersectionTypeAnnotation" : case "UnionTypeAnnotation" : return parent . type === "NullableTypeAnnotation" ; case "Literal" : return ( parent . type === "MemberExpression" && isNumber . check ( node . value ) && name === "object" && parent . object === node ) ; case "NumericLiteral" : return ( parent . type === "MemberExpression" && name === "object" && parent . object === node ) ; case "YieldExpression" : case "AwaitExpression" : case "AssignmentExpression" : case "ConditionalExpression" : switch ( parent . type ) { case "UnaryExpression" : case "SpreadElement" : case "SpreadProperty" : case "BinaryExpression" : case "LogicalExpression" : return true ; case "CallExpression" : case "NewExpression" : return name === "callee" && parent . callee === node ; case "ConditionalExpression" : return name === "test" && parent . test === node ; case "MemberExpression" : return name === "object" && parent . object === node ; default : return false ; } case "ArrowFunctionExpression" : if ( n . CallExpression . check ( parent ) && name === "callee" && parent . callee === node ) { return true ; } if ( n . MemberExpression . check ( parent ) && name === "object" && parent . object === node ) { return true ; } if ( n . TSAsExpression && n . TSAsExpression . check ( parent ) && name === "expression" && parent . expression === node ) { return true ; } return isBinary ( parent ) ; case "ObjectExpression" : if ( parent . type === "ArrowFunctionExpression" && name === "body" && parent . body === node ) { return true ; } break ; case "TSAsExpression" : if ( parent . type === "ArrowFunctionExpression" && name === "body" && parent . body === node && node . expression . type === "ObjectExpression" ) { return true ; } break ; case "CallExpression" : if ( name === "declaration" && n . ExportDefaultDeclaration . check ( parent ) && n . FunctionExpression . check ( node . callee ) ) { return true ; } } if ( parent . type === "NewExpression" && name === "callee" && parent . callee === node ) { return containsCallExpression ( node ) ; } if ( assumeExpressionContext !== true && ! this . canBeFirstInStatement ( ) && this . firstInStatement ( ) ) { return true ; } return false ; } ; function isBinary ( node ) { return n . BinaryExpression . check ( node ) || n . LogicalExpression . check ( node ) ; } function isUnaryLike ( node ) { return ( n . UnaryExpression . check ( node ) || ( n . SpreadElement && n . SpreadElement . check ( node ) ) || ( n . SpreadProperty && n . SpreadProperty . check ( node ) ) ) ; } function containsCallExpression ( node ) { if ( n . CallExpression . check ( node ) ) { return true ; } if ( isArray . check ( node ) ) { return node . some ( containsCallExpression ) ; } if ( n . Node . check ( node ) ) { return types . someField ( node , ( _name , child ) => containsCallExpression ( child ) ) ; } return false ; } FPp . canBeFirstInStatement = function ( ) { const node = this . getNode ( ) ; if ( n . FunctionExpression . check ( node ) ) { return false ; } if ( n . ObjectExpression . check ( node ) ) { return false ; } if ( n . ClassExpression . check ( node ) ) { return false ; } return true ; } ; FPp . firstInStatement = function ( ) { const s = this . stack ; let parentName , parent ; let childName , child ; for ( let i = s . length - 1 ; i >= 0 ; i -= 2 ) { if ( n . Node . check ( s [ i ] ) ) { childName = parentName ; child = parent ; parentName = s [ i - 1 ] ; parent = s [ i ] ; } if ( ! parent || ! child ) { continue ; } if ( n . BlockStatement . check ( parent ) && parentName === "body" && childName === 0 ) { assert . strictEqual ( parent . body [ 0 ] , child ) ; return true ; } if ( n . ExpressionStatement . check ( parent ) && childName === "expression" ) { assert . strictEqual ( parent . expression , child ) ; return true ; } if ( n . AssignmentExpression . check ( parent ) && childName === "left" ) { assert . strictEqual ( parent . left , child ) ; return true ; } if ( n . ArrowFunctionExpression . check ( parent ) && childName === "body" ) { assert . strictEqual ( parent . body , child ) ; return true ; } if ( n . SequenceExpression . check ( parent ) && parentName === "expressions" && childName === 0 ) { assert . strictEqual ( parent . expressions [ 0 ] , child ) ; continue ; } if ( n . CallExpression . check ( parent ) && childName === "callee" ) { assert . strictEqual ( parent . callee , child ) ; continue ; } if ( n . MemberExpression . check ( parent ) && childName === "object" ) { assert . strictEqual ( parent . object , child ) ; continue ; } if ( n . ConditionalExpression . check ( parent ) && childName === "test" ) { assert . strictEqual ( parent . test , child ) ; continue ; } if ( isBinary ( parent ) && childName === "left" ) { assert . strictEqual ( parent . left , child ) ; continue ; } if ( n . UnaryExpression . check ( parent ) && ! parent . prefix && childName === "argument" ) { assert . strictEqual ( parent . argument , child ) ; continue ; } return false ; } return true ; } ; export default FastPath ;